okstra 0.108.0 → 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 (642) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/prompts/profiles/_common-contract.md +1 -1
  4. package/runtime/python/okstra_ctl/graphify_cmd.py +225 -0
  5. package/runtime/python/okstra_ctl/resolve_task_key.py +15 -9
  6. package/runtime/python/okstra_project/__init__.py +2 -0
  7. package/runtime/python/okstra_project/state.py +36 -0
  8. package/runtime/python/okstra_vendor/__init__.py +41 -0
  9. package/runtime/python/okstra_vendor/graphify/.vendored-version +1 -0
  10. package/runtime/python/okstra_vendor/graphify/__init__.py +28 -0
  11. package/runtime/python/okstra_vendor/graphify/__main__.py +1371 -0
  12. package/runtime/python/okstra_vendor/graphify/analyze.py +540 -0
  13. package/runtime/python/okstra_vendor/graphify/benchmark.py +129 -0
  14. package/runtime/python/okstra_vendor/graphify/build.py +107 -0
  15. package/runtime/python/okstra_vendor/graphify/cache.py +169 -0
  16. package/runtime/python/okstra_vendor/graphify/cluster.py +137 -0
  17. package/runtime/python/okstra_vendor/graphify/detect.py +510 -0
  18. package/runtime/python/okstra_vendor/graphify/export.py +1014 -0
  19. package/runtime/python/okstra_vendor/graphify/extract.py +3277 -0
  20. package/runtime/python/okstra_vendor/graphify/hooks.py +220 -0
  21. package/runtime/python/okstra_vendor/graphify/ingest.py +297 -0
  22. package/runtime/python/okstra_vendor/graphify/manifest.py +4 -0
  23. package/runtime/python/okstra_vendor/graphify/report.py +175 -0
  24. package/runtime/python/okstra_vendor/graphify/security.py +203 -0
  25. package/runtime/python/okstra_vendor/graphify/serve.py +373 -0
  26. package/runtime/python/okstra_vendor/graphify/skill-aider.md +1184 -0
  27. package/runtime/python/okstra_vendor/graphify/skill-claw.md +1184 -0
  28. package/runtime/python/okstra_vendor/graphify/skill-codex.md +1242 -0
  29. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +1268 -0
  30. package/runtime/python/okstra_vendor/graphify/skill-droid.md +1239 -0
  31. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +1183 -0
  32. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +1238 -0
  33. package/runtime/python/okstra_vendor/graphify/skill-trae.md +1208 -0
  34. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +253 -0
  35. package/runtime/python/okstra_vendor/graphify/skill-windows.md +1245 -0
  36. package/runtime/python/okstra_vendor/graphify/skill.md +1319 -0
  37. package/runtime/python/okstra_vendor/graphify/transcribe.py +182 -0
  38. package/runtime/python/okstra_vendor/graphify/validate.py +72 -0
  39. package/runtime/python/okstra_vendor/graphify/watch.py +188 -0
  40. package/runtime/python/okstra_vendor/graphify/wiki.py +214 -0
  41. package/runtime/python/okstra_vendor/networkx/__init__.py +62 -0
  42. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +134 -0
  43. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +26 -0
  44. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +259 -0
  45. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +71 -0
  46. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +412 -0
  47. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +396 -0
  48. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +150 -0
  49. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +149 -0
  50. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +369 -0
  51. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +44 -0
  52. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +143 -0
  53. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +53 -0
  54. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +265 -0
  55. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  56. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +41 -0
  57. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +112 -0
  58. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +199 -0
  59. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +146 -0
  60. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +59 -0
  61. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +78 -0
  62. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +303 -0
  63. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +8 -0
  64. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +94 -0
  65. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +31 -0
  66. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +306 -0
  67. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +1014 -0
  68. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +274 -0
  69. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +68 -0
  70. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +1508 -0
  71. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +255 -0
  72. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +83 -0
  73. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +5 -0
  74. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +122 -0
  75. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +302 -0
  76. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +255 -0
  77. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +160 -0
  78. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +127 -0
  79. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  80. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +81 -0
  81. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +143 -0
  82. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +122 -0
  83. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +174 -0
  84. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +107 -0
  85. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +87 -0
  86. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +164 -0
  87. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +88 -0
  88. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +322 -0
  89. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +290 -0
  90. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +289 -0
  91. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +57 -0
  92. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +360 -0
  93. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +105 -0
  94. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +603 -0
  95. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +316 -0
  96. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +590 -0
  97. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +232 -0
  98. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +526 -0
  99. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +112 -0
  100. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +69 -0
  101. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +125 -0
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +192 -0
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +84 -0
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +33 -0
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +240 -0
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +334 -0
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +407 -0
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +218 -0
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +327 -0
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +138 -0
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +409 -0
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +35 -0
  114. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +80 -0
  115. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +168 -0
  116. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +205 -0
  117. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +164 -0
  118. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +20 -0
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +591 -0
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +236 -0
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +282 -0
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +364 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +227 -0
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +96 -0
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +150 -0
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +107 -0
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +357 -0
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +130 -0
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +787 -0
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +88 -0
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +331 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +150 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +200 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +128 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +209 -0
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +141 -0
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +361 -0
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  139. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +923 -0
  140. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +354 -0
  141. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +274 -0
  142. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +259 -0
  143. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +147 -0
  144. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +43 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +144 -0
  146. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +73 -0
  147. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +186 -0
  148. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +277 -0
  149. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +122 -0
  150. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +345 -0
  151. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +220 -0
  152. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +344 -0
  153. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +87 -0
  154. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +140 -0
  155. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +82 -0
  156. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +110 -0
  157. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +302 -0
  158. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +64 -0
  159. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +181 -0
  160. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +95 -0
  161. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +172 -0
  162. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +443 -0
  163. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +818 -0
  164. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +732 -0
  165. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +4 -0
  166. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +505 -0
  167. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +565 -0
  168. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  169. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +863 -0
  170. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +163 -0
  171. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +28 -0
  172. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +153 -0
  173. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +354 -0
  174. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +171 -0
  175. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +30 -0
  176. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +216 -0
  177. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +79 -0
  178. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +338 -0
  179. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +162 -0
  180. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +220 -0
  181. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +384 -0
  182. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +227 -0
  183. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +452 -0
  184. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +347 -0
  185. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  186. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +147 -0
  187. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +157 -0
  188. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +85 -0
  189. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +106 -0
  190. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +91 -0
  191. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +241 -0
  192. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +138 -0
  193. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +76 -0
  194. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +264 -0
  195. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +152 -0
  196. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +340 -0
  197. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +139 -0
  198. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +26 -0
  199. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +6 -0
  200. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +115 -0
  201. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +394 -0
  202. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +282 -0
  203. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +71 -0
  204. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +359 -0
  205. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  206. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +70 -0
  207. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +248 -0
  208. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +138 -0
  209. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +55 -0
  210. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +193 -0
  211. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +96 -0
  212. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +196 -0
  213. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +11 -0
  214. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +811 -0
  215. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +616 -0
  216. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +408 -0
  217. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +1270 -0
  218. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +592 -0
  219. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +220 -0
  220. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +235 -0
  221. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +152 -0
  222. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  223. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +421 -0
  224. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +309 -0
  225. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +249 -0
  226. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +502 -0
  227. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +488 -0
  228. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +323 -0
  229. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +280 -0
  230. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +102 -0
  231. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +88 -0
  232. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +588 -0
  233. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +142 -0
  234. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +416 -0
  235. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +1234 -0
  236. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +677 -0
  237. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +1392 -0
  238. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +1095 -0
  239. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +272 -0
  240. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +142 -0
  241. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +268 -0
  242. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +167 -0
  243. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +470 -0
  244. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +11 -0
  245. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +370 -0
  246. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +407 -0
  247. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +238 -0
  248. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +241 -0
  249. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +178 -0
  250. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +611 -0
  251. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +356 -0
  252. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +662 -0
  253. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +425 -0
  254. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +300 -0
  255. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  256. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  257. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  258. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  259. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +128 -0
  260. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +573 -0
  261. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +155 -0
  262. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +475 -0
  263. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +481 -0
  264. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  265. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +194 -0
  266. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +435 -0
  267. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +483 -0
  268. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +57 -0
  269. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +196 -0
  270. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +107 -0
  271. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +7 -0
  272. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +1306 -0
  273. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +336 -0
  274. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +1262 -0
  275. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +352 -0
  276. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +308 -0
  277. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  278. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  279. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  280. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  281. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  282. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +719 -0
  283. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +103 -0
  284. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +490 -0
  285. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +64 -0
  286. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +212 -0
  287. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +202 -0
  288. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +1655 -0
  289. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +3118 -0
  290. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +196 -0
  291. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +264 -0
  292. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +1102 -0
  293. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +192 -0
  294. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +2 -0
  295. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +337 -0
  296. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +498 -0
  297. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  298. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +77 -0
  299. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +213 -0
  300. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +687 -0
  301. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +280 -0
  302. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +1148 -0
  303. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +27 -0
  304. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +738 -0
  305. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +544 -0
  306. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +78 -0
  307. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +59 -0
  308. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +219 -0
  309. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +155 -0
  310. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +4 -0
  311. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +324 -0
  312. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +468 -0
  313. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +633 -0
  314. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  315. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +328 -0
  316. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +451 -0
  317. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +491 -0
  318. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +55 -0
  319. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +77 -0
  320. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +73 -0
  321. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +464 -0
  322. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +1463 -0
  323. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +306 -0
  324. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +98 -0
  325. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +167 -0
  326. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +138 -0
  327. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +5 -0
  328. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +239 -0
  329. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +264 -0
  330. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +716 -0
  331. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  332. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +254 -0
  333. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +212 -0
  334. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +88 -0
  335. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +511 -0
  336. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +149 -0
  337. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +983 -0
  338. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +625 -0
  339. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +2542 -0
  340. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +2107 -0
  341. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +966 -0
  342. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +404 -0
  343. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +30 -0
  344. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +296 -0
  345. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +374 -0
  346. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +564 -0
  347. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +406 -0
  348. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  349. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +23 -0
  350. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +154 -0
  351. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +144 -0
  352. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +109 -0
  353. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +136 -0
  354. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +129 -0
  355. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +300 -0
  356. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +678 -0
  357. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +80 -0
  358. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +266 -0
  359. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +85 -0
  360. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +171 -0
  361. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +984 -0
  362. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +340 -0
  363. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +835 -0
  364. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +831 -0
  365. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +85 -0
  366. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +299 -0
  367. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +115 -0
  368. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +58 -0
  369. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +314 -0
  370. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +872 -0
  371. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +163 -0
  372. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +46 -0
  373. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +24 -0
  374. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +26 -0
  375. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +615 -0
  376. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +459 -0
  377. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +556 -0
  378. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +179 -0
  379. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +62 -0
  380. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +15 -0
  381. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +140 -0
  382. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +60 -0
  383. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +27 -0
  384. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +274 -0
  385. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +556 -0
  386. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +57 -0
  387. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +37 -0
  388. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +88 -0
  389. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +149 -0
  390. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +1158 -0
  391. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +803 -0
  392. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +76 -0
  393. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +8 -0
  394. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +138 -0
  395. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +191 -0
  396. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +642 -0
  397. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +179 -0
  398. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +270 -0
  399. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +431 -0
  400. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +161 -0
  401. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +248 -0
  402. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +41 -0
  403. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +103 -0
  404. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +54 -0
  405. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +157 -0
  406. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +981 -0
  407. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +142 -0
  408. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +406 -0
  409. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +5 -0
  410. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +90 -0
  411. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +576 -0
  412. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +529 -0
  413. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +185 -0
  414. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +182 -0
  415. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  416. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +25 -0
  417. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +203 -0
  418. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +307 -0
  419. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +147 -0
  420. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +131 -0
  421. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +7 -0
  422. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +1042 -0
  423. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +413 -0
  424. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +88 -0
  425. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +219 -0
  426. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +1281 -0
  427. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +106 -0
  428. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +273 -0
  429. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  430. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +624 -0
  431. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +114 -0
  432. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +79 -0
  433. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +99 -0
  434. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +934 -0
  435. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +53 -0
  436. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +174 -0
  437. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +500 -0
  438. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +76 -0
  439. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +86 -0
  440. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +77 -0
  441. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +278 -0
  442. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +13 -0
  443. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +435 -0
  444. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +1363 -0
  445. package/runtime/python/okstra_vendor/networkx/classes/filters.py +95 -0
  446. package/runtime/python/okstra_vendor/networkx/classes/function.py +1549 -0
  447. package/runtime/python/okstra_vendor/networkx/classes/graph.py +2082 -0
  448. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +269 -0
  449. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +977 -0
  450. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +1294 -0
  451. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +1447 -0
  452. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  453. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +192 -0
  454. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +476 -0
  455. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +362 -0
  456. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +331 -0
  457. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +110 -0
  458. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +177 -0
  459. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +1045 -0
  460. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +950 -0
  461. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +12 -0
  462. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +349 -0
  463. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +459 -0
  464. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +528 -0
  465. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +1421 -0
  466. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +131 -0
  467. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +371 -0
  468. package/runtime/python/okstra_vendor/networkx/conftest.py +261 -0
  469. package/runtime/python/okstra_vendor/networkx/convert.py +502 -0
  470. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +1314 -0
  471. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +7 -0
  472. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +2036 -0
  473. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +470 -0
  474. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +570 -0
  475. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +361 -0
  476. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +2978 -0
  477. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  478. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  479. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  480. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  481. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  482. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  483. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  484. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +237 -0
  485. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +229 -0
  486. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +285 -0
  487. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +631 -0
  488. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +146 -0
  489. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +1582 -0
  490. package/runtime/python/okstra_vendor/networkx/exception.py +131 -0
  491. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +34 -0
  492. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  493. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +227 -0
  494. package/runtime/python/okstra_vendor/networkx/generators/classic.py +1091 -0
  495. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +68 -0
  496. package/runtime/python/okstra_vendor/networkx/generators/community.py +1070 -0
  497. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +886 -0
  498. package/runtime/python/okstra_vendor/networkx/generators/directed.py +572 -0
  499. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +174 -0
  500. package/runtime/python/okstra_vendor/networkx/generators/ego.py +66 -0
  501. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +499 -0
  502. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +1037 -0
  503. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +163 -0
  504. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +443 -0
  505. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +125 -0
  506. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +70 -0
  507. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +664 -0
  508. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +405 -0
  509. package/runtime/python/okstra_vendor/networkx/generators/line.py +501 -0
  510. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +110 -0
  511. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +259 -0
  512. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +117 -0
  513. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +1416 -0
  514. package/runtime/python/okstra_vendor/networkx/generators/small.py +1070 -0
  515. package/runtime/python/okstra_vendor/networkx/generators/social.py +554 -0
  516. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +120 -0
  517. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +54 -0
  518. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +131 -0
  519. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  520. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +75 -0
  521. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +642 -0
  522. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +20 -0
  523. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +362 -0
  524. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +224 -0
  525. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +189 -0
  526. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +103 -0
  527. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +39 -0
  528. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +182 -0
  529. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +488 -0
  530. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +133 -0
  531. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +221 -0
  532. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +28 -0
  533. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +144 -0
  534. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +125 -0
  535. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +264 -0
  536. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +316 -0
  537. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +30 -0
  538. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +82 -0
  539. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +33 -0
  540. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +495 -0
  541. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +220 -0
  542. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +49 -0
  543. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +72 -0
  544. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +92 -0
  545. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +64 -0
  546. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +195 -0
  547. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +15 -0
  548. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +74 -0
  549. package/runtime/python/okstra_vendor/networkx/generators/trees.py +1070 -0
  550. package/runtime/python/okstra_vendor/networkx/generators/triads.py +94 -0
  551. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +188 -0
  552. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +13 -0
  553. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +650 -0
  554. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +466 -0
  555. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +77 -0
  556. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +168 -0
  557. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +512 -0
  558. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +166 -0
  559. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +186 -0
  560. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  561. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +400 -0
  562. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +108 -0
  563. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +40 -0
  564. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +275 -0
  565. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +334 -0
  566. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +86 -0
  567. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +70 -0
  568. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +17 -0
  569. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +330 -0
  570. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +489 -0
  571. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +1084 -0
  572. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +879 -0
  573. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +427 -0
  574. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +1053 -0
  575. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +19 -0
  576. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +156 -0
  577. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +190 -0
  578. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +261 -0
  579. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  580. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +78 -0
  581. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +78 -0
  582. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +109 -0
  583. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +48 -0
  584. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +137 -0
  585. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +108 -0
  586. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +393 -0
  587. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +113 -0
  588. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +286 -0
  589. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +379 -0
  590. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  591. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +354 -0
  592. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +318 -0
  593. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +612 -0
  594. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +744 -0
  595. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +181 -0
  596. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +1531 -0
  597. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +30 -0
  598. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +63 -0
  599. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +128 -0
  600. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +166 -0
  601. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +1742 -0
  602. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +851 -0
  603. package/runtime/python/okstra_vendor/networkx/relabel.py +285 -0
  604. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  605. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +248 -0
  606. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +321 -0
  607. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +531 -0
  608. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +349 -0
  609. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +281 -0
  610. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +40 -0
  611. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +11 -0
  612. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +96 -0
  613. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +349 -0
  614. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +8 -0
  615. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +8 -0
  616. package/runtime/python/okstra_vendor/networkx/utils/backends.py +2171 -0
  617. package/runtime/python/okstra_vendor/networkx/utils/configs.py +396 -0
  618. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +1233 -0
  619. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +338 -0
  620. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +297 -0
  621. package/runtime/python/okstra_vendor/networkx/utils/misc.py +703 -0
  622. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +198 -0
  623. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +159 -0
  624. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  625. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +11 -0
  626. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +225 -0
  627. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +263 -0
  628. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +510 -0
  629. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +131 -0
  630. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +268 -0
  631. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +393 -0
  632. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +53 -0
  633. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +63 -0
  634. package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +55 -0
  635. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +106 -0
  636. package/runtime/skills/okstra-graphify/SKILL.md +161 -0
  637. package/runtime/skills/okstra-inspect/SKILL.md +17 -9
  638. package/runtime/templates/reports/settings.template.json +4 -0
  639. package/src/cli-registry.mjs +7 -0
  640. package/src/commands/graphify.mjs +32 -0
  641. package/src/commands/lifecycle/doctor.mjs +9 -0
  642. package/src/lib/skill-catalog.mjs +1 -0
@@ -0,0 +1,2107 @@
1
+ """Functions measuring similarity using graph edit distance.
2
+
3
+ The graph edit distance is the number of edge/node changes needed
4
+ to make two graphs isomorphic.
5
+
6
+ The default algorithm/implementation is sub-optimal for some graphs.
7
+ The problem of finding the exact Graph Edit Distance (GED) is NP-hard
8
+ so it is often slow. If the simple interface `graph_edit_distance`
9
+ takes too long for your graph, try `optimize_graph_edit_distance`
10
+ and/or `optimize_edit_paths`.
11
+
12
+ At the same time, I encourage capable people to investigate
13
+ alternative GED algorithms, in order to improve the choices available.
14
+ """
15
+
16
+ import math
17
+ import time
18
+ from dataclasses import dataclass
19
+ from itertools import product
20
+
21
+ import networkx as nx
22
+ from networkx.utils import np_random_state
23
+
24
+ __all__ = [
25
+ "graph_edit_distance",
26
+ "optimal_edit_paths",
27
+ "optimize_graph_edit_distance",
28
+ "optimize_edit_paths",
29
+ "simrank_similarity",
30
+ "panther_similarity",
31
+ "panther_vector_similarity",
32
+ "generate_random_paths",
33
+ ]
34
+
35
+
36
+ @nx._dispatchable(
37
+ graphs={"G1": 0, "G2": 1}, preserve_edge_attrs=True, preserve_node_attrs=True
38
+ )
39
+ def graph_edit_distance(
40
+ G1,
41
+ G2,
42
+ node_match=None,
43
+ edge_match=None,
44
+ node_subst_cost=None,
45
+ node_del_cost=None,
46
+ node_ins_cost=None,
47
+ edge_subst_cost=None,
48
+ edge_del_cost=None,
49
+ edge_ins_cost=None,
50
+ roots=None,
51
+ upper_bound=None,
52
+ timeout=None,
53
+ ):
54
+ """Returns GED (graph edit distance) between graphs G1 and G2.
55
+
56
+ Graph edit distance is a graph similarity measure analogous to
57
+ Levenshtein distance for strings. It is defined as minimum cost
58
+ of edit path (sequence of node and edge edit operations)
59
+ transforming graph G1 to graph isomorphic to G2.
60
+
61
+ Parameters
62
+ ----------
63
+ G1, G2: graphs
64
+ The two graphs G1 and G2 must be of the same type.
65
+
66
+ node_match : callable
67
+ A function that returns True if node n1 in G1 and n2 in G2
68
+ should be considered equal during matching.
69
+
70
+ The function will be called like
71
+
72
+ node_match(G1.nodes[n1], G2.nodes[n2]).
73
+
74
+ That is, the function will receive the node attribute
75
+ dictionaries for n1 and n2 as inputs.
76
+
77
+ Ignored if node_subst_cost is specified. If neither
78
+ node_match nor node_subst_cost are specified then node
79
+ attributes are not considered.
80
+
81
+ edge_match : callable
82
+ A function that returns True if the edge attribute dictionaries
83
+ for the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should
84
+ be considered equal during matching.
85
+
86
+ The function will be called like
87
+
88
+ edge_match(G1[u1][v1], G2[u2][v2]).
89
+
90
+ That is, the function will receive the edge attribute
91
+ dictionaries of the edges under consideration.
92
+
93
+ Ignored if edge_subst_cost is specified. If neither
94
+ edge_match nor edge_subst_cost are specified then edge
95
+ attributes are not considered.
96
+
97
+ node_subst_cost, node_del_cost, node_ins_cost : callable
98
+ Functions that return the costs of node substitution, node
99
+ deletion, and node insertion, respectively.
100
+
101
+ The functions will be called like
102
+
103
+ node_subst_cost(G1.nodes[n1], G2.nodes[n2]),
104
+ node_del_cost(G1.nodes[n1]),
105
+ node_ins_cost(G2.nodes[n2]).
106
+
107
+ That is, the functions will receive the node attribute
108
+ dictionaries as inputs. The functions are expected to return
109
+ positive numeric values.
110
+
111
+ Function node_subst_cost overrides node_match if specified.
112
+ If neither node_match nor node_subst_cost are specified then
113
+ default node substitution cost of 0 is used (node attributes
114
+ are not considered during matching).
115
+
116
+ If node_del_cost is not specified then default node deletion
117
+ cost of 1 is used. If node_ins_cost is not specified then
118
+ default node insertion cost of 1 is used.
119
+
120
+ edge_subst_cost, edge_del_cost, edge_ins_cost : callable
121
+ Functions that return the costs of edge substitution, edge
122
+ deletion, and edge insertion, respectively.
123
+
124
+ The functions will be called like
125
+
126
+ edge_subst_cost(G1[u1][v1], G2[u2][v2]),
127
+ edge_del_cost(G1[u1][v1]),
128
+ edge_ins_cost(G2[u2][v2]).
129
+
130
+ That is, the functions will receive the edge attribute
131
+ dictionaries as inputs. The functions are expected to return
132
+ positive numeric values.
133
+
134
+ Function edge_subst_cost overrides edge_match if specified.
135
+ If neither edge_match nor edge_subst_cost are specified then
136
+ default edge substitution cost of 0 is used (edge attributes
137
+ are not considered during matching).
138
+
139
+ If edge_del_cost is not specified then default edge deletion
140
+ cost of 1 is used. If edge_ins_cost is not specified then
141
+ default edge insertion cost of 1 is used.
142
+
143
+ roots : 2-tuple
144
+ Tuple where first element is a node in G1 and the second
145
+ is a node in G2.
146
+ These nodes are forced to be matched in the comparison to
147
+ allow comparison between rooted graphs.
148
+
149
+ upper_bound : numeric
150
+ Maximum edit distance to consider. Return None if no edit
151
+ distance under or equal to upper_bound exists.
152
+
153
+ timeout : numeric
154
+ Maximum number of seconds to execute.
155
+ After timeout is met, the current best GED is returned.
156
+
157
+ Examples
158
+ --------
159
+ >>> G1 = nx.cycle_graph(6)
160
+ >>> G2 = nx.wheel_graph(7)
161
+ >>> nx.graph_edit_distance(G1, G2)
162
+ 7.0
163
+
164
+ >>> G1 = nx.star_graph(5)
165
+ >>> G2 = nx.star_graph(5)
166
+ >>> nx.graph_edit_distance(G1, G2, roots=(0, 0))
167
+ 0.0
168
+ >>> nx.graph_edit_distance(G1, G2, roots=(1, 0))
169
+ 8.0
170
+
171
+ See Also
172
+ --------
173
+ optimal_edit_paths, optimize_graph_edit_distance,
174
+
175
+ is_isomorphic: test for graph edit distance of 0
176
+
177
+ References
178
+ ----------
179
+ .. [1] Zeina Abu-Aisheh, Romain Raveaux, Jean-Yves Ramel, Patrick
180
+ Martineau. An Exact Graph Edit Distance Algorithm for Solving
181
+ Pattern Recognition Problems. 4th International Conference on
182
+ Pattern Recognition Applications and Methods 2015, Jan 2015,
183
+ Lisbon, Portugal. 2015,
184
+ <10.5220/0005209202710278>. <hal-01168816>
185
+ https://hal.archives-ouvertes.fr/hal-01168816
186
+
187
+ """
188
+ bestcost = None
189
+ for _, _, cost in optimize_edit_paths(
190
+ G1,
191
+ G2,
192
+ node_match,
193
+ edge_match,
194
+ node_subst_cost,
195
+ node_del_cost,
196
+ node_ins_cost,
197
+ edge_subst_cost,
198
+ edge_del_cost,
199
+ edge_ins_cost,
200
+ upper_bound,
201
+ True,
202
+ roots,
203
+ timeout,
204
+ ):
205
+ # assert bestcost is None or cost < bestcost
206
+ bestcost = cost
207
+ return bestcost
208
+
209
+
210
+ @nx._dispatchable(graphs={"G1": 0, "G2": 1})
211
+ def optimal_edit_paths(
212
+ G1,
213
+ G2,
214
+ node_match=None,
215
+ edge_match=None,
216
+ node_subst_cost=None,
217
+ node_del_cost=None,
218
+ node_ins_cost=None,
219
+ edge_subst_cost=None,
220
+ edge_del_cost=None,
221
+ edge_ins_cost=None,
222
+ upper_bound=None,
223
+ ):
224
+ """Returns all minimum-cost edit paths transforming G1 to G2.
225
+
226
+ Graph edit path is a sequence of node and edge edit operations
227
+ transforming graph G1 to graph isomorphic to G2. Edit operations
228
+ include substitutions, deletions, and insertions.
229
+
230
+ Parameters
231
+ ----------
232
+ G1, G2: graphs
233
+ The two graphs G1 and G2 must be of the same type.
234
+
235
+ node_match : callable
236
+ A function that returns True if node n1 in G1 and n2 in G2
237
+ should be considered equal during matching.
238
+
239
+ The function will be called like
240
+
241
+ node_match(G1.nodes[n1], G2.nodes[n2]).
242
+
243
+ That is, the function will receive the node attribute
244
+ dictionaries for n1 and n2 as inputs.
245
+
246
+ Ignored if node_subst_cost is specified. If neither
247
+ node_match nor node_subst_cost are specified then node
248
+ attributes are not considered.
249
+
250
+ edge_match : callable
251
+ A function that returns True if the edge attribute dictionaries
252
+ for the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should
253
+ be considered equal during matching.
254
+
255
+ The function will be called like
256
+
257
+ edge_match(G1[u1][v1], G2[u2][v2]).
258
+
259
+ That is, the function will receive the edge attribute
260
+ dictionaries of the edges under consideration.
261
+
262
+ Ignored if edge_subst_cost is specified. If neither
263
+ edge_match nor edge_subst_cost are specified then edge
264
+ attributes are not considered.
265
+
266
+ node_subst_cost, node_del_cost, node_ins_cost : callable
267
+ Functions that return the costs of node substitution, node
268
+ deletion, and node insertion, respectively.
269
+
270
+ The functions will be called like
271
+
272
+ node_subst_cost(G1.nodes[n1], G2.nodes[n2]),
273
+ node_del_cost(G1.nodes[n1]),
274
+ node_ins_cost(G2.nodes[n2]).
275
+
276
+ That is, the functions will receive the node attribute
277
+ dictionaries as inputs. The functions are expected to return
278
+ positive numeric values.
279
+
280
+ Function node_subst_cost overrides node_match if specified.
281
+ If neither node_match nor node_subst_cost are specified then
282
+ default node substitution cost of 0 is used (node attributes
283
+ are not considered during matching).
284
+
285
+ If node_del_cost is not specified then default node deletion
286
+ cost of 1 is used. If node_ins_cost is not specified then
287
+ default node insertion cost of 1 is used.
288
+
289
+ edge_subst_cost, edge_del_cost, edge_ins_cost : callable
290
+ Functions that return the costs of edge substitution, edge
291
+ deletion, and edge insertion, respectively.
292
+
293
+ The functions will be called like
294
+
295
+ edge_subst_cost(G1[u1][v1], G2[u2][v2]),
296
+ edge_del_cost(G1[u1][v1]),
297
+ edge_ins_cost(G2[u2][v2]).
298
+
299
+ That is, the functions will receive the edge attribute
300
+ dictionaries as inputs. The functions are expected to return
301
+ positive numeric values.
302
+
303
+ Function edge_subst_cost overrides edge_match if specified.
304
+ If neither edge_match nor edge_subst_cost are specified then
305
+ default edge substitution cost of 0 is used (edge attributes
306
+ are not considered during matching).
307
+
308
+ If edge_del_cost is not specified then default edge deletion
309
+ cost of 1 is used. If edge_ins_cost is not specified then
310
+ default edge insertion cost of 1 is used.
311
+
312
+ upper_bound : numeric
313
+ Maximum edit distance to consider.
314
+
315
+ Returns
316
+ -------
317
+ edit_paths : list of tuples (node_edit_path, edge_edit_path)
318
+ - node_edit_path : list of tuples ``(u, v)`` indicating node transformations
319
+ between `G1` and `G2`. ``u`` is `None` for insertion, ``v`` is `None`
320
+ for deletion.
321
+ - edge_edit_path : list of tuples ``((u1, v1), (u2, v2))`` indicating edge
322
+ transformations between `G1` and `G2`. ``(None, (u2,v2))`` for insertion
323
+ and ``((u1,v1), None)`` for deletion.
324
+
325
+ cost : numeric
326
+ Optimal edit path cost (graph edit distance). When the cost
327
+ is zero, it indicates that `G1` and `G2` are isomorphic.
328
+
329
+ Examples
330
+ --------
331
+ >>> G1 = nx.cycle_graph(4)
332
+ >>> G2 = nx.wheel_graph(5)
333
+ >>> paths, cost = nx.optimal_edit_paths(G1, G2)
334
+ >>> len(paths)
335
+ 40
336
+ >>> cost
337
+ 5.0
338
+
339
+ Notes
340
+ -----
341
+ To transform `G1` into a graph isomorphic to `G2`, apply the node
342
+ and edge edits in the returned ``edit_paths``.
343
+ In the case of isomorphic graphs, the cost is zero, and the paths
344
+ represent different isomorphic mappings (isomorphisms). That is, the
345
+ edits involve renaming nodes and edges to match the structure of `G2`.
346
+
347
+ See Also
348
+ --------
349
+ graph_edit_distance, optimize_edit_paths
350
+
351
+ References
352
+ ----------
353
+ .. [1] Zeina Abu-Aisheh, Romain Raveaux, Jean-Yves Ramel, Patrick
354
+ Martineau. An Exact Graph Edit Distance Algorithm for Solving
355
+ Pattern Recognition Problems. 4th International Conference on
356
+ Pattern Recognition Applications and Methods 2015, Jan 2015,
357
+ Lisbon, Portugal. 2015,
358
+ <10.5220/0005209202710278>. <hal-01168816>
359
+ https://hal.archives-ouvertes.fr/hal-01168816
360
+
361
+ """
362
+ paths = []
363
+ bestcost = None
364
+ for vertex_path, edge_path, cost in optimize_edit_paths(
365
+ G1,
366
+ G2,
367
+ node_match,
368
+ edge_match,
369
+ node_subst_cost,
370
+ node_del_cost,
371
+ node_ins_cost,
372
+ edge_subst_cost,
373
+ edge_del_cost,
374
+ edge_ins_cost,
375
+ upper_bound,
376
+ False,
377
+ ):
378
+ # assert bestcost is None or cost <= bestcost
379
+ if bestcost is not None and cost < bestcost:
380
+ paths = []
381
+ paths.append((vertex_path, edge_path))
382
+ bestcost = cost
383
+ return paths, bestcost
384
+
385
+
386
+ @nx._dispatchable(graphs={"G1": 0, "G2": 1})
387
+ def optimize_graph_edit_distance(
388
+ G1,
389
+ G2,
390
+ node_match=None,
391
+ edge_match=None,
392
+ node_subst_cost=None,
393
+ node_del_cost=None,
394
+ node_ins_cost=None,
395
+ edge_subst_cost=None,
396
+ edge_del_cost=None,
397
+ edge_ins_cost=None,
398
+ upper_bound=None,
399
+ ):
400
+ """Returns consecutive approximations of GED (graph edit distance)
401
+ between graphs G1 and G2.
402
+
403
+ Graph edit distance is a graph similarity measure analogous to
404
+ Levenshtein distance for strings. It is defined as minimum cost
405
+ of edit path (sequence of node and edge edit operations)
406
+ transforming graph G1 to graph isomorphic to G2.
407
+
408
+ Parameters
409
+ ----------
410
+ G1, G2: graphs
411
+ The two graphs G1 and G2 must be of the same type.
412
+
413
+ node_match : callable
414
+ A function that returns True if node n1 in G1 and n2 in G2
415
+ should be considered equal during matching.
416
+
417
+ The function will be called like
418
+
419
+ node_match(G1.nodes[n1], G2.nodes[n2]).
420
+
421
+ That is, the function will receive the node attribute
422
+ dictionaries for n1 and n2 as inputs.
423
+
424
+ Ignored if node_subst_cost is specified. If neither
425
+ node_match nor node_subst_cost are specified then node
426
+ attributes are not considered.
427
+
428
+ edge_match : callable
429
+ A function that returns True if the edge attribute dictionaries
430
+ for the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should
431
+ be considered equal during matching.
432
+
433
+ The function will be called like
434
+
435
+ edge_match(G1[u1][v1], G2[u2][v2]).
436
+
437
+ That is, the function will receive the edge attribute
438
+ dictionaries of the edges under consideration.
439
+
440
+ Ignored if edge_subst_cost is specified. If neither
441
+ edge_match nor edge_subst_cost are specified then edge
442
+ attributes are not considered.
443
+
444
+ node_subst_cost, node_del_cost, node_ins_cost : callable
445
+ Functions that return the costs of node substitution, node
446
+ deletion, and node insertion, respectively.
447
+
448
+ The functions will be called like
449
+
450
+ node_subst_cost(G1.nodes[n1], G2.nodes[n2]),
451
+ node_del_cost(G1.nodes[n1]),
452
+ node_ins_cost(G2.nodes[n2]).
453
+
454
+ That is, the functions will receive the node attribute
455
+ dictionaries as inputs. The functions are expected to return
456
+ positive numeric values.
457
+
458
+ Function node_subst_cost overrides node_match if specified.
459
+ If neither node_match nor node_subst_cost are specified then
460
+ default node substitution cost of 0 is used (node attributes
461
+ are not considered during matching).
462
+
463
+ If node_del_cost is not specified then default node deletion
464
+ cost of 1 is used. If node_ins_cost is not specified then
465
+ default node insertion cost of 1 is used.
466
+
467
+ edge_subst_cost, edge_del_cost, edge_ins_cost : callable
468
+ Functions that return the costs of edge substitution, edge
469
+ deletion, and edge insertion, respectively.
470
+
471
+ The functions will be called like
472
+
473
+ edge_subst_cost(G1[u1][v1], G2[u2][v2]),
474
+ edge_del_cost(G1[u1][v1]),
475
+ edge_ins_cost(G2[u2][v2]).
476
+
477
+ That is, the functions will receive the edge attribute
478
+ dictionaries as inputs. The functions are expected to return
479
+ positive numeric values.
480
+
481
+ Function edge_subst_cost overrides edge_match if specified.
482
+ If neither edge_match nor edge_subst_cost are specified then
483
+ default edge substitution cost of 0 is used (edge attributes
484
+ are not considered during matching).
485
+
486
+ If edge_del_cost is not specified then default edge deletion
487
+ cost of 1 is used. If edge_ins_cost is not specified then
488
+ default edge insertion cost of 1 is used.
489
+
490
+ upper_bound : numeric
491
+ Maximum edit distance to consider.
492
+
493
+ Returns
494
+ -------
495
+ Generator of consecutive approximations of graph edit distance.
496
+
497
+ Examples
498
+ --------
499
+ >>> G1 = nx.cycle_graph(6)
500
+ >>> G2 = nx.wheel_graph(7)
501
+ >>> for v in nx.optimize_graph_edit_distance(G1, G2):
502
+ ... minv = v
503
+ >>> minv
504
+ 7.0
505
+
506
+ See Also
507
+ --------
508
+ graph_edit_distance, optimize_edit_paths
509
+
510
+ References
511
+ ----------
512
+ .. [1] Zeina Abu-Aisheh, Romain Raveaux, Jean-Yves Ramel, Patrick
513
+ Martineau. An Exact Graph Edit Distance Algorithm for Solving
514
+ Pattern Recognition Problems. 4th International Conference on
515
+ Pattern Recognition Applications and Methods 2015, Jan 2015,
516
+ Lisbon, Portugal. 2015,
517
+ <10.5220/0005209202710278>. <hal-01168816>
518
+ https://hal.archives-ouvertes.fr/hal-01168816
519
+ """
520
+ for _, _, cost in optimize_edit_paths(
521
+ G1,
522
+ G2,
523
+ node_match,
524
+ edge_match,
525
+ node_subst_cost,
526
+ node_del_cost,
527
+ node_ins_cost,
528
+ edge_subst_cost,
529
+ edge_del_cost,
530
+ edge_ins_cost,
531
+ upper_bound,
532
+ True,
533
+ ):
534
+ yield cost
535
+
536
+
537
+ @nx._dispatchable(
538
+ graphs={"G1": 0, "G2": 1}, preserve_edge_attrs=True, preserve_node_attrs=True
539
+ )
540
+ def optimize_edit_paths(
541
+ G1,
542
+ G2,
543
+ node_match=None,
544
+ edge_match=None,
545
+ node_subst_cost=None,
546
+ node_del_cost=None,
547
+ node_ins_cost=None,
548
+ edge_subst_cost=None,
549
+ edge_del_cost=None,
550
+ edge_ins_cost=None,
551
+ upper_bound=None,
552
+ strictly_decreasing=True,
553
+ roots=None,
554
+ timeout=None,
555
+ ):
556
+ """GED (graph edit distance) calculation: advanced interface.
557
+
558
+ Graph edit path is a sequence of node and edge edit operations
559
+ transforming graph G1 to graph isomorphic to G2. Edit operations
560
+ include substitutions, deletions, and insertions.
561
+
562
+ Graph edit distance is defined as minimum cost of edit path.
563
+
564
+ Parameters
565
+ ----------
566
+ G1, G2: graphs
567
+ The two graphs G1 and G2 must be of the same type.
568
+
569
+ node_match : callable
570
+ A function that returns True if node n1 in G1 and n2 in G2
571
+ should be considered equal during matching.
572
+
573
+ The function will be called like
574
+
575
+ node_match(G1.nodes[n1], G2.nodes[n2]).
576
+
577
+ That is, the function will receive the node attribute
578
+ dictionaries for n1 and n2 as inputs.
579
+
580
+ Ignored if node_subst_cost is specified. If neither
581
+ node_match nor node_subst_cost are specified then node
582
+ attributes are not considered.
583
+
584
+ edge_match : callable
585
+ A function that returns True if the edge attribute dictionaries
586
+ for the pair of nodes (u1, v1) in G1 and (u2, v2) in G2 should
587
+ be considered equal during matching.
588
+
589
+ The function will be called like
590
+
591
+ edge_match(G1[u1][v1], G2[u2][v2]).
592
+
593
+ That is, the function will receive the edge attribute
594
+ dictionaries of the edges under consideration.
595
+
596
+ Ignored if edge_subst_cost is specified. If neither
597
+ edge_match nor edge_subst_cost are specified then edge
598
+ attributes are not considered.
599
+
600
+ node_subst_cost, node_del_cost, node_ins_cost : callable
601
+ Functions that return the costs of node substitution, node
602
+ deletion, and node insertion, respectively.
603
+
604
+ The functions will be called like
605
+
606
+ node_subst_cost(G1.nodes[n1], G2.nodes[n2]),
607
+ node_del_cost(G1.nodes[n1]),
608
+ node_ins_cost(G2.nodes[n2]).
609
+
610
+ That is, the functions will receive the node attribute
611
+ dictionaries as inputs. The functions are expected to return
612
+ positive numeric values.
613
+
614
+ Function node_subst_cost overrides node_match if specified.
615
+ If neither node_match nor node_subst_cost are specified then
616
+ default node substitution cost of 0 is used (node attributes
617
+ are not considered during matching).
618
+
619
+ If node_del_cost is not specified then default node deletion
620
+ cost of 1 is used. If node_ins_cost is not specified then
621
+ default node insertion cost of 1 is used.
622
+
623
+ edge_subst_cost, edge_del_cost, edge_ins_cost : callable
624
+ Functions that return the costs of edge substitution, edge
625
+ deletion, and edge insertion, respectively.
626
+
627
+ The functions will be called like
628
+
629
+ edge_subst_cost(G1[u1][v1], G2[u2][v2]),
630
+ edge_del_cost(G1[u1][v1]),
631
+ edge_ins_cost(G2[u2][v2]).
632
+
633
+ That is, the functions will receive the edge attribute
634
+ dictionaries as inputs. The functions are expected to return
635
+ positive numeric values.
636
+
637
+ Function edge_subst_cost overrides edge_match if specified.
638
+ If neither edge_match nor edge_subst_cost are specified then
639
+ default edge substitution cost of 0 is used (edge attributes
640
+ are not considered during matching).
641
+
642
+ If edge_del_cost is not specified then default edge deletion
643
+ cost of 1 is used. If edge_ins_cost is not specified then
644
+ default edge insertion cost of 1 is used.
645
+
646
+ upper_bound : numeric
647
+ Maximum edit distance to consider.
648
+
649
+ strictly_decreasing : bool
650
+ If True, return consecutive approximations of strictly
651
+ decreasing cost. Otherwise, return all edit paths of cost
652
+ less than or equal to the previous minimum cost.
653
+
654
+ roots : 2-tuple
655
+ Tuple where first element is a node in G1 and the second
656
+ is a node in G2.
657
+ These nodes are forced to be matched in the comparison to
658
+ allow comparison between rooted graphs.
659
+
660
+ timeout : numeric
661
+ Maximum number of seconds to execute.
662
+ After timeout is met, the current best GED is returned.
663
+
664
+ Returns
665
+ -------
666
+ Generator of tuples (node_edit_path, edge_edit_path, cost)
667
+ node_edit_path : list of tuples (u, v)
668
+ edge_edit_path : list of tuples ((u1, v1), (u2, v2))
669
+ cost : numeric
670
+
671
+ See Also
672
+ --------
673
+ graph_edit_distance, optimize_graph_edit_distance, optimal_edit_paths
674
+
675
+ References
676
+ ----------
677
+ .. [1] Zeina Abu-Aisheh, Romain Raveaux, Jean-Yves Ramel, Patrick
678
+ Martineau. An Exact Graph Edit Distance Algorithm for Solving
679
+ Pattern Recognition Problems. 4th International Conference on
680
+ Pattern Recognition Applications and Methods 2015, Jan 2015,
681
+ Lisbon, Portugal. 2015,
682
+ <10.5220/0005209202710278>. <hal-01168816>
683
+ https://hal.archives-ouvertes.fr/hal-01168816
684
+
685
+ """
686
+ # TODO: support DiGraph
687
+
688
+ import numpy as np
689
+ import scipy as sp
690
+
691
+ @dataclass
692
+ class CostMatrix:
693
+ C: ...
694
+ lsa_row_ind: ...
695
+ lsa_col_ind: ...
696
+ ls: ...
697
+
698
+ def make_CostMatrix(C, m, n):
699
+ # assert(C.shape == (m + n, m + n))
700
+ lsa_row_ind, lsa_col_ind = sp.optimize.linear_sum_assignment(C)
701
+
702
+ # Fixup dummy assignments:
703
+ # each substitution i<->j should have dummy assignment m+j<->n+i
704
+ # NOTE: fast reduce of Cv relies on it
705
+ # Create masks for substitution and dummy indices
706
+ is_subst = (lsa_row_ind < m) & (lsa_col_ind < n)
707
+ is_dummy = (lsa_row_ind >= m) & (lsa_col_ind >= n)
708
+
709
+ # Map dummy assignments to the correct indices
710
+ lsa_row_ind[is_dummy] = lsa_col_ind[is_subst] + m
711
+ lsa_col_ind[is_dummy] = lsa_row_ind[is_subst] + n
712
+
713
+ return CostMatrix(
714
+ C, lsa_row_ind, lsa_col_ind, C[lsa_row_ind, lsa_col_ind].sum()
715
+ )
716
+
717
+ def extract_C(C, i, j, m, n):
718
+ # assert(C.shape == (m + n, m + n))
719
+ row_ind = [k in i or k - m in j for k in range(m + n)]
720
+ col_ind = [k in j or k - n in i for k in range(m + n)]
721
+ return C[row_ind, :][:, col_ind]
722
+
723
+ def reduce_C(C, i, j, m, n):
724
+ # assert(C.shape == (m + n, m + n))
725
+ row_ind = [k not in i and k - m not in j for k in range(m + n)]
726
+ col_ind = [k not in j and k - n not in i for k in range(m + n)]
727
+ return C[row_ind, :][:, col_ind]
728
+
729
+ def reduce_ind(ind, i):
730
+ # assert set(ind) == set(range(len(ind)))
731
+ rind = ind[[k not in i for k in ind]]
732
+ for k in set(i):
733
+ rind[rind >= k] -= 1
734
+ return rind
735
+
736
+ def match_edges(u, v, pending_g, pending_h, Ce, matched_uv=None):
737
+ """
738
+ Parameters:
739
+ u, v: matched vertices, u=None or v=None for
740
+ deletion/insertion
741
+ pending_g, pending_h: lists of edges not yet mapped
742
+ Ce: CostMatrix of pending edge mappings
743
+ matched_uv: partial vertex edit path
744
+ list of tuples (u, v) of previously matched vertex
745
+ mappings u<->v, u=None or v=None for
746
+ deletion/insertion
747
+
748
+ Returns:
749
+ list of (i, j): indices of edge mappings g<->h
750
+ localCe: local CostMatrix of edge mappings
751
+ (basically submatrix of Ce at cross of rows i, cols j)
752
+ """
753
+ M = len(pending_g)
754
+ N = len(pending_h)
755
+ # assert Ce.C.shape == (M + N, M + N)
756
+
757
+ # only attempt to match edges after one node match has been made
758
+ # this will stop self-edges on the first node being automatically deleted
759
+ # even when a substitution is the better option
760
+
761
+ substitution_possible = M and N
762
+ at_least_one_node_match = matched_uv is None or len(matched_uv) == 0
763
+ if at_least_one_node_match and substitution_possible:
764
+ g_ind = []
765
+ h_ind = []
766
+ else:
767
+ g_ind = [
768
+ i
769
+ for i in range(M)
770
+ if pending_g[i][:2] == (u, u)
771
+ or any(
772
+ pending_g[i][:2] in ((p, u), (u, p), (p, p)) for p, q in matched_uv
773
+ )
774
+ ]
775
+ h_ind = [
776
+ j
777
+ for j in range(N)
778
+ if pending_h[j][:2] == (v, v)
779
+ or any(
780
+ pending_h[j][:2] in ((q, v), (v, q), (q, q)) for p, q in matched_uv
781
+ )
782
+ ]
783
+
784
+ m = len(g_ind)
785
+ n = len(h_ind)
786
+
787
+ if m or n:
788
+ C = extract_C(Ce.C, g_ind, h_ind, M, N)
789
+ # assert C.shape == (m + n, m + n)
790
+
791
+ # Forbid structurally invalid matches
792
+ # NOTE: inf remembered from Ce construction
793
+ for k, i in enumerate(g_ind):
794
+ g = pending_g[i][:2]
795
+ for l, j in enumerate(h_ind):
796
+ h = pending_h[j][:2]
797
+ if nx.is_directed(G1) or nx.is_directed(G2):
798
+ if any(
799
+ g == (p, u) and h == (q, v) or g == (u, p) and h == (v, q)
800
+ for p, q in matched_uv
801
+ ):
802
+ continue
803
+ else:
804
+ if any(
805
+ g in ((p, u), (u, p)) and h in ((q, v), (v, q))
806
+ for p, q in matched_uv
807
+ ):
808
+ continue
809
+ if g == (u, u) or any(g == (p, p) for p, q in matched_uv):
810
+ continue
811
+ if h == (v, v) or any(h == (q, q) for p, q in matched_uv):
812
+ continue
813
+ C[k, l] = inf
814
+
815
+ localCe = make_CostMatrix(C, m, n)
816
+ ij = [
817
+ (
818
+ g_ind[k] if k < m else M + h_ind[l],
819
+ h_ind[l] if l < n else N + g_ind[k],
820
+ )
821
+ for k, l in zip(localCe.lsa_row_ind, localCe.lsa_col_ind)
822
+ if k < m or l < n
823
+ ]
824
+
825
+ else:
826
+ ij = []
827
+ localCe = CostMatrix(np.empty((0, 0)), [], [], 0)
828
+
829
+ return ij, localCe
830
+
831
+ def reduce_Ce(Ce, ij, m, n):
832
+ if len(ij):
833
+ i, j = zip(*ij)
834
+ m_i = m - sum(1 for t in i if t < m)
835
+ n_j = n - sum(1 for t in j if t < n)
836
+ return make_CostMatrix(reduce_C(Ce.C, i, j, m, n), m_i, n_j)
837
+ return Ce
838
+
839
+ def get_edit_ops(
840
+ matched_uv, pending_u, pending_v, Cv, pending_g, pending_h, Ce, matched_cost
841
+ ):
842
+ """
843
+ Parameters:
844
+ matched_uv: partial vertex edit path
845
+ list of tuples (u, v) of vertex mappings u<->v,
846
+ u=None or v=None for deletion/insertion
847
+ pending_u, pending_v: lists of vertices not yet mapped
848
+ Cv: CostMatrix of pending vertex mappings
849
+ pending_g, pending_h: lists of edges not yet mapped
850
+ Ce: CostMatrix of pending edge mappings
851
+ matched_cost: cost of partial edit path
852
+
853
+ Returns:
854
+ sequence of
855
+ (i, j): indices of vertex mapping u<->v
856
+ Cv_ij: reduced CostMatrix of pending vertex mappings
857
+ (basically Cv with row i, col j removed)
858
+ list of (x, y): indices of edge mappings g<->h
859
+ Ce_xy: reduced CostMatrix of pending edge mappings
860
+ (basically Ce with rows x, cols y removed)
861
+ cost: total cost of edit operation
862
+ NOTE: most promising ops first
863
+ """
864
+ m = len(pending_u)
865
+ n = len(pending_v)
866
+ # assert Cv.C.shape == (m + n, m + n)
867
+
868
+ # 1) a vertex mapping from optimal linear sum assignment
869
+ i, j = min(
870
+ (k, l) for k, l in zip(Cv.lsa_row_ind, Cv.lsa_col_ind) if k < m or l < n
871
+ )
872
+ xy, localCe = match_edges(
873
+ pending_u[i] if i < m else None,
874
+ pending_v[j] if j < n else None,
875
+ pending_g,
876
+ pending_h,
877
+ Ce,
878
+ matched_uv,
879
+ )
880
+ Ce_xy = reduce_Ce(Ce, xy, len(pending_g), len(pending_h))
881
+ # assert Ce.ls <= localCe.ls + Ce_xy.ls
882
+ if prune(matched_cost + Cv.ls + localCe.ls + Ce_xy.ls):
883
+ pass
884
+ else:
885
+ # get reduced Cv efficiently
886
+ Cv_ij = CostMatrix(
887
+ reduce_C(Cv.C, (i,), (j,), m, n),
888
+ reduce_ind(Cv.lsa_row_ind, (i, m + j)),
889
+ reduce_ind(Cv.lsa_col_ind, (j, n + i)),
890
+ Cv.ls - Cv.C[i, j],
891
+ )
892
+ yield (i, j), Cv_ij, xy, Ce_xy, Cv.C[i, j] + localCe.ls
893
+
894
+ # 2) other candidates, sorted by lower-bound cost estimate
895
+ other = []
896
+ fixed_i, fixed_j = i, j
897
+ if m <= n:
898
+ candidates = (
899
+ (t, fixed_j)
900
+ for t in range(m + n)
901
+ if t != fixed_i and (t < m or t == m + fixed_j)
902
+ )
903
+ else:
904
+ candidates = (
905
+ (fixed_i, t)
906
+ for t in range(m + n)
907
+ if t != fixed_j and (t < n or t == n + fixed_i)
908
+ )
909
+ for i, j in candidates:
910
+ if prune(matched_cost + Cv.C[i, j] + Ce.ls):
911
+ continue
912
+ Cv_ij = make_CostMatrix(
913
+ reduce_C(Cv.C, (i,), (j,), m, n),
914
+ m - 1 if i < m else m,
915
+ n - 1 if j < n else n,
916
+ )
917
+ # assert Cv.ls <= Cv.C[i, j] + Cv_ij.ls
918
+ if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + Ce.ls):
919
+ continue
920
+ xy, localCe = match_edges(
921
+ pending_u[i] if i < m else None,
922
+ pending_v[j] if j < n else None,
923
+ pending_g,
924
+ pending_h,
925
+ Ce,
926
+ matched_uv,
927
+ )
928
+ if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + localCe.ls):
929
+ continue
930
+ Ce_xy = reduce_Ce(Ce, xy, len(pending_g), len(pending_h))
931
+ # assert Ce.ls <= localCe.ls + Ce_xy.ls
932
+ if prune(matched_cost + Cv.C[i, j] + Cv_ij.ls + localCe.ls + Ce_xy.ls):
933
+ continue
934
+ other.append(((i, j), Cv_ij, xy, Ce_xy, Cv.C[i, j] + localCe.ls))
935
+
936
+ yield from sorted(other, key=lambda t: t[4] + t[1].ls + t[3].ls)
937
+
938
+ def get_edit_paths(
939
+ matched_uv,
940
+ pending_u,
941
+ pending_v,
942
+ Cv,
943
+ matched_gh,
944
+ pending_g,
945
+ pending_h,
946
+ Ce,
947
+ matched_cost,
948
+ ):
949
+ """
950
+ Parameters:
951
+ matched_uv: partial vertex edit path
952
+ list of tuples (u, v) of vertex mappings u<->v,
953
+ u=None or v=None for deletion/insertion
954
+ pending_u, pending_v: lists of vertices not yet mapped
955
+ Cv: CostMatrix of pending vertex mappings
956
+ matched_gh: partial edge edit path
957
+ list of tuples (g, h) of edge mappings g<->h,
958
+ g=None or h=None for deletion/insertion
959
+ pending_g, pending_h: lists of edges not yet mapped
960
+ Ce: CostMatrix of pending edge mappings
961
+ matched_cost: cost of partial edit path
962
+
963
+ Returns:
964
+ sequence of (vertex_path, edge_path, cost)
965
+ vertex_path: complete vertex edit path
966
+ list of tuples (u, v) of vertex mappings u<->v,
967
+ u=None or v=None for deletion/insertion
968
+ edge_path: complete edge edit path
969
+ list of tuples (g, h) of edge mappings g<->h,
970
+ g=None or h=None for deletion/insertion
971
+ cost: total cost of edit path
972
+ NOTE: path costs are non-increasing
973
+ """
974
+ if prune(matched_cost + Cv.ls + Ce.ls):
975
+ return
976
+
977
+ if not max(len(pending_u), len(pending_v)):
978
+ # assert not len(pending_g)
979
+ # assert not len(pending_h)
980
+ # path completed!
981
+ # assert matched_cost <= maxcost_value
982
+ nonlocal maxcost_value
983
+ maxcost_value = min(maxcost_value, matched_cost)
984
+ yield matched_uv, matched_gh, matched_cost
985
+
986
+ else:
987
+ edit_ops = get_edit_ops(
988
+ matched_uv,
989
+ pending_u,
990
+ pending_v,
991
+ Cv,
992
+ pending_g,
993
+ pending_h,
994
+ Ce,
995
+ matched_cost,
996
+ )
997
+ for ij, Cv_ij, xy, Ce_xy, edit_cost in edit_ops:
998
+ i, j = ij
999
+ # assert Cv.C[i, j] + sum(Ce.C[t] for t in xy) == edit_cost
1000
+ if prune(matched_cost + edit_cost + Cv_ij.ls + Ce_xy.ls):
1001
+ continue
1002
+
1003
+ # dive deeper
1004
+ u = pending_u.pop(i) if i < len(pending_u) else None
1005
+ v = pending_v.pop(j) if j < len(pending_v) else None
1006
+ matched_uv.append((u, v))
1007
+ for x, y in xy:
1008
+ len_g = len(pending_g)
1009
+ len_h = len(pending_h)
1010
+ matched_gh.append(
1011
+ (
1012
+ pending_g[x] if x < len_g else None,
1013
+ pending_h[y] if y < len_h else None,
1014
+ )
1015
+ )
1016
+ sortedx = sorted(x for x, y in xy)
1017
+ sortedy = sorted(y for x, y in xy)
1018
+ G = [
1019
+ (pending_g.pop(x) if x < len(pending_g) else None)
1020
+ for x in reversed(sortedx)
1021
+ ]
1022
+ H = [
1023
+ (pending_h.pop(y) if y < len(pending_h) else None)
1024
+ for y in reversed(sortedy)
1025
+ ]
1026
+
1027
+ yield from get_edit_paths(
1028
+ matched_uv,
1029
+ pending_u,
1030
+ pending_v,
1031
+ Cv_ij,
1032
+ matched_gh,
1033
+ pending_g,
1034
+ pending_h,
1035
+ Ce_xy,
1036
+ matched_cost + edit_cost,
1037
+ )
1038
+
1039
+ # backtrack
1040
+ if u is not None:
1041
+ pending_u.insert(i, u)
1042
+ if v is not None:
1043
+ pending_v.insert(j, v)
1044
+ matched_uv.pop()
1045
+ for x, g in zip(sortedx, reversed(G)):
1046
+ if g is not None:
1047
+ pending_g.insert(x, g)
1048
+ for y, h in zip(sortedy, reversed(H)):
1049
+ if h is not None:
1050
+ pending_h.insert(y, h)
1051
+ for _ in xy:
1052
+ matched_gh.pop()
1053
+
1054
+ # Initialization
1055
+
1056
+ pending_u = list(G1.nodes)
1057
+ pending_v = list(G2.nodes)
1058
+
1059
+ initial_cost = 0
1060
+ if roots:
1061
+ root_u, root_v = roots
1062
+ if root_u not in pending_u or root_v not in pending_v:
1063
+ raise nx.NodeNotFound("Root node not in graph.")
1064
+
1065
+ # remove roots from pending
1066
+ pending_u.remove(root_u)
1067
+ pending_v.remove(root_v)
1068
+
1069
+ # cost matrix of vertex mappings
1070
+ m = len(pending_u)
1071
+ n = len(pending_v)
1072
+ C = np.zeros((m + n, m + n))
1073
+ if node_subst_cost:
1074
+ C[0:m, 0:n] = np.array(
1075
+ [
1076
+ node_subst_cost(G1.nodes[u], G2.nodes[v])
1077
+ for u in pending_u
1078
+ for v in pending_v
1079
+ ]
1080
+ ).reshape(m, n)
1081
+ if roots:
1082
+ initial_cost = node_subst_cost(G1.nodes[root_u], G2.nodes[root_v])
1083
+ elif node_match:
1084
+ C[0:m, 0:n] = np.array(
1085
+ [
1086
+ 1 - int(node_match(G1.nodes[u], G2.nodes[v]))
1087
+ for u in pending_u
1088
+ for v in pending_v
1089
+ ]
1090
+ ).reshape(m, n)
1091
+ if roots:
1092
+ initial_cost = 1 - node_match(G1.nodes[root_u], G2.nodes[root_v])
1093
+ else:
1094
+ # all zeroes
1095
+ pass
1096
+ # assert not min(m, n) or C[0:m, 0:n].min() >= 0
1097
+ if node_del_cost:
1098
+ del_costs = [node_del_cost(G1.nodes[u]) for u in pending_u]
1099
+ else:
1100
+ del_costs = [1] * len(pending_u)
1101
+ # assert not m or min(del_costs) >= 0
1102
+ if node_ins_cost:
1103
+ ins_costs = [node_ins_cost(G2.nodes[v]) for v in pending_v]
1104
+ else:
1105
+ ins_costs = [1] * len(pending_v)
1106
+ # assert not n or min(ins_costs) >= 0
1107
+ inf = C[0:m, 0:n].sum() + sum(del_costs) + sum(ins_costs) + 1
1108
+ C[0:m, n : n + m] = np.array(
1109
+ [del_costs[i] if i == j else inf for i in range(m) for j in range(m)]
1110
+ ).reshape(m, m)
1111
+ C[m : m + n, 0:n] = np.array(
1112
+ [ins_costs[i] if i == j else inf for i in range(n) for j in range(n)]
1113
+ ).reshape(n, n)
1114
+ Cv = make_CostMatrix(C, m, n)
1115
+
1116
+ pending_g = list(G1.edges)
1117
+ pending_h = list(G2.edges)
1118
+
1119
+ # cost matrix of edge mappings
1120
+ m = len(pending_g)
1121
+ n = len(pending_h)
1122
+ C = np.zeros((m + n, m + n))
1123
+ if edge_subst_cost:
1124
+ C[0:m, 0:n] = np.array(
1125
+ [
1126
+ edge_subst_cost(G1.edges[g], G2.edges[h])
1127
+ for g in pending_g
1128
+ for h in pending_h
1129
+ ]
1130
+ ).reshape(m, n)
1131
+ elif edge_match:
1132
+ C[0:m, 0:n] = np.array(
1133
+ [
1134
+ 1 - int(edge_match(G1.edges[g], G2.edges[h]))
1135
+ for g in pending_g
1136
+ for h in pending_h
1137
+ ]
1138
+ ).reshape(m, n)
1139
+ else:
1140
+ # all zeroes
1141
+ pass
1142
+ # assert not min(m, n) or C[0:m, 0:n].min() >= 0
1143
+ if edge_del_cost:
1144
+ del_costs = [edge_del_cost(G1.edges[g]) for g in pending_g]
1145
+ else:
1146
+ del_costs = [1] * len(pending_g)
1147
+ # assert not m or min(del_costs) >= 0
1148
+ if edge_ins_cost:
1149
+ ins_costs = [edge_ins_cost(G2.edges[h]) for h in pending_h]
1150
+ else:
1151
+ ins_costs = [1] * len(pending_h)
1152
+ # assert not n or min(ins_costs) >= 0
1153
+ inf = C[0:m, 0:n].sum() + sum(del_costs) + sum(ins_costs) + 1
1154
+ C[0:m, n : n + m] = np.array(
1155
+ [del_costs[i] if i == j else inf for i in range(m) for j in range(m)]
1156
+ ).reshape(m, m)
1157
+ C[m : m + n, 0:n] = np.array(
1158
+ [ins_costs[i] if i == j else inf for i in range(n) for j in range(n)]
1159
+ ).reshape(n, n)
1160
+ Ce = make_CostMatrix(C, m, n)
1161
+
1162
+ maxcost_value = Cv.C.sum() + Ce.C.sum() + 1
1163
+
1164
+ if timeout is not None:
1165
+ if timeout <= 0:
1166
+ raise nx.NetworkXError("Timeout value must be greater than 0")
1167
+ start = time.perf_counter()
1168
+
1169
+ def prune(cost):
1170
+ if timeout is not None:
1171
+ if time.perf_counter() - start > timeout:
1172
+ return True
1173
+ if upper_bound is not None:
1174
+ if cost > upper_bound:
1175
+ return True
1176
+ if cost > maxcost_value:
1177
+ return True
1178
+ if strictly_decreasing and cost >= maxcost_value:
1179
+ return True
1180
+ return False
1181
+
1182
+ # Now go!
1183
+
1184
+ done_uv = [] if roots is None else [roots]
1185
+
1186
+ for vertex_path, edge_path, cost in get_edit_paths(
1187
+ done_uv, pending_u, pending_v, Cv, [], pending_g, pending_h, Ce, initial_cost
1188
+ ):
1189
+ # assert sorted(G1.nodes) == sorted(u for u, v in vertex_path if u is not None)
1190
+ # assert sorted(G2.nodes) == sorted(v for u, v in vertex_path if v is not None)
1191
+ # assert sorted(G1.edges) == sorted(g for g, h in edge_path if g is not None)
1192
+ # assert sorted(G2.edges) == sorted(h for g, h in edge_path if h is not None)
1193
+ # print(vertex_path, edge_path, cost, file = sys.stderr)
1194
+ # assert cost == maxcost_value
1195
+ yield list(vertex_path), list(edge_path), float(cost)
1196
+
1197
+
1198
+ @nx._dispatchable
1199
+ def simrank_similarity(
1200
+ G,
1201
+ source=None,
1202
+ target=None,
1203
+ importance_factor=0.9,
1204
+ max_iterations=1000,
1205
+ tolerance=1e-4,
1206
+ ):
1207
+ """Returns the SimRank similarity of nodes in the graph ``G``.
1208
+
1209
+ SimRank is a similarity metric that says "two objects are considered
1210
+ to be similar if they are referenced by similar objects." [1]_.
1211
+
1212
+ The pseudo-code definition from the paper is::
1213
+
1214
+ def simrank(G, u, v):
1215
+ in_neighbors_u = G.predecessors(u)
1216
+ in_neighbors_v = G.predecessors(v)
1217
+ scale = C / (len(in_neighbors_u) * len(in_neighbors_v))
1218
+ return scale * sum(
1219
+ simrank(G, w, x) for w, x in product(in_neighbors_u, in_neighbors_v)
1220
+ )
1221
+
1222
+ where ``G`` is the graph, ``u`` is the source, ``v`` is the target,
1223
+ and ``C`` is a float decay or importance factor between 0 and 1.
1224
+
1225
+ The SimRank algorithm for determining node similarity is defined in
1226
+ [2]_.
1227
+
1228
+ Parameters
1229
+ ----------
1230
+ G : NetworkX graph
1231
+ A NetworkX graph
1232
+
1233
+ source : node
1234
+ If this is specified, the returned dictionary maps each node
1235
+ ``v`` in the graph to the similarity between ``source`` and
1236
+ ``v``.
1237
+
1238
+ target : node
1239
+ If both ``source`` and ``target`` are specified, the similarity
1240
+ value between ``source`` and ``target`` is returned. If
1241
+ ``target`` is specified but ``source`` is not, this argument is
1242
+ ignored.
1243
+
1244
+ importance_factor : float
1245
+ The relative importance of indirect neighbors with respect to
1246
+ direct neighbors.
1247
+
1248
+ max_iterations : integer
1249
+ Maximum number of iterations.
1250
+
1251
+ tolerance : float
1252
+ Error tolerance used to check convergence. When an iteration of
1253
+ the algorithm finds that no similarity value changes more than
1254
+ this amount, the algorithm halts.
1255
+
1256
+ Returns
1257
+ -------
1258
+ similarity : dictionary or float
1259
+ If ``source`` and ``target`` are both ``None``, this returns a
1260
+ dictionary of dictionaries, where keys are node pairs and value
1261
+ are similarity of the pair of nodes.
1262
+
1263
+ If ``source`` is not ``None`` but ``target`` is, this returns a
1264
+ dictionary mapping node to the similarity of ``source`` and that
1265
+ node.
1266
+
1267
+ If neither ``source`` nor ``target`` is ``None``, this returns
1268
+ the similarity value for the given pair of nodes.
1269
+
1270
+ Raises
1271
+ ------
1272
+ ExceededMaxIterations
1273
+ If the algorithm does not converge within ``max_iterations``.
1274
+
1275
+ NodeNotFound
1276
+ If either ``source`` or ``target`` is not in `G`.
1277
+
1278
+ Examples
1279
+ --------
1280
+ >>> G = nx.cycle_graph(2)
1281
+ >>> nx.simrank_similarity(G)
1282
+ {0: {0: 1.0, 1: 0.0}, 1: {0: 0.0, 1: 1.0}}
1283
+ >>> nx.simrank_similarity(G, source=0)
1284
+ {0: 1.0, 1: 0.0}
1285
+ >>> nx.simrank_similarity(G, source=0, target=0)
1286
+ 1.0
1287
+
1288
+ The result of this function can be converted to a numpy array
1289
+ representing the SimRank matrix by using the node order of the
1290
+ graph to determine which row and column represent each node.
1291
+ Other ordering of nodes is also possible.
1292
+
1293
+ >>> import numpy as np
1294
+ >>> sim = nx.simrank_similarity(G)
1295
+ >>> np.array([[sim[u][v] for v in G] for u in G])
1296
+ array([[1., 0.],
1297
+ [0., 1.]])
1298
+ >>> sim_1d = nx.simrank_similarity(G, source=0)
1299
+ >>> np.array([sim[0][v] for v in G])
1300
+ array([1., 0.])
1301
+
1302
+ References
1303
+ ----------
1304
+ .. [1] https://en.wikipedia.org/wiki/SimRank
1305
+ .. [2] G. Jeh and J. Widom.
1306
+ "SimRank: a measure of structural-context similarity",
1307
+ In KDD'02: Proceedings of the Eighth ACM SIGKDD
1308
+ International Conference on Knowledge Discovery and Data Mining,
1309
+ pp. 538--543. ACM Press, 2002.
1310
+ """
1311
+ import numpy as np
1312
+
1313
+ nodelist = list(G)
1314
+ if source is not None:
1315
+ if source not in nodelist:
1316
+ raise nx.NodeNotFound(f"Source node {source} not in G")
1317
+ else:
1318
+ s_indx = nodelist.index(source)
1319
+ else:
1320
+ s_indx = None
1321
+
1322
+ if target is not None:
1323
+ if target not in nodelist:
1324
+ raise nx.NodeNotFound(f"Target node {target} not in G")
1325
+ else:
1326
+ t_indx = nodelist.index(target)
1327
+ else:
1328
+ t_indx = None
1329
+
1330
+ x = _simrank_similarity_numpy(
1331
+ G, s_indx, t_indx, importance_factor, max_iterations, tolerance
1332
+ )
1333
+
1334
+ if isinstance(x, np.ndarray):
1335
+ if x.ndim == 1:
1336
+ return dict(zip(G, x.tolist()))
1337
+ # else x.ndim == 2
1338
+ return {u: dict(zip(G, row)) for u, row in zip(G, x.tolist())}
1339
+ return float(x)
1340
+
1341
+
1342
+ def _simrank_similarity_python(
1343
+ G,
1344
+ source=None,
1345
+ target=None,
1346
+ importance_factor=0.9,
1347
+ max_iterations=1000,
1348
+ tolerance=1e-4,
1349
+ ):
1350
+ """Returns the SimRank similarity of nodes in the graph ``G``.
1351
+
1352
+ This pure Python version is provided for pedagogical purposes.
1353
+
1354
+ Examples
1355
+ --------
1356
+ >>> G = nx.cycle_graph(2)
1357
+ >>> nx.similarity._simrank_similarity_python(G)
1358
+ {0: {0: 1, 1: 0.0}, 1: {0: 0.0, 1: 1}}
1359
+ >>> nx.similarity._simrank_similarity_python(G, source=0)
1360
+ {0: 1, 1: 0.0}
1361
+ >>> nx.similarity._simrank_similarity_python(G, source=0, target=0)
1362
+ 1
1363
+ """
1364
+ # build up our similarity adjacency dictionary output
1365
+ newsim = {u: {v: 1 if u == v else 0 for v in G} for u in G}
1366
+
1367
+ # These functions compute the update to the similarity value of the nodes
1368
+ # `u` and `v` with respect to the previous similarity values.
1369
+ def avg_sim(s):
1370
+ return sum(newsim[w][x] for (w, x) in s) / len(s) if s else 0.0
1371
+
1372
+ Gadj = G.pred if G.is_directed() else G.adj
1373
+
1374
+ def sim(u, v):
1375
+ return importance_factor * avg_sim(list(product(Gadj[u], Gadj[v])))
1376
+
1377
+ for its in range(max_iterations):
1378
+ oldsim = newsim
1379
+ newsim = {u: {v: sim(u, v) if u != v else 1 for v in G} for u in G}
1380
+ is_close = all(
1381
+ all(
1382
+ abs(newsim[u][v] - old) <= tolerance * (1 + abs(old))
1383
+ for v, old in nbrs.items()
1384
+ )
1385
+ for u, nbrs in oldsim.items()
1386
+ )
1387
+ if is_close:
1388
+ break
1389
+
1390
+ if its + 1 == max_iterations:
1391
+ raise nx.ExceededMaxIterations(
1392
+ f"simrank did not converge after {max_iterations} iterations."
1393
+ )
1394
+
1395
+ if source is not None and target is not None:
1396
+ return newsim[source][target]
1397
+ if source is not None:
1398
+ return newsim[source]
1399
+ return newsim
1400
+
1401
+
1402
+ def _simrank_similarity_numpy(
1403
+ G,
1404
+ source=None,
1405
+ target=None,
1406
+ importance_factor=0.9,
1407
+ max_iterations=1000,
1408
+ tolerance=1e-4,
1409
+ ):
1410
+ """Calculate SimRank of nodes in ``G`` using matrices with ``numpy``.
1411
+
1412
+ The SimRank algorithm for determining node similarity is defined in
1413
+ [1]_.
1414
+
1415
+ Parameters
1416
+ ----------
1417
+ G : NetworkX graph
1418
+ A NetworkX graph
1419
+
1420
+ source : node
1421
+ If this is specified, the returned dictionary maps each node
1422
+ ``v`` in the graph to the similarity between ``source`` and
1423
+ ``v``.
1424
+
1425
+ target : node
1426
+ If both ``source`` and ``target`` are specified, the similarity
1427
+ value between ``source`` and ``target`` is returned. If
1428
+ ``target`` is specified but ``source`` is not, this argument is
1429
+ ignored.
1430
+
1431
+ importance_factor : float
1432
+ The relative importance of indirect neighbors with respect to
1433
+ direct neighbors.
1434
+
1435
+ max_iterations : integer
1436
+ Maximum number of iterations.
1437
+
1438
+ tolerance : float
1439
+ Error tolerance used to check convergence. When an iteration of
1440
+ the algorithm finds that no similarity value changes more than
1441
+ this amount, the algorithm halts.
1442
+
1443
+ Returns
1444
+ -------
1445
+ similarity : numpy array or float
1446
+ If ``source`` and ``target`` are both ``None``, this returns a
1447
+ 2D array containing SimRank scores of the nodes.
1448
+
1449
+ If ``source`` is not ``None`` but ``target`` is, this returns an
1450
+ 1D array containing SimRank scores of ``source`` and that
1451
+ node.
1452
+
1453
+ If neither ``source`` nor ``target`` is ``None``, this returns
1454
+ the similarity value for the given pair of nodes.
1455
+
1456
+ Examples
1457
+ --------
1458
+ >>> G = nx.cycle_graph(2)
1459
+ >>> nx.similarity._simrank_similarity_numpy(G)
1460
+ array([[1., 0.],
1461
+ [0., 1.]])
1462
+ >>> nx.similarity._simrank_similarity_numpy(G, source=0)
1463
+ array([1., 0.])
1464
+ >>> nx.similarity._simrank_similarity_numpy(G, source=0, target=0)
1465
+ 1.0
1466
+
1467
+ References
1468
+ ----------
1469
+ .. [1] G. Jeh and J. Widom.
1470
+ "SimRank: a measure of structural-context similarity",
1471
+ In KDD'02: Proceedings of the Eighth ACM SIGKDD
1472
+ International Conference on Knowledge Discovery and Data Mining,
1473
+ pp. 538--543. ACM Press, 2002.
1474
+ """
1475
+ # This algorithm follows roughly
1476
+ #
1477
+ # S = max{C * (A.T * S * A), I}
1478
+ #
1479
+ # where C is the importance factor, A is the column normalized
1480
+ # adjacency matrix, and I is the identity matrix.
1481
+ import numpy as np
1482
+
1483
+ adjacency_matrix = nx.to_numpy_array(G)
1484
+
1485
+ # column-normalize the ``adjacency_matrix``
1486
+ s = np.array(adjacency_matrix.sum(axis=0))
1487
+ s[s == 0] = 1
1488
+ adjacency_matrix /= s # adjacency_matrix.sum(axis=0)
1489
+
1490
+ newsim = np.eye(len(G), dtype=np.float64)
1491
+ for its in range(max_iterations):
1492
+ prevsim = newsim.copy()
1493
+ newsim = importance_factor * ((adjacency_matrix.T @ prevsim) @ adjacency_matrix)
1494
+ np.fill_diagonal(newsim, 1.0)
1495
+
1496
+ if np.allclose(prevsim, newsim, atol=tolerance):
1497
+ break
1498
+
1499
+ if its + 1 == max_iterations:
1500
+ raise nx.ExceededMaxIterations(
1501
+ f"simrank did not converge after {max_iterations} iterations."
1502
+ )
1503
+
1504
+ if source is not None and target is not None:
1505
+ return float(newsim[source, target])
1506
+ if source is not None:
1507
+ return newsim[source]
1508
+ return newsim
1509
+
1510
+
1511
+ @np_random_state("seed")
1512
+ def _prepare_panther_paths(
1513
+ G,
1514
+ source,
1515
+ path_length=5,
1516
+ c=0.5,
1517
+ delta=0.1,
1518
+ eps=None,
1519
+ weight="weight",
1520
+ remove_isolates=True,
1521
+ k=None,
1522
+ seed=None,
1523
+ ):
1524
+ """Common preparation code for Panther similarity algorithms.
1525
+
1526
+ Parameters
1527
+ ----------
1528
+ G : NetworkX graph
1529
+ A NetworkX graph
1530
+ source : node
1531
+ Source node for similarity calculation
1532
+ path_length : int
1533
+ How long the randomly generated paths should be
1534
+ c : float
1535
+ A universal constant that controls the number of random paths to generate
1536
+ delta : float
1537
+ The probability parameter for similarity approximation
1538
+ eps : float or None
1539
+ The error bound for similarity approximation
1540
+ weight : string or None
1541
+ The name of an edge attribute that holds the numerical value used as a weight
1542
+ remove_isolates : bool
1543
+ Whether to remove isolated nodes from graph processing
1544
+ k : int or None
1545
+ The number of most similar nodes to return. If provided, validates that
1546
+ ``k`` is not greater than the number of nodes in the graph.
1547
+ seed : integer, random_state, or None (default)
1548
+ Indicator of random number generation state.
1549
+ See :ref:`Randomness<randomness>`.
1550
+
1551
+ Returns
1552
+ -------
1553
+ PantherPaths
1554
+ A tuple containing the prepared data:
1555
+ - G: The graph (possibly with isolates removed)
1556
+ - inv_node_map: Dictionary mapping node names to indices
1557
+ - index_map: Populated index map of paths
1558
+ - inv_sample_size: Inverse of sample size (for fast calculation)
1559
+ - eps: Error bound for similarity approximation
1560
+ """
1561
+ import numpy as np
1562
+
1563
+ if source not in G:
1564
+ raise nx.NodeNotFound(f"Source node {source} not in G")
1565
+
1566
+ isolates = set(nx.isolates(G))
1567
+
1568
+ if source in isolates:
1569
+ raise nx.NetworkXUnfeasible(
1570
+ f"Panther similarity is not defined for the isolated source node {source}."
1571
+ )
1572
+
1573
+ if remove_isolates:
1574
+ G = G.subgraph(node for node in G if node not in isolates).copy()
1575
+
1576
+ # According to [1], they empirically determined
1577
+ # a good value for ``eps`` to be sqrt( 1 / |E| )
1578
+ if eps is None:
1579
+ eps = np.sqrt(1.0 / G.number_of_edges())
1580
+
1581
+ num_nodes = G.number_of_nodes()
1582
+
1583
+ # Check if k is provided and validate it against the number of nodes
1584
+ if k is not None and not remove_isolates: # For panther_vector_similarity
1585
+ if num_nodes < k:
1586
+ raise nx.NetworkXUnfeasible(
1587
+ f"The number of requested nodes {k} is greater than the number of nodes {num_nodes}."
1588
+ )
1589
+
1590
+ inv_node_map = {name: index for index, name in enumerate(G)}
1591
+
1592
+ # Calculate the sample size ``R`` for how many paths
1593
+ # to randomly generate
1594
+ t_choose_2 = math.comb(path_length, 2)
1595
+ sample_size = int((c / eps**2) * (np.log2(t_choose_2) + 1 + np.log(1 / delta)))
1596
+ index_map = {}
1597
+
1598
+ # Check for isolated nodes before generating random paths
1599
+ # If there are still isolated nodes in the graph after filtering,
1600
+ # they will cause issues with path generation
1601
+ remaining_isolates = set(nx.isolates(G))
1602
+ if remaining_isolates:
1603
+ raise nx.NetworkXUnfeasible(
1604
+ f"Cannot generate random paths with isolated nodes present: {remaining_isolates}"
1605
+ )
1606
+
1607
+ # Generate the random paths and populate the index_map
1608
+ for _ in generate_random_paths(
1609
+ G,
1610
+ sample_size,
1611
+ path_length=path_length,
1612
+ index_map=index_map,
1613
+ weight=weight,
1614
+ seed=seed,
1615
+ ):
1616
+ # NOTE: index_map is modified in-place by `generate_random_paths`
1617
+ pass
1618
+
1619
+ return (
1620
+ G, # The graph with isolated nodes removed
1621
+ inv_node_map,
1622
+ index_map,
1623
+ 1 / sample_size,
1624
+ eps,
1625
+ )
1626
+
1627
+
1628
+ @np_random_state("seed")
1629
+ @nx._dispatchable(edge_attrs="weight")
1630
+ def panther_similarity(
1631
+ G,
1632
+ source,
1633
+ k=5,
1634
+ path_length=5,
1635
+ c=0.5,
1636
+ delta=0.1,
1637
+ eps=None,
1638
+ weight="weight",
1639
+ seed=None,
1640
+ ):
1641
+ r"""Returns the Panther similarity of nodes in the graph `G` to node ``v``.
1642
+
1643
+ Panther is a similarity metric that says "two objects are considered
1644
+ to be similar if they frequently appear on the same paths." [1]_.
1645
+
1646
+ Parameters
1647
+ ----------
1648
+ G : NetworkX graph
1649
+ A NetworkX graph
1650
+ source : node
1651
+ Source node for which to find the top `k` similar other nodes
1652
+ k : int (default = 5)
1653
+ The number of most similar nodes to return.
1654
+ path_length : int (default = 5)
1655
+ How long the randomly generated paths should be (``T`` in [1]_)
1656
+ c : float (default = 0.5)
1657
+ A universal constant that controls the number of random paths to generate.
1658
+ Higher values increase the number of sample paths and potentially improve
1659
+ accuracy at the cost of more computation. Defaults to 0.5 as recommended
1660
+ in [1]_.
1661
+ delta : float (default = 0.1)
1662
+ The probability that the similarity $S$ is not an epsilon-approximation to (R, phi),
1663
+ where $R$ is the number of random paths and $\phi$ is the probability
1664
+ that an element sampled from a set $A \subseteq D$, where $D$ is the domain.
1665
+ eps : float or None (default = None)
1666
+ The error bound for similarity approximation. This controls the accuracy
1667
+ of the sampled paths in representing the true similarity. Smaller values
1668
+ yield more accurate results but require more sample paths. If `None`, a
1669
+ value of ``sqrt(1/|E|)`` is used, which the authors found empirically
1670
+ effective.
1671
+ weight : string or None, optional (default="weight")
1672
+ The name of an edge attribute that holds the numerical value
1673
+ used as a weight. If None then each edge has weight 1.
1674
+ seed : integer, random_state, or None (default)
1675
+ Indicator of random number generation state.
1676
+ See :ref:`Randomness<randomness>`.
1677
+
1678
+ Returns
1679
+ -------
1680
+ similarity : dictionary
1681
+ Dictionary of nodes to similarity scores (as floats). Note:
1682
+ the self-similarity (i.e., ``v``) will not be included in
1683
+ the returned dictionary. So, for ``k = 5``, a dictionary of
1684
+ top 4 nodes and their similarity scores will be returned.
1685
+
1686
+ Raises
1687
+ ------
1688
+ NetworkXUnfeasible
1689
+ If `source` is an isolated node.
1690
+
1691
+ NodeNotFound
1692
+ If `source` is not in `G`.
1693
+
1694
+ Notes
1695
+ -----
1696
+ The isolated nodes in `G` are ignored.
1697
+
1698
+ Examples
1699
+ --------
1700
+ >>> G = nx.star_graph(10)
1701
+ >>> sim = nx.panther_similarity(G, 0)
1702
+
1703
+ References
1704
+ ----------
1705
+ .. [1] Zhang, J., Tang, J., Ma, C., Tong, H., Jing, Y., & Li, J.
1706
+ Panther: Fast top-k similarity search on large networks.
1707
+ In Proceedings of the ACM SIGKDD International Conference
1708
+ on Knowledge Discovery and Data Mining (Vol. 2015-August, pp. 1445–1454).
1709
+ Association for Computing Machinery. https://doi.org/10.1145/2783258.2783267.
1710
+ """
1711
+ import numpy as np
1712
+
1713
+ # Use helper method to prepare common data structures
1714
+ G, inv_node_map, index_map, inv_sample_size, eps = _prepare_panther_paths(
1715
+ G,
1716
+ source,
1717
+ path_length=path_length,
1718
+ c=c,
1719
+ delta=delta,
1720
+ eps=eps,
1721
+ weight=weight,
1722
+ k=k,
1723
+ seed=seed,
1724
+ )
1725
+
1726
+ num_nodes = G.number_of_nodes()
1727
+ node_list = list(G.nodes)
1728
+
1729
+ # Check number of nodes after any modifications by _prepare_panther_paths
1730
+ if num_nodes < k:
1731
+ raise nx.NetworkXUnfeasible(
1732
+ f"The number of requested nodes {k} is greater than the number of nodes {num_nodes}."
1733
+ )
1734
+
1735
+ S = np.zeros(num_nodes)
1736
+ source_paths = set(index_map[source])
1737
+
1738
+ # Calculate the path similarities
1739
+ # between ``source`` (v) and ``node`` (v_j)
1740
+ # using our inverted index mapping of
1741
+ # vertices to paths
1742
+ for node, paths in index_map.items():
1743
+ # Only consider paths where both
1744
+ # ``node`` and ``source`` are present
1745
+ common_paths = source_paths.intersection(paths)
1746
+ S[inv_node_map[node]] = len(common_paths) * inv_sample_size
1747
+
1748
+ # Retrieve top ``k+1`` similar to account for removing self-similarity
1749
+ # Note: the below performed anywhere from 4-10x faster
1750
+ # (depending on input sizes) vs the equivalent ``np.argsort(S)[::-1]``
1751
+ partition_k = min(k + 1, num_nodes)
1752
+ top_k_unsorted = np.argpartition(S, -partition_k)[-partition_k:]
1753
+ top_k_sorted = top_k_unsorted[np.argsort(S[top_k_unsorted])][::-1]
1754
+
1755
+ # Add back the similarity scores
1756
+ # Convert numpy scalars to native Python types for dispatch compatibility
1757
+ top_k_with_val = dict(
1758
+ zip((node_list[i] for i in top_k_sorted), S[top_k_sorted].tolist())
1759
+ )
1760
+
1761
+ # Remove the self-similarity
1762
+ top_k_with_val.pop(source, None)
1763
+ return top_k_with_val
1764
+
1765
+
1766
+ @np_random_state("seed")
1767
+ @nx._dispatchable(edge_attrs="weight")
1768
+ def panther_vector_similarity(
1769
+ G,
1770
+ source,
1771
+ *,
1772
+ D=10,
1773
+ k=5,
1774
+ path_length=5,
1775
+ c=0.5,
1776
+ delta=0.1,
1777
+ eps=None,
1778
+ weight="weight",
1779
+ seed=None,
1780
+ ):
1781
+ r"""Returns the Panther vector similarity (Panther++) of nodes in `G`.
1782
+
1783
+ Computes similarity between nodes based on the "Panther++" algorithm [1]_, which extends
1784
+ the basic Panther algorithm by using feature vectors to better capture structural
1785
+ similarity.
1786
+
1787
+ While basic Panther similarity measures how often two nodes appear on the same paths,
1788
+ Panther vector similarity (Panther++) creates a ``D``-dimensional feature vector for each
1789
+ node using its top similarity scores with other nodes, then computes similarity based
1790
+ on the Euclidean distance between these feature vectors. This approach better captures
1791
+ structural similarity and addresses the bias towards close neighbors present in
1792
+ the original Panther algorithm.
1793
+
1794
+ This approach is preferred when:
1795
+
1796
+ 1. You need better structural similarity than basic path co-occurrence
1797
+ 2. You want to overcome the close-neighbor bias of standard Panther
1798
+ 3. You're working with large graphs where k-d tree indexing would be beneficial
1799
+ 4. Graph edit distance-like similarity is more appropriate than path co-occurrence
1800
+
1801
+ Parameters
1802
+ ----------
1803
+ G : NetworkX graph
1804
+ A NetworkX graph
1805
+ source : node
1806
+ Source node for which to find the top ``k`` similar other nodes
1807
+ D : int
1808
+ The number of similarity scores to use (in descending order)
1809
+ for each feature vector. Defaults to 10. Note that the original paper
1810
+ used D=50 [1]_, but KDTree is optimized for lower dimensions.
1811
+ k : int
1812
+ The number of most similar nodes to return
1813
+ path_length : int
1814
+ How long the randomly generated paths should be (``T`` in [1]_)
1815
+ c : float
1816
+ A universal constant that controls the number of random paths to generate.
1817
+ Higher values increase the number of sample paths and potentially improve
1818
+ accuracy at the cost of more computation. Defaults to 0.5 as recommended
1819
+ in [1]_.
1820
+ delta : float
1821
+ The probability that ``S`` is not an epsilon-approximation to (R, phi)
1822
+ eps : float
1823
+ The error bound for similarity approximation. This controls the accuracy
1824
+ of the sampled paths in representing the true similarity. Smaller values
1825
+ yield more accurate results but require more sample paths. If None, a
1826
+ value of ``sqrt(1/|E|)`` is used, which the authors found empirically
1827
+ effective.
1828
+ weight : string or None, optional (default="weight")
1829
+ The name of an edge attribute that holds the numerical value
1830
+ used as a weight. If `None` then each edge has weight 1.
1831
+ seed : integer, random_state, or None (default)
1832
+ Indicator of random number generation state.
1833
+ See :ref:`Randomness<randomness>`.
1834
+
1835
+ Returns
1836
+ -------
1837
+ similarity : dict
1838
+ Dict of nodes to similarity scores (as floats).
1839
+ Note: the self-similarity (i.e., `node`) is not included in the dict.
1840
+
1841
+ Examples
1842
+ --------
1843
+ >>> G = nx.star_graph(100)
1844
+
1845
+ The "hub" node is distinct from the "spoke" nodes
1846
+
1847
+ >>> from pprint import pprint
1848
+ >>> pprint(nx.panther_vector_similarity(G, source=0, seed=42))
1849
+ {35: 0.10402634656233918,
1850
+ 61: 0.10434063328712018,
1851
+ 65: 0.10401247833456054,
1852
+ 85: 0.10506718868571752,
1853
+ 88: 0.10402634656233918}
1854
+
1855
+ But "spoke" nodes are similar to one another
1856
+
1857
+ >>> result = nx.panther_vector_similarity(G, source=1, seed=42)
1858
+ >>> len(result)
1859
+ 5
1860
+ >>> all(similarity == 1.0 for similarity in result.values())
1861
+ True
1862
+
1863
+ Notes
1864
+ -----
1865
+ Results may be nondeterministic when feature vectors have the same distances,
1866
+ as the KDTree's internal tie-breaking behavior can vary between runs.
1867
+ Using the same ``seed`` parameter ensures reproducible results.
1868
+
1869
+ References
1870
+ ----------
1871
+ .. [1] Zhang, J., Tang, J., Ma, C., Tong, H., Jing, Y., & Li, J.
1872
+ Panther: Fast top-k similarity search on large networks.
1873
+ In Proceedings of the ACM SIGKDD International Conference
1874
+ on Knowledge Discovery and Data Mining (Vol. 2015-August, pp. 1445–1454).
1875
+ Association for Computing Machinery. https://doi.org/10.1145/2783258.2783267.
1876
+ """
1877
+ import numpy as np
1878
+ import scipy as sp
1879
+
1880
+ # Use helper method to prepare common data structures but keep isolates in the graph
1881
+ G, inv_node_map, index_map, inv_sample_size, eps = _prepare_panther_paths(
1882
+ G,
1883
+ source,
1884
+ path_length=path_length,
1885
+ c=c,
1886
+ delta=delta,
1887
+ eps=eps,
1888
+ weight=weight,
1889
+ remove_isolates=False,
1890
+ k=k,
1891
+ seed=seed,
1892
+ )
1893
+ num_nodes = G.number_of_nodes()
1894
+ node_list = list(G.nodes)
1895
+
1896
+ # Ensure D doesn't exceed the number of nodes
1897
+ if num_nodes < D:
1898
+ raise nx.NetworkXUnfeasible(
1899
+ f"The number of requested similarity scores {D} is greater than the number of nodes {num_nodes}."
1900
+ )
1901
+
1902
+ similarities = np.zeros((num_nodes, num_nodes))
1903
+ theta = np.zeros((num_nodes, D))
1904
+ index_map_sets = {node: set(paths) for node, paths in index_map.items()}
1905
+
1906
+ # Calculate the path similarities for each node
1907
+ for vi_idx, vi in enumerate(G.nodes):
1908
+ vi_paths = index_map_sets[vi]
1909
+
1910
+ for node, node_paths in index_map_sets.items():
1911
+ # Calculate similarity score
1912
+ common_path_count = len(vi_paths.intersection(node_paths))
1913
+ similarities[vi_idx, inv_node_map[node]] = (
1914
+ common_path_count * inv_sample_size
1915
+ )
1916
+
1917
+ # Build up the feature vector using the largest D similarity scores
1918
+ theta[vi_idx] = np.sort(np.partition(similarities[vi_idx], -D)[-D:])[::-1]
1919
+
1920
+ # Insert the feature vectors into a k-d tree
1921
+ # for fast retrieval
1922
+ kdtree = sp.spatial.KDTree(theta)
1923
+
1924
+ # Retrieve top ``k+1`` similar vertices (i.e., vectors)
1925
+ # (based on their Euclidean distance)
1926
+ # Note that it's k+1 because the source node will be included and later removed
1927
+ query_k = min(k + 1, num_nodes)
1928
+ neighbor_distances, nearest_neighbors = kdtree.query(
1929
+ theta[inv_node_map[source]], k=query_k
1930
+ )
1931
+
1932
+ # Ensure results are always arrays (KDTree returns scalars when k=1)
1933
+ neighbor_distances = np.atleast_1d(neighbor_distances)
1934
+ nearest_neighbors = np.atleast_1d(nearest_neighbors)
1935
+
1936
+ # The paper defines the similarity S(v_i, v_j) as
1937
+ # 1 / || Theta(v_i) - Theta(v_j) ||
1938
+ # Calculate reciprocals and normalize to [0, 1] range
1939
+
1940
+ # Handle the case where distances are very small or zero (common in small graphs)
1941
+ # Use the passed in eps parameter instead of defining a new epsilon
1942
+ neighbor_distances = np.maximum(neighbor_distances, eps)
1943
+ similarities = 1 / neighbor_distances
1944
+
1945
+ # Always normalize to ensure values are between 0 and 1
1946
+ if len(similarities) > 0 and (max_sim := np.max(similarities)) > 0:
1947
+ similarities /= max_sim
1948
+
1949
+ # Add back the similarity scores (i.e., distances)
1950
+ # Convert numpy scalars to native Python types for dispatch compatibility
1951
+ top_k_with_val = dict(
1952
+ zip((node_list[n] for n in nearest_neighbors), similarities.tolist())
1953
+ )
1954
+
1955
+ # Remove the self-similarity
1956
+ top_k_with_val.pop(source, None)
1957
+
1958
+ # Ensure we return exactly k results (sorted by similarity)
1959
+ if len(top_k_with_val) > k:
1960
+ sorted_items = sorted(top_k_with_val.items(), key=lambda x: x[1], reverse=True)
1961
+ top_k_with_val = dict(sorted_items[:k])
1962
+
1963
+ return top_k_with_val
1964
+
1965
+
1966
+ @np_random_state("seed")
1967
+ @nx._dispatchable(edge_attrs="weight")
1968
+ def generate_random_paths(
1969
+ G,
1970
+ sample_size,
1971
+ path_length=5,
1972
+ index_map=None,
1973
+ weight="weight",
1974
+ seed=None,
1975
+ *,
1976
+ source=None,
1977
+ ):
1978
+ """Randomly generate `sample_size` paths of length `path_length`.
1979
+
1980
+ Parameters
1981
+ ----------
1982
+ G : NetworkX graph
1983
+ A NetworkX graph
1984
+ sample_size : integer
1985
+ The number of paths to generate. This is ``R`` in [1]_.
1986
+ path_length : integer (default = 5)
1987
+ The maximum size of the path to randomly generate.
1988
+ This is ``T`` in [1]_. According to the paper, ``T >= 5`` is
1989
+ recommended.
1990
+ index_map : dictionary, optional
1991
+ If provided, this will be populated with the inverted
1992
+ index of nodes mapped to the set of generated random path
1993
+ indices within ``paths``.
1994
+ weight : string or None, optional (default="weight")
1995
+ The name of an edge attribute that holds the numerical value
1996
+ used as a weight. If None then each edge has weight 1.
1997
+ seed : integer, random_state, or None (default)
1998
+ Indicator of random number generation state.
1999
+ See :ref:`Randomness<randomness>`.
2000
+ source : node, optional
2001
+ Node to use as the starting point for all generated paths.
2002
+ If None then starting nodes are selected at random with uniform probability.
2003
+
2004
+ Returns
2005
+ -------
2006
+ paths : generator of lists
2007
+ Generator of `sample_size` paths each with length `path_length`.
2008
+
2009
+ Examples
2010
+ --------
2011
+ The generator yields `sample_size` number of paths of length `path_length`
2012
+ drawn from `G`:
2013
+
2014
+ >>> G = nx.complete_graph(5)
2015
+ >>> next(nx.generate_random_paths(G, sample_size=1, path_length=3, seed=42))
2016
+ [3, 4, 2, 3]
2017
+ >>> list(nx.generate_random_paths(G, sample_size=3, path_length=4, seed=42))
2018
+ [[3, 4, 2, 3, 0], [2, 0, 2, 1, 0], [2, 0, 4, 3, 0]]
2019
+
2020
+ By passing a dictionary into `index_map`, it will build an
2021
+ inverted index mapping of nodes to the paths in which that node is present:
2022
+
2023
+ >>> G = nx.wheel_graph(10)
2024
+ >>> index_map = {}
2025
+ >>> random_paths = list(
2026
+ ... nx.generate_random_paths(G, sample_size=3, index_map=index_map, seed=2771)
2027
+ ... )
2028
+ >>> random_paths
2029
+ [[3, 2, 1, 9, 8, 7], [4, 0, 5, 6, 7, 8], [3, 0, 5, 0, 9, 8]]
2030
+ >>> paths_containing_node_0 = [
2031
+ ... random_paths[path_idx] for path_idx in index_map.get(0, [])
2032
+ ... ]
2033
+ >>> paths_containing_node_0
2034
+ [[4, 0, 5, 6, 7, 8], [3, 0, 5, 0, 9, 8]]
2035
+
2036
+ References
2037
+ ----------
2038
+ .. [1] Zhang, J., Tang, J., Ma, C., Tong, H., Jing, Y., & Li, J.
2039
+ Panther: Fast top-k similarity search on large networks.
2040
+ In Proceedings of the ACM SIGKDD International Conference
2041
+ on Knowledge Discovery and Data Mining (Vol. 2015-August, pp. 1445–1454).
2042
+ Association for Computing Machinery. https://doi.org/10.1145/2783258.2783267.
2043
+ """
2044
+ import numpy as np
2045
+
2046
+ randint_fn = (
2047
+ seed.integers if isinstance(seed, np.random.Generator) else seed.randint
2048
+ )
2049
+
2050
+ # Calculate transition probabilities between
2051
+ # every pair of vertices according to Eq. (3)
2052
+ adj_mat = nx.to_numpy_array(G, weight=weight)
2053
+
2054
+ # Handle isolated nodes by checking for zero row sums
2055
+ row_sums = adj_mat.sum(axis=1).reshape(-1, 1)
2056
+ inv_row_sums = np.reciprocal(row_sums)
2057
+ transition_probabilities = adj_mat * inv_row_sums
2058
+
2059
+ node_map = list(G)
2060
+ num_nodes = G.number_of_nodes()
2061
+
2062
+ for path_index in range(sample_size):
2063
+ if source is None:
2064
+ # Sample current vertex v = v_i uniformly at random
2065
+ node_index = randint_fn(num_nodes)
2066
+ node = node_map[node_index]
2067
+ else:
2068
+ if source not in node_map:
2069
+ raise nx.NodeNotFound(f"Initial node {source} not in G")
2070
+
2071
+ node = source
2072
+ node_index = node_map.index(node)
2073
+
2074
+ # Add v into p_r and add p_r into the path set
2075
+ # of v, i.e., P_v
2076
+ path = [node]
2077
+
2078
+ # Build the inverted index (P_v) of vertices to paths
2079
+ if index_map is not None:
2080
+ if node in index_map:
2081
+ index_map[node].add(path_index)
2082
+ else:
2083
+ index_map[node] = {path_index}
2084
+
2085
+ starting_index = node_index
2086
+ for _ in range(path_length):
2087
+ # Randomly sample a neighbor (v_j) according
2088
+ # to transition probabilities from ``node`` (v) to its neighbors
2089
+ nbr_index = seed.choice(
2090
+ num_nodes, p=transition_probabilities[starting_index]
2091
+ )
2092
+
2093
+ # Set current vertex (v = v_j)
2094
+ starting_index = nbr_index
2095
+
2096
+ # Add v into p_r
2097
+ nbr_node = node_map[nbr_index]
2098
+ path.append(nbr_node)
2099
+
2100
+ # Add p_r into P_v
2101
+ if index_map is not None:
2102
+ if nbr_node in index_map:
2103
+ index_map[nbr_node].add(path_index)
2104
+ else:
2105
+ index_map[nbr_node] = {path_index}
2106
+
2107
+ yield path