okstra 0.130.4 → 0.131.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 (365) hide show
  1. package/README.md +0 -1
  2. package/docs/architecture.md +2 -2
  3. package/docs/cli.md +1 -1
  4. package/docs/for-ai/README.md +0 -2
  5. package/docs/for-ai/skills/okstra-run.md +13 -2
  6. package/docs/for-ai/skills/okstra-setup.md +4 -1
  7. package/docs/project-structure-overview.md +5 -4
  8. package/docs/task-process/implementation-planning.md +13 -0
  9. package/docs/task-process/implementation.md +17 -2
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/workers/report-writer-worker.md +7 -0
  13. package/runtime/bin/lib/okstra/usage.sh +4 -4
  14. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  15. package/runtime/prompts/lead/plan-body-verification.md +26 -5
  16. package/runtime/prompts/lead/report-writer.md +6 -2
  17. package/runtime/prompts/profiles/_common-contract.md +2 -1
  18. package/runtime/prompts/profiles/_implementation-deliverable.md +6 -1
  19. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  20. package/runtime/prompts/profiles/_implementation-verifier.md +33 -10
  21. package/runtime/prompts/profiles/final-verification.md +21 -6
  22. package/runtime/prompts/profiles/implementation-planning.md +16 -4
  23. package/runtime/python/okstra_ctl/codex_dispatch.py +31 -0
  24. package/runtime/python/okstra_ctl/conformance.py +49 -11
  25. package/runtime/python/okstra_ctl/report_finalize.py +52 -1
  26. package/runtime/python/okstra_ctl/run.py +15 -3
  27. package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
  28. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
  29. package/runtime/python/okstra_vendor/__init__.py +0 -44
  30. package/runtime/schemas/final-report-v1.0.schema.json +99 -5
  31. package/runtime/skills/okstra-run/SKILL.md +11 -4
  32. package/runtime/skills/okstra-setup/SKILL.md +3 -2
  33. package/runtime/skills/okstra-setup/references/project-config.md +16 -7
  34. package/runtime/templates/reports/final-report.template.md +15 -9
  35. package/runtime/templates/reports/i18n/en.json +1 -0
  36. package/runtime/templates/reports/i18n/ko.json +1 -0
  37. package/runtime/validators/lib/fixtures.sh +6 -0
  38. package/runtime/validators/validate-run.py +1722 -98
  39. package/src/cli-registry.mjs +0 -7
  40. package/src/commands/lifecycle/doctor.mjs +0 -9
  41. package/src/lib/skill-catalog.mjs +0 -1
  42. package/docs/for-ai/skills/okstra-graphify.md +0 -64
  43. package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
  44. package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
  45. package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
  46. package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
  47. package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
  48. package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
  49. package/runtime/python/okstra_vendor/graphify/build.py +0 -107
  50. package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
  51. package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
  52. package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
  53. package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
  54. package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
  55. package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
  56. package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
  57. package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
  58. package/runtime/python/okstra_vendor/graphify/report.py +0 -175
  59. package/runtime/python/okstra_vendor/graphify/security.py +0 -203
  60. package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
  61. package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
  62. package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
  63. package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
  64. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
  65. package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
  66. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
  67. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
  68. package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
  69. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
  70. package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
  71. package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
  72. package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
  73. package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
  74. package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
  75. package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
  76. package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
  77. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
  78. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
  79. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
  80. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
  81. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
  82. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
  83. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
  84. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
  85. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
  86. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
  87. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
  88. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
  89. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
  90. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
  91. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
  92. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
  93. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
  94. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
  95. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
  96. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
  97. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
  98. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
  99. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
  100. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
  101. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
  114. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
  116. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
  117. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
  118. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
  139. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
  140. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
  141. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
  142. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
  143. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
  144. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
  145. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
  146. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
  147. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
  148. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
  149. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
  150. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
  151. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
  152. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
  153. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
  154. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
  155. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
  156. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
  157. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
  158. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
  159. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
  160. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
  161. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
  162. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
  163. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
  164. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
  165. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
  166. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
  167. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
  168. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
  169. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
  170. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
  171. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
  172. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
  173. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
  174. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
  175. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
  176. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
  177. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
  178. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
  179. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
  180. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
  181. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
  182. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
  183. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
  184. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
  185. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
  186. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
  187. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
  188. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
  189. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
  190. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
  191. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
  192. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
  193. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
  194. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
  195. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
  196. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
  197. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
  198. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
  199. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
  200. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
  201. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
  202. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
  203. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
  204. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
  205. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
  206. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
  207. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
  208. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
  209. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
  210. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
  211. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
  212. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
  213. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
  214. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
  215. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
  216. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
  217. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
  218. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
  219. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
  220. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
  221. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
  222. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
  223. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
  224. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
  225. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
  226. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
  227. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
  228. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
  229. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
  230. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
  231. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
  232. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
  233. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
  234. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
  235. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
  236. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
  237. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
  238. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
  239. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
  240. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
  241. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
  242. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
  243. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
  244. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
  245. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
  246. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
  247. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
  248. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
  249. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
  250. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
  251. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
  252. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
  253. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
  254. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
  255. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
  256. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
  257. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
  258. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
  259. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
  260. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
  261. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
  262. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
  263. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
  264. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
  265. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
  266. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
  267. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
  268. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
  269. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
  270. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
  271. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
  272. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
  273. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
  274. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
  275. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
  276. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
  277. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
  278. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
  279. package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
  280. package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
  281. package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
  282. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
  283. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
  284. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
  285. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
  286. package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
  287. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
  288. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
  289. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
  290. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
  291. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
  292. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
  293. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
  294. package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
  295. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
  296. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  297. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
  298. package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
  299. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
  300. package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
  301. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
  302. package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
  303. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
  304. package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
  305. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
  306. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
  307. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
  308. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
  309. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
  310. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
  311. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
  312. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
  313. package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
  314. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
  315. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
  316. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
  317. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
  318. package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
  319. package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
  320. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
  321. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
  322. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
  323. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
  324. package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
  325. package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
  326. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
  327. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
  328. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
  329. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
  330. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
  331. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
  332. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
  333. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
  334. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
  335. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
  336. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
  337. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
  338. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
  339. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
  340. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
  341. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
  342. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
  343. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
  344. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
  345. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
  346. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
  347. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
  348. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
  349. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
  350. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
  351. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
  352. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
  353. package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
  354. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
  355. package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
  356. package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
  357. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
  358. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
  359. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
  360. package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
  361. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
  362. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
  363. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
  364. package/runtime/skills/okstra-graphify/SKILL.md +0 -169
  365. package/src/commands/graphify.mjs +0 -32
@@ -1,1270 +0,0 @@
1
- """
2
- Algorithms for finding k-edge-augmentations
3
-
4
- A k-edge-augmentation is a set of edges, that once added to a graph, ensures
5
- that the graph is k-edge-connected; i.e. the graph cannot be disconnected
6
- unless k or more edges are removed. Typically, the goal is to find the
7
- augmentation with minimum weight. In general, it is not guaranteed that a
8
- k-edge-augmentation exists.
9
-
10
- See Also
11
- --------
12
- :mod:`edge_kcomponents` : algorithms for finding k-edge-connected components
13
- :mod:`connectivity` : algorithms for determining edge connectivity.
14
- """
15
-
16
- import itertools as it
17
- import math
18
- from collections import defaultdict, namedtuple
19
-
20
- import networkx as nx
21
- from networkx.utils import not_implemented_for, py_random_state
22
-
23
- __all__ = ["k_edge_augmentation", "is_k_edge_connected", "is_locally_k_edge_connected"]
24
-
25
-
26
- @not_implemented_for("directed")
27
- @not_implemented_for("multigraph")
28
- @nx._dispatchable
29
- def is_k_edge_connected(G, k):
30
- """Tests to see if a graph is k-edge-connected.
31
-
32
- Is it impossible to disconnect the graph by removing fewer than k edges?
33
- If so, then G is k-edge-connected.
34
-
35
- Parameters
36
- ----------
37
- G : NetworkX graph
38
- An undirected graph.
39
-
40
- k : integer
41
- edge connectivity to test for
42
-
43
- Returns
44
- -------
45
- boolean
46
- True if G is k-edge-connected.
47
-
48
- See Also
49
- --------
50
- :func:`is_locally_k_edge_connected`
51
-
52
- Examples
53
- --------
54
- >>> G = nx.barbell_graph(10, 0)
55
- >>> nx.is_k_edge_connected(G, k=1)
56
- True
57
- >>> nx.is_k_edge_connected(G, k=2)
58
- False
59
- """
60
- if k < 1:
61
- raise ValueError(f"k must be positive, not {k}")
62
- # First try to quickly determine if G is not k-edge-connected
63
- if G.number_of_nodes() < k + 1:
64
- return False
65
- elif any(d < k for n, d in G.degree()):
66
- return False
67
- else:
68
- # Otherwise perform the full check
69
- if k == 1:
70
- return nx.is_connected(G)
71
- elif k == 2:
72
- return nx.is_connected(G) and not nx.has_bridges(G)
73
- else:
74
- return nx.edge_connectivity(G, cutoff=k) >= k
75
-
76
-
77
- @not_implemented_for("directed")
78
- @not_implemented_for("multigraph")
79
- @nx._dispatchable
80
- def is_locally_k_edge_connected(G, s, t, k):
81
- """Tests to see if an edge in a graph is locally k-edge-connected.
82
-
83
- Is it impossible to disconnect s and t by removing fewer than k edges?
84
- If so, then s and t are locally k-edge-connected in G.
85
-
86
- Parameters
87
- ----------
88
- G : NetworkX graph
89
- An undirected graph.
90
-
91
- s : node
92
- Source node
93
-
94
- t : node
95
- Target node
96
-
97
- k : integer
98
- local edge connectivity for nodes s and t
99
-
100
- Returns
101
- -------
102
- boolean
103
- True if s and t are locally k-edge-connected in G.
104
-
105
- See Also
106
- --------
107
- :func:`is_k_edge_connected`
108
-
109
- Examples
110
- --------
111
- >>> from networkx.algorithms.connectivity import is_locally_k_edge_connected
112
- >>> G = nx.barbell_graph(10, 0)
113
- >>> is_locally_k_edge_connected(G, 5, 15, k=1)
114
- True
115
- >>> is_locally_k_edge_connected(G, 5, 15, k=2)
116
- False
117
- >>> is_locally_k_edge_connected(G, 1, 5, k=2)
118
- True
119
- """
120
- if k < 1:
121
- raise ValueError(f"k must be positive, not {k}")
122
-
123
- # First try to quickly determine s, t is not k-locally-edge-connected in G
124
- if G.degree(s) < k or G.degree(t) < k:
125
- return False
126
- else:
127
- # Otherwise perform the full check
128
- if k == 1:
129
- return nx.has_path(G, s, t)
130
- else:
131
- localk = nx.connectivity.local_edge_connectivity(G, s, t, cutoff=k)
132
- return localk >= k
133
-
134
-
135
- @not_implemented_for("directed")
136
- @not_implemented_for("multigraph")
137
- @nx._dispatchable
138
- def k_edge_augmentation(G, k, avail=None, weight=None, partial=False):
139
- """Finds set of edges to k-edge-connect G.
140
-
141
- Adding edges from the augmentation to G make it impossible to disconnect G
142
- unless k or more edges are removed. This function uses the most efficient
143
- function available (depending on the value of k and if the problem is
144
- weighted or unweighted) to search for a minimum weight subset of available
145
- edges that k-edge-connects G. In general, finding a k-edge-augmentation is
146
- NP-hard, so solutions are not guaranteed to be minimal. Furthermore, a
147
- k-edge-augmentation may not exist.
148
-
149
- Parameters
150
- ----------
151
- G : NetworkX graph
152
- An undirected graph.
153
-
154
- k : integer
155
- Desired edge connectivity
156
-
157
- avail : dict or a set of 2 or 3 tuples
158
- The available edges that can be used in the augmentation.
159
-
160
- If unspecified, then all edges in the complement of G are available.
161
- Otherwise, each item is an available edge (with an optional weight).
162
-
163
- In the unweighted case, each item is an edge ``(u, v)``.
164
-
165
- In the weighted case, each item is a 3-tuple ``(u, v, d)`` or a dict
166
- with items ``(u, v): d``. The third item, ``d``, can be a dictionary
167
- or a real number. If ``d`` is a dictionary ``d[weight]``
168
- correspondings to the weight.
169
-
170
- weight : string
171
- key to use to find weights if ``avail`` is a set of 3-tuples where the
172
- third item in each tuple is a dictionary.
173
-
174
- partial : boolean
175
- If partial is True and no feasible k-edge-augmentation exists, then all
176
- a partial k-edge-augmentation is generated. Adding the edges in a
177
- partial augmentation to G, minimizes the number of k-edge-connected
178
- components and maximizes the edge connectivity between those
179
- components. For details, see :func:`partial_k_edge_augmentation`.
180
-
181
- Yields
182
- ------
183
- edge : tuple
184
- Edges that, once added to G, would cause G to become k-edge-connected.
185
- If partial is False, an error is raised if this is not possible.
186
- Otherwise, generated edges form a partial augmentation, which
187
- k-edge-connects any part of G where it is possible, and maximally
188
- connects the remaining parts.
189
-
190
- Raises
191
- ------
192
- NetworkXUnfeasible
193
- If partial is False and no k-edge-augmentation exists.
194
-
195
- NetworkXNotImplemented
196
- If the input graph is directed or a multigraph.
197
-
198
- ValueError:
199
- If k is less than 1
200
-
201
- Notes
202
- -----
203
- When k=1 this returns an optimal solution.
204
-
205
- When k=2 and ``avail`` is None, this returns an optimal solution.
206
- Otherwise when k=2, this returns a 2-approximation of the optimal solution.
207
-
208
- For k>3, this problem is NP-hard and this uses a randomized algorithm that
209
- produces a feasible solution, but provides no guarantees on the
210
- solution weight.
211
-
212
- Examples
213
- --------
214
- >>> # Unweighted cases
215
- >>> G = nx.path_graph((1, 2, 3, 4))
216
- >>> G.add_node(5)
217
- >>> sorted(nx.k_edge_augmentation(G, k=1))
218
- [(1, 5)]
219
- >>> sorted(nx.k_edge_augmentation(G, k=2))
220
- [(1, 5), (5, 4)]
221
- >>> sorted(nx.k_edge_augmentation(G, k=3))
222
- [(1, 4), (1, 5), (2, 5), (3, 5), (4, 5)]
223
- >>> complement = list(nx.k_edge_augmentation(G, k=5, partial=True))
224
- >>> G.add_edges_from(complement)
225
- >>> nx.edge_connectivity(G)
226
- 4
227
-
228
- >>> # Weighted cases
229
- >>> G = nx.path_graph((1, 2, 3, 4))
230
- >>> G.add_node(5)
231
- >>> # avail can be a tuple with a dict
232
- >>> avail = [(1, 5, {"weight": 11}), (2, 5, {"weight": 10})]
233
- >>> sorted(nx.k_edge_augmentation(G, k=1, avail=avail, weight="weight"))
234
- [(2, 5)]
235
- >>> # or avail can be a 3-tuple with a real number
236
- >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
237
- >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
238
- [(1, 5), (2, 5), (4, 5)]
239
- >>> # or avail can be a dict
240
- >>> avail = {(1, 5): 11, (2, 5): 10, (4, 3): 1, (4, 5): 51}
241
- >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail))
242
- [(1, 5), (2, 5), (4, 5)]
243
- >>> # If augmentation is infeasible, then a partial solution can be found
244
- >>> avail = {(1, 5): 11}
245
- >>> sorted(nx.k_edge_augmentation(G, k=2, avail=avail, partial=True))
246
- [(1, 5)]
247
- """
248
- try:
249
- if k <= 0:
250
- raise ValueError(f"k must be a positive integer, not {k}")
251
- elif G.number_of_nodes() < k + 1:
252
- msg = f"impossible to {k} connect in graph with less than {k + 1} nodes"
253
- raise nx.NetworkXUnfeasible(msg)
254
- elif avail is not None and len(avail) == 0:
255
- if not nx.is_k_edge_connected(G, k):
256
- raise nx.NetworkXUnfeasible("no available edges")
257
- aug_edges = []
258
- elif k == 1:
259
- aug_edges = one_edge_augmentation(
260
- G, avail=avail, weight=weight, partial=partial
261
- )
262
- elif k == 2:
263
- aug_edges = bridge_augmentation(G, avail=avail, weight=weight)
264
- else:
265
- # raise NotImplementedError(f'not implemented for k>2. k={k}')
266
- aug_edges = greedy_k_edge_augmentation(
267
- G, k=k, avail=avail, weight=weight, seed=0
268
- )
269
- # Do eager evaluation so we can catch any exceptions
270
- # Before executing partial code.
271
- yield from list(aug_edges)
272
- except nx.NetworkXUnfeasible:
273
- if partial:
274
- # Return all available edges
275
- if avail is None:
276
- aug_edges = complement_edges(G)
277
- else:
278
- # If we can't k-edge-connect the entire graph, try to
279
- # k-edge-connect as much as possible
280
- aug_edges = partial_k_edge_augmentation(
281
- G, k=k, avail=avail, weight=weight
282
- )
283
- yield from aug_edges
284
- else:
285
- raise
286
-
287
-
288
- @nx._dispatchable
289
- def partial_k_edge_augmentation(G, k, avail, weight=None):
290
- """Finds augmentation that k-edge-connects as much of the graph as possible.
291
-
292
- When a k-edge-augmentation is not possible, we can still try to find a
293
- small set of edges that partially k-edge-connects as much of the graph as
294
- possible. All possible edges are generated between remaining parts.
295
- This minimizes the number of k-edge-connected subgraphs in the resulting
296
- graph and maximizes the edge connectivity between those subgraphs.
297
-
298
- Parameters
299
- ----------
300
- G : NetworkX graph
301
- An undirected graph.
302
-
303
- k : integer
304
- Desired edge connectivity
305
-
306
- avail : dict or a set of 2 or 3 tuples
307
- For more details, see :func:`k_edge_augmentation`.
308
-
309
- weight : string
310
- key to use to find weights if ``avail`` is a set of 3-tuples.
311
- For more details, see :func:`k_edge_augmentation`.
312
-
313
- Yields
314
- ------
315
- edge : tuple
316
- Edges in the partial augmentation of G. These edges k-edge-connect any
317
- part of G where it is possible, and maximally connects the remaining
318
- parts. In other words, all edges from avail are generated except for
319
- those within subgraphs that have already become k-edge-connected.
320
-
321
- Notes
322
- -----
323
- Construct H that augments G with all edges in avail.
324
- Find the k-edge-subgraphs of H.
325
- For each k-edge-subgraph, if the number of nodes is more than k, then find
326
- the k-edge-augmentation of that graph and add it to the solution. Then add
327
- all edges in avail between k-edge subgraphs to the solution.
328
-
329
- See Also
330
- --------
331
- :func:`k_edge_augmentation`
332
-
333
- Examples
334
- --------
335
- >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
336
- >>> G.add_node(8)
337
- >>> avail = [(1, 3), (1, 4), (1, 5), (2, 4), (2, 5), (3, 5), (1, 8)]
338
- >>> sorted(partial_k_edge_augmentation(G, k=2, avail=avail))
339
- [(1, 5), (1, 8)]
340
- """
341
-
342
- def _edges_between_disjoint(H, only1, only2):
343
- """finds edges between disjoint nodes"""
344
- only1_adj = {u: set(H.adj[u]) for u in only1}
345
- for u, neighbs in only1_adj.items():
346
- # Find the neighbors of u in only1 that are also in only2
347
- neighbs12 = neighbs.intersection(only2)
348
- for v in neighbs12:
349
- yield (u, v)
350
-
351
- avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
352
-
353
- # Find which parts of the graph can be k-edge-connected
354
- H = G.copy()
355
- H.add_edges_from(
356
- (
357
- (u, v, {"weight": w, "generator": (u, v)})
358
- for (u, v), w in zip(avail, avail_w)
359
- )
360
- )
361
- k_edge_subgraphs = list(nx.k_edge_subgraphs(H, k=k))
362
-
363
- # Generate edges to k-edge-connect internal subgraphs
364
- for nodes in k_edge_subgraphs:
365
- if len(nodes) > 1:
366
- # Get the k-edge-connected subgraph
367
- C = H.subgraph(nodes).copy()
368
- # Find the internal edges that were available
369
- sub_avail = {
370
- d["generator"]: d["weight"]
371
- for (u, v, d) in C.edges(data=True)
372
- if "generator" in d
373
- }
374
- # Remove potential augmenting edges
375
- C.remove_edges_from(sub_avail.keys())
376
- # Find a subset of these edges that makes the component
377
- # k-edge-connected and ignore the rest
378
- yield from nx.k_edge_augmentation(C, k=k, avail=sub_avail)
379
-
380
- # Generate all edges between CCs that could not be k-edge-connected
381
- for cc1, cc2 in it.combinations(k_edge_subgraphs, 2):
382
- for u, v in _edges_between_disjoint(H, cc1, cc2):
383
- d = H.get_edge_data(u, v)
384
- edge = d.get("generator", None)
385
- if edge is not None:
386
- yield edge
387
-
388
-
389
- @not_implemented_for("multigraph")
390
- @not_implemented_for("directed")
391
- @nx._dispatchable
392
- def one_edge_augmentation(G, avail=None, weight=None, partial=False):
393
- """Finds minimum weight set of edges to connect G.
394
-
395
- Equivalent to :func:`k_edge_augmentation` when k=1. Adding the resulting
396
- edges to G will make it 1-edge-connected. The solution is optimal for both
397
- weighted and non-weighted variants.
398
-
399
- Parameters
400
- ----------
401
- G : NetworkX graph
402
- An undirected graph.
403
-
404
- avail : dict or a set of 2 or 3 tuples
405
- For more details, see :func:`k_edge_augmentation`.
406
-
407
- weight : string
408
- key to use to find weights if ``avail`` is a set of 3-tuples.
409
- For more details, see :func:`k_edge_augmentation`.
410
-
411
- partial : boolean
412
- If partial is True and no feasible k-edge-augmentation exists, then the
413
- augmenting edges minimize the number of connected components.
414
-
415
- Yields
416
- ------
417
- edge : tuple
418
- Edges in the one-augmentation of G
419
-
420
- Raises
421
- ------
422
- NetworkXUnfeasible
423
- If partial is False and no one-edge-augmentation exists.
424
-
425
- Notes
426
- -----
427
- Uses either :func:`unconstrained_one_edge_augmentation` or
428
- :func:`weighted_one_edge_augmentation` depending on whether ``avail`` is
429
- specified. Both algorithms are based on finding a minimum spanning tree.
430
- As such both algorithms find optimal solutions and run in linear time.
431
-
432
- See Also
433
- --------
434
- :func:`k_edge_augmentation`
435
- """
436
- if avail is None:
437
- return unconstrained_one_edge_augmentation(G)
438
- else:
439
- return weighted_one_edge_augmentation(
440
- G, avail=avail, weight=weight, partial=partial
441
- )
442
-
443
-
444
- @not_implemented_for("multigraph")
445
- @not_implemented_for("directed")
446
- @nx._dispatchable
447
- def bridge_augmentation(G, avail=None, weight=None):
448
- """Finds the a set of edges that bridge connects G.
449
-
450
- Equivalent to :func:`k_edge_augmentation` when k=2, and partial=False.
451
- Adding the resulting edges to G will make it 2-edge-connected. If no
452
- constraints are specified the returned set of edges is minimum an optimal,
453
- otherwise the solution is approximated.
454
-
455
- Parameters
456
- ----------
457
- G : NetworkX graph
458
- An undirected graph.
459
-
460
- avail : dict or a set of 2 or 3 tuples
461
- For more details, see :func:`k_edge_augmentation`.
462
-
463
- weight : string
464
- key to use to find weights if ``avail`` is a set of 3-tuples.
465
- For more details, see :func:`k_edge_augmentation`.
466
-
467
- Yields
468
- ------
469
- edge : tuple
470
- Edges in the bridge-augmentation of G
471
-
472
- Raises
473
- ------
474
- NetworkXUnfeasible
475
- If no bridge-augmentation exists.
476
-
477
- Notes
478
- -----
479
- If there are no constraints the solution can be computed in linear time
480
- using :func:`unconstrained_bridge_augmentation`. Otherwise, the problem
481
- becomes NP-hard and is the solution is approximated by
482
- :func:`weighted_bridge_augmentation`.
483
-
484
- See Also
485
- --------
486
- :func:`k_edge_augmentation`
487
- """
488
- if G.number_of_nodes() < 3:
489
- raise nx.NetworkXUnfeasible("impossible to bridge connect less than 3 nodes")
490
- if avail is None:
491
- return unconstrained_bridge_augmentation(G)
492
- else:
493
- return weighted_bridge_augmentation(G, avail, weight=weight)
494
-
495
-
496
- # --- Algorithms and Helpers ---
497
-
498
-
499
- def _ordered(u, v):
500
- """Returns the nodes in an undirected edge in lower-triangular order"""
501
- return (u, v) if u < v else (v, u)
502
-
503
-
504
- def _unpack_available_edges(avail, weight=None, G=None):
505
- """Helper to separate avail into edges and corresponding weights"""
506
- if weight is None:
507
- weight = "weight"
508
- if isinstance(avail, dict):
509
- avail_uv = list(avail.keys())
510
- avail_w = list(avail.values())
511
- else:
512
-
513
- def _try_getitem(d):
514
- try:
515
- return d[weight]
516
- except TypeError:
517
- return d
518
-
519
- avail_uv = [tup[0:2] for tup in avail]
520
- avail_w = [1 if len(tup) == 2 else _try_getitem(tup[-1]) for tup in avail]
521
-
522
- if G is not None:
523
- # Edges already in the graph are filtered
524
- flags = [not G.has_edge(u, v) for u, v in avail_uv]
525
- avail_uv = list(it.compress(avail_uv, flags))
526
- avail_w = list(it.compress(avail_w, flags))
527
- return avail_uv, avail_w
528
-
529
-
530
- MetaEdge = namedtuple("MetaEdge", ("meta_uv", "uv", "w"))
531
-
532
-
533
- def _lightest_meta_edges(mapping, avail_uv, avail_w):
534
- """Maps available edges in the original graph to edges in the metagraph.
535
-
536
- Parameters
537
- ----------
538
- mapping : dict
539
- mapping produced by :func:`collapse`, that maps each node in the
540
- original graph to a node in the meta graph
541
-
542
- avail_uv : list
543
- list of edges
544
-
545
- avail_w : list
546
- list of edge weights
547
-
548
- Notes
549
- -----
550
- Each node in the metagraph is a k-edge-connected component in the original
551
- graph. We don't care about any edge within the same k-edge-connected
552
- component, so we ignore self edges. We also are only interested in the
553
- minimum weight edge bridging each k-edge-connected component so, we group
554
- the edges by meta-edge and take the lightest in each group.
555
-
556
- Examples
557
- --------
558
- >>> # Each group represents a meta-node
559
- >>> groups = ([1, 2, 3], [4, 5], [6])
560
- >>> mapping = {n: meta_n for meta_n, ns in enumerate(groups) for n in ns}
561
- >>> avail_uv = [(1, 2), (3, 6), (1, 4), (5, 2), (6, 1), (2, 6), (3, 1)]
562
- >>> avail_w = [20, 99, 20, 15, 50, 99, 20]
563
- >>> sorted(_lightest_meta_edges(mapping, avail_uv, avail_w))
564
- [MetaEdge(meta_uv=(0, 1), uv=(5, 2), w=15), MetaEdge(meta_uv=(0, 2), uv=(6, 1), w=50)]
565
- """
566
- grouped_wuv = defaultdict(list)
567
- for w, (u, v) in zip(avail_w, avail_uv):
568
- # Order the meta-edge so it can be used as a dict key
569
- meta_uv = _ordered(mapping[u], mapping[v])
570
- # Group each available edge using the meta-edge as a key
571
- grouped_wuv[meta_uv].append((w, u, v))
572
-
573
- # Now that all available edges are grouped, choose one per group
574
- for (mu, mv), choices_wuv in grouped_wuv.items():
575
- # Ignore available edges within the same meta-node
576
- if mu != mv:
577
- # Choose the lightest available edge belonging to each meta-edge
578
- w, u, v = min(choices_wuv)
579
- yield MetaEdge((mu, mv), (u, v), w)
580
-
581
-
582
- @nx._dispatchable
583
- def unconstrained_one_edge_augmentation(G):
584
- """Finds the smallest set of edges to connect G.
585
-
586
- This is a variant of the unweighted MST problem.
587
- If G is not empty, a feasible solution always exists.
588
-
589
- Parameters
590
- ----------
591
- G : NetworkX graph
592
- An undirected graph.
593
-
594
- Yields
595
- ------
596
- edge : tuple
597
- Edges in the one-edge-augmentation of G
598
-
599
- See Also
600
- --------
601
- :func:`one_edge_augmentation`
602
- :func:`k_edge_augmentation`
603
-
604
- Examples
605
- --------
606
- >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
607
- >>> G.add_nodes_from([6, 7, 8])
608
- >>> sorted(unconstrained_one_edge_augmentation(G))
609
- [(1, 4), (4, 6), (6, 7), (7, 8)]
610
- """
611
- ccs1 = list(nx.connected_components(G))
612
- C = collapse(G, ccs1)
613
- # When we are not constrained, we can just make a meta graph tree.
614
- meta_nodes = list(C.nodes())
615
- # build a path in the metagraph
616
- meta_aug = list(zip(meta_nodes, meta_nodes[1:]))
617
- # map that path to the original graph
618
- inverse = defaultdict(list)
619
- for k, v in C.graph["mapping"].items():
620
- inverse[v].append(k)
621
- for mu, mv in meta_aug:
622
- yield (inverse[mu][0], inverse[mv][0])
623
-
624
-
625
- @nx._dispatchable
626
- def weighted_one_edge_augmentation(G, avail, weight=None, partial=False):
627
- """Finds the minimum weight set of edges to connect G if one exists.
628
-
629
- This is a variant of the weighted MST problem.
630
-
631
- Parameters
632
- ----------
633
- G : NetworkX graph
634
- An undirected graph.
635
-
636
- avail : dict or a set of 2 or 3 tuples
637
- For more details, see :func:`k_edge_augmentation`.
638
-
639
- weight : string
640
- key to use to find weights if ``avail`` is a set of 3-tuples.
641
- For more details, see :func:`k_edge_augmentation`.
642
-
643
- partial : boolean
644
- If partial is True and no feasible k-edge-augmentation exists, then the
645
- augmenting edges minimize the number of connected components.
646
-
647
- Yields
648
- ------
649
- edge : tuple
650
- Edges in the subset of avail chosen to connect G.
651
-
652
- See Also
653
- --------
654
- :func:`one_edge_augmentation`
655
- :func:`k_edge_augmentation`
656
-
657
- Examples
658
- --------
659
- >>> G = nx.Graph([(1, 2), (2, 3), (4, 5)])
660
- >>> G.add_nodes_from([6, 7, 8])
661
- >>> # any edge not in avail has an implicit weight of infinity
662
- >>> avail = [(1, 3), (1, 5), (4, 7), (4, 8), (6, 1), (8, 1), (8, 2)]
663
- >>> sorted(weighted_one_edge_augmentation(G, avail))
664
- [(1, 5), (4, 7), (6, 1), (8, 1)]
665
- >>> # find another solution by giving large weights to edges in the
666
- >>> # previous solution (note some of the old edges must be used)
667
- >>> avail = [(1, 3), (1, 5, 99), (4, 7, 9), (6, 1, 99), (8, 1, 99), (8, 2)]
668
- >>> sorted(weighted_one_edge_augmentation(G, avail))
669
- [(1, 5), (4, 7), (6, 1), (8, 2)]
670
- """
671
- avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
672
- # Collapse CCs in the original graph into nodes in a metagraph
673
- # Then find an MST of the metagraph instead of the original graph
674
- C = collapse(G, nx.connected_components(G))
675
- mapping = C.graph["mapping"]
676
- # Assign each available edge to an edge in the metagraph
677
- candidate_mapping = _lightest_meta_edges(mapping, avail_uv, avail_w)
678
- # nx.set_edge_attributes(C, name='weight', values=0)
679
- C.add_edges_from(
680
- (mu, mv, {"weight": w, "generator": uv})
681
- for (mu, mv), uv, w in candidate_mapping
682
- )
683
- # Find MST of the meta graph
684
- meta_mst = nx.minimum_spanning_tree(C)
685
- if not partial and not nx.is_connected(meta_mst):
686
- raise nx.NetworkXUnfeasible("Not possible to connect G with available edges")
687
- # Yield the edge that generated the meta-edge
688
- for mu, mv, d in meta_mst.edges(data=True):
689
- if "generator" in d:
690
- edge = d["generator"]
691
- yield edge
692
-
693
-
694
- @nx._dispatchable
695
- def unconstrained_bridge_augmentation(G):
696
- """Finds an optimal 2-edge-augmentation of G using the fewest edges.
697
-
698
- This is an implementation of the algorithm detailed in [1]_.
699
- The basic idea is to construct a meta-graph of bridge-ccs, connect leaf
700
- nodes of the trees to connect the entire graph, and finally connect the
701
- leafs of the tree in dfs-preorder to bridge connect the entire graph.
702
-
703
- Parameters
704
- ----------
705
- G : NetworkX graph
706
- An undirected graph.
707
-
708
- Yields
709
- ------
710
- edge : tuple
711
- Edges in the bridge augmentation of G
712
-
713
- Notes
714
- -----
715
- Input: a graph G.
716
- First find the bridge components of G and collapse each bridge-cc into a
717
- node of a metagraph graph C, which is guaranteed to be a forest of trees.
718
-
719
- C contains p "leafs" --- nodes with exactly one incident edge.
720
- C contains q "isolated nodes" --- nodes with no incident edges.
721
-
722
- Theorem: If p + q > 1, then at least :math:`ceil(p / 2) + q` edges are
723
- needed to bridge connect C. This algorithm achieves this min number.
724
-
725
- The method first adds enough edges to make G into a tree and then pairs
726
- leafs in a simple fashion.
727
-
728
- Let n be the number of trees in C. Let v(i) be an isolated vertex in the
729
- i-th tree if one exists, otherwise it is a pair of distinct leafs nodes
730
- in the i-th tree. Alternating edges from these sets (i.e. adding edges
731
- A1 = [(v(i)[0], v(i + 1)[1]), v(i + 1)[0], v(i + 2)[1])...]) connects C
732
- into a tree T. This tree has p' = p + 2q - 2(n -1) leafs and no isolated
733
- vertices. A1 has n - 1 edges. The next step finds ceil(p' / 2) edges to
734
- biconnect any tree with p' leafs.
735
-
736
- Convert T into an arborescence T' by picking an arbitrary root node with
737
- degree >= 2 and directing all edges away from the root. Note the
738
- implementation implicitly constructs T'.
739
-
740
- The leafs of T are the nodes with no existing edges in T'.
741
- Order the leafs of T' by DFS preorder. Then break this list in half
742
- and add the zipped pairs to A2.
743
-
744
- The set A = A1 + A2 is the minimum augmentation in the metagraph.
745
-
746
- To convert this to edges in the original graph
747
-
748
- References
749
- ----------
750
- .. [1] Eswaran, Kapali P., and R. Endre Tarjan. (1975) Augmentation problems.
751
- http://epubs.siam.org/doi/abs/10.1137/0205044
752
-
753
- See Also
754
- --------
755
- :func:`bridge_augmentation`
756
- :func:`k_edge_augmentation`
757
-
758
- Examples
759
- --------
760
- >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
761
- >>> sorted(unconstrained_bridge_augmentation(G))
762
- [(1, 7)]
763
- >>> G = nx.path_graph((1, 2, 3, 2, 4, 5, 6, 7))
764
- >>> sorted(unconstrained_bridge_augmentation(G))
765
- [(1, 3), (3, 7)]
766
- >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)])
767
- >>> G.add_node(4)
768
- >>> sorted(unconstrained_bridge_augmentation(G))
769
- [(1, 4), (4, 0)]
770
- """
771
- # -----
772
- # Mapping of terms from (Eswaran and Tarjan):
773
- # G = G_0 - the input graph
774
- # C = G_0' - the bridge condensation of G. (This is a forest of trees)
775
- # A1 = A_1 - the edges to connect the forest into a tree
776
- # leaf = pendant - a node with degree of 1
777
-
778
- # alpha(v) = maps the node v in G to its meta-node in C
779
- # beta(x) = maps the meta-node x in C to any node in the bridge
780
- # component of G corresponding to x.
781
-
782
- # find the 2-edge-connected components of G
783
- bridge_ccs = list(nx.connectivity.bridge_components(G))
784
- # condense G into an forest C
785
- C = collapse(G, bridge_ccs)
786
-
787
- # Choose pairs of distinct leaf nodes in each tree. If this is not
788
- # possible then make a pair using the single isolated node in the tree.
789
- vset1 = [
790
- tuple(cc) * 2 # case1: an isolated node
791
- if len(cc) == 1
792
- else sorted(cc, key=C.degree)[0:2] # case2: pair of leaf nodes
793
- for cc in nx.connected_components(C)
794
- ]
795
- if len(vset1) > 1:
796
- # Use this set to construct edges that connect C into a tree.
797
- nodes1 = [vs[0] for vs in vset1]
798
- nodes2 = [vs[1] for vs in vset1]
799
- A1 = list(zip(nodes1[1:], nodes2))
800
- else:
801
- A1 = []
802
- # Connect each tree in the forest to construct an arborescence
803
- T = C.copy()
804
- T.add_edges_from(A1)
805
-
806
- # If there are only two leaf nodes, we simply connect them.
807
- leafs = [n for n, d in T.degree() if d == 1]
808
- if len(leafs) == 1:
809
- A2 = []
810
- if len(leafs) == 2:
811
- A2 = [tuple(leafs)]
812
- else:
813
- # Choose an arbitrary non-leaf root
814
- try:
815
- root = next(n for n, d in T.degree() if d > 1)
816
- except StopIteration: # no nodes found with degree > 1
817
- return
818
- # order the leaves of C by (induced directed) preorder
819
- v2 = [n for n in nx.dfs_preorder_nodes(T, root) if T.degree(n) == 1]
820
- # connecting first half of the leafs in pre-order to the second
821
- # half will bridge connect the tree with the fewest edges.
822
- half = math.ceil(len(v2) / 2)
823
- A2 = list(zip(v2[:half], v2[-half:]))
824
-
825
- # collect the edges used to augment the original forest
826
- aug_tree_edges = A1 + A2
827
-
828
- # Construct the mapping (beta) from meta-nodes to regular nodes
829
- inverse = defaultdict(list)
830
- for k, v in C.graph["mapping"].items():
831
- inverse[v].append(k)
832
- # sort so we choose minimum degree nodes first
833
- inverse = {
834
- mu: sorted(mapped, key=lambda u: (G.degree(u), u))
835
- for mu, mapped in inverse.items()
836
- }
837
-
838
- # For each meta-edge, map back to an arbitrary pair in the original graph
839
- G2 = G.copy()
840
- for mu, mv in aug_tree_edges:
841
- # Find the first available edge that doesn't exist and return it
842
- for u, v in it.product(inverse[mu], inverse[mv]):
843
- if not G2.has_edge(u, v):
844
- G2.add_edge(u, v)
845
- yield u, v
846
- break
847
-
848
-
849
- @nx._dispatchable
850
- def weighted_bridge_augmentation(G, avail, weight=None):
851
- """Finds an approximate min-weight 2-edge-augmentation of G.
852
-
853
- This is an implementation of the approximation algorithm detailed in [1]_.
854
- It chooses a set of edges from avail to add to G that renders it
855
- 2-edge-connected if such a subset exists. This is done by finding a
856
- minimum spanning arborescence of a specially constructed metagraph.
857
-
858
- Parameters
859
- ----------
860
- G : NetworkX graph
861
- An undirected graph.
862
-
863
- avail : set of 2 or 3 tuples.
864
- candidate edges (with optional weights) to choose from
865
-
866
- weight : string
867
- key to use to find weights if avail is a set of 3-tuples where the
868
- third item in each tuple is a dictionary.
869
-
870
- Yields
871
- ------
872
- edge : tuple
873
- Edges in the subset of avail chosen to bridge augment G.
874
-
875
- Notes
876
- -----
877
- Finding a weighted 2-edge-augmentation is NP-hard.
878
- Any edge not in ``avail`` is considered to have a weight of infinity.
879
- The approximation factor is 2 if ``G`` is connected and 3 if it is not.
880
- Runs in :math:`O(m + n log(n))` time
881
-
882
- References
883
- ----------
884
- .. [1] Khuller, Samir, and Ramakrishna Thurimella. (1993) Approximation
885
- algorithms for graph augmentation.
886
- http://www.sciencedirect.com/science/article/pii/S0196677483710102
887
-
888
- See Also
889
- --------
890
- :func:`bridge_augmentation`
891
- :func:`k_edge_augmentation`
892
-
893
- Examples
894
- --------
895
- >>> G = nx.path_graph((1, 2, 3, 4))
896
- >>> # When the weights are equal, (1, 4) is the best
897
- >>> avail = [(1, 4, 1), (1, 3, 1), (2, 4, 1)]
898
- >>> sorted(weighted_bridge_augmentation(G, avail))
899
- [(1, 4)]
900
- >>> # Giving (1, 4) a high weight makes the two edge solution the best.
901
- >>> avail = [(1, 4, 1000), (1, 3, 1), (2, 4, 1)]
902
- >>> sorted(weighted_bridge_augmentation(G, avail))
903
- [(1, 3), (2, 4)]
904
- >>> # ------
905
- >>> G = nx.path_graph((1, 2, 3, 4))
906
- >>> G.add_node(5)
907
- >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 1)]
908
- >>> sorted(weighted_bridge_augmentation(G, avail=avail))
909
- [(1, 5), (4, 5)]
910
- >>> avail = [(1, 5, 11), (2, 5, 10), (4, 3, 1), (4, 5, 51)]
911
- >>> sorted(weighted_bridge_augmentation(G, avail=avail))
912
- [(1, 5), (2, 5), (4, 5)]
913
- """
914
-
915
- if weight is None:
916
- weight = "weight"
917
-
918
- # If input G is not connected the approximation factor increases to 3
919
- if not nx.is_connected(G):
920
- H = G.copy()
921
- connectors = list(one_edge_augmentation(H, avail=avail, weight=weight))
922
- H.add_edges_from(connectors)
923
-
924
- yield from connectors
925
- else:
926
- connectors = []
927
- H = G
928
-
929
- if len(avail) == 0:
930
- if nx.has_bridges(H):
931
- raise nx.NetworkXUnfeasible("no augmentation possible")
932
-
933
- avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=H)
934
-
935
- # Collapse input into a metagraph. Meta nodes are bridge-ccs
936
- bridge_ccs = nx.connectivity.bridge_components(H)
937
- C = collapse(H, bridge_ccs)
938
-
939
- # Use the meta graph to shrink avail to a small feasible subset
940
- mapping = C.graph["mapping"]
941
- # Choose the minimum weight feasible edge in each group
942
- meta_to_wuv = {
943
- (mu, mv): (w, uv)
944
- for (mu, mv), uv, w in _lightest_meta_edges(mapping, avail_uv, avail_w)
945
- }
946
-
947
- # Mapping of terms from (Khuller and Thurimella):
948
- # C : G_0 = (V, E^0)
949
- # This is the metagraph where each node is a 2-edge-cc in G.
950
- # The edges in C represent bridges in the original graph.
951
- # (mu, mv) : E - E^0 # they group both avail and given edges in E
952
- # T : \Gamma
953
- # D : G^D = (V, E_D)
954
-
955
- # The paper uses ancestor because children point to parents, which is
956
- # contrary to networkx standards. So, we actually need to run
957
- # nx.least_common_ancestor on the reversed Tree.
958
-
959
- # Pick an arbitrary leaf from C as the root
960
- try:
961
- root = next(n for n, d in C.degree() if d == 1)
962
- except StopIteration: # no nodes found with degree == 1
963
- return
964
- # Root C into a tree TR by directing all edges away from the root
965
- # Note in their paper T directs edges towards the root
966
- TR = nx.dfs_tree(C, root)
967
-
968
- # Add to D the directed edges of T and set their weight to zero
969
- # This indicates that it costs nothing to use edges that were given.
970
- D = nx.reverse(TR).copy()
971
-
972
- nx.set_edge_attributes(D, name="weight", values=0)
973
-
974
- # The LCA of mu and mv in T is the shared ancestor of mu and mv that is
975
- # located farthest from the root.
976
- lca_gen = nx.tree_all_pairs_lowest_common_ancestor(
977
- TR, root=root, pairs=meta_to_wuv.keys()
978
- )
979
-
980
- for (mu, mv), lca in lca_gen:
981
- w, uv = meta_to_wuv[(mu, mv)]
982
- if lca == mu:
983
- # If u is an ancestor of v in TR, then add edge u->v to D
984
- D.add_edge(lca, mv, weight=w, generator=uv)
985
- elif lca == mv:
986
- # If v is an ancestor of u in TR, then add edge v->u to D
987
- D.add_edge(lca, mu, weight=w, generator=uv)
988
- else:
989
- # If neither u nor v is a ancestor of the other in TR
990
- # let t = lca(TR, u, v) and add edges t->u and t->v
991
- # Track the original edge that GENERATED these edges.
992
- D.add_edge(lca, mu, weight=w, generator=uv)
993
- D.add_edge(lca, mv, weight=w, generator=uv)
994
-
995
- # Then compute a minimum rooted branching
996
- try:
997
- # Note the original edges must be directed towards to root for the
998
- # branching to give us a bridge-augmentation.
999
- A = _minimum_rooted_branching(D, root)
1000
- except nx.NetworkXException as err:
1001
- # If there is no branching then augmentation is not possible
1002
- raise nx.NetworkXUnfeasible("no 2-edge-augmentation possible") from err
1003
-
1004
- # For each edge e, in the branching that did not belong to the directed
1005
- # tree T, add the corresponding edge that **GENERATED** it (this is not
1006
- # necessarily e itself!)
1007
-
1008
- # ensure the third case does not generate edges twice
1009
- bridge_connectors = set()
1010
- for mu, mv in A.edges():
1011
- data = D.get_edge_data(mu, mv)
1012
- if "generator" in data:
1013
- # Add the avail edge that generated the branching edge.
1014
- edge = data["generator"]
1015
- bridge_connectors.add(edge)
1016
-
1017
- yield from bridge_connectors
1018
-
1019
-
1020
- def _minimum_rooted_branching(D, root):
1021
- """Helper function to compute a minimum rooted branching (aka rooted
1022
- arborescence)
1023
-
1024
- Before the branching can be computed, the directed graph must be rooted by
1025
- removing the predecessors of root.
1026
-
1027
- A branching / arborescence of rooted graph G is a subgraph that contains a
1028
- directed path from the root to every other vertex. It is the directed
1029
- analog of the minimum spanning tree problem.
1030
-
1031
- References
1032
- ----------
1033
- [1] Khuller, Samir (2002) Advanced Algorithms Lecture 24 Notes.
1034
- https://web.archive.org/web/20121030033722/https://www.cs.umd.edu/class/spring2011/cmsc651/lec07.pdf
1035
- """
1036
- rooted = D.copy()
1037
- # root the graph by removing all predecessors to `root`.
1038
- rooted.remove_edges_from([(u, root) for u in D.predecessors(root)])
1039
- # Then compute the branching / arborescence.
1040
- A = nx.minimum_spanning_arborescence(rooted)
1041
- return A
1042
-
1043
-
1044
- @nx._dispatchable(returns_graph=True)
1045
- def collapse(G, grouped_nodes):
1046
- """Collapses each group of nodes into a single node.
1047
-
1048
- This is similar to condensation, but works on undirected graphs.
1049
-
1050
- Parameters
1051
- ----------
1052
- G : NetworkX Graph
1053
-
1054
- grouped_nodes: list or generator
1055
- Grouping of nodes to collapse. The grouping must be disjoint.
1056
- If grouped_nodes are strongly_connected_components then this is
1057
- equivalent to :func:`condensation`.
1058
-
1059
- Returns
1060
- -------
1061
- C : NetworkX Graph
1062
- The collapsed graph C of G with respect to the node grouping. The node
1063
- labels are integers corresponding to the index of the component in the
1064
- list of grouped_nodes. C has a graph attribute named 'mapping' with a
1065
- dictionary mapping the original nodes to the nodes in C to which they
1066
- belong. Each node in C also has a node attribute 'members' with the set
1067
- of original nodes in G that form the group that the node in C
1068
- represents.
1069
-
1070
- Examples
1071
- --------
1072
- >>> # Collapses a graph using disjoint groups, but not necessarily connected
1073
- >>> G = nx.Graph([(1, 0), (2, 3), (3, 1), (3, 4), (4, 5), (5, 6), (5, 7)])
1074
- >>> G.add_node("A")
1075
- >>> grouped_nodes = [{0, 1, 2, 3}, {5, 6, 7}]
1076
- >>> C = collapse(G, grouped_nodes)
1077
- >>> members = nx.get_node_attributes(C, "members")
1078
- >>> sorted(members.keys())
1079
- [0, 1, 2, 3]
1080
- >>> member_values = set(map(frozenset, members.values()))
1081
- >>> assert {0, 1, 2, 3} in member_values
1082
- >>> assert {4} in member_values
1083
- >>> assert {5, 6, 7} in member_values
1084
- >>> assert {"A"} in member_values
1085
- """
1086
- mapping = {}
1087
- members = {}
1088
- C = G.__class__()
1089
- i = 0 # required if G is empty
1090
- remaining = set(G.nodes())
1091
- for i, group in enumerate(grouped_nodes):
1092
- group = set(group)
1093
- assert remaining.issuperset(group), (
1094
- "grouped nodes must exist in G and be disjoint"
1095
- )
1096
- remaining.difference_update(group)
1097
- members[i] = group
1098
- mapping.update((n, i) for n in group)
1099
- # remaining nodes are in their own group
1100
- for i, node in enumerate(remaining, start=i + 1):
1101
- group = {node}
1102
- members[i] = group
1103
- mapping.update((n, i) for n in group)
1104
- number_of_groups = i + 1
1105
- C.add_nodes_from(range(number_of_groups))
1106
- C.add_edges_from(
1107
- (mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
1108
- )
1109
- # Add a list of members (ie original nodes) to each node (ie scc) in C.
1110
- nx.set_node_attributes(C, name="members", values=members)
1111
- # Add mapping dict as graph attribute
1112
- C.graph["mapping"] = mapping
1113
- return C
1114
-
1115
-
1116
- @nx._dispatchable
1117
- def complement_edges(G):
1118
- """Returns only the edges in the complement of G
1119
-
1120
- Parameters
1121
- ----------
1122
- G : NetworkX Graph
1123
-
1124
- Yields
1125
- ------
1126
- edge : tuple
1127
- Edges in the complement of G
1128
-
1129
- Examples
1130
- --------
1131
- >>> G = nx.path_graph((1, 2, 3, 4))
1132
- >>> sorted(complement_edges(G))
1133
- [(1, 3), (1, 4), (2, 4)]
1134
- >>> G = nx.path_graph((1, 2, 3, 4), nx.DiGraph())
1135
- >>> sorted(complement_edges(G))
1136
- [(1, 3), (1, 4), (2, 1), (2, 4), (3, 1), (3, 2), (4, 1), (4, 2), (4, 3)]
1137
- >>> G = nx.complete_graph(1000)
1138
- >>> sorted(complement_edges(G))
1139
- []
1140
- """
1141
- G_adj = G._adj # Store as a variable to eliminate attribute lookup
1142
- if G.is_directed():
1143
- for u, v in it.combinations(G.nodes(), 2):
1144
- if v not in G_adj[u]:
1145
- yield (u, v)
1146
- if u not in G_adj[v]:
1147
- yield (v, u)
1148
- else:
1149
- for u, v in it.combinations(G.nodes(), 2):
1150
- if v not in G_adj[u]:
1151
- yield (u, v)
1152
-
1153
-
1154
- def _compat_shuffle(rng, input):
1155
- """wrapper around rng.shuffle for python 2 compatibility reasons"""
1156
- rng.shuffle(input)
1157
-
1158
-
1159
- @not_implemented_for("multigraph")
1160
- @not_implemented_for("directed")
1161
- @py_random_state(4)
1162
- @nx._dispatchable
1163
- def greedy_k_edge_augmentation(G, k, avail=None, weight=None, seed=None):
1164
- """Greedy algorithm for finding a k-edge-augmentation
1165
-
1166
- Parameters
1167
- ----------
1168
- G : NetworkX graph
1169
- An undirected graph.
1170
-
1171
- k : integer
1172
- Desired edge connectivity
1173
-
1174
- avail : dict or a set of 2 or 3 tuples
1175
- For more details, see :func:`k_edge_augmentation`.
1176
-
1177
- weight : string
1178
- key to use to find weights if ``avail`` is a set of 3-tuples.
1179
- For more details, see :func:`k_edge_augmentation`.
1180
-
1181
- seed : integer, random_state, or None (default)
1182
- Indicator of random number generation state.
1183
- See :ref:`Randomness<randomness>`.
1184
-
1185
- Yields
1186
- ------
1187
- edge : tuple
1188
- Edges in the greedy augmentation of G
1189
-
1190
- Notes
1191
- -----
1192
- The algorithm is simple. Edges are incrementally added between parts of the
1193
- graph that are not yet locally k-edge-connected. Then edges are from the
1194
- augmenting set are pruned as long as local-edge-connectivity is not broken.
1195
-
1196
- This algorithm is greedy and does not provide optimality guarantees. It
1197
- exists only to provide :func:`k_edge_augmentation` with the ability to
1198
- generate a feasible solution for arbitrary k.
1199
-
1200
- See Also
1201
- --------
1202
- :func:`k_edge_augmentation`
1203
-
1204
- Examples
1205
- --------
1206
- >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
1207
- >>> sorted(greedy_k_edge_augmentation(G, k=2))
1208
- [(1, 7)]
1209
- >>> sorted(greedy_k_edge_augmentation(G, k=1, avail=[]))
1210
- []
1211
- >>> G = nx.path_graph((1, 2, 3, 4, 5, 6, 7))
1212
- >>> avail = {(u, v): 1 for (u, v) in complement_edges(G)}
1213
- >>> # randomized pruning process can produce different solutions
1214
- >>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=2))
1215
- [(1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (2, 4), (2, 6), (3, 7), (5, 7)]
1216
- >>> sorted(greedy_k_edge_augmentation(G, k=4, avail=avail, seed=3))
1217
- [(1, 3), (1, 5), (1, 6), (2, 4), (2, 6), (3, 7), (4, 7), (5, 7)]
1218
- """
1219
- # Result set
1220
- aug_edges = []
1221
-
1222
- done = is_k_edge_connected(G, k)
1223
- if done:
1224
- return
1225
- if avail is None:
1226
- # all edges are available
1227
- avail_uv = list(complement_edges(G))
1228
- avail_w = [1] * len(avail_uv)
1229
- else:
1230
- # Get the unique set of unweighted edges
1231
- avail_uv, avail_w = _unpack_available_edges(avail, weight=weight, G=G)
1232
-
1233
- # Greedy: order lightest edges. Use degree sum to tie-break
1234
- tiebreaker = [sum(map(G.degree, uv)) for uv in avail_uv]
1235
- avail_wduv = sorted(zip(avail_w, tiebreaker, avail_uv))
1236
- avail_uv = [uv for w, d, uv in avail_wduv]
1237
-
1238
- # Incrementally add edges in until we are k-connected
1239
- H = G.copy()
1240
- for u, v in avail_uv:
1241
- done = False
1242
- if not is_locally_k_edge_connected(H, u, v, k=k):
1243
- # Only add edges in parts that are not yet locally k-edge-connected
1244
- aug_edges.append((u, v))
1245
- H.add_edge(u, v)
1246
- # Did adding this edge help?
1247
- if H.degree(u) >= k and H.degree(v) >= k:
1248
- done = is_k_edge_connected(H, k)
1249
- if done:
1250
- break
1251
-
1252
- # Check for feasibility
1253
- if not done:
1254
- raise nx.NetworkXUnfeasible("not able to k-edge-connect with available edges")
1255
-
1256
- # Randomized attempt to reduce the size of the solution
1257
- _compat_shuffle(seed, aug_edges)
1258
- for u, v in list(aug_edges):
1259
- # Don't remove if we know it would break connectivity
1260
- if H.degree(u) <= k or H.degree(v) <= k:
1261
- continue
1262
- H.remove_edge(u, v)
1263
- aug_edges.remove((u, v))
1264
- if not is_k_edge_connected(H, k=k):
1265
- # If removing this edge breaks feasibility, undo
1266
- H.add_edge(u, v)
1267
- aug_edges.append((u, v))
1268
-
1269
- # Generate results
1270
- yield from aug_edges