okstra 0.130.4 → 0.131.1

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 +2019 -96
  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,1148 +0,0 @@
1
- """Functions for computing and verifying matchings in a graph."""
2
-
3
- from itertools import combinations, repeat
4
-
5
- import networkx as nx
6
- from networkx.utils import not_implemented_for
7
-
8
- __all__ = [
9
- "is_matching",
10
- "is_maximal_matching",
11
- "is_perfect_matching",
12
- "max_weight_matching",
13
- "min_weight_matching",
14
- "maximal_matching",
15
- ]
16
-
17
-
18
- @not_implemented_for("multigraph")
19
- @not_implemented_for("directed")
20
- @nx._dispatchable
21
- def maximal_matching(G):
22
- r"""Find a maximal matching in the graph.
23
-
24
- A matching is a subset of edges in which no node occurs more than once.
25
- A maximal matching cannot add more edges and still be a matching.
26
-
27
- Parameters
28
- ----------
29
- G : NetworkX graph
30
- Undirected graph
31
-
32
- Returns
33
- -------
34
- matching : set
35
- A maximal matching of the graph.
36
-
37
- Examples
38
- --------
39
- >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)])
40
- >>> sorted(nx.maximal_matching(G))
41
- [(1, 2), (3, 5)]
42
-
43
- Notes
44
- -----
45
- The algorithm greedily selects a maximal matching M of the graph G
46
- (i.e. no superset of M exists). It runs in $O(|E|)$ time.
47
- """
48
- matching = set()
49
- nodes = set()
50
- for edge in G.edges():
51
- # If the edge isn't covered, add it to the matching
52
- # then remove neighborhood of u and v from consideration.
53
- u, v = edge
54
- if u not in nodes and v not in nodes and u != v:
55
- matching.add(edge)
56
- nodes.update(edge)
57
- return matching
58
-
59
-
60
- def matching_dict_to_set(matching):
61
- """Converts matching dict format to matching set format
62
-
63
- Converts a dictionary representing a matching (as returned by
64
- :func:`max_weight_matching`) to a set representing a matching (as
65
- returned by :func:`maximal_matching`).
66
-
67
- In the definition of maximal matching adopted by NetworkX,
68
- self-loops are not allowed, so the provided dictionary is expected
69
- to never have any mapping from a key to itself. However, the
70
- dictionary is expected to have mirrored key/value pairs, for
71
- example, key ``u`` with value ``v`` and key ``v`` with value ``u``.
72
-
73
- """
74
- edges = set()
75
- for edge in matching.items():
76
- u, v = edge
77
- if (v, u) in edges or edge in edges:
78
- continue
79
- if u == v:
80
- raise nx.NetworkXError(f"Selfloops cannot appear in matchings {edge}")
81
- edges.add(edge)
82
- return edges
83
-
84
-
85
- @nx._dispatchable
86
- def is_matching(G, matching):
87
- """Return True if ``matching`` is a valid matching of ``G``
88
-
89
- A *matching* in a graph is a set of edges in which no two distinct
90
- edges share a common endpoint. Each node is incident to at most one
91
- edge in the matching. The edges are said to be independent.
92
-
93
- Parameters
94
- ----------
95
- G : NetworkX graph
96
-
97
- matching : dict or set
98
- A dictionary or set representing a matching. If a dictionary, it
99
- must have ``matching[u] == v`` and ``matching[v] == u`` for each
100
- edge ``(u, v)`` in the matching. If a set, it must have elements
101
- of the form ``(u, v)``, where ``(u, v)`` is an edge in the
102
- matching.
103
-
104
- Returns
105
- -------
106
- bool
107
- Whether the given set or dictionary represents a valid matching
108
- in the graph.
109
-
110
- Raises
111
- ------
112
- NetworkXError
113
- If the proposed matching has an edge to a node not in G.
114
- Or if the matching is not a collection of 2-tuple edges.
115
-
116
- Examples
117
- --------
118
- >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)])
119
- >>> nx.is_maximal_matching(G, {1: 3, 2: 4}) # using dict to represent matching
120
- True
121
-
122
- >>> nx.is_matching(G, {(1, 3), (2, 4)}) # using set to represent matching
123
- True
124
-
125
- """
126
- if isinstance(matching, dict):
127
- matching = matching_dict_to_set(matching)
128
-
129
- nodes = set()
130
- for edge in matching:
131
- if len(edge) != 2:
132
- raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
133
- u, v = edge
134
- if u not in G or v not in G:
135
- raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
136
- if u == v:
137
- return False
138
- if not G.has_edge(u, v):
139
- return False
140
- if u in nodes or v in nodes:
141
- return False
142
- nodes.update(edge)
143
- return True
144
-
145
-
146
- @nx._dispatchable
147
- def is_maximal_matching(G, matching):
148
- """Return True if ``matching`` is a maximal matching of ``G``
149
-
150
- A *maximal matching* in a graph is a matching in which adding any
151
- edge would cause the set to no longer be a valid matching.
152
-
153
- Parameters
154
- ----------
155
- G : NetworkX graph
156
-
157
- matching : dict or set
158
- A dictionary or set representing a matching. If a dictionary, it
159
- must have ``matching[u] == v`` and ``matching[v] == u`` for each
160
- edge ``(u, v)`` in the matching. If a set, it must have elements
161
- of the form ``(u, v)``, where ``(u, v)`` is an edge in the
162
- matching.
163
-
164
- Returns
165
- -------
166
- bool
167
- Whether the given set or dictionary represents a valid maximal
168
- matching in the graph.
169
-
170
- Examples
171
- --------
172
- >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)])
173
- >>> nx.is_maximal_matching(G, {(1, 2), (3, 4)})
174
- True
175
-
176
- """
177
- if isinstance(matching, dict):
178
- matching = matching_dict_to_set(matching)
179
- # If the given set is not a matching, then it is not a maximal matching.
180
- edges = set()
181
- nodes = set()
182
- for edge in matching:
183
- if len(edge) != 2:
184
- raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
185
- u, v = edge
186
- if u not in G or v not in G:
187
- raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
188
- if u == v:
189
- return False
190
- if not G.has_edge(u, v):
191
- return False
192
- if u in nodes or v in nodes:
193
- return False
194
- nodes.update(edge)
195
- edges.add(edge)
196
- edges.add((v, u))
197
- # A matching is maximal if adding any new edge from G to it
198
- # causes the resulting set to match some node twice.
199
- # Be careful to check for adding selfloops
200
- for u, v in G.edges:
201
- if (u, v) not in edges:
202
- # could add edge (u, v) to edges and have a bigger matching
203
- if u not in nodes and v not in nodes and u != v:
204
- return False
205
- return True
206
-
207
-
208
- @nx._dispatchable
209
- def is_perfect_matching(G, matching):
210
- """Return True if ``matching`` is a perfect matching for ``G``
211
-
212
- A *perfect matching* in a graph is a matching in which exactly one edge
213
- is incident upon each vertex.
214
-
215
- Parameters
216
- ----------
217
- G : NetworkX graph
218
-
219
- matching : dict or set
220
- A dictionary or set representing a matching. If a dictionary, it
221
- must have ``matching[u] == v`` and ``matching[v] == u`` for each
222
- edge ``(u, v)`` in the matching. If a set, it must have elements
223
- of the form ``(u, v)``, where ``(u, v)`` is an edge in the
224
- matching.
225
-
226
- Returns
227
- -------
228
- bool
229
- Whether the given set or dictionary represents a valid perfect
230
- matching in the graph.
231
-
232
- Examples
233
- --------
234
- >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 6)])
235
- >>> my_match = {1: 2, 3: 5, 4: 6}
236
- >>> nx.is_perfect_matching(G, my_match)
237
- True
238
-
239
- """
240
- if isinstance(matching, dict):
241
- matching = matching_dict_to_set(matching)
242
-
243
- nodes = set()
244
- for edge in matching:
245
- if len(edge) != 2:
246
- raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}")
247
- u, v = edge
248
- if u not in G or v not in G:
249
- raise nx.NetworkXError(f"matching contains edge {edge} with node not in G")
250
- if u == v:
251
- return False
252
- if not G.has_edge(u, v):
253
- return False
254
- if u in nodes or v in nodes:
255
- return False
256
- nodes.update(edge)
257
- return len(nodes) == len(G)
258
-
259
-
260
- @not_implemented_for("multigraph")
261
- @not_implemented_for("directed")
262
- @nx._dispatchable(edge_attrs="weight")
263
- def min_weight_matching(G, weight="weight"):
264
- """Compute a minimum-weight maximum-cardinality matching of `G`.
265
-
266
- The minimum-weight maximum-cardinality matching is the matching
267
- that has the minimum weight among all maximum-cardinality matchings.
268
-
269
- Use the maximum-weight algorithm with edge weights subtracted
270
- from the maximum weight of all edges.
271
-
272
- A matching is a subset of edges in which no node occurs more than once.
273
- The weight of a matching is the sum of the weights of its edges.
274
- A maximal matching cannot add more edges and still be a matching.
275
- The cardinality of a matching is the number of matched edges.
276
-
277
- This method replaces the edge weights with 1 plus the maximum edge weight
278
- minus the original edge weight.
279
-
280
- new_weight = (max_weight + 1) - edge_weight
281
-
282
- then runs :func:`max_weight_matching` with the new weights.
283
- The max weight matching with these new weights corresponds
284
- to the min weight matching using the original weights.
285
- Adding 1 to the max edge weight keeps all edge weights positive
286
- and as integers if they started as integers.
287
-
288
- Read the documentation of `max_weight_matching` for more information.
289
-
290
- Parameters
291
- ----------
292
- G : NetworkX graph
293
- Undirected graph
294
-
295
- weight: string, optional (default='weight')
296
- Edge data key corresponding to the edge weight.
297
- If key not found, uses 1 as weight.
298
-
299
- Returns
300
- -------
301
- matching : set
302
- A minimal weight matching of the graph.
303
-
304
- See Also
305
- --------
306
- max_weight_matching
307
- """
308
- if len(G.edges) == 0:
309
- return max_weight_matching(G, maxcardinality=True, weight=weight)
310
- G_edges = G.edges(data=weight, default=1)
311
- max_weight = 1 + max(w for _, _, w in G_edges)
312
- InvG = nx.Graph()
313
- edges = ((u, v, max_weight - w) for u, v, w in G_edges)
314
- InvG.add_weighted_edges_from(edges, weight=weight)
315
- return max_weight_matching(InvG, maxcardinality=True, weight=weight)
316
-
317
-
318
- @not_implemented_for("multigraph")
319
- @not_implemented_for("directed")
320
- @nx._dispatchable(edge_attrs="weight")
321
- def max_weight_matching(G, maxcardinality=False, weight="weight"):
322
- """Compute a maximum-weighted matching of G.
323
-
324
- A matching is a subset of edges in which no node occurs more than once.
325
- The weight of a matching is the sum of the weights of its edges.
326
- A maximal matching cannot add more edges and still be a matching.
327
- The cardinality of a matching is the number of matched edges.
328
-
329
- Parameters
330
- ----------
331
- G : NetworkX graph
332
- Undirected graph
333
-
334
- maxcardinality: bool, optional (default=False)
335
- If maxcardinality is True, compute the maximum-cardinality matching
336
- with maximum weight among all maximum-cardinality matchings.
337
-
338
- weight: string, optional (default='weight')
339
- Edge data key corresponding to the edge weight.
340
- If key not found, uses 1 as weight.
341
-
342
-
343
- Returns
344
- -------
345
- matching : set
346
- A maximal matching of the graph.
347
-
348
- Examples
349
- --------
350
- >>> G = nx.Graph()
351
- >>> edges = [(1, 2, 6), (1, 3, 2), (2, 3, 1), (2, 4, 7), (3, 5, 9), (4, 5, 3)]
352
- >>> G.add_weighted_edges_from(edges)
353
- >>> sorted(nx.max_weight_matching(G))
354
- [(2, 4), (5, 3)]
355
-
356
- Notes
357
- -----
358
- If G has edges with weight attributes the edge data are used as
359
- weight values else the weights are assumed to be 1.
360
-
361
- This function takes time O(number_of_nodes ** 3).
362
-
363
- If all edge weights are integers, the algorithm uses only integer
364
- computations. If floating point weights are used, the algorithm
365
- could return a slightly suboptimal matching due to numeric
366
- precision errors.
367
-
368
- This method is based on the "blossom" method for finding augmenting
369
- paths and the "primal-dual" method for finding a matching of maximum
370
- weight, both methods invented by Jack Edmonds [1]_.
371
-
372
- Bipartite graphs can also be matched using the functions present in
373
- :mod:`networkx.algorithms.bipartite.matching`.
374
-
375
- References
376
- ----------
377
- .. [1] "Efficient Algorithms for Finding Maximum Matching in Graphs",
378
- Zvi Galil, ACM Computing Surveys, 1986.
379
- """
380
- #
381
- # The algorithm is taken from "Efficient Algorithms for Finding Maximum
382
- # Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986.
383
- # It is based on the "blossom" method for finding augmenting paths and
384
- # the "primal-dual" method for finding a matching of maximum weight, both
385
- # methods invented by Jack Edmonds.
386
- #
387
- # A C program for maximum weight matching by Ed Rothberg was used
388
- # extensively to validate this new code.
389
- #
390
- # Many terms used in the code comments are explained in the paper
391
- # by Galil. You will probably need the paper to make sense of this code.
392
- #
393
-
394
- class NoNode:
395
- """Dummy value which is different from any node."""
396
-
397
- class Blossom:
398
- """Representation of a non-trivial blossom or sub-blossom."""
399
-
400
- __slots__ = ["childs", "edges", "mybestedges"]
401
-
402
- # b.childs is an ordered list of b's sub-blossoms, starting with
403
- # the base and going round the blossom.
404
-
405
- # b.edges is the list of b's connecting edges, such that
406
- # b.edges[i] = (v, w) where v is a vertex in b.childs[i]
407
- # and w is a vertex in b.childs[wrap(i+1)].
408
-
409
- # If b is a top-level S-blossom,
410
- # b.mybestedges is a list of least-slack edges to neighboring
411
- # S-blossoms, or None if no such list has been computed yet.
412
- # This is used for efficient computation of delta3.
413
-
414
- # Generate the blossom's leaf vertices.
415
- def leaves(self):
416
- stack = [*self.childs]
417
- while stack:
418
- t = stack.pop()
419
- if isinstance(t, Blossom):
420
- stack.extend(t.childs)
421
- else:
422
- yield t
423
-
424
- # Get a list of vertices.
425
- gnodes = list(G)
426
- if not gnodes:
427
- return set() # don't bother with empty graphs
428
-
429
- # Find the maximum edge weight.
430
- maxweight = 0
431
- allinteger = True
432
- for i, j, d in G.edges(data=True):
433
- wt = d.get(weight, 1)
434
- if i != j and wt > maxweight:
435
- maxweight = wt
436
- allinteger = allinteger and (str(type(wt)).split("'")[1] in ("int", "long"))
437
-
438
- # If v is a matched vertex, mate[v] is its partner vertex.
439
- # If v is a single vertex, v does not occur as a key in mate.
440
- # Initially all vertices are single; updated during augmentation.
441
- mate = {}
442
-
443
- # If b is a top-level blossom,
444
- # label.get(b) is None if b is unlabeled (free),
445
- # 1 if b is an S-blossom,
446
- # 2 if b is a T-blossom.
447
- # The label of a vertex is found by looking at the label of its top-level
448
- # containing blossom.
449
- # If v is a vertex inside a T-blossom, label[v] is 2 iff v is reachable
450
- # from an S-vertex outside the blossom.
451
- # Labels are assigned during a stage and reset after each augmentation.
452
- label = {}
453
-
454
- # If b is a labeled top-level blossom,
455
- # labeledge[b] = (v, w) is the edge through which b obtained its label
456
- # such that w is a vertex in b, or None if b's base vertex is single.
457
- # If w is a vertex inside a T-blossom and label[w] == 2,
458
- # labeledge[w] = (v, w) is an edge through which w is reachable from
459
- # outside the blossom.
460
- labeledge = {}
461
-
462
- # If v is a vertex, inblossom[v] is the top-level blossom to which v
463
- # belongs.
464
- # If v is a top-level vertex, inblossom[v] == v since v is itself
465
- # a (trivial) top-level blossom.
466
- # Initially all vertices are top-level trivial blossoms.
467
- inblossom = dict(zip(gnodes, gnodes))
468
-
469
- # If b is a sub-blossom,
470
- # blossomparent[b] is its immediate parent (sub-)blossom.
471
- # If b is a top-level blossom, blossomparent[b] is None.
472
- blossomparent = dict(zip(gnodes, repeat(None)))
473
-
474
- # If b is a (sub-)blossom,
475
- # blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
476
- blossombase = dict(zip(gnodes, gnodes))
477
-
478
- # If w is a free vertex (or an unreached vertex inside a T-blossom),
479
- # bestedge[w] = (v, w) is the least-slack edge from an S-vertex,
480
- # or None if there is no such edge.
481
- # If b is a (possibly trivial) top-level S-blossom,
482
- # bestedge[b] = (v, w) is the least-slack edge to a different S-blossom
483
- # (v inside b), or None if there is no such edge.
484
- # This is used for efficient computation of delta2 and delta3.
485
- bestedge = {}
486
-
487
- # If v is a vertex,
488
- # dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
489
- # optimization problem (if all edge weights are integers, multiplication
490
- # by two ensures that all values remain integers throughout the algorithm).
491
- # Initially, u(v) = maxweight / 2.
492
- dualvar = dict(zip(gnodes, repeat(maxweight)))
493
-
494
- # If b is a non-trivial blossom,
495
- # blossomdual[b] = z(b) where z(b) is b's variable in the dual
496
- # optimization problem.
497
- blossomdual = {}
498
-
499
- # If (v, w) in allowedge or (w, v) in allowedg, then the edge
500
- # (v, w) is known to have zero slack in the optimization problem;
501
- # otherwise the edge may or may not have zero slack.
502
- allowedge = {}
503
-
504
- # Queue of newly discovered S-vertices.
505
- queue = []
506
-
507
- # Return 2 * slack of edge (v, w) (does not work inside blossoms).
508
- def slack(v, w):
509
- return dualvar[v] + dualvar[w] - 2 * G[v][w].get(weight, 1)
510
-
511
- # Assign label t to the top-level blossom containing vertex w,
512
- # coming through an edge from vertex v.
513
- def assignLabel(w, t, v):
514
- b = inblossom[w]
515
- assert label.get(w) is None and label.get(b) is None
516
- label[w] = label[b] = t
517
- if v is not None:
518
- labeledge[w] = labeledge[b] = (v, w)
519
- else:
520
- labeledge[w] = labeledge[b] = None
521
- bestedge[w] = bestedge[b] = None
522
- if t == 1:
523
- # b became an S-vertex/blossom; add it(s vertices) to the queue.
524
- if isinstance(b, Blossom):
525
- queue.extend(b.leaves())
526
- else:
527
- queue.append(b)
528
- elif t == 2:
529
- # b became a T-vertex/blossom; assign label S to its mate.
530
- # (If b is a non-trivial blossom, its base is the only vertex
531
- # with an external mate.)
532
- base = blossombase[b]
533
- assignLabel(mate[base], 1, base)
534
-
535
- # Trace back from vertices v and w to discover either a new blossom
536
- # or an augmenting path. Return the base vertex of the new blossom,
537
- # or NoNode if an augmenting path was found.
538
- def scanBlossom(v, w):
539
- # Trace back from v and w, placing breadcrumbs as we go.
540
- path = []
541
- base = NoNode
542
- while v is not NoNode:
543
- # Look for a breadcrumb in v's blossom or put a new breadcrumb.
544
- b = inblossom[v]
545
- if label[b] & 4:
546
- base = blossombase[b]
547
- break
548
- assert label[b] == 1
549
- path.append(b)
550
- label[b] = 5
551
- # Trace one step back.
552
- if labeledge[b] is None:
553
- # The base of blossom b is single; stop tracing this path.
554
- assert blossombase[b] not in mate
555
- v = NoNode
556
- else:
557
- assert labeledge[b][0] == mate[blossombase[b]]
558
- v = labeledge[b][0]
559
- b = inblossom[v]
560
- assert label[b] == 2
561
- # b is a T-blossom; trace one more step back.
562
- v = labeledge[b][0]
563
- # Swap v and w so that we alternate between both paths.
564
- if w is not NoNode:
565
- v, w = w, v
566
- # Remove breadcrumbs.
567
- for b in path:
568
- label[b] = 1
569
- # Return base vertex, if we found one.
570
- return base
571
-
572
- # Construct a new blossom with given base, through S-vertices v and w.
573
- # Label the new blossom as S; set its dual variable to zero;
574
- # relabel its T-vertices to S and add them to the queue.
575
- def addBlossom(base, v, w):
576
- bb = inblossom[base]
577
- bv = inblossom[v]
578
- bw = inblossom[w]
579
- # Create blossom.
580
- b = Blossom()
581
- blossombase[b] = base
582
- blossomparent[b] = None
583
- blossomparent[bb] = b
584
- # Make list of sub-blossoms and their interconnecting edge endpoints.
585
- b.childs = path = []
586
- b.edges = edgs = [(v, w)]
587
- # Trace back from v to base.
588
- while bv != bb:
589
- # Add bv to the new blossom.
590
- blossomparent[bv] = b
591
- path.append(bv)
592
- edgs.append(labeledge[bv])
593
- assert label[bv] == 2 or (
594
- label[bv] == 1 and labeledge[bv][0] == mate[blossombase[bv]]
595
- )
596
- # Trace one step back.
597
- v = labeledge[bv][0]
598
- bv = inblossom[v]
599
- # Add base sub-blossom; reverse lists.
600
- path.append(bb)
601
- path.reverse()
602
- edgs.reverse()
603
- # Trace back from w to base.
604
- while bw != bb:
605
- # Add bw to the new blossom.
606
- blossomparent[bw] = b
607
- path.append(bw)
608
- edgs.append((labeledge[bw][1], labeledge[bw][0]))
609
- assert label[bw] == 2 or (
610
- label[bw] == 1 and labeledge[bw][0] == mate[blossombase[bw]]
611
- )
612
- # Trace one step back.
613
- w = labeledge[bw][0]
614
- bw = inblossom[w]
615
- # Set label to S.
616
- assert label[bb] == 1
617
- label[b] = 1
618
- labeledge[b] = labeledge[bb]
619
- # Set dual variable to zero.
620
- blossomdual[b] = 0
621
- # Relabel vertices.
622
- for v in b.leaves():
623
- if label[inblossom[v]] == 2:
624
- # This T-vertex now turns into an S-vertex because it becomes
625
- # part of an S-blossom; add it to the queue.
626
- queue.append(v)
627
- inblossom[v] = b
628
- # Compute b.mybestedges.
629
- bestedgeto = {}
630
- for bv in path:
631
- if isinstance(bv, Blossom):
632
- if bv.mybestedges is not None:
633
- # Walk this subblossom's least-slack edges.
634
- nblist = bv.mybestedges
635
- # The sub-blossom won't need this data again.
636
- bv.mybestedges = None
637
- else:
638
- # This subblossom does not have a list of least-slack
639
- # edges; get the information from the vertices.
640
- nblist = [
641
- (v, w) for v in bv.leaves() for w in G.neighbors(v) if v != w
642
- ]
643
- else:
644
- nblist = [(bv, w) for w in G.neighbors(bv) if bv != w]
645
- for k in nblist:
646
- (i, j) = k
647
- if inblossom[j] == b:
648
- i, j = j, i
649
- bj = inblossom[j]
650
- if (
651
- bj != b
652
- and label.get(bj) == 1
653
- and ((bj not in bestedgeto) or slack(i, j) < slack(*bestedgeto[bj]))
654
- ):
655
- bestedgeto[bj] = k
656
- # Forget about least-slack edge of the subblossom.
657
- bestedge[bv] = None
658
- b.mybestedges = list(bestedgeto.values())
659
- # Select bestedge[b].
660
- mybestedge = None
661
- bestedge[b] = None
662
- for k in b.mybestedges:
663
- kslack = slack(*k)
664
- if mybestedge is None or kslack < mybestslack:
665
- mybestedge = k
666
- mybestslack = kslack
667
- bestedge[b] = mybestedge
668
-
669
- # Expand the given top-level blossom.
670
- def expandBlossom(b, endstage):
671
- # This is an obnoxiously complicated recursive function for the sake of
672
- # a stack-transformation. So, we hack around the complexity by using
673
- # a trampoline pattern. By yielding the arguments to each recursive
674
- # call, we keep the actual callstack flat.
675
-
676
- def _recurse(b, endstage):
677
- # Convert sub-blossoms into top-level blossoms.
678
- for s in b.childs:
679
- blossomparent[s] = None
680
- if isinstance(s, Blossom):
681
- if endstage and blossomdual[s] == 0:
682
- # Recursively expand this sub-blossom.
683
- yield s
684
- else:
685
- for v in s.leaves():
686
- inblossom[v] = s
687
- else:
688
- inblossom[s] = s
689
- # If we expand a T-blossom during a stage, its sub-blossoms must be
690
- # relabeled.
691
- if (not endstage) and label.get(b) == 2:
692
- # Start at the sub-blossom through which the expanding
693
- # blossom obtained its label, and relabel sub-blossoms untili
694
- # we reach the base.
695
- # Figure out through which sub-blossom the expanding blossom
696
- # obtained its label initially.
697
- entrychild = inblossom[labeledge[b][1]]
698
- # Decide in which direction we will go round the blossom.
699
- j = b.childs.index(entrychild)
700
- if j & 1:
701
- # Start index is odd; go forward and wrap.
702
- j -= len(b.childs)
703
- jstep = 1
704
- else:
705
- # Start index is even; go backward.
706
- jstep = -1
707
- # Move along the blossom until we get to the base.
708
- v, w = labeledge[b]
709
- while j != 0:
710
- # Relabel the T-sub-blossom.
711
- if jstep == 1:
712
- p, q = b.edges[j]
713
- else:
714
- q, p = b.edges[j - 1]
715
- label[w] = None
716
- label[q] = None
717
- assignLabel(w, 2, v)
718
- # Step to the next S-sub-blossom and note its forward edge.
719
- allowedge[(p, q)] = allowedge[(q, p)] = True
720
- j += jstep
721
- if jstep == 1:
722
- v, w = b.edges[j]
723
- else:
724
- w, v = b.edges[j - 1]
725
- # Step to the next T-sub-blossom.
726
- allowedge[(v, w)] = allowedge[(w, v)] = True
727
- j += jstep
728
- # Relabel the base T-sub-blossom WITHOUT stepping through to
729
- # its mate (so don't call assignLabel).
730
- bw = b.childs[j]
731
- label[w] = label[bw] = 2
732
- labeledge[w] = labeledge[bw] = (v, w)
733
- bestedge[bw] = None
734
- # Continue along the blossom until we get back to entrychild.
735
- j += jstep
736
- while b.childs[j] != entrychild:
737
- # Examine the vertices of the sub-blossom to see whether
738
- # it is reachable from a neighboring S-vertex outside the
739
- # expanding blossom.
740
- bv = b.childs[j]
741
- if label.get(bv) == 1:
742
- # This sub-blossom just got label S through one of its
743
- # neighbors; leave it be.
744
- j += jstep
745
- continue
746
- if isinstance(bv, Blossom):
747
- for v in bv.leaves():
748
- if label.get(v):
749
- break
750
- else:
751
- v = bv
752
- # If the sub-blossom contains a reachable vertex, assign
753
- # label T to the sub-blossom.
754
- if label.get(v):
755
- assert label[v] == 2
756
- assert inblossom[v] == bv
757
- label[v] = None
758
- label[mate[blossombase[bv]]] = None
759
- assignLabel(v, 2, labeledge[v][0])
760
- j += jstep
761
- # Remove the expanded blossom entirely.
762
- label.pop(b, None)
763
- labeledge.pop(b, None)
764
- bestedge.pop(b, None)
765
- del blossomparent[b]
766
- del blossombase[b]
767
- del blossomdual[b]
768
-
769
- # Now, we apply the trampoline pattern. We simulate a recursive
770
- # callstack by maintaining a stack of generators, each yielding a
771
- # sequence of function arguments. We grow the stack by appending a call
772
- # to _recurse on each argument tuple, and shrink the stack whenever a
773
- # generator is exhausted.
774
- stack = [_recurse(b, endstage)]
775
- while stack:
776
- top = stack[-1]
777
- for s in top:
778
- stack.append(_recurse(s, endstage))
779
- break
780
- else:
781
- stack.pop()
782
-
783
- # Swap matched/unmatched edges over an alternating path through blossom b
784
- # between vertex v and the base vertex. Keep blossom bookkeeping
785
- # consistent.
786
- def augmentBlossom(b, v):
787
- # This is an obnoxiously complicated recursive function for the sake of
788
- # a stack-transformation. So, we hack around the complexity by using
789
- # a trampoline pattern. By yielding the arguments to each recursive
790
- # call, we keep the actual callstack flat.
791
-
792
- def _recurse(b, v):
793
- # Bubble up through the blossom tree from vertex v to an immediate
794
- # sub-blossom of b.
795
- t = v
796
- while blossomparent[t] != b:
797
- t = blossomparent[t]
798
- # Recursively deal with the first sub-blossom.
799
- if isinstance(t, Blossom):
800
- yield (t, v)
801
- # Decide in which direction we will go round the blossom.
802
- i = j = b.childs.index(t)
803
- if i & 1:
804
- # Start index is odd; go forward and wrap.
805
- j -= len(b.childs)
806
- jstep = 1
807
- else:
808
- # Start index is even; go backward.
809
- jstep = -1
810
- # Move along the blossom until we get to the base.
811
- while j != 0:
812
- # Step to the next sub-blossom and augment it recursively.
813
- j += jstep
814
- t = b.childs[j]
815
- if jstep == 1:
816
- w, x = b.edges[j]
817
- else:
818
- x, w = b.edges[j - 1]
819
- if isinstance(t, Blossom):
820
- yield (t, w)
821
- # Step to the next sub-blossom and augment it recursively.
822
- j += jstep
823
- t = b.childs[j]
824
- if isinstance(t, Blossom):
825
- yield (t, x)
826
- # Match the edge connecting those sub-blossoms.
827
- mate[w] = x
828
- mate[x] = w
829
- # Rotate the list of sub-blossoms to put the new base at the front.
830
- b.childs = b.childs[i:] + b.childs[:i]
831
- b.edges = b.edges[i:] + b.edges[:i]
832
- blossombase[b] = blossombase[b.childs[0]]
833
- assert blossombase[b] == v
834
-
835
- # Now, we apply the trampoline pattern. We simulate a recursive
836
- # callstack by maintaining a stack of generators, each yielding a
837
- # sequence of function arguments. We grow the stack by appending a call
838
- # to _recurse on each argument tuple, and shrink the stack whenever a
839
- # generator is exhausted.
840
- stack = [_recurse(b, v)]
841
- while stack:
842
- top = stack[-1]
843
- for args in top:
844
- stack.append(_recurse(*args))
845
- break
846
- else:
847
- stack.pop()
848
-
849
- # Swap matched/unmatched edges over an alternating path between two
850
- # single vertices. The augmenting path runs through S-vertices v and w.
851
- def augmentMatching(v, w):
852
- for s, j in ((v, w), (w, v)):
853
- # Match vertex s to vertex j. Then trace back from s
854
- # until we find a single vertex, swapping matched and unmatched
855
- # edges as we go.
856
- while 1:
857
- bs = inblossom[s]
858
- assert label[bs] == 1
859
- assert (labeledge[bs] is None and blossombase[bs] not in mate) or (
860
- labeledge[bs][0] == mate[blossombase[bs]]
861
- )
862
- # Augment through the S-blossom from s to base.
863
- if isinstance(bs, Blossom):
864
- augmentBlossom(bs, s)
865
- # Update mate[s]
866
- mate[s] = j
867
- # Trace one step back.
868
- if labeledge[bs] is None:
869
- # Reached single vertex; stop.
870
- break
871
- t = labeledge[bs][0]
872
- bt = inblossom[t]
873
- assert label[bt] == 2
874
- # Trace one more step back.
875
- s, j = labeledge[bt]
876
- # Augment through the T-blossom from j to base.
877
- assert blossombase[bt] == t
878
- if isinstance(bt, Blossom):
879
- augmentBlossom(bt, j)
880
- # Update mate[j]
881
- mate[j] = s
882
-
883
- # Verify that the optimum solution has been reached.
884
- def verifyOptimum():
885
- if maxcardinality:
886
- # Vertices may have negative dual;
887
- # find a constant non-negative number to add to all vertex duals.
888
- vdualoffset = max(0, -min(dualvar.values()))
889
- else:
890
- vdualoffset = 0
891
- # 0. all dual variables are non-negative
892
- assert min(dualvar.values()) + vdualoffset >= 0
893
- assert len(blossomdual) == 0 or min(blossomdual.values()) >= 0
894
- # 0. all edges have non-negative slack and
895
- # 1. all matched edges have zero slack;
896
- for i, j, d in G.edges(data=True):
897
- wt = d.get(weight, 1)
898
- if i == j:
899
- continue # ignore self-loops
900
- s = dualvar[i] + dualvar[j] - 2 * wt
901
- iblossoms = [i]
902
- jblossoms = [j]
903
- while blossomparent[iblossoms[-1]] is not None:
904
- iblossoms.append(blossomparent[iblossoms[-1]])
905
- while blossomparent[jblossoms[-1]] is not None:
906
- jblossoms.append(blossomparent[jblossoms[-1]])
907
- iblossoms.reverse()
908
- jblossoms.reverse()
909
- for bi, bj in zip(iblossoms, jblossoms):
910
- if bi != bj:
911
- break
912
- s += 2 * blossomdual[bi]
913
- assert s >= 0
914
- if mate.get(i) == j or mate.get(j) == i:
915
- assert mate[i] == j and mate[j] == i
916
- assert s == 0
917
- # 2. all single vertices have zero dual value;
918
- for v in gnodes:
919
- assert (v in mate) or dualvar[v] + vdualoffset == 0
920
- # 3. all blossoms with positive dual value are full.
921
- for b in blossomdual:
922
- if blossomdual[b] > 0:
923
- assert len(b.edges) % 2 == 1
924
- for i, j in b.edges[1::2]:
925
- assert mate[i] == j and mate[j] == i
926
- # Ok.
927
-
928
- # Main loop: continue until no further improvement is possible.
929
- while 1:
930
- # Each iteration of this loop is a "stage".
931
- # A stage finds an augmenting path and uses that to improve
932
- # the matching.
933
-
934
- # Remove labels from top-level blossoms/vertices.
935
- label.clear()
936
- labeledge.clear()
937
-
938
- # Forget all about least-slack edges.
939
- bestedge.clear()
940
- for b in blossomdual:
941
- b.mybestedges = None
942
-
943
- # Loss of labeling means that we can not be sure that currently
944
- # allowable edges remain allowable throughout this stage.
945
- allowedge.clear()
946
-
947
- # Make queue empty.
948
- queue[:] = []
949
-
950
- # Label single blossoms/vertices with S and put them in the queue.
951
- for v in gnodes:
952
- if (v not in mate) and label.get(inblossom[v]) is None:
953
- assignLabel(v, 1, None)
954
-
955
- # Loop until we succeed in augmenting the matching.
956
- augmented = 0
957
- while 1:
958
- # Each iteration of this loop is a "substage".
959
- # A substage tries to find an augmenting path;
960
- # if found, the path is used to improve the matching and
961
- # the stage ends. If there is no augmenting path, the
962
- # primal-dual method is used to pump some slack out of
963
- # the dual variables.
964
-
965
- # Continue labeling until all vertices which are reachable
966
- # through an alternating path have got a label.
967
- while queue and not augmented:
968
- # Take an S vertex from the queue.
969
- v = queue.pop()
970
- assert label[inblossom[v]] == 1
971
-
972
- # Scan its neighbors:
973
- for w in G.neighbors(v):
974
- if w == v:
975
- continue # ignore self-loops
976
- # w is a neighbor to v
977
- bv = inblossom[v]
978
- bw = inblossom[w]
979
- if bv == bw:
980
- # this edge is internal to a blossom; ignore it
981
- continue
982
- if (v, w) not in allowedge:
983
- kslack = slack(v, w)
984
- if kslack <= 0:
985
- # edge k has zero slack => it is allowable
986
- allowedge[(v, w)] = allowedge[(w, v)] = True
987
- if (v, w) in allowedge:
988
- if label.get(bw) is None:
989
- # (C1) w is a free vertex;
990
- # label w with T and label its mate with S (R12).
991
- assignLabel(w, 2, v)
992
- elif label.get(bw) == 1:
993
- # (C2) w is an S-vertex (not in the same blossom);
994
- # follow back-links to discover either an
995
- # augmenting path or a new blossom.
996
- base = scanBlossom(v, w)
997
- if base is not NoNode:
998
- # Found a new blossom; add it to the blossom
999
- # bookkeeping and turn it into an S-blossom.
1000
- addBlossom(base, v, w)
1001
- else:
1002
- # Found an augmenting path; augment the
1003
- # matching and end this stage.
1004
- augmentMatching(v, w)
1005
- augmented = 1
1006
- break
1007
- elif label.get(w) is None:
1008
- # w is inside a T-blossom, but w itself has not
1009
- # yet been reached from outside the blossom;
1010
- # mark it as reached (we need this to relabel
1011
- # during T-blossom expansion).
1012
- assert label[bw] == 2
1013
- label[w] = 2
1014
- labeledge[w] = (v, w)
1015
- elif label.get(bw) == 1:
1016
- # keep track of the least-slack non-allowable edge to
1017
- # a different S-blossom.
1018
- if bestedge.get(bv) is None or kslack < slack(*bestedge[bv]):
1019
- bestedge[bv] = (v, w)
1020
- elif label.get(w) is None:
1021
- # w is a free vertex (or an unreached vertex inside
1022
- # a T-blossom) but we can not reach it yet;
1023
- # keep track of the least-slack edge that reaches w.
1024
- if bestedge.get(w) is None or kslack < slack(*bestedge[w]):
1025
- bestedge[w] = (v, w)
1026
-
1027
- if augmented:
1028
- break
1029
-
1030
- # There is no augmenting path under these constraints;
1031
- # compute delta and reduce slack in the optimization problem.
1032
- # (Note that our vertex dual variables, edge slacks and delta's
1033
- # are pre-multiplied by two.)
1034
- deltatype = -1
1035
- delta = deltaedge = deltablossom = None
1036
-
1037
- # Compute delta1: the minimum value of any vertex dual.
1038
- if not maxcardinality:
1039
- deltatype = 1
1040
- delta = min(dualvar.values())
1041
-
1042
- # Compute delta2: the minimum slack on any edge between
1043
- # an S-vertex and a free vertex.
1044
- for v in G.nodes():
1045
- if label.get(inblossom[v]) is None and bestedge.get(v) is not None:
1046
- d = slack(*bestedge[v])
1047
- if deltatype == -1 or d < delta:
1048
- delta = d
1049
- deltatype = 2
1050
- deltaedge = bestedge[v]
1051
-
1052
- # Compute delta3: half the minimum slack on any edge between
1053
- # a pair of S-blossoms.
1054
- for b in blossomparent:
1055
- if (
1056
- blossomparent[b] is None
1057
- and label.get(b) == 1
1058
- and bestedge.get(b) is not None
1059
- ):
1060
- kslack = slack(*bestedge[b])
1061
- if allinteger:
1062
- assert (kslack % 2) == 0
1063
- d = kslack // 2
1064
- else:
1065
- d = kslack / 2.0
1066
- if deltatype == -1 or d < delta:
1067
- delta = d
1068
- deltatype = 3
1069
- deltaedge = bestedge[b]
1070
-
1071
- # Compute delta4: minimum z variable of any T-blossom.
1072
- for b in blossomdual:
1073
- if (
1074
- blossomparent[b] is None
1075
- and label.get(b) == 2
1076
- and (deltatype == -1 or blossomdual[b] < delta)
1077
- ):
1078
- delta = blossomdual[b]
1079
- deltatype = 4
1080
- deltablossom = b
1081
-
1082
- if deltatype == -1:
1083
- # No further improvement possible; max-cardinality optimum
1084
- # reached. Do a final delta update to make the optimum
1085
- # verifiable.
1086
- assert maxcardinality
1087
- deltatype = 1
1088
- delta = max(0, min(dualvar.values()))
1089
-
1090
- # Update dual variables according to delta.
1091
- for v in gnodes:
1092
- if label.get(inblossom[v]) == 1:
1093
- # S-vertex: 2*u = 2*u - 2*delta
1094
- dualvar[v] -= delta
1095
- elif label.get(inblossom[v]) == 2:
1096
- # T-vertex: 2*u = 2*u + 2*delta
1097
- dualvar[v] += delta
1098
- for b in blossomdual:
1099
- if blossomparent[b] is None:
1100
- if label.get(b) == 1:
1101
- # top-level S-blossom: z = z + 2*delta
1102
- blossomdual[b] += delta
1103
- elif label.get(b) == 2:
1104
- # top-level T-blossom: z = z - 2*delta
1105
- blossomdual[b] -= delta
1106
-
1107
- # Take action at the point where minimum delta occurred.
1108
- if deltatype == 1:
1109
- # No further improvement possible; optimum reached.
1110
- break
1111
- elif deltatype == 2:
1112
- # Use the least-slack edge to continue the search.
1113
- (v, w) = deltaedge
1114
- assert label[inblossom[v]] == 1
1115
- allowedge[(v, w)] = allowedge[(w, v)] = True
1116
- queue.append(v)
1117
- elif deltatype == 3:
1118
- # Use the least-slack edge to continue the search.
1119
- (v, w) = deltaedge
1120
- allowedge[(v, w)] = allowedge[(w, v)] = True
1121
- assert label[inblossom[v]] == 1
1122
- queue.append(v)
1123
- elif deltatype == 4:
1124
- # Expand the least-z blossom.
1125
- expandBlossom(deltablossom, False)
1126
-
1127
- # End of a this substage.
1128
-
1129
- # Paranoia check that the matching is symmetric.
1130
- for v in mate:
1131
- assert mate[mate[v]] == v
1132
-
1133
- # Stop when no more augmenting path can be found.
1134
- if not augmented:
1135
- break
1136
-
1137
- # End of a stage; expand all S-blossoms which have zero dual.
1138
- for b in list(blossomdual.keys()):
1139
- if b not in blossomdual:
1140
- continue # already expanded
1141
- if blossomparent[b] is None and label.get(b) == 1 and blossomdual[b] == 0:
1142
- expandBlossom(b, True)
1143
-
1144
- # Verify that we reached the optimum solution (only for integer weights).
1145
- if allinteger:
1146
- verifyOptimum()
1147
-
1148
- return matching_dict_to_set(mate)