okstra 0.130.3 → 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 (367) 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 +21 -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/report_views.py +32 -5
  27. package/runtime/python/okstra_ctl/run.py +15 -3
  28. package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
  29. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
  30. package/runtime/python/okstra_vendor/__init__.py +0 -44
  31. package/runtime/schemas/final-report-v1.0.schema.json +101 -6
  32. package/runtime/skills/okstra-run/SKILL.md +11 -4
  33. package/runtime/skills/okstra-setup/SKILL.md +3 -2
  34. package/runtime/skills/okstra-setup/references/project-config.md +16 -7
  35. package/runtime/templates/reports/final-report.template.md +18 -12
  36. package/runtime/templates/reports/i18n/en.json +1 -0
  37. package/runtime/templates/reports/i18n/ko.json +1 -0
  38. package/runtime/templates/reports/report.css +18 -0
  39. package/runtime/validators/lib/fixtures.sh +6 -0
  40. package/runtime/validators/validate-run.py +1722 -98
  41. package/src/cli-registry.mjs +0 -7
  42. package/src/commands/lifecycle/doctor.mjs +0 -9
  43. package/src/lib/skill-catalog.mjs +0 -1
  44. package/docs/for-ai/skills/okstra-graphify.md +0 -64
  45. package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
  46. package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
  47. package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
  48. package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
  49. package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
  50. package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
  51. package/runtime/python/okstra_vendor/graphify/build.py +0 -107
  52. package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
  53. package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
  54. package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
  55. package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
  56. package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
  57. package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
  58. package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
  59. package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
  60. package/runtime/python/okstra_vendor/graphify/report.py +0 -175
  61. package/runtime/python/okstra_vendor/graphify/security.py +0 -203
  62. package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
  63. package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
  64. package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
  65. package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
  66. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
  67. package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
  68. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
  69. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
  70. package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
  71. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
  72. package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
  73. package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
  74. package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
  75. package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
  76. package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
  77. package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
  78. package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
  79. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
  80. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
  81. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
  82. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
  83. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
  84. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
  85. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
  86. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
  87. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
  88. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
  89. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
  90. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
  91. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
  92. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
  93. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
  94. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
  95. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
  96. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
  97. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
  98. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
  99. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
  100. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
  101. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
  114. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
  116. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
  117. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
  118. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
  139. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
  140. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
  141. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
  142. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
  143. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
  144. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
  145. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
  146. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
  147. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
  148. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
  149. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
  150. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
  151. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
  152. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
  153. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
  154. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
  155. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
  156. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
  157. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
  158. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
  159. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
  160. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
  161. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
  162. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
  163. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
  164. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
  165. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
  166. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
  167. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
  168. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
  169. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
  170. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
  171. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
  172. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
  173. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
  174. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
  175. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
  176. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
  177. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
  178. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
  179. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
  180. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
  181. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
  182. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
  183. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
  184. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
  185. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
  186. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
  187. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
  188. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
  189. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
  190. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
  191. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
  192. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
  193. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
  194. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
  195. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
  196. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
  197. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
  198. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
  199. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
  200. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
  201. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
  202. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
  203. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
  204. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
  205. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
  206. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
  207. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
  208. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
  209. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
  210. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
  211. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
  212. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
  213. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
  214. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
  215. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
  216. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
  217. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
  218. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
  219. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
  220. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
  221. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
  222. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
  223. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
  224. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
  225. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
  226. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
  227. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
  228. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
  229. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
  230. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
  231. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
  232. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
  233. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
  234. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
  235. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
  236. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
  237. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
  238. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
  239. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
  240. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
  241. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
  242. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
  243. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
  244. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
  245. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
  246. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
  247. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
  248. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
  249. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
  250. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
  251. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
  252. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
  253. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
  254. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
  255. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
  256. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
  257. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
  258. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
  259. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
  260. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
  261. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
  262. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
  263. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
  264. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
  265. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
  266. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
  267. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
  268. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
  269. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
  270. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
  271. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
  272. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
  273. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
  274. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
  275. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
  276. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
  277. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
  278. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
  279. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
  280. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
  281. package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
  282. package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
  283. package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
  284. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
  285. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
  286. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
  287. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
  288. package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
  289. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
  290. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
  291. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
  292. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
  293. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
  294. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
  295. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
  296. package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
  297. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
  298. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  299. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
  300. package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
  301. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
  302. package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
  303. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
  304. package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
  305. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
  306. package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
  307. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
  308. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
  309. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
  310. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
  311. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
  312. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
  313. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
  314. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
  315. package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
  316. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
  317. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
  318. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
  319. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
  320. package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
  321. package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
  322. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
  323. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
  324. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
  325. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
  326. package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
  327. package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
  328. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
  329. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
  330. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
  331. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
  332. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
  333. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
  334. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
  335. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
  336. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
  337. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
  338. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
  339. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
  340. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
  341. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
  342. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
  343. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
  344. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
  345. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
  346. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
  347. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
  348. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
  349. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
  350. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
  351. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
  352. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
  353. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
  354. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
  355. package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
  356. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
  357. package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
  358. package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
  359. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
  360. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
  361. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
  362. package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
  363. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
  364. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
  365. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
  366. package/runtime/skills/okstra-graphify/SKILL.md +0 -169
  367. package/src/commands/graphify.mjs +0 -32
@@ -1,1463 +0,0 @@
1
- from collections import defaultdict
2
- from copy import deepcopy
3
-
4
- import networkx as nx
5
-
6
- __all__ = ["check_planarity", "is_planar", "PlanarEmbedding"]
7
-
8
-
9
- @nx._dispatchable
10
- def is_planar(G):
11
- """Returns True if and only if `G` is planar.
12
-
13
- A graph is *planar* iff it can be drawn in a plane without
14
- any edge intersections.
15
-
16
- Parameters
17
- ----------
18
- G : NetworkX graph
19
-
20
- Returns
21
- -------
22
- bool
23
- Whether the graph is planar.
24
-
25
- Examples
26
- --------
27
- >>> G = nx.Graph([(0, 1), (0, 2)])
28
- >>> nx.is_planar(G)
29
- True
30
- >>> nx.is_planar(nx.complete_graph(5))
31
- False
32
-
33
- See Also
34
- --------
35
- check_planarity :
36
- Check if graph is planar *and* return a `PlanarEmbedding` instance if True.
37
- """
38
-
39
- return check_planarity(G, counterexample=False)[0]
40
-
41
-
42
- @nx._dispatchable(returns_graph=True)
43
- def check_planarity(G, counterexample=False):
44
- """Check if a graph is planar and return a counterexample or an embedding.
45
-
46
- A graph is planar iff it can be drawn in a plane without
47
- any edge intersections.
48
-
49
- Parameters
50
- ----------
51
- G : NetworkX graph
52
- counterexample : bool
53
- A Kuratowski subgraph (to proof non planarity) is only returned if set
54
- to true.
55
-
56
- Returns
57
- -------
58
- (is_planar, certificate) : (bool, NetworkX graph) tuple
59
- is_planar is true if the graph is planar.
60
- If the graph is planar `certificate` is a PlanarEmbedding
61
- otherwise it is a Kuratowski subgraph.
62
-
63
- Examples
64
- --------
65
- >>> G = nx.Graph([(0, 1), (0, 2)])
66
- >>> is_planar, P = nx.check_planarity(G)
67
- >>> print(is_planar)
68
- True
69
-
70
- When `G` is planar, a `PlanarEmbedding` instance is returned:
71
-
72
- >>> P.get_data()
73
- {0: [1, 2], 1: [0], 2: [0]}
74
-
75
- Notes
76
- -----
77
- A (combinatorial) embedding consists of cyclic orderings of the incident
78
- edges at each vertex. Given such an embedding there are multiple approaches
79
- discussed in literature to drawing the graph (subject to various
80
- constraints, e.g. integer coordinates), see e.g. [2].
81
-
82
- The planarity check algorithm and extraction of the combinatorial embedding
83
- is based on the Left-Right Planarity Test [1].
84
-
85
- A counterexample is only generated if the corresponding parameter is set,
86
- because the complexity of the counterexample generation is higher.
87
-
88
- See also
89
- --------
90
- is_planar :
91
- Check for planarity without creating a `PlanarEmbedding` or counterexample.
92
-
93
- References
94
- ----------
95
- .. [1] Ulrik Brandes:
96
- The Left-Right Planarity Test
97
- 2009
98
- http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.217.9208
99
- .. [2] Takao Nishizeki, Md Saidur Rahman:
100
- Planar graph drawing
101
- Lecture Notes Series on Computing: Volume 12
102
- 2004
103
- """
104
-
105
- planarity_state = LRPlanarity(G)
106
- embedding = planarity_state.lr_planarity()
107
- if embedding is None:
108
- # graph is not planar
109
- if counterexample:
110
- return False, get_counterexample(G)
111
- else:
112
- return False, None
113
- else:
114
- # graph is planar
115
- return True, embedding
116
-
117
-
118
- @nx._dispatchable(returns_graph=True)
119
- def check_planarity_recursive(G, counterexample=False):
120
- """Recursive version of :meth:`check_planarity`."""
121
- planarity_state = LRPlanarity(G)
122
- embedding = planarity_state.lr_planarity_recursive()
123
- if embedding is None:
124
- # graph is not planar
125
- if counterexample:
126
- return False, get_counterexample_recursive(G)
127
- else:
128
- return False, None
129
- else:
130
- # graph is planar
131
- return True, embedding
132
-
133
-
134
- @nx._dispatchable(returns_graph=True)
135
- def get_counterexample(G):
136
- """Obtains a Kuratowski subgraph.
137
-
138
- Raises nx.NetworkXException if G is planar.
139
-
140
- The function removes edges such that the graph is still not planar.
141
- At some point the removal of any edge would make the graph planar.
142
- This subgraph must be a Kuratowski subgraph.
143
-
144
- Parameters
145
- ----------
146
- G : NetworkX graph
147
-
148
- Returns
149
- -------
150
- subgraph : NetworkX graph
151
- A Kuratowski subgraph that proves that G is not planar.
152
-
153
- """
154
- # copy graph
155
- G = nx.Graph(G)
156
-
157
- if check_planarity(G)[0]:
158
- raise nx.NetworkXException("G is planar - no counter example.")
159
-
160
- # find Kuratowski subgraph
161
- subgraph = nx.Graph()
162
- for u in G:
163
- nbrs = list(G[u])
164
- for v in nbrs:
165
- G.remove_edge(u, v)
166
- if check_planarity(G)[0]:
167
- G.add_edge(u, v)
168
- subgraph.add_edge(u, v)
169
-
170
- return subgraph
171
-
172
-
173
- @nx._dispatchable(returns_graph=True)
174
- def get_counterexample_recursive(G):
175
- """Recursive version of :meth:`get_counterexample`."""
176
-
177
- # copy graph
178
- G = nx.Graph(G)
179
-
180
- if check_planarity_recursive(G)[0]:
181
- raise nx.NetworkXException("G is planar - no counter example.")
182
-
183
- # find Kuratowski subgraph
184
- subgraph = nx.Graph()
185
- for u in G:
186
- nbrs = list(G[u])
187
- for v in nbrs:
188
- G.remove_edge(u, v)
189
- if check_planarity_recursive(G)[0]:
190
- G.add_edge(u, v)
191
- subgraph.add_edge(u, v)
192
-
193
- return subgraph
194
-
195
-
196
- class Interval:
197
- """Represents a set of return edges.
198
-
199
- All return edges in an interval induce a same constraint on the contained
200
- edges, which means that all edges must either have a left orientation or
201
- all edges must have a right orientation.
202
- """
203
-
204
- def __init__(self, low=None, high=None):
205
- self.low = low
206
- self.high = high
207
-
208
- def empty(self):
209
- """Check if the interval is empty"""
210
- return self.low is None and self.high is None
211
-
212
- def copy(self):
213
- """Returns a copy of this interval"""
214
- return Interval(self.low, self.high)
215
-
216
- def conflicting(self, b, planarity_state):
217
- """Returns True if interval I conflicts with edge b"""
218
- return (
219
- not self.empty()
220
- and planarity_state.lowpt[self.high] > planarity_state.lowpt[b]
221
- )
222
-
223
-
224
- class ConflictPair:
225
- """Represents a different constraint between two intervals.
226
-
227
- The edges in the left interval must have a different orientation than
228
- the one in the right interval.
229
- """
230
-
231
- def __init__(self, left=Interval(), right=Interval()):
232
- self.left = left
233
- self.right = right
234
-
235
- def swap(self):
236
- """Swap left and right intervals"""
237
- temp = self.left
238
- self.left = self.right
239
- self.right = temp
240
-
241
- def lowest(self, planarity_state):
242
- """Returns the lowest lowpoint of a conflict pair"""
243
- if self.left.empty():
244
- return planarity_state.lowpt[self.right.low]
245
- if self.right.empty():
246
- return planarity_state.lowpt[self.left.low]
247
- return min(
248
- planarity_state.lowpt[self.left.low], planarity_state.lowpt[self.right.low]
249
- )
250
-
251
-
252
- def top_of_stack(l):
253
- """Returns the element on top of the stack."""
254
- if not l:
255
- return None
256
- return l[-1]
257
-
258
-
259
- class LRPlanarity:
260
- """A class to maintain the state during planarity check."""
261
-
262
- __slots__ = [
263
- "G",
264
- "roots",
265
- "height",
266
- "lowpt",
267
- "lowpt2",
268
- "nesting_depth",
269
- "parent_edge",
270
- "DG",
271
- "adjs",
272
- "ordered_adjs",
273
- "ref",
274
- "side",
275
- "S",
276
- "stack_bottom",
277
- "lowpt_edge",
278
- "left_ref",
279
- "right_ref",
280
- "embedding",
281
- ]
282
-
283
- def __init__(self, G):
284
- # copy G without adding self-loops
285
- self.G = nx.Graph()
286
- self.G.add_nodes_from(G.nodes)
287
- for e in G.edges:
288
- if e[0] != e[1]:
289
- self.G.add_edge(e[0], e[1])
290
-
291
- self.roots = []
292
-
293
- # distance from tree root
294
- self.height = defaultdict(lambda: None)
295
-
296
- self.lowpt = {} # height of lowest return point of an edge
297
- self.lowpt2 = {} # height of second lowest return point
298
- self.nesting_depth = {} # for nesting order
299
-
300
- # None -> missing edge
301
- self.parent_edge = defaultdict(lambda: None)
302
-
303
- # oriented DFS graph
304
- self.DG = nx.DiGraph()
305
- self.DG.add_nodes_from(G.nodes)
306
-
307
- self.adjs = {}
308
- self.ordered_adjs = {}
309
-
310
- self.ref = defaultdict(lambda: None)
311
- self.side = defaultdict(lambda: 1)
312
-
313
- # stack of conflict pairs
314
- self.S = []
315
- self.stack_bottom = {}
316
- self.lowpt_edge = {}
317
-
318
- self.left_ref = {}
319
- self.right_ref = {}
320
-
321
- self.embedding = PlanarEmbedding()
322
-
323
- def lr_planarity(self):
324
- """Execute the LR planarity test.
325
-
326
- Returns
327
- -------
328
- embedding : dict
329
- If the graph is planar an embedding is returned. Otherwise None.
330
- """
331
- if self.G.order() > 2 and self.G.size() > 3 * self.G.order() - 6:
332
- # graph is not planar
333
- return None
334
-
335
- # make adjacency lists for dfs
336
- for v in self.G:
337
- self.adjs[v] = list(self.G[v])
338
-
339
- # orientation of the graph by depth first search traversal
340
- for v in self.G:
341
- if self.height[v] is None:
342
- self.height[v] = 0
343
- self.roots.append(v)
344
- self.dfs_orientation(v)
345
-
346
- # Free no longer used variables
347
- self.G = None
348
- self.lowpt2 = None
349
- self.adjs = None
350
-
351
- # testing
352
- for v in self.DG: # sort the adjacency lists by nesting depth
353
- # note: this sorting leads to non linear time
354
- self.ordered_adjs[v] = sorted(
355
- self.DG[v], key=lambda x: self.nesting_depth[(v, x)]
356
- )
357
- for v in self.roots:
358
- if not self.dfs_testing(v):
359
- return None
360
-
361
- # Free no longer used variables
362
- self.height = None
363
- self.lowpt = None
364
- self.S = None
365
- self.stack_bottom = None
366
- self.lowpt_edge = None
367
-
368
- for e in self.DG.edges:
369
- self.nesting_depth[e] = self.sign(e) * self.nesting_depth[e]
370
-
371
- self.embedding.add_nodes_from(self.DG.nodes)
372
- for v in self.DG:
373
- # sort the adjacency lists again
374
- self.ordered_adjs[v] = sorted(
375
- self.DG[v], key=lambda x: self.nesting_depth[(v, x)]
376
- )
377
- # initialize the embedding
378
- previous_node = None
379
- for w in self.ordered_adjs[v]:
380
- self.embedding.add_half_edge(v, w, ccw=previous_node)
381
- previous_node = w
382
-
383
- # Free no longer used variables
384
- self.DG = None
385
- self.nesting_depth = None
386
- self.ref = None
387
-
388
- # compute the complete embedding
389
- for v in self.roots:
390
- self.dfs_embedding(v)
391
-
392
- # Free no longer used variables
393
- self.roots = None
394
- self.parent_edge = None
395
- self.ordered_adjs = None
396
- self.left_ref = None
397
- self.right_ref = None
398
- self.side = None
399
-
400
- return self.embedding
401
-
402
- def lr_planarity_recursive(self):
403
- """Recursive version of :meth:`lr_planarity`."""
404
- if self.G.order() > 2 and self.G.size() > 3 * self.G.order() - 6:
405
- # graph is not planar
406
- return None
407
-
408
- # orientation of the graph by depth first search traversal
409
- for v in self.G:
410
- if self.height[v] is None:
411
- self.height[v] = 0
412
- self.roots.append(v)
413
- self.dfs_orientation_recursive(v)
414
-
415
- # Free no longer used variable
416
- self.G = None
417
-
418
- # testing
419
- for v in self.DG: # sort the adjacency lists by nesting depth
420
- # note: this sorting leads to non linear time
421
- self.ordered_adjs[v] = sorted(
422
- self.DG[v], key=lambda x: self.nesting_depth[(v, x)]
423
- )
424
- for v in self.roots:
425
- if not self.dfs_testing_recursive(v):
426
- return None
427
-
428
- for e in self.DG.edges:
429
- self.nesting_depth[e] = self.sign_recursive(e) * self.nesting_depth[e]
430
-
431
- self.embedding.add_nodes_from(self.DG.nodes)
432
- for v in self.DG:
433
- # sort the adjacency lists again
434
- self.ordered_adjs[v] = sorted(
435
- self.DG[v], key=lambda x: self.nesting_depth[(v, x)]
436
- )
437
- # initialize the embedding
438
- previous_node = None
439
- for w in self.ordered_adjs[v]:
440
- self.embedding.add_half_edge(v, w, ccw=previous_node)
441
- previous_node = w
442
-
443
- # compute the complete embedding
444
- for v in self.roots:
445
- self.dfs_embedding_recursive(v)
446
-
447
- return self.embedding
448
-
449
- def dfs_orientation(self, v):
450
- """Orient the graph by DFS, compute lowpoints and nesting order."""
451
- # the recursion stack
452
- dfs_stack = [v]
453
- # index of next edge to handle in adjacency list of each node
454
- ind = defaultdict(lambda: 0)
455
- # boolean to indicate whether to skip the initial work for an edge
456
- skip_init = defaultdict(lambda: False)
457
-
458
- while dfs_stack:
459
- v = dfs_stack.pop()
460
- e = self.parent_edge[v]
461
-
462
- for w in self.adjs[v][ind[v] :]:
463
- vw = (v, w)
464
-
465
- if not skip_init[vw]:
466
- if (v, w) in self.DG.edges or (w, v) in self.DG.edges:
467
- ind[v] += 1
468
- continue # the edge was already oriented
469
-
470
- self.DG.add_edge(v, w) # orient the edge
471
-
472
- self.lowpt[vw] = self.height[v]
473
- self.lowpt2[vw] = self.height[v]
474
- if self.height[w] is None: # (v, w) is a tree edge
475
- self.parent_edge[w] = vw
476
- self.height[w] = self.height[v] + 1
477
-
478
- dfs_stack.append(v) # revisit v after finishing w
479
- dfs_stack.append(w) # visit w next
480
- skip_init[vw] = True # don't redo this block
481
- break # handle next node in dfs_stack (i.e. w)
482
- else: # (v, w) is a back edge
483
- self.lowpt[vw] = self.height[w]
484
-
485
- # determine nesting graph
486
- self.nesting_depth[vw] = 2 * self.lowpt[vw]
487
- if self.lowpt2[vw] < self.height[v]: # chordal
488
- self.nesting_depth[vw] += 1
489
-
490
- # update lowpoints of parent edge e
491
- if e is not None:
492
- if self.lowpt[vw] < self.lowpt[e]:
493
- self.lowpt2[e] = min(self.lowpt[e], self.lowpt2[vw])
494
- self.lowpt[e] = self.lowpt[vw]
495
- elif self.lowpt[vw] > self.lowpt[e]:
496
- self.lowpt2[e] = min(self.lowpt2[e], self.lowpt[vw])
497
- else:
498
- self.lowpt2[e] = min(self.lowpt2[e], self.lowpt2[vw])
499
-
500
- ind[v] += 1
501
-
502
- def dfs_orientation_recursive(self, v):
503
- """Recursive version of :meth:`dfs_orientation`."""
504
- e = self.parent_edge[v]
505
- for w in self.G[v]:
506
- if (v, w) in self.DG.edges or (w, v) in self.DG.edges:
507
- continue # the edge was already oriented
508
- vw = (v, w)
509
- self.DG.add_edge(v, w) # orient the edge
510
-
511
- self.lowpt[vw] = self.height[v]
512
- self.lowpt2[vw] = self.height[v]
513
- if self.height[w] is None: # (v, w) is a tree edge
514
- self.parent_edge[w] = vw
515
- self.height[w] = self.height[v] + 1
516
- self.dfs_orientation_recursive(w)
517
- else: # (v, w) is a back edge
518
- self.lowpt[vw] = self.height[w]
519
-
520
- # determine nesting graph
521
- self.nesting_depth[vw] = 2 * self.lowpt[vw]
522
- if self.lowpt2[vw] < self.height[v]: # chordal
523
- self.nesting_depth[vw] += 1
524
-
525
- # update lowpoints of parent edge e
526
- if e is not None:
527
- if self.lowpt[vw] < self.lowpt[e]:
528
- self.lowpt2[e] = min(self.lowpt[e], self.lowpt2[vw])
529
- self.lowpt[e] = self.lowpt[vw]
530
- elif self.lowpt[vw] > self.lowpt[e]:
531
- self.lowpt2[e] = min(self.lowpt2[e], self.lowpt[vw])
532
- else:
533
- self.lowpt2[e] = min(self.lowpt2[e], self.lowpt2[vw])
534
-
535
- def dfs_testing(self, v):
536
- """Test for LR partition."""
537
- # the recursion stack
538
- dfs_stack = [v]
539
- # index of next edge to handle in adjacency list of each node
540
- ind = defaultdict(lambda: 0)
541
- # boolean to indicate whether to skip the initial work for an edge
542
- skip_init = defaultdict(lambda: False)
543
-
544
- while dfs_stack:
545
- v = dfs_stack.pop()
546
- e = self.parent_edge[v]
547
- # to indicate whether to skip the final block after the for loop
548
- skip_final = False
549
-
550
- for w in self.ordered_adjs[v][ind[v] :]:
551
- ei = (v, w)
552
-
553
- if not skip_init[ei]:
554
- self.stack_bottom[ei] = top_of_stack(self.S)
555
-
556
- if ei == self.parent_edge[w]: # tree edge
557
- dfs_stack.append(v) # revisit v after finishing w
558
- dfs_stack.append(w) # visit w next
559
- skip_init[ei] = True # don't redo this block
560
- skip_final = True # skip final work after breaking
561
- break # handle next node in dfs_stack (i.e. w)
562
- else: # back edge
563
- self.lowpt_edge[ei] = ei
564
- self.S.append(ConflictPair(right=Interval(ei, ei)))
565
-
566
- # integrate new return edges
567
- if self.lowpt[ei] < self.height[v]:
568
- if w == self.ordered_adjs[v][0]: # e_i has return edge
569
- self.lowpt_edge[e] = self.lowpt_edge[ei]
570
- else: # add constraints of e_i
571
- if not self.add_constraints(ei, e):
572
- # graph is not planar
573
- return False
574
-
575
- ind[v] += 1
576
-
577
- if not skip_final:
578
- # remove back edges returning to parent
579
- if e is not None: # v isn't root
580
- self.remove_back_edges(e)
581
-
582
- return True
583
-
584
- def dfs_testing_recursive(self, v):
585
- """Recursive version of :meth:`dfs_testing`."""
586
- e = self.parent_edge[v]
587
- for w in self.ordered_adjs[v]:
588
- ei = (v, w)
589
- self.stack_bottom[ei] = top_of_stack(self.S)
590
- if ei == self.parent_edge[w]: # tree edge
591
- if not self.dfs_testing_recursive(w):
592
- return False
593
- else: # back edge
594
- self.lowpt_edge[ei] = ei
595
- self.S.append(ConflictPair(right=Interval(ei, ei)))
596
-
597
- # integrate new return edges
598
- if self.lowpt[ei] < self.height[v]:
599
- if w == self.ordered_adjs[v][0]: # e_i has return edge
600
- self.lowpt_edge[e] = self.lowpt_edge[ei]
601
- else: # add constraints of e_i
602
- if not self.add_constraints(ei, e):
603
- # graph is not planar
604
- return False
605
-
606
- # remove back edges returning to parent
607
- if e is not None: # v isn't root
608
- self.remove_back_edges(e)
609
- return True
610
-
611
- def add_constraints(self, ei, e):
612
- P = ConflictPair()
613
- # merge return edges of e_i into P.right
614
- while True:
615
- Q = self.S.pop()
616
- if not Q.left.empty():
617
- Q.swap()
618
- if not Q.left.empty(): # not planar
619
- return False
620
- if self.lowpt[Q.right.low] > self.lowpt[e]:
621
- # merge intervals
622
- if P.right.empty(): # topmost interval
623
- P.right = Q.right.copy()
624
- else:
625
- self.ref[P.right.low] = Q.right.high
626
- P.right.low = Q.right.low
627
- else: # align
628
- self.ref[Q.right.low] = self.lowpt_edge[e]
629
- if top_of_stack(self.S) == self.stack_bottom[ei]:
630
- break
631
- # merge conflicting return edges of e_1,...,e_i-1 into P.L
632
- while top_of_stack(self.S).left.conflicting(ei, self) or top_of_stack(
633
- self.S
634
- ).right.conflicting(ei, self):
635
- Q = self.S.pop()
636
- if Q.right.conflicting(ei, self):
637
- Q.swap()
638
- if Q.right.conflicting(ei, self): # not planar
639
- return False
640
- # merge interval below lowpt(e_i) into P.R
641
- self.ref[P.right.low] = Q.right.high
642
- if Q.right.low is not None:
643
- P.right.low = Q.right.low
644
-
645
- if P.left.empty(): # topmost interval
646
- P.left = Q.left.copy()
647
- else:
648
- self.ref[P.left.low] = Q.left.high
649
- P.left.low = Q.left.low
650
-
651
- if not (P.left.empty() and P.right.empty()):
652
- self.S.append(P)
653
- return True
654
-
655
- def remove_back_edges(self, e):
656
- u = e[0]
657
- # trim back edges ending at parent u
658
- # drop entire conflict pairs
659
- while self.S and top_of_stack(self.S).lowest(self) == self.height[u]:
660
- P = self.S.pop()
661
- if P.left.low is not None:
662
- self.side[P.left.low] = -1
663
-
664
- if self.S: # one more conflict pair to consider
665
- P = self.S.pop()
666
- # trim left interval
667
- while P.left.high is not None and P.left.high[1] == u:
668
- P.left.high = self.ref[P.left.high]
669
- if P.left.high is None and P.left.low is not None:
670
- # just emptied
671
- self.ref[P.left.low] = P.right.low
672
- self.side[P.left.low] = -1
673
- P.left.low = None
674
- # trim right interval
675
- while P.right.high is not None and P.right.high[1] == u:
676
- P.right.high = self.ref[P.right.high]
677
- if P.right.high is None and P.right.low is not None:
678
- # just emptied
679
- self.ref[P.right.low] = P.left.low
680
- self.side[P.right.low] = -1
681
- P.right.low = None
682
- self.S.append(P)
683
-
684
- # side of e is side of a highest return edge
685
- if self.lowpt[e] < self.height[u]: # e has return edge
686
- hl = top_of_stack(self.S).left.high
687
- hr = top_of_stack(self.S).right.high
688
-
689
- if hl is not None and (hr is None or self.lowpt[hl] > self.lowpt[hr]):
690
- self.ref[e] = hl
691
- else:
692
- self.ref[e] = hr
693
-
694
- def dfs_embedding(self, v):
695
- """Completes the embedding."""
696
- # the recursion stack
697
- dfs_stack = [v]
698
- # index of next edge to handle in adjacency list of each node
699
- ind = defaultdict(lambda: 0)
700
-
701
- while dfs_stack:
702
- v = dfs_stack.pop()
703
-
704
- for w in self.ordered_adjs[v][ind[v] :]:
705
- ind[v] += 1
706
- ei = (v, w)
707
-
708
- if ei == self.parent_edge[w]: # tree edge
709
- self.embedding.add_half_edge_first(w, v)
710
- self.left_ref[v] = w
711
- self.right_ref[v] = w
712
-
713
- dfs_stack.append(v) # revisit v after finishing w
714
- dfs_stack.append(w) # visit w next
715
- break # handle next node in dfs_stack (i.e. w)
716
- else: # back edge
717
- if self.side[ei] == 1:
718
- self.embedding.add_half_edge(w, v, ccw=self.right_ref[w])
719
- else:
720
- self.embedding.add_half_edge(w, v, cw=self.left_ref[w])
721
- self.left_ref[w] = v
722
-
723
- def dfs_embedding_recursive(self, v):
724
- """Recursive version of :meth:`dfs_embedding`."""
725
- for w in self.ordered_adjs[v]:
726
- ei = (v, w)
727
- if ei == self.parent_edge[w]: # tree edge
728
- self.embedding.add_half_edge_first(w, v)
729
- self.left_ref[v] = w
730
- self.right_ref[v] = w
731
- self.dfs_embedding_recursive(w)
732
- else: # back edge
733
- if self.side[ei] == 1:
734
- # place v directly after right_ref[w] in embed. list of w
735
- self.embedding.add_half_edge(w, v, ccw=self.right_ref[w])
736
- else:
737
- # place v directly before left_ref[w] in embed. list of w
738
- self.embedding.add_half_edge(w, v, cw=self.left_ref[w])
739
- self.left_ref[w] = v
740
-
741
- def sign(self, e):
742
- """Resolve the relative side of an edge to the absolute side."""
743
- # the recursion stack
744
- dfs_stack = [e]
745
- # dict to remember reference edges
746
- old_ref = defaultdict(lambda: None)
747
-
748
- while dfs_stack:
749
- e = dfs_stack.pop()
750
-
751
- if self.ref[e] is not None:
752
- dfs_stack.append(e) # revisit e after finishing self.ref[e]
753
- dfs_stack.append(self.ref[e]) # visit self.ref[e] next
754
- old_ref[e] = self.ref[e] # remember value of self.ref[e]
755
- self.ref[e] = None
756
- else:
757
- self.side[e] *= self.side[old_ref[e]]
758
-
759
- return self.side[e]
760
-
761
- def sign_recursive(self, e):
762
- """Recursive version of :meth:`sign`."""
763
- if self.ref[e] is not None:
764
- self.side[e] = self.side[e] * self.sign_recursive(self.ref[e])
765
- self.ref[e] = None
766
- return self.side[e]
767
-
768
-
769
- class PlanarEmbedding(nx.DiGraph):
770
- """Represents a planar graph with its planar embedding.
771
-
772
- The planar embedding is given by a `combinatorial embedding
773
- <https://en.wikipedia.org/wiki/Graph_embedding#Combinatorial_embedding>`_.
774
-
775
- .. note:: `check_planarity` is the preferred way to check if a graph is planar.
776
-
777
- **Neighbor ordering:**
778
-
779
- In comparison to a usual graph structure, the embedding also stores the
780
- order of all neighbors for every vertex.
781
- The order of the neighbors can be given in clockwise (cw) direction or
782
- counterclockwise (ccw) direction. This order is stored as edge attributes
783
- in the underlying directed graph. For the edge (u, v) the edge attribute
784
- 'cw' is set to the neighbor of u that follows immediately after v in
785
- clockwise direction.
786
-
787
- In order for a PlanarEmbedding to be valid it must fulfill multiple
788
- conditions. It is possible to check if these conditions are fulfilled with
789
- the method :meth:`check_structure`.
790
- The conditions are:
791
-
792
- * Edges must go in both directions (because the edge attributes differ)
793
- * Every edge must have a 'cw' and 'ccw' attribute which corresponds to a
794
- correct planar embedding.
795
-
796
- As long as a PlanarEmbedding is invalid only the following methods should
797
- be called:
798
-
799
- * :meth:`add_half_edge`
800
- * :meth:`connect_components`
801
-
802
- Even though the graph is a subclass of nx.DiGraph, it can still be used
803
- for algorithms that require undirected graphs, because the method
804
- :meth:`is_directed` is overridden. This is possible, because a valid
805
- PlanarGraph must have edges in both directions.
806
-
807
- **Half edges:**
808
-
809
- In methods like `add_half_edge` the term "half-edge" is used, which is
810
- a term that is used in `doubly connected edge lists
811
- <https://en.wikipedia.org/wiki/Doubly_connected_edge_list>`_. It is used
812
- to emphasize that the edge is only in one direction and there exists
813
- another half-edge in the opposite direction.
814
- While conventional edges always have two faces (including outer face) next
815
- to them, it is possible to assign each half-edge *exactly one* face.
816
- For a half-edge (u, v) that is oriented such that u is below v then the
817
- face that belongs to (u, v) is to the right of this half-edge.
818
-
819
- See Also
820
- --------
821
- is_planar :
822
- Preferred way to check if an existing graph is planar.
823
-
824
- check_planarity :
825
- A convenient way to create a `PlanarEmbedding`. If not planar,
826
- it returns a subgraph that shows this.
827
-
828
- Examples
829
- --------
830
-
831
- Create an embedding of a star graph (compare `nx.star_graph(3)`):
832
-
833
- >>> G = nx.PlanarEmbedding()
834
- >>> G.add_half_edge(0, 1)
835
- >>> G.add_half_edge(0, 2, ccw=1)
836
- >>> G.add_half_edge(0, 3, ccw=2)
837
- >>> G.add_half_edge(1, 0)
838
- >>> G.add_half_edge(2, 0)
839
- >>> G.add_half_edge(3, 0)
840
-
841
- Alternatively the same embedding can also be defined in counterclockwise
842
- orientation. The following results in exactly the same PlanarEmbedding:
843
-
844
- >>> G = nx.PlanarEmbedding()
845
- >>> G.add_half_edge(0, 1)
846
- >>> G.add_half_edge(0, 3, cw=1)
847
- >>> G.add_half_edge(0, 2, cw=3)
848
- >>> G.add_half_edge(1, 0)
849
- >>> G.add_half_edge(2, 0)
850
- >>> G.add_half_edge(3, 0)
851
-
852
- After creating a graph, it is possible to validate that the PlanarEmbedding
853
- object is correct:
854
-
855
- >>> G.check_structure()
856
-
857
- """
858
-
859
- def __init__(self, incoming_graph_data=None, **attr):
860
- super().__init__(incoming_graph_data=incoming_graph_data, **attr)
861
- self.add_edge = self._forbidden
862
- self.add_edges_from = self._forbidden
863
- self.add_weighted_edges_from = self._forbidden
864
-
865
- def _forbidden(self, *args, **kwargs):
866
- """Forbidden operation
867
-
868
- Any edge additions to a PlanarEmbedding should be done using
869
- method `add_half_edge`.
870
- """
871
- raise NotImplementedError(
872
- "Use `add_half_edge` method to add edges to a PlanarEmbedding."
873
- )
874
-
875
- def get_data(self):
876
- """Converts the adjacency structure into a better readable structure.
877
-
878
- Returns
879
- -------
880
- embedding : dict
881
- A dict mapping all nodes to a list of neighbors sorted in
882
- clockwise order.
883
-
884
- See Also
885
- --------
886
- set_data
887
-
888
- """
889
- embedding = {}
890
- for v in self:
891
- embedding[v] = list(self.neighbors_cw_order(v))
892
- return embedding
893
-
894
- def set_data(self, data):
895
- """Inserts edges according to given sorted neighbor list.
896
-
897
- The input format is the same as the output format of get_data().
898
-
899
- Parameters
900
- ----------
901
- data : dict
902
- A dict mapping all nodes to a list of neighbors sorted in
903
- clockwise order.
904
-
905
- See Also
906
- --------
907
- get_data
908
-
909
- """
910
- for v in data:
911
- ref = None
912
- for w in reversed(data[v]):
913
- self.add_half_edge(v, w, cw=ref)
914
- ref = w
915
-
916
- def remove_node(self, n):
917
- """Remove node n.
918
-
919
- Removes the node n and all adjacent edges, updating the
920
- PlanarEmbedding to account for any resulting edge removal.
921
- Attempting to remove a non-existent node will raise an exception.
922
-
923
- Parameters
924
- ----------
925
- n : node
926
- A node in the graph
927
-
928
- Raises
929
- ------
930
- NetworkXError
931
- If n is not in the graph.
932
-
933
- See Also
934
- --------
935
- remove_nodes_from
936
-
937
- """
938
- try:
939
- for u in self._pred[n]:
940
- succs_u = self._succ[u]
941
- un_cw = succs_u[n]["cw"]
942
- un_ccw = succs_u[n]["ccw"]
943
- del succs_u[n]
944
- del self._pred[u][n]
945
- if n != un_cw:
946
- succs_u[un_cw]["ccw"] = un_ccw
947
- succs_u[un_ccw]["cw"] = un_cw
948
- del self._node[n]
949
- del self._succ[n]
950
- del self._pred[n]
951
- except KeyError as err: # NetworkXError if n not in self
952
- raise nx.NetworkXError(
953
- f"The node {n} is not in the planar embedding."
954
- ) from err
955
- nx._clear_cache(self)
956
-
957
- def remove_nodes_from(self, nodes):
958
- """Remove multiple nodes.
959
-
960
- Parameters
961
- ----------
962
- nodes : iterable container
963
- A container of nodes (list, dict, set, etc.). If a node
964
- in the container is not in the graph it is silently ignored.
965
-
966
- See Also
967
- --------
968
- remove_node
969
-
970
- Notes
971
- -----
972
- When removing nodes from an iterator over the graph you are changing,
973
- a `RuntimeError` will be raised with message:
974
- `RuntimeError: dictionary changed size during iteration`. This
975
- happens when the graph's underlying dictionary is modified during
976
- iteration. To avoid this error, evaluate the iterator into a separate
977
- object, e.g. by using `list(iterator_of_nodes)`, and pass this
978
- object to `G.remove_nodes_from`.
979
-
980
- """
981
- for n in nodes:
982
- if n in self._node:
983
- self.remove_node(n)
984
- # silently skip non-existing nodes
985
-
986
- def neighbors_cw_order(self, v):
987
- """Generator for the neighbors of v in clockwise order.
988
-
989
- Parameters
990
- ----------
991
- v : node
992
-
993
- Yields
994
- ------
995
- node
996
-
997
- """
998
- succs = self._succ[v]
999
- if not succs:
1000
- # v has no neighbors
1001
- return
1002
- start_node = next(reversed(succs))
1003
- yield start_node
1004
- current_node = succs[start_node]["cw"]
1005
- while start_node != current_node:
1006
- yield current_node
1007
- current_node = succs[current_node]["cw"]
1008
-
1009
- def add_half_edge(self, start_node, end_node, *, cw=None, ccw=None):
1010
- """Adds a half-edge from `start_node` to `end_node`.
1011
-
1012
- If the half-edge is not the first one out of `start_node`, a reference
1013
- node must be provided either in the clockwise (parameter `cw`) or in
1014
- the counterclockwise (parameter `ccw`) direction. Only one of `cw`/`ccw`
1015
- can be specified (or neither in the case of the first edge).
1016
- Note that specifying a reference in the clockwise (`cw`) direction means
1017
- inserting the new edge in the first counterclockwise position with
1018
- respect to the reference (and vice-versa).
1019
-
1020
- Parameters
1021
- ----------
1022
- start_node : node
1023
- Start node of inserted edge.
1024
- end_node : node
1025
- End node of inserted edge.
1026
- cw, ccw: node
1027
- End node of reference edge.
1028
- Omit or pass `None` if adding the first out-half-edge of `start_node`.
1029
-
1030
-
1031
- Raises
1032
- ------
1033
- NetworkXException
1034
- If the `cw` or `ccw` node is not a successor of `start_node`.
1035
- If `start_node` has successors, but neither `cw` or `ccw` is provided.
1036
- If both `cw` and `ccw` are specified.
1037
-
1038
- See Also
1039
- --------
1040
- connect_components
1041
- """
1042
-
1043
- succs = self._succ.get(start_node)
1044
- if succs:
1045
- # there is already some edge out of start_node
1046
- leftmost_nbr = next(reversed(self._succ[start_node]))
1047
- if cw is not None:
1048
- if cw not in succs:
1049
- raise nx.NetworkXError("Invalid clockwise reference node.")
1050
- if ccw is not None:
1051
- raise nx.NetworkXError("Only one of cw/ccw can be specified.")
1052
- ref_ccw = succs[cw]["ccw"]
1053
- super().add_edge(start_node, end_node, cw=cw, ccw=ref_ccw)
1054
- succs[ref_ccw]["cw"] = end_node
1055
- succs[cw]["ccw"] = end_node
1056
- # when (cw == leftmost_nbr), the newly added neighbor is
1057
- # already at the end of dict self._succ[start_node] and
1058
- # takes the place of the former leftmost_nbr
1059
- move_leftmost_nbr_to_end = cw != leftmost_nbr
1060
- elif ccw is not None:
1061
- if ccw not in succs:
1062
- raise nx.NetworkXError("Invalid counterclockwise reference node.")
1063
- ref_cw = succs[ccw]["cw"]
1064
- super().add_edge(start_node, end_node, cw=ref_cw, ccw=ccw)
1065
- succs[ref_cw]["ccw"] = end_node
1066
- succs[ccw]["cw"] = end_node
1067
- move_leftmost_nbr_to_end = True
1068
- else:
1069
- raise nx.NetworkXError(
1070
- "Node already has out-half-edge(s), either cw or ccw reference node required."
1071
- )
1072
- if move_leftmost_nbr_to_end:
1073
- # LRPlanarity (via self.add_half_edge_first()) requires that
1074
- # we keep track of the leftmost neighbor, which we accomplish
1075
- # by keeping it as the last key in dict self._succ[start_node]
1076
- succs[leftmost_nbr] = succs.pop(leftmost_nbr)
1077
-
1078
- else:
1079
- if cw is not None or ccw is not None:
1080
- raise nx.NetworkXError("Invalid reference node.")
1081
- # adding the first edge out of start_node
1082
- super().add_edge(start_node, end_node, ccw=end_node, cw=end_node)
1083
-
1084
- def check_structure(self):
1085
- """Runs without exceptions if this object is valid.
1086
-
1087
- Checks that the following properties are fulfilled:
1088
-
1089
- * Edges go in both directions (because the edge attributes differ).
1090
- * Every edge has a 'cw' and 'ccw' attribute which corresponds to a
1091
- correct planar embedding.
1092
-
1093
- Running this method verifies that the underlying Graph must be planar.
1094
-
1095
- Raises
1096
- ------
1097
- NetworkXException
1098
- This exception is raised with a short explanation if the
1099
- PlanarEmbedding is invalid.
1100
- """
1101
- # Check fundamental structure
1102
- for v in self:
1103
- try:
1104
- sorted_nbrs = set(self.neighbors_cw_order(v))
1105
- except KeyError as err:
1106
- msg = f"Bad embedding. Missing orientation for a neighbor of {v}"
1107
- raise nx.NetworkXException(msg) from err
1108
-
1109
- unsorted_nbrs = set(self[v])
1110
- if sorted_nbrs != unsorted_nbrs:
1111
- msg = "Bad embedding. Edge orientations not set correctly."
1112
- raise nx.NetworkXException(msg)
1113
- for w in self[v]:
1114
- # Check if opposite half-edge exists
1115
- if not self.has_edge(w, v):
1116
- msg = "Bad embedding. Opposite half-edge is missing."
1117
- raise nx.NetworkXException(msg)
1118
-
1119
- # Check planarity
1120
- counted_half_edges = set()
1121
- for component in nx.connected_components(self):
1122
- if len(component) == 1:
1123
- # Don't need to check single node component
1124
- continue
1125
- num_nodes = len(component)
1126
- num_half_edges = 0
1127
- num_faces = 0
1128
- for v in component:
1129
- for w in self.neighbors_cw_order(v):
1130
- num_half_edges += 1
1131
- if (v, w) not in counted_half_edges:
1132
- # We encountered a new face
1133
- num_faces += 1
1134
- # Mark all half-edges belonging to this face
1135
- self.traverse_face(v, w, counted_half_edges)
1136
- num_edges = num_half_edges // 2 # num_half_edges is even
1137
- if num_nodes - num_edges + num_faces != 2:
1138
- # The result does not match Euler's formula
1139
- msg = "Bad embedding. The graph does not match Euler's formula"
1140
- raise nx.NetworkXException(msg)
1141
-
1142
- def add_half_edge_ccw(self, start_node, end_node, reference_neighbor):
1143
- """Adds a half-edge from start_node to end_node.
1144
-
1145
- The half-edge is added counter clockwise next to the existing half-edge
1146
- (start_node, reference_neighbor).
1147
-
1148
- Parameters
1149
- ----------
1150
- start_node : node
1151
- Start node of inserted edge.
1152
- end_node : node
1153
- End node of inserted edge.
1154
- reference_neighbor: node
1155
- End node of reference edge.
1156
-
1157
- Raises
1158
- ------
1159
- NetworkXException
1160
- If the reference_neighbor does not exist.
1161
-
1162
- See Also
1163
- --------
1164
- add_half_edge
1165
- add_half_edge_cw
1166
- connect_components
1167
-
1168
- """
1169
- self.add_half_edge(start_node, end_node, cw=reference_neighbor)
1170
-
1171
- def add_half_edge_cw(self, start_node, end_node, reference_neighbor):
1172
- """Adds a half-edge from start_node to end_node.
1173
-
1174
- The half-edge is added clockwise next to the existing half-edge
1175
- (start_node, reference_neighbor).
1176
-
1177
- Parameters
1178
- ----------
1179
- start_node : node
1180
- Start node of inserted edge.
1181
- end_node : node
1182
- End node of inserted edge.
1183
- reference_neighbor: node
1184
- End node of reference edge.
1185
-
1186
- Raises
1187
- ------
1188
- NetworkXException
1189
- If the reference_neighbor does not exist.
1190
-
1191
- See Also
1192
- --------
1193
- add_half_edge
1194
- add_half_edge_ccw
1195
- connect_components
1196
- """
1197
- self.add_half_edge(start_node, end_node, ccw=reference_neighbor)
1198
-
1199
- def remove_edge(self, u, v):
1200
- """Remove the edge between u and v.
1201
-
1202
- Parameters
1203
- ----------
1204
- u, v : nodes
1205
- Remove the half-edges (u, v) and (v, u) and update the
1206
- edge ordering around the removed edge.
1207
-
1208
- Raises
1209
- ------
1210
- NetworkXError
1211
- If there is not an edge between u and v.
1212
-
1213
- See Also
1214
- --------
1215
- remove_edges_from : remove a collection of edges
1216
- """
1217
- try:
1218
- succs_u = self._succ[u]
1219
- succs_v = self._succ[v]
1220
- uv_cw = succs_u[v]["cw"]
1221
- uv_ccw = succs_u[v]["ccw"]
1222
- vu_cw = succs_v[u]["cw"]
1223
- vu_ccw = succs_v[u]["ccw"]
1224
- del succs_u[v]
1225
- del self._pred[v][u]
1226
- del succs_v[u]
1227
- del self._pred[u][v]
1228
- if v != uv_cw:
1229
- succs_u[uv_cw]["ccw"] = uv_ccw
1230
- succs_u[uv_ccw]["cw"] = uv_cw
1231
- if u != vu_cw:
1232
- succs_v[vu_cw]["ccw"] = vu_ccw
1233
- succs_v[vu_ccw]["cw"] = vu_cw
1234
- except KeyError as err:
1235
- raise nx.NetworkXError(
1236
- f"The edge {u}-{v} is not in the planar embedding."
1237
- ) from err
1238
- nx._clear_cache(self)
1239
-
1240
- def remove_edges_from(self, ebunch):
1241
- """Remove all edges specified in ebunch.
1242
-
1243
- Parameters
1244
- ----------
1245
- ebunch: list or container of edge tuples
1246
- Each pair of half-edges between the nodes given in the tuples
1247
- will be removed from the graph. The nodes can be passed as:
1248
-
1249
- - 2-tuples (u, v) half-edges (u, v) and (v, u).
1250
- - 3-tuples (u, v, k) where k is ignored.
1251
-
1252
- See Also
1253
- --------
1254
- remove_edge : remove a single edge
1255
-
1256
- Notes
1257
- -----
1258
- Will fail silently if an edge in ebunch is not in the graph.
1259
-
1260
- Examples
1261
- --------
1262
- >>> G = nx.path_graph(4) # or DiGraph, MultiGraph, MultiDiGraph, etc
1263
- >>> ebunch = [(1, 2), (2, 3)]
1264
- >>> G.remove_edges_from(ebunch)
1265
- """
1266
- for e in ebunch:
1267
- u, v = e[:2] # ignore edge data
1268
- # assuming that the PlanarEmbedding is valid, if the half_edge
1269
- # (u, v) is in the graph, then so is half_edge (v, u)
1270
- if u in self._succ and v in self._succ[u]:
1271
- self.remove_edge(u, v)
1272
-
1273
- def connect_components(self, v, w):
1274
- """Adds half-edges for (v, w) and (w, v) at some position.
1275
-
1276
- This method should only be called if v and w are in different
1277
- components, or it might break the embedding.
1278
- This especially means that if `connect_components(v, w)`
1279
- is called it is not allowed to call `connect_components(w, v)`
1280
- afterwards. The neighbor orientations in both directions are
1281
- all set correctly after the first call.
1282
-
1283
- Parameters
1284
- ----------
1285
- v : node
1286
- w : node
1287
-
1288
- See Also
1289
- --------
1290
- add_half_edge
1291
- """
1292
- if v in self._succ and self._succ[v]:
1293
- ref = next(reversed(self._succ[v]))
1294
- else:
1295
- ref = None
1296
- self.add_half_edge(v, w, cw=ref)
1297
- if w in self._succ and self._succ[w]:
1298
- ref = next(reversed(self._succ[w]))
1299
- else:
1300
- ref = None
1301
- self.add_half_edge(w, v, cw=ref)
1302
-
1303
- def add_half_edge_first(self, start_node, end_node):
1304
- """Add a half-edge and set end_node as start_node's leftmost neighbor.
1305
-
1306
- The new edge is inserted counterclockwise with respect to the current
1307
- leftmost neighbor, if there is one.
1308
-
1309
- Parameters
1310
- ----------
1311
- start_node : node
1312
- end_node : node
1313
-
1314
- See Also
1315
- --------
1316
- add_half_edge
1317
- connect_components
1318
- """
1319
- succs = self._succ.get(start_node)
1320
- # the leftmost neighbor is the last entry in the
1321
- # self._succ[start_node] dict
1322
- leftmost_nbr = next(reversed(succs)) if succs else None
1323
- self.add_half_edge(start_node, end_node, cw=leftmost_nbr)
1324
-
1325
- def next_face_half_edge(self, v, w):
1326
- """Returns the following half-edge left of a face.
1327
-
1328
- Parameters
1329
- ----------
1330
- v : node
1331
- w : node
1332
-
1333
- Returns
1334
- -------
1335
- half-edge : tuple
1336
- """
1337
- new_node = self[w][v]["ccw"]
1338
- return w, new_node
1339
-
1340
- def traverse_face(self, v, w, mark_half_edges=None):
1341
- """Returns nodes on the face that belong to the half-edge (v, w).
1342
-
1343
- The face that is traversed lies to the right of the half-edge (in an
1344
- orientation where v is below w).
1345
-
1346
- Optionally it is possible to pass a set to which all encountered half
1347
- edges are added. Before calling this method, this set must not include
1348
- any half-edges that belong to the face.
1349
-
1350
- Parameters
1351
- ----------
1352
- v : node
1353
- Start node of half-edge.
1354
- w : node
1355
- End node of half-edge.
1356
- mark_half_edges: set, optional
1357
- Set to which all encountered half-edges are added.
1358
-
1359
- Returns
1360
- -------
1361
- face : list
1362
- A list of nodes that lie on this face.
1363
- """
1364
- if mark_half_edges is None:
1365
- mark_half_edges = set()
1366
-
1367
- face_nodes = [v]
1368
- mark_half_edges.add((v, w))
1369
- prev_node = v
1370
- cur_node = w
1371
- # Last half-edge is (incoming_node, v)
1372
- incoming_node = self[v][w]["cw"]
1373
-
1374
- while cur_node != v or prev_node != incoming_node:
1375
- face_nodes.append(cur_node)
1376
- prev_node, cur_node = self.next_face_half_edge(prev_node, cur_node)
1377
- if (prev_node, cur_node) in mark_half_edges:
1378
- raise nx.NetworkXException("Bad planar embedding. Impossible face.")
1379
- mark_half_edges.add((prev_node, cur_node))
1380
-
1381
- return face_nodes
1382
-
1383
- def is_directed(self):
1384
- """A valid PlanarEmbedding is undirected.
1385
-
1386
- All reverse edges are contained, i.e. for every existing
1387
- half-edge (v, w) the half-edge in the opposite direction (w, v) is also
1388
- contained.
1389
- """
1390
- return False
1391
-
1392
- def copy(self, as_view=False):
1393
- if as_view is True:
1394
- return nx.graphviews.generic_graph_view(self)
1395
- G = self.__class__()
1396
- G.graph.update(self.graph)
1397
- G.add_nodes_from((n, d.copy()) for n, d in self._node.items())
1398
- super(self.__class__, G).add_edges_from(
1399
- (u, v, datadict.copy())
1400
- for u, nbrs in self._adj.items()
1401
- for v, datadict in nbrs.items()
1402
- )
1403
- return G
1404
-
1405
- def to_undirected(self, reciprocal=False, as_view=False):
1406
- """
1407
- Returns a non-embedding undirected representation of the graph.
1408
-
1409
- This method strips the planar embedding information and provides
1410
- a simple undirected graph representation. While creating the undirected graph,
1411
- all edge attributes are retained except the ``"cw"`` and ``"ccw"`` attributes
1412
- which are removed from the edge data. Those attributes are specific to
1413
- the requirements of planar embeddings.
1414
-
1415
- Parameters
1416
- ----------
1417
- reciprocal : bool (optional)
1418
- Not supported for PlanarEmbedding. This parameter raises an exception
1419
- if used. All valid embeddings include reciprocal half-edges by definition,
1420
- making this parameter unnecessary.
1421
- as_view : bool (optional, default=False)
1422
- Not supported for PlanarEmbedding. This parameter raises an exception
1423
- if used.
1424
-
1425
- Returns
1426
- -------
1427
- G : Graph
1428
- An undirected graph with the same name and nodes as the PlanarEmbedding.
1429
- Edges are included with their data, except for the ``"cw"`` and ``"ccw"``
1430
- attributes, which are omitted.
1431
-
1432
-
1433
- Notes
1434
- -----
1435
- - If edges exist in both directions ``(u, v)`` and ``(v, u)`` in the PlanarEmbedding,
1436
- attributes for the resulting undirected edge will be combined, excluding ``"cw"``
1437
- and ``"ccw"``.
1438
- - A deep copy is made of the other edge attributes as well as the
1439
- node and graph attributes, ensuring independence of the resulting graph.
1440
- - Subclass-specific data structures used in the original graph may not transfer
1441
- to the undirected graph. The resulting graph will be of type ``nx.Graph``.
1442
- """
1443
-
1444
- if reciprocal:
1445
- raise ValueError(
1446
- "'reciprocal=True' is not supported for PlanarEmbedding.\n"
1447
- "All valid embeddings include reciprocal half-edges by definition,\n"
1448
- "making this parameter unnecessary."
1449
- )
1450
-
1451
- if as_view:
1452
- raise ValueError("'as_view=True' is not supported for PlanarEmbedding.")
1453
-
1454
- graph_class = self.to_undirected_class()
1455
- G = graph_class()
1456
- G.graph.update(deepcopy(self.graph))
1457
- G.add_nodes_from((n, deepcopy(d)) for n, d in self._node.items())
1458
- G.add_edges_from(
1459
- (u, v, {k: deepcopy(v) for k, v in d.items() if k not in {"cw", "ccw"}})
1460
- for u, nbrs in self._adj.items()
1461
- for v, d in nbrs.items()
1462
- )
1463
- return G