okstra 0.114.0 → 0.116.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 (337) hide show
  1. package/docs/kr/architecture.md +2 -2
  2. package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +51 -0
  3. package/package.json +1 -1
  4. package/runtime/BUILD.json +2 -2
  5. package/runtime/agents/workers/report-writer-worker.md +1 -0
  6. package/runtime/prompts/launch.template.md +34 -0
  7. package/runtime/prompts/lead/convergence.md +2 -0
  8. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  9. package/runtime/prompts/lead/report-writer.md +1 -1
  10. package/runtime/prompts/profiles/implementation-planning.md +1 -0
  11. package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
  12. package/runtime/python/okstra_ctl/incremental_carry.py +70 -0
  13. package/runtime/python/okstra_ctl/incremental_scope.py +89 -0
  14. package/runtime/python/okstra_ctl/report_views.py +151 -12
  15. package/runtime/python/okstra_ctl/run.py +24 -24
  16. package/runtime/python/okstra_ctl/stage_targets.py +26 -0
  17. package/runtime/python/okstra_ctl/worktree.py +1 -1
  18. package/runtime/python/okstra_token_usage/claude.py +58 -19
  19. package/runtime/python/okstra_token_usage/collect.py +3 -1
  20. package/runtime/python/okstra_vendor/__init__.py +3 -0
  21. package/runtime/schemas/final-report-v1.0.schema.json +39 -0
  22. package/runtime/templates/reports/final-report.template.md +27 -0
  23. package/runtime/templates/reports/i18n/en.json +23 -12
  24. package/runtime/templates/reports/i18n/ko.json +23 -12
  25. package/runtime/templates/reports/report.css +55 -0
  26. package/runtime/templates/reports/report.js +53 -1
  27. package/runtime/validators/validate-run.py +50 -0
  28. package/src/cli-registry.mjs +14 -0
  29. package/src/commands/execute/incremental-carry.mjs +20 -0
  30. package/src/commands/execute/incremental-scope.mjs +20 -0
  31. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  32. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
  33. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
  34. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
  35. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
  36. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
  37. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
  38. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
  39. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
  40. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
  41. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
  42. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
  43. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
  44. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
  45. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
  46. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  47. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
  48. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
  49. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
  50. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
  51. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
  52. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
  53. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  54. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
  55. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
  56. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
  57. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
  58. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
  59. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
  60. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
  61. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
  62. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
  63. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
  64. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
  65. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
  66. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
  67. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  68. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
  69. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
  70. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
  71. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
  72. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
  73. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
  74. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
  75. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
  76. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
  77. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
  78. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
  79. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
  80. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
  81. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
  82. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
  83. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
  84. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
  85. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
  86. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
  87. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
  88. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  89. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
  90. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  91. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
  92. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
  93. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
  94. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
  95. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
  96. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
  97. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
  98. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
  99. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
  100. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
  101. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
  102. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
  103. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
  104. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  105. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
  106. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
  107. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
  108. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
  109. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
  110. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
  111. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  112. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
  113. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
  114. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
  115. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
  116. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
  117. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
  118. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
  119. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
  120. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  121. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  122. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  124. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
  125. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
  126. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
  127. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
  128. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
  129. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  130. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  131. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
  136. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
  137. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
  138. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
  139. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
  140. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
  141. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
  142. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
  143. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
  144. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
  146. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
  147. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
  148. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  149. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
  150. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
  151. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
  152. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
  153. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  154. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
  155. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
  156. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
  157. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
  158. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
  159. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
  160. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  161. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
  162. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
  163. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
  164. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
  165. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
  166. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
  167. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
  168. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
  169. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
  170. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
  171. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
  172. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
  173. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
  174. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
  175. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
  176. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
  177. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
  178. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
  179. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
  180. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
  181. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
  182. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
  183. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
  184. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
  185. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
  186. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
  187. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
  188. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
  189. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
  190. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
  191. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
  192. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
  193. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
  194. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
  195. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
  196. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
  197. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
  198. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
  199. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
  200. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
  201. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
  202. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
  203. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
  204. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
  205. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
  206. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
  207. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
  208. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
  209. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
  210. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
  211. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
  212. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
  213. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
  214. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
  215. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
  216. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
  217. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
  218. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  219. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
  220. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
  221. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
  222. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
  223. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
  224. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  225. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
  226. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
  227. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
  228. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
  229. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
  230. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
  231. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
  232. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  233. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
  234. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
  235. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
  236. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
  237. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
  238. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
  239. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
  240. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
  241. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
  242. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
  243. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
  244. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
  245. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
  246. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
  247. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
  248. package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
  249. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  250. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  251. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  252. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  253. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  254. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  255. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  256. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
  257. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
  258. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
  259. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
  260. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
  261. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
  262. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  263. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
  264. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
  265. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
  266. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
  267. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
  268. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
  269. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
  270. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
  271. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
  272. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
  273. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
  274. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
  275. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
  276. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
  277. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
  278. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
  279. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
  280. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
  281. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
  282. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
  283. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
  284. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
  285. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
  286. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
  287. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
  288. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
  289. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
  290. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
  291. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  292. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
  293. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
  294. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
  295. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
  296. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
  297. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
  298. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
  299. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  300. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
  301. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
  302. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
  303. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
  304. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  305. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
  306. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
  307. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
  308. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
  309. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
  310. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
  311. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
  312. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
  313. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
  314. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
  315. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
  316. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  317. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
  318. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
  319. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
  320. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
  321. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
  322. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
  323. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
  324. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
  325. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
  326. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
  327. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  328. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
  329. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
  330. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
  331. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
  332. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
  333. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
  334. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
  335. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
  336. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
  337. package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +0 -55
@@ -1,1655 +0,0 @@
1
- import itertools as it
2
-
3
- import pytest
4
-
5
- import networkx as nx
6
- from networkx import vf2pp_all_isomorphisms, vf2pp_is_isomorphic, vf2pp_isomorphism
7
-
8
- labels_same = ["blue"]
9
-
10
- labels_many = [
11
- "white",
12
- "red",
13
- "blue",
14
- "green",
15
- "orange",
16
- "black",
17
- "purple",
18
- "yellow",
19
- "brown",
20
- "cyan",
21
- "solarized",
22
- "pink",
23
- "none",
24
- ]
25
-
26
- graph_classes = [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
27
-
28
-
29
- class TestPreCheck:
30
- def test_first_graph_empty(self):
31
- G1 = nx.Graph()
32
- G2 = nx.Graph([(0, 1), (1, 2)])
33
- assert not vf2pp_is_isomorphic(G1, G2)
34
-
35
- def test_second_graph_empty(self):
36
- G1 = nx.Graph([(0, 1), (1, 2)])
37
- G2 = nx.Graph()
38
- assert not vf2pp_is_isomorphic(G1, G2)
39
-
40
- def test_different_order1(self):
41
- G1 = nx.path_graph(5)
42
- G2 = nx.path_graph(6)
43
- assert not vf2pp_is_isomorphic(G1, G2)
44
-
45
- def test_different_order2(self):
46
- G1 = nx.barbell_graph(100, 20)
47
- G2 = nx.barbell_graph(101, 20)
48
- assert not vf2pp_is_isomorphic(G1, G2)
49
-
50
- def test_different_order3(self):
51
- G1 = nx.complete_graph(7)
52
- G2 = nx.complete_graph(8)
53
- assert not vf2pp_is_isomorphic(G1, G2)
54
-
55
- def test_different_degree_sequences1(self):
56
- G1 = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (0, 4)])
57
- G2 = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3), (0, 4), (2, 5)])
58
- assert not vf2pp_is_isomorphic(G1, G2)
59
-
60
- G2.remove_node(3)
61
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(["a"]))), "label")
62
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle("a"))), "label")
63
-
64
- assert vf2pp_is_isomorphic(G1, G2)
65
-
66
- def test_different_degree_sequences2(self):
67
- G1 = nx.Graph(
68
- [
69
- (0, 1),
70
- (1, 2),
71
- (0, 2),
72
- (2, 3),
73
- (3, 4),
74
- (4, 5),
75
- (5, 6),
76
- (6, 3),
77
- (4, 7),
78
- (7, 8),
79
- (8, 3),
80
- ]
81
- )
82
- G2 = G1.copy()
83
- G2.add_edge(8, 0)
84
- assert not vf2pp_is_isomorphic(G1, G2)
85
-
86
- G1.add_edge(6, 1)
87
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(["a"]))), "label")
88
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle("a"))), "label")
89
-
90
- assert vf2pp_is_isomorphic(G1, G2)
91
-
92
- def test_different_degree_sequences3(self):
93
- G1 = nx.Graph([(0, 1), (0, 2), (1, 2), (2, 3), (2, 4), (3, 4), (2, 5), (2, 6)])
94
- G2 = nx.Graph(
95
- [(0, 1), (0, 6), (0, 2), (1, 2), (2, 3), (2, 4), (3, 4), (2, 5), (2, 6)]
96
- )
97
- assert not vf2pp_is_isomorphic(G1, G2)
98
-
99
- G1.add_edge(3, 5)
100
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(["a"]))), "label")
101
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle("a"))), "label")
102
-
103
- assert vf2pp_is_isomorphic(G1, G2)
104
-
105
- def test_label_distribution(self):
106
- G1 = nx.Graph([(0, 1), (0, 2), (1, 2), (2, 3), (2, 4), (3, 4), (2, 5), (2, 6)])
107
- G2 = nx.Graph([(0, 1), (0, 2), (1, 2), (2, 3), (2, 4), (3, 4), (2, 5), (2, 6)])
108
-
109
- colors1 = ["blue", "blue", "blue", "yellow", "black", "purple", "purple"]
110
- colors2 = ["blue", "blue", "yellow", "yellow", "black", "purple", "purple"]
111
-
112
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(colors1[::-1]))), "label")
113
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(colors2[::-1]))), "label")
114
-
115
- assert not vf2pp_is_isomorphic(G1, G2, node_label="label")
116
- G2.nodes[3]["label"] = "blue"
117
- assert vf2pp_is_isomorphic(G1, G2, node_label="label")
118
-
119
-
120
- class TestAllGraphTypesEdgeCases:
121
- @pytest.mark.parametrize("graph_type", graph_classes)
122
- def test_both_graphs_empty(self, graph_type):
123
- G = graph_type()
124
- H = graph_type()
125
- assert vf2pp_isomorphism(G, H) is None
126
-
127
- G.add_node(0)
128
-
129
- assert vf2pp_isomorphism(G, H) is None
130
- assert vf2pp_isomorphism(H, G) is None
131
-
132
- H.add_node(0)
133
- assert vf2pp_isomorphism(G, H) == {0: 0}
134
-
135
- @pytest.mark.parametrize("graph_type", graph_classes)
136
- def test_first_graph_empty(self, graph_type):
137
- G = graph_type()
138
- H = graph_type([(0, 1)])
139
- assert vf2pp_isomorphism(G, H) is None
140
-
141
- @pytest.mark.parametrize("graph_type", graph_classes)
142
- def test_second_graph_empty(self, graph_type):
143
- G = graph_type([(0, 1)])
144
- H = graph_type()
145
- assert vf2pp_isomorphism(G, H) is None
146
-
147
-
148
- class TestGraphISOVF2pp:
149
- def test_custom_graph1_same_labels(self):
150
- G1 = nx.Graph()
151
-
152
- mapped = {1: "A", 2: "B", 3: "C", 4: "D", 5: "Z", 6: "E"}
153
- edges1 = [(1, 2), (1, 3), (1, 4), (2, 3), (2, 6), (3, 4), (5, 1), (5, 2)]
154
- # 5-1 5-1 D-A
155
- # |/|\ |/|\ |/|\ 6-X; 2-C; 5-D; 1-A; 3-B; 7-E; 4-Z
156
- # 2-3-4 2-3-4 C-B-Z
157
- # | | |/ | |/
158
- # 6 6 7 X E
159
-
160
- G1.add_edges_from(edges1)
161
- G2 = nx.relabel_nodes(G1, mapped)
162
-
163
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
164
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_many))), "label")
165
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
166
-
167
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
168
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
169
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
170
-
171
- # Add edge making G1 symmetrical
172
- G1.add_edge(3, 7)
173
- G1.nodes[7]["label"] = "blue"
174
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
175
-
176
- all_self_mappings = list(vf2pp_all_isomorphisms(G1, G1))
177
- assert len(all_self_mappings) == 2
178
- # check that one of the mappings is not the identity for node 2
179
- assert sum(1 for m in all_self_mappings if m[2] == 2) == 1
180
-
181
- # Make G2 isomorphic to G1
182
- G2.add_edges_from([(mapped[3], "X"), (mapped[6], mapped[5])])
183
- G1.add_edge(4, 7)
184
- G2.nodes["X"]["label"] = "blue"
185
- assert vf2pp_isomorphism(G1, G2, node_label="label")
186
-
187
- # Re-structure maintaining isomorphism
188
- G1.remove_edges_from([(1, 4), (1, 3)])
189
- G2.remove_edges_from([(mapped[1], mapped[5]), (mapped[1], mapped[2])])
190
- assert vf2pp_isomorphism(G1, G2, node_label="label")
191
-
192
- def test_custom_graph2_same_labels(self):
193
- G1 = nx.Graph()
194
-
195
- mapped = {1: "A", 2: "C", 3: "D", 4: "E", 5: "G", 7: "B", 6: "F"}
196
- edges1 = [(1, 2), (1, 5), (5, 6), (2, 3), (2, 4), (3, 4), (4, 5), (2, 7)]
197
- # 6-5
198
- # /|\ 6-F; 2-C; 3-D; 1-A; 7-B; 4-E; 5-G
199
- # 1-2-4
200
- # /|/
201
- # 7 3
202
-
203
- G1.add_edges_from(edges1)
204
- G2 = nx.relabel_nodes(G1, mapped)
205
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
206
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
207
-
208
- assert vf2pp_isomorphism(G1, G2, node_label="label")
209
-
210
- # Obtain two isomorphic subgraphs from the graph
211
- G2.remove_edge(mapped[1], mapped[2])
212
- G2.add_edge(mapped[1], mapped[4])
213
- H1 = nx.Graph(G1.subgraph([2, 3, 4, 7]))
214
- H2 = nx.Graph(G2.subgraph([mapped[1], mapped[4], mapped[5], mapped[6]]))
215
- assert vf2pp_isomorphism(H1, H2, node_label="label")
216
-
217
- # Add edges maintaining isomorphism
218
- H1.add_edges_from([(3, 7), (4, 7)])
219
- H2.add_edges_from([(mapped[1], mapped[6]), (mapped[4], mapped[6])])
220
- assert vf2pp_isomorphism(H1, H2, node_label="label")
221
-
222
- def test_custom_graph2_different_labels(self):
223
- G1 = nx.Graph()
224
-
225
- mapped = {1: "A", 2: "C", 3: "D", 4: "E", 5: "G", 7: "B", 6: "F"}
226
- edges1 = [(1, 2), (1, 5), (5, 6), (2, 3), (2, 4), (3, 4), (4, 5), (2, 7)]
227
-
228
- G1.add_edges_from(edges1)
229
- G2 = nx.relabel_nodes(G1, mapped)
230
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
231
- nx.set_node_attributes(
232
- G2,
233
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
234
- "label",
235
- )
236
-
237
- # Adding new nodes
238
- G1.add_node(0)
239
- G2.add_node("Z")
240
- G1.nodes[0]["label"] = G1.nodes[1]["label"]
241
- G2.nodes["Z"]["label"] = G1.nodes[1]["label"]
242
- mapped.update({0: "Z"})
243
-
244
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
245
-
246
- # Change the color of one of the nodes
247
- G2.nodes["Z"]["label"] = G1.nodes[2]["label"]
248
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
249
-
250
- # Add an extra edge
251
- G1.nodes[0]["label"] = "blue"
252
- G2.nodes["Z"]["label"] = "blue"
253
- G1.add_edge(0, 1)
254
-
255
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
256
-
257
- # Add extra edge to both
258
- G2.add_edge("Z", "A")
259
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
260
-
261
- def test_custom_graph3_same_labels(self):
262
- G1 = nx.Graph()
263
-
264
- mapped = {1: 9, 2: 8, 3: 7, 4: 6, 5: 3, 8: 5, 9: 4, 7: 1, 6: 2}
265
- edges1 = [
266
- (1, 2),
267
- (1, 3),
268
- (2, 3),
269
- (3, 4),
270
- (4, 5),
271
- (4, 7),
272
- (4, 9),
273
- (5, 8),
274
- (8, 9),
275
- (5, 6),
276
- (6, 7),
277
- (5, 2),
278
- ]
279
- G1.add_edges_from(edges1)
280
- G2 = nx.relabel_nodes(G1, mapped)
281
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
282
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
283
- assert vf2pp_isomorphism(G1, G2, node_label="label")
284
-
285
- # Connect nodes maintaining symmetry
286
- G1.add_edges_from([(6, 9), (7, 8)])
287
- G2.add_edges_from([(mapped[6], mapped[8]), (mapped[7], mapped[9])])
288
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
289
-
290
- # Make isomorphic
291
- G1.add_edges_from([(6, 8), (7, 9)])
292
- G2.add_edges_from([(mapped[6], mapped[9]), (mapped[7], mapped[8])])
293
- assert vf2pp_isomorphism(G1, G2, node_label="label")
294
-
295
- # Connect more nodes
296
- G1.add_edges_from([(2, 7), (3, 6)])
297
- G2.add_edges_from([(mapped[2], mapped[7]), (mapped[3], mapped[6])])
298
- G1.add_node(10)
299
- G2.add_node("Z")
300
- G1.nodes[10]["label"] = "blue"
301
- G2.nodes["Z"]["label"] = "blue"
302
-
303
- assert vf2pp_isomorphism(G1, G2, node_label="label")
304
-
305
- # Connect the newly added node, to opposite sides of the graph
306
- G1.add_edges_from([(10, 1), (10, 5), (10, 8)])
307
- G2.add_edges_from([("Z", mapped[1]), ("Z", mapped[4]), ("Z", mapped[9])])
308
- assert vf2pp_isomorphism(G1, G2, node_label="label")
309
-
310
- # Get two subgraphs that are not isomorphic but are easy to make
311
- H1 = nx.Graph(G1.subgraph([2, 3, 4, 5, 6, 7, 10]))
312
- H2 = nx.Graph(
313
- G2.subgraph(
314
- [mapped[4], mapped[5], mapped[6], mapped[7], mapped[8], mapped[9], "Z"]
315
- )
316
- )
317
- assert vf2pp_isomorphism(H1, H2, node_label="label") is None
318
-
319
- # Restructure both to make them isomorphic
320
- H1.add_edges_from([(10, 2), (10, 6), (3, 6), (2, 7), (2, 6), (3, 7)])
321
- H2.add_edges_from(
322
- [("Z", mapped[7]), (mapped[6], mapped[9]), (mapped[7], mapped[8])]
323
- )
324
- assert vf2pp_isomorphism(H1, H2, node_label="label")
325
-
326
- # Add edges with opposite direction in each Graph
327
- H1.add_edge(3, 5)
328
- H2.add_edge(mapped[5], mapped[7])
329
- assert vf2pp_isomorphism(H1, H2, node_label="label") is None
330
-
331
- def test_custom_graph3_different_labels(self):
332
- G1 = nx.Graph()
333
-
334
- mapped = {1: 9, 2: 8, 3: 7, 4: 6, 5: 3, 8: 5, 9: 4, 7: 1, 6: 2}
335
- edges1 = [
336
- (1, 2),
337
- (1, 3),
338
- (2, 3),
339
- (3, 4),
340
- (4, 5),
341
- (4, 7),
342
- (4, 9),
343
- (5, 8),
344
- (8, 9),
345
- (5, 6),
346
- (6, 7),
347
- (5, 2),
348
- ]
349
- G1.add_edges_from(edges1)
350
- G2 = nx.relabel_nodes(G1, mapped)
351
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
352
- nx.set_node_attributes(
353
- G2,
354
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
355
- "label",
356
- )
357
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
358
-
359
- # Add extra edge to G1
360
- G1.add_edge(1, 7)
361
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
362
-
363
- # Compensate in G2
364
- G2.add_edge(9, 1)
365
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
366
-
367
- # Add extra node
368
- G1.add_node("A")
369
- G2.add_node("K")
370
- G1.nodes["A"]["label"] = "green"
371
- G2.nodes["K"]["label"] = "green"
372
- mapped.update({"A": "K"})
373
-
374
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
375
-
376
- # Connect A to one side of G1 and K to the opposite
377
- G1.add_edge("A", 6)
378
- G2.add_edge("K", 5)
379
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
380
-
381
- # Make the graphs symmetrical
382
- G1.add_edge(1, 5)
383
- G1.add_edge(2, 9)
384
- G2.add_edge(9, 3)
385
- G2.add_edge(8, 4)
386
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
387
-
388
- # Assign same colors so the two opposite sides are identical
389
- for node in G1.nodes():
390
- color = "red"
391
- G1.nodes[node]["label"] = color
392
- G2.nodes[mapped[node]]["label"] = color
393
-
394
- assert vf2pp_isomorphism(G1, G2, node_label="label")
395
-
396
- def test_custom_graph4_different_labels(self):
397
- G1 = nx.Graph()
398
- edges1 = [
399
- (1, 2),
400
- (2, 3),
401
- (3, 8),
402
- (3, 4),
403
- (4, 5),
404
- (4, 6),
405
- (3, 6),
406
- (8, 7),
407
- (8, 9),
408
- (5, 9),
409
- (10, 11),
410
- (11, 12),
411
- (12, 13),
412
- (11, 13),
413
- ]
414
-
415
- mapped = {
416
- 1: "n",
417
- 2: "m",
418
- 3: "l",
419
- 4: "j",
420
- 5: "k",
421
- 6: "i",
422
- 7: "g",
423
- 8: "h",
424
- 9: "f",
425
- 10: "b",
426
- 11: "a",
427
- 12: "d",
428
- 13: "e",
429
- }
430
-
431
- G1.add_edges_from(edges1)
432
- G2 = nx.relabel_nodes(G1, mapped)
433
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
434
- nx.set_node_attributes(
435
- G2,
436
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
437
- "label",
438
- )
439
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
440
-
441
- def test_custom_graph4_same_labels(self):
442
- G1 = nx.Graph()
443
- edges1 = [
444
- (1, 2),
445
- (2, 3),
446
- (3, 8),
447
- (3, 4),
448
- (4, 5),
449
- (4, 6),
450
- (3, 6),
451
- (8, 7),
452
- (8, 9),
453
- (5, 9),
454
- (10, 11),
455
- (11, 12),
456
- (12, 13),
457
- (11, 13),
458
- ]
459
-
460
- mapped = {
461
- 1: "n",
462
- 2: "m",
463
- 3: "l",
464
- 4: "j",
465
- 5: "k",
466
- 6: "i",
467
- 7: "g",
468
- 8: "h",
469
- 9: "f",
470
- 10: "b",
471
- 11: "a",
472
- 12: "d",
473
- 13: "e",
474
- }
475
-
476
- G1.add_edges_from(edges1)
477
- G2 = nx.relabel_nodes(G1, mapped)
478
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
479
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
480
- assert vf2pp_isomorphism(G1, G2, node_label="label")
481
-
482
- all_self_mappings = list(vf2pp_all_isomorphisms(G1, G1))
483
- assert len(all_self_mappings) == 2
484
- # check that one of the mappings is not the identity for node 2
485
- assert sum(1 for m in all_self_mappings if m[12] == 12) == 1
486
-
487
- # Add nodes of different label
488
- G1.add_node(0)
489
- G2.add_node("z")
490
- G1.nodes[0]["label"] = "green"
491
- G2.nodes["z"]["label"] = "blue"
492
-
493
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
494
-
495
- # Make the labels identical
496
- G2.nodes["z"]["label"] = "green"
497
- assert vf2pp_isomorphism(G1, G2, node_label="label")
498
-
499
- # Change the structure of the graphs, keeping them isomorphic
500
- G1.add_edge(2, 5)
501
- G2.remove_edge("i", "l")
502
- G2.add_edge("g", "l")
503
- G2.add_edge("m", "f")
504
- assert vf2pp_isomorphism(G1, G2, node_label="label")
505
-
506
- # Change the structure of the disconnected sub-graph, keeping it isomorphic
507
- G1.remove_node(13)
508
- G2.remove_node("d")
509
- assert vf2pp_isomorphism(G1, G2, node_label="label")
510
-
511
- # Connect the newly added node to the disconnected graph, which now is just a path of size 3
512
- G1.add_edge(0, 10)
513
- G2.add_edge("e", "z")
514
- assert vf2pp_isomorphism(G1, G2, node_label="label")
515
-
516
- # Connect the two disconnected sub-graphs, forming a single graph
517
- G1.add_edge(11, 3)
518
- G1.add_edge(0, 8)
519
- G2.add_edge("a", "l")
520
- G2.add_edge("z", "j")
521
- assert vf2pp_isomorphism(G1, G2, node_label="label")
522
-
523
- def test_custom_graph5_same_labels(self):
524
- # Lots of symmetries
525
- # 1
526
- # /|\
527
- # / 2 \
528
- # | / \ |
529
- # 5-6 3-4
530
- # | \ / |
531
- # \ 7 /
532
- # \|/
533
- # 8
534
- G1 = nx.Graph()
535
- edges1 = [
536
- (1, 5),
537
- (1, 2),
538
- (1, 4),
539
- (2, 3),
540
- (2, 6),
541
- (3, 4),
542
- (3, 7),
543
- (4, 8),
544
- (5, 8),
545
- (5, 6),
546
- (6, 7),
547
- (7, 8),
548
- ]
549
- mapped = {1: "a", 2: "h", 3: "d", 4: "i", 5: "g", 6: "b", 7: "j", 8: "c"}
550
-
551
- G1.add_edges_from(edges1)
552
- G2 = nx.relabel_nodes(G1, mapped)
553
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
554
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
555
- assert vf2pp_isomorphism(G1, G2, node_label="label")
556
-
557
- all_self_mappings = list(vf2pp_all_isomorphisms(G1, G1))
558
- assert len(all_self_mappings) == 48
559
- # check that one of the mappings is not the identity for node 2
560
- assert all(sum(1 for m in all_self_mappings if m[u] == u) == 6 for u in G1)
561
-
562
- # Add different edges in each graph, maintaining symmetry
563
- G1.add_edges_from([(3, 6), (2, 7), (2, 5), (1, 3), (4, 7), (6, 8)])
564
- G2.add_edges_from(
565
- [
566
- (mapped[6], mapped[3]),
567
- (mapped[2], mapped[7]),
568
- (mapped[1], mapped[6]),
569
- (mapped[5], mapped[7]),
570
- (mapped[3], mapped[8]),
571
- (mapped[2], mapped[4]),
572
- ]
573
- )
574
- assert vf2pp_isomorphism(G1, G2, node_label="label")
575
-
576
- # Obtain two different but isomorphic subgraphs from G1 and G2
577
- H1 = nx.Graph(G1.subgraph([1, 5, 8, 6, 7, 3]))
578
- H2 = nx.Graph(
579
- G2.subgraph(
580
- [mapped[1], mapped[4], mapped[8], mapped[7], mapped[3], mapped[5]]
581
- )
582
- )
583
- assert vf2pp_isomorphism(H1, H2, node_label="label")
584
-
585
- # Delete corresponding node from the two graphs
586
- H1.remove_node(8)
587
- H2.remove_node(mapped[7])
588
- assert vf2pp_isomorphism(H1, H2, node_label="label")
589
-
590
- # Re-orient, maintaining isomorphism
591
- H1.add_edge(1, 6)
592
- H1.remove_edge(3, 6)
593
- assert vf2pp_isomorphism(H1, H2, node_label="label")
594
-
595
- def test_custom_graph5_different_labels(self):
596
- G1 = nx.Graph()
597
- edges1 = [
598
- (1, 5),
599
- (1, 2),
600
- (1, 4),
601
- (2, 3),
602
- (2, 6),
603
- (3, 4),
604
- (3, 7),
605
- (4, 8),
606
- (5, 8),
607
- (5, 6),
608
- (6, 7),
609
- (7, 8),
610
- ]
611
- mapped = {1: "a", 2: "h", 3: "d", 4: "i", 5: "g", 6: "b", 7: "j", 8: "c"}
612
-
613
- G1.add_edges_from(edges1)
614
- G2 = nx.relabel_nodes(G1, mapped)
615
-
616
- colors = ["red", "blue", "grey", "none", "brown", "solarized", "yellow", "pink"]
617
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
618
- nx.set_node_attributes(
619
- G2,
620
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
621
- "label",
622
- )
623
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
624
-
625
- # Assign different colors to matching nodes
626
- c = 0
627
- for node in G1.nodes():
628
- color1 = colors[c]
629
- color2 = colors[(c + 3) % len(colors)]
630
- G1.nodes[node]["label"] = color1
631
- G2.nodes[mapped[node]]["label"] = color2
632
- c += 1
633
-
634
- assert vf2pp_isomorphism(G1, G2, node_label="label") is None
635
-
636
- # Get symmetrical sub-graphs of G1,G2 and compare them
637
- H1 = G1.subgraph([1, 5])
638
- H2 = G2.subgraph(["i", "c"])
639
- c = 0
640
- for node1, node2 in zip(H1.nodes(), H2.nodes()):
641
- H1.nodes[node1]["label"] = "red"
642
- H2.nodes[node2]["label"] = "red"
643
- c += 1
644
-
645
- assert vf2pp_isomorphism(H1, H2, node_label="label")
646
-
647
- def test_disconnected_graph_all_same_labels(self):
648
- G1 = nx.Graph()
649
- G1.add_nodes_from(list(range(10)))
650
-
651
- mapped = {0: 9, 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0}
652
- G2 = nx.relabel_nodes(G1, mapped)
653
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
654
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
655
- assert vf2pp_isomorphism(G1, G2, node_label="label")
656
-
657
- def test_disconnected_graph_all_different_labels(self):
658
- G1 = nx.Graph()
659
- G1.add_nodes_from(list(range(10)))
660
-
661
- mapped = {0: 9, 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0}
662
- G2 = nx.relabel_nodes(G1, mapped)
663
-
664
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
665
- nx.set_node_attributes(
666
- G2,
667
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
668
- "label",
669
- )
670
- assert vf2pp_isomorphism(G1, G2, node_label="label") == mapped
671
-
672
- def test_disconnected_graph_some_same_labels(self):
673
- G1 = nx.Graph()
674
- G1.add_nodes_from(list(range(10)))
675
-
676
- mapped = {0: 9, 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0}
677
- G2 = nx.relabel_nodes(G1, mapped)
678
-
679
- colors = [
680
- "white",
681
- "white",
682
- "white",
683
- "purple",
684
- "purple",
685
- "red",
686
- "red",
687
- "pink",
688
- "pink",
689
- "pink",
690
- ]
691
-
692
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(colors))), "label")
693
- nx.set_node_attributes(
694
- G2, dict(zip([mapped[n] for n in G1], it.cycle(colors))), "label"
695
- )
696
-
697
- assert vf2pp_isomorphism(G1, G2, node_label="label")
698
-
699
-
700
- class TestMultiGraphISOVF2pp:
701
- def test_custom_multigraph1_same_labels(self):
702
- G1 = nx.MultiGraph()
703
-
704
- mapped = {1: "A", 2: "B", 3: "C", 4: "D", 5: "Z", 6: "E"}
705
- edges1 = [
706
- (1, 2),
707
- (1, 3),
708
- (1, 4),
709
- (1, 4),
710
- (1, 4),
711
- (2, 3),
712
- (2, 6),
713
- (2, 6),
714
- (3, 4),
715
- (3, 4),
716
- (5, 1),
717
- (5, 1),
718
- (5, 2),
719
- (5, 2),
720
- ]
721
-
722
- G1.add_edges_from(edges1)
723
- G2 = nx.relabel_nodes(G1, mapped)
724
-
725
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
726
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
727
- m = vf2pp_isomorphism(G1, G2, node_label="label")
728
- assert m
729
-
730
- # Transfer the 2-clique to the right side of G1
731
- G1.remove_edges_from([(2, 6), (2, 6)])
732
- G1.add_edges_from([(3, 6), (3, 6)])
733
- m = vf2pp_isomorphism(G1, G2, node_label="label")
734
- assert not m
735
-
736
- # Delete an edges, making them symmetrical, so the position of the 2-clique doesn't matter
737
- G2.remove_edge(mapped[1], mapped[4])
738
- G1.remove_edge(1, 4)
739
- m = vf2pp_isomorphism(G1, G2, node_label="label")
740
- assert m
741
-
742
- # Add self-loops
743
- G1.add_edges_from([(5, 5), (5, 5), (1, 1)])
744
- m = vf2pp_isomorphism(G1, G2, node_label="label")
745
- assert not m
746
-
747
- # Compensate in G2
748
- G2.add_edges_from(
749
- [(mapped[1], mapped[1]), (mapped[4], mapped[4]), (mapped[4], mapped[4])]
750
- )
751
- m = vf2pp_isomorphism(G1, G2, node_label="label")
752
- assert m
753
-
754
- def test_custom_multigraph1_different_labels(self):
755
- G1 = nx.MultiGraph()
756
-
757
- mapped = {1: "A", 2: "B", 3: "C", 4: "D", 5: "Z", 6: "E"}
758
- edges1 = [
759
- (1, 2),
760
- (1, 3),
761
- (1, 4),
762
- (1, 4),
763
- (1, 4),
764
- (2, 3),
765
- (2, 6),
766
- (2, 6),
767
- (3, 4),
768
- (3, 4),
769
- (5, 1),
770
- (5, 1),
771
- (5, 2),
772
- (5, 2),
773
- ]
774
-
775
- G1.add_edges_from(edges1)
776
- G2 = nx.relabel_nodes(G1, mapped)
777
-
778
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
779
- nx.set_node_attributes(
780
- G2,
781
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
782
- "label",
783
- )
784
- m = vf2pp_isomorphism(G1, G2, node_label="label")
785
- assert m
786
- assert m == mapped
787
-
788
- # Re-structure G1, maintaining the degree sequence
789
- G1.remove_edge(1, 4)
790
- G1.add_edge(1, 5)
791
- m = vf2pp_isomorphism(G1, G2, node_label="label")
792
- assert not m
793
-
794
- # Restructure G2, making it isomorphic to G1
795
- G2.remove_edge("A", "D")
796
- G2.add_edge("A", "Z")
797
- m = vf2pp_isomorphism(G1, G2, node_label="label")
798
- assert m
799
- assert m == mapped
800
-
801
- # Add edge from node to itself
802
- G1.add_edges_from([(6, 6), (6, 6), (6, 6)])
803
- m = vf2pp_isomorphism(G1, G2, node_label="label")
804
- assert not m
805
-
806
- # Same for G2
807
- G2.add_edges_from([("E", "E"), ("E", "E"), ("E", "E")])
808
- m = vf2pp_isomorphism(G1, G2, node_label="label")
809
- assert m
810
- assert m == mapped
811
-
812
- def test_custom_multigraph2_same_labels(self):
813
- G1 = nx.MultiGraph()
814
-
815
- mapped = {1: "A", 2: "C", 3: "D", 4: "E", 5: "G", 7: "B", 6: "F"}
816
- edges1 = [
817
- (1, 2),
818
- (1, 2),
819
- (1, 5),
820
- (1, 5),
821
- (1, 5),
822
- (5, 6),
823
- (2, 3),
824
- (2, 3),
825
- (2, 4),
826
- (3, 4),
827
- (3, 4),
828
- (4, 5),
829
- (4, 5),
830
- (4, 5),
831
- (2, 7),
832
- (2, 7),
833
- (2, 7),
834
- ]
835
-
836
- G1.add_edges_from(edges1)
837
- G2 = nx.relabel_nodes(G1, mapped)
838
-
839
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
840
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
841
- m = vf2pp_isomorphism(G1, G2, node_label="label")
842
- assert m
843
-
844
- # Obtain two non-isomorphic subgraphs from the graph
845
- G2.add_edge(mapped[1], mapped[4])
846
- H1 = nx.MultiGraph(G1.subgraph([2, 3, 4, 7]))
847
- H2 = nx.MultiGraph(G2.subgraph([mapped[1], mapped[4], mapped[5], mapped[6]]))
848
-
849
- m = vf2pp_isomorphism(H1, H2, node_label="label")
850
- assert not m
851
-
852
- # Make them isomorphic
853
- H1.remove_edge(3, 4)
854
- H1.add_edges_from([(2, 3), (2, 4), (2, 4)])
855
- H2.add_edges_from([(mapped[5], mapped[6]), (mapped[5], mapped[6])])
856
- m = vf2pp_isomorphism(H1, H2, node_label="label")
857
- assert m
858
-
859
- # Remove triangle edge
860
- H1.remove_edges_from([(2, 3), (2, 3), (2, 3)])
861
- H2.remove_edges_from([(mapped[5], mapped[4])] * 3)
862
- m = vf2pp_isomorphism(H1, H2, node_label="label")
863
- assert m
864
-
865
- # Change the edge orientation such that H1 is rotated H2
866
- H1.remove_edges_from([(2, 7), (2, 7)])
867
- H1.add_edges_from([(3, 4), (3, 4)])
868
- m = vf2pp_isomorphism(H1, H2, node_label="label")
869
- assert m
870
-
871
- # Add extra edges maintaining degree sequence, but in a non-symmetrical manner
872
- H2.add_edge(mapped[5], mapped[1])
873
- H1.add_edge(3, 4)
874
- m = vf2pp_isomorphism(H1, H2, node_label="label")
875
- assert not m
876
-
877
- def test_custom_multigraph2_different_labels(self):
878
- G1 = nx.MultiGraph()
879
-
880
- mapped = {1: "A", 2: "C", 3: "D", 4: "E", 5: "G", 7: "B", 6: "F"}
881
- edges1 = [
882
- (1, 2),
883
- (1, 2),
884
- (1, 5),
885
- (1, 5),
886
- (1, 5),
887
- (5, 6),
888
- (2, 3),
889
- (2, 3),
890
- (2, 4),
891
- (3, 4),
892
- (3, 4),
893
- (4, 5),
894
- (4, 5),
895
- (4, 5),
896
- (2, 7),
897
- (2, 7),
898
- (2, 7),
899
- ]
900
-
901
- G1.add_edges_from(edges1)
902
- G2 = nx.relabel_nodes(G1, mapped)
903
-
904
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
905
- nx.set_node_attributes(
906
- G2,
907
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
908
- "label",
909
- )
910
- m = vf2pp_isomorphism(G1, G2, node_label="label")
911
- assert m
912
- assert m == mapped
913
-
914
- # Re-structure G1
915
- G1.remove_edge(2, 7)
916
- G1.add_edge(5, 6)
917
-
918
- m = vf2pp_isomorphism(G1, G2, node_label="label")
919
- assert not m
920
-
921
- # Same for G2
922
- G2.remove_edge("B", "C")
923
- G2.add_edge("G", "F")
924
-
925
- m = vf2pp_isomorphism(G1, G2, node_label="label")
926
- assert m
927
- assert m == mapped
928
-
929
- # Delete node from G1 and G2, keeping them isomorphic
930
- G1.remove_node(3)
931
- G2.remove_node("D")
932
- m = vf2pp_isomorphism(G1, G2, node_label="label")
933
- assert m
934
-
935
- # Change G1 edges
936
- G1.remove_edge(1, 2)
937
- G1.remove_edge(2, 7)
938
- m = vf2pp_isomorphism(G1, G2, node_label="label")
939
- assert not m
940
-
941
- # Make G2 identical to G1, but with different edge orientation and different labels
942
- G2.add_edges_from([("A", "C"), ("C", "E"), ("C", "E")])
943
- G2.remove_edges_from(
944
- [("A", "G"), ("A", "G"), ("F", "G"), ("E", "G"), ("E", "G")]
945
- )
946
-
947
- m = vf2pp_isomorphism(G1, G2, node_label="label")
948
- assert not m
949
-
950
- # Make all labels the same, so G1 and G2 are also isomorphic
951
- for n1, n2 in zip(G1.nodes(), G2.nodes()):
952
- G1.nodes[n1]["label"] = "blue"
953
- G2.nodes[n2]["label"] = "blue"
954
-
955
- m = vf2pp_isomorphism(G1, G2, node_label="label")
956
- assert m
957
-
958
- def test_custom_multigraph3_same_labels(self):
959
- G1 = nx.MultiGraph()
960
-
961
- mapped = {1: 9, 2: 8, 3: 7, 4: 6, 5: 3, 8: 5, 9: 4, 7: 1, 6: 2}
962
- edges1 = [
963
- (1, 2),
964
- (1, 3),
965
- (1, 3),
966
- (2, 3),
967
- (2, 3),
968
- (3, 4),
969
- (4, 5),
970
- (4, 7),
971
- (4, 9),
972
- (4, 9),
973
- (4, 9),
974
- (5, 8),
975
- (5, 8),
976
- (8, 9),
977
- (8, 9),
978
- (5, 6),
979
- (6, 7),
980
- (6, 7),
981
- (6, 7),
982
- (5, 2),
983
- ]
984
- G1.add_edges_from(edges1)
985
- G2 = nx.relabel_nodes(G1, mapped)
986
-
987
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
988
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
989
- m = vf2pp_isomorphism(G1, G2, node_label="label")
990
- assert m
991
-
992
- # Connect nodes maintaining symmetry
993
- G1.add_edges_from([(6, 9), (7, 8), (5, 8), (4, 9), (4, 9)])
994
- G2.add_edges_from(
995
- [
996
- (mapped[6], mapped[8]),
997
- (mapped[7], mapped[9]),
998
- (mapped[5], mapped[8]),
999
- (mapped[4], mapped[9]),
1000
- (mapped[4], mapped[9]),
1001
- ]
1002
- )
1003
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1004
- assert not m
1005
-
1006
- # Make isomorphic
1007
- G1.add_edges_from([(6, 8), (6, 8), (7, 9), (7, 9), (7, 9)])
1008
- G2.add_edges_from(
1009
- [
1010
- (mapped[6], mapped[8]),
1011
- (mapped[6], mapped[9]),
1012
- (mapped[7], mapped[8]),
1013
- (mapped[7], mapped[9]),
1014
- (mapped[7], mapped[9]),
1015
- ]
1016
- )
1017
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1018
- assert m
1019
-
1020
- # Connect more nodes
1021
- G1.add_edges_from([(2, 7), (2, 7), (3, 6), (3, 6)])
1022
- G2.add_edges_from(
1023
- [
1024
- (mapped[2], mapped[7]),
1025
- (mapped[2], mapped[7]),
1026
- (mapped[3], mapped[6]),
1027
- (mapped[3], mapped[6]),
1028
- ]
1029
- )
1030
- G1.add_node(10)
1031
- G2.add_node("Z")
1032
- G1.nodes[10]["label"] = "blue"
1033
- G2.nodes["Z"]["label"] = "blue"
1034
-
1035
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1036
- assert m
1037
-
1038
- # Connect the newly added node, to opposite sides of the graph
1039
- G1.add_edges_from([(10, 1), (10, 5), (10, 8), (10, 10), (10, 10)])
1040
- G2.add_edges_from(
1041
- [
1042
- ("Z", mapped[1]),
1043
- ("Z", mapped[4]),
1044
- ("Z", mapped[9]),
1045
- ("Z", "Z"),
1046
- ("Z", "Z"),
1047
- ]
1048
- )
1049
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1050
- assert not m
1051
-
1052
- # We connected the new node to opposite sides, so G1 must be symmetrical
1053
- # to G2. Re-structure them to be so
1054
- G1.remove_edges_from([(1, 3), (4, 9), (4, 9), (7, 9)])
1055
- G2.remove_edges_from(
1056
- [
1057
- (mapped[1], mapped[3]),
1058
- (mapped[4], mapped[9]),
1059
- (mapped[4], mapped[9]),
1060
- (mapped[7], mapped[9]),
1061
- ]
1062
- )
1063
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1064
- assert m
1065
-
1066
- # Get two subgraphs that are not isomorphic but are easy to make
1067
- H1 = nx.Graph(G1.subgraph([2, 3, 4, 5, 6, 7, 10]))
1068
- H2 = nx.Graph(
1069
- G2.subgraph(
1070
- [mapped[4], mapped[5], mapped[6], mapped[7], mapped[8], mapped[9], "Z"]
1071
- )
1072
- )
1073
-
1074
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1075
- assert not m
1076
-
1077
- # Restructure both to make them isomorphic
1078
- H1.add_edges_from([(10, 2), (10, 6), (3, 6), (2, 7), (2, 6), (3, 7)])
1079
- H2.add_edges_from(
1080
- [("Z", mapped[7]), (mapped[6], mapped[9]), (mapped[7], mapped[8])]
1081
- )
1082
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1083
- assert m
1084
-
1085
- # Remove one self-loop in H2
1086
- H2.remove_edge("Z", "Z")
1087
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1088
- assert not m
1089
-
1090
- # Compensate in H1
1091
- H1.remove_edge(10, 10)
1092
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1093
- assert m
1094
-
1095
- def test_custom_multigraph3_different_labels(self):
1096
- G1 = nx.MultiGraph()
1097
-
1098
- mapped = {1: 9, 2: 8, 3: 7, 4: 6, 5: 3, 8: 5, 9: 4, 7: 1, 6: 2}
1099
- edges1 = [
1100
- (1, 2),
1101
- (1, 3),
1102
- (1, 3),
1103
- (2, 3),
1104
- (2, 3),
1105
- (3, 4),
1106
- (4, 5),
1107
- (4, 7),
1108
- (4, 9),
1109
- (4, 9),
1110
- (4, 9),
1111
- (5, 8),
1112
- (5, 8),
1113
- (8, 9),
1114
- (8, 9),
1115
- (5, 6),
1116
- (6, 7),
1117
- (6, 7),
1118
- (6, 7),
1119
- (5, 2),
1120
- ]
1121
-
1122
- G1.add_edges_from(edges1)
1123
- G2 = nx.relabel_nodes(G1, mapped)
1124
-
1125
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
1126
- nx.set_node_attributes(
1127
- G2,
1128
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
1129
- "label",
1130
- )
1131
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1132
- assert m
1133
- assert m == mapped
1134
-
1135
- # Delete edge maintaining isomorphism
1136
- G1.remove_edge(4, 9)
1137
- G2.remove_edge(4, 6)
1138
-
1139
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1140
- assert m
1141
- assert m == mapped
1142
-
1143
- # Change edge orientation such that G1 mirrors G2
1144
- G1.add_edges_from([(4, 9), (1, 2), (1, 2)])
1145
- G1.remove_edges_from([(1, 3), (1, 3)])
1146
- G2.add_edges_from([(3, 5), (7, 9)])
1147
- G2.remove_edge(8, 9)
1148
-
1149
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1150
- assert not m
1151
-
1152
- # Make all labels the same, so G1 and G2 are also isomorphic
1153
- for n1, n2 in zip(G1.nodes(), G2.nodes()):
1154
- G1.nodes[n1]["label"] = "blue"
1155
- G2.nodes[n2]["label"] = "blue"
1156
-
1157
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1158
- assert m
1159
-
1160
- G1.add_node(10)
1161
- G2.add_node("Z")
1162
- G1.nodes[10]["label"] = "green"
1163
- G2.nodes["Z"]["label"] = "green"
1164
-
1165
- # Add different number of edges between the new nodes and themselves
1166
- G1.add_edges_from([(10, 10), (10, 10)])
1167
- G2.add_edges_from([("Z", "Z")])
1168
-
1169
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1170
- assert not m
1171
-
1172
- # Make the number of self-edges equal
1173
- G1.remove_edge(10, 10)
1174
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1175
- assert m
1176
-
1177
- # Connect the new node to the graph
1178
- G1.add_edges_from([(10, 3), (10, 4)])
1179
- G2.add_edges_from([("Z", 8), ("Z", 3)])
1180
-
1181
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1182
- assert m
1183
-
1184
- # Remove central node
1185
- G1.remove_node(4)
1186
- G2.remove_node(3)
1187
- G1.add_edges_from([(5, 6), (5, 6), (5, 7)])
1188
- G2.add_edges_from([(1, 6), (1, 6), (6, 2)])
1189
-
1190
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1191
- assert m
1192
-
1193
- def test_custom_multigraph4_same_labels(self):
1194
- G1 = nx.MultiGraph()
1195
- edges1 = [
1196
- (1, 2),
1197
- (1, 2),
1198
- (2, 2),
1199
- (2, 3),
1200
- (3, 8),
1201
- (3, 8),
1202
- (3, 4),
1203
- (4, 5),
1204
- (4, 5),
1205
- (4, 5),
1206
- (4, 6),
1207
- (3, 6),
1208
- (3, 6),
1209
- (6, 6),
1210
- (8, 7),
1211
- (7, 7),
1212
- (8, 9),
1213
- (9, 9),
1214
- (8, 9),
1215
- (8, 9),
1216
- (5, 9),
1217
- (10, 11),
1218
- (11, 12),
1219
- (12, 13),
1220
- (11, 13),
1221
- (10, 10),
1222
- (10, 11),
1223
- (11, 13),
1224
- ]
1225
-
1226
- mapped = {
1227
- 1: "n",
1228
- 2: "m",
1229
- 3: "l",
1230
- 4: "j",
1231
- 5: "k",
1232
- 6: "i",
1233
- 7: "g",
1234
- 8: "h",
1235
- 9: "f",
1236
- 10: "b",
1237
- 11: "a",
1238
- 12: "d",
1239
- 13: "e",
1240
- }
1241
-
1242
- G1.add_edges_from(edges1)
1243
- G2 = nx.relabel_nodes(G1, mapped)
1244
-
1245
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
1246
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
1247
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1248
- assert m
1249
-
1250
- # Add extra but corresponding edges to both graphs
1251
- G1.add_edges_from([(2, 2), (2, 3), (2, 8), (3, 4)])
1252
- G2.add_edges_from([("m", "m"), ("m", "l"), ("m", "h"), ("l", "j")])
1253
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1254
- assert m
1255
-
1256
- # Obtain subgraphs
1257
- H1 = nx.MultiGraph(G1.subgraph([2, 3, 4, 6, 10, 11, 12, 13]))
1258
- H2 = nx.MultiGraph(
1259
- G2.subgraph(
1260
- [
1261
- mapped[2],
1262
- mapped[3],
1263
- mapped[8],
1264
- mapped[9],
1265
- mapped[10],
1266
- mapped[11],
1267
- mapped[12],
1268
- mapped[13],
1269
- ]
1270
- )
1271
- )
1272
-
1273
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1274
- assert not m
1275
-
1276
- # Make them isomorphic
1277
- H2.remove_edges_from(
1278
- [(mapped[3], mapped[2]), (mapped[9], mapped[8]), (mapped[2], mapped[2])]
1279
- )
1280
- H2.add_edges_from([(mapped[9], mapped[9]), (mapped[2], mapped[8])])
1281
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1282
- assert m
1283
-
1284
- # Re-structure the disconnected sub-graph
1285
- H1.remove_node(12)
1286
- H2.remove_node(mapped[12])
1287
- H1.add_edge(13, 13)
1288
- H2.add_edge(mapped[13], mapped[13])
1289
-
1290
- # Connect the two disconnected components, forming a single graph
1291
- H1.add_edges_from([(3, 13), (6, 11)])
1292
- H2.add_edges_from([(mapped[8], mapped[10]), (mapped[2], mapped[11])])
1293
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1294
- assert m
1295
-
1296
- # Change orientation of self-loops in one graph, maintaining the degree sequence
1297
- H1.remove_edges_from([(2, 2), (3, 6)])
1298
- H1.add_edges_from([(6, 6), (2, 3)])
1299
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1300
- assert not m
1301
-
1302
- def test_custom_multigraph4_different_labels(self):
1303
- G1 = nx.MultiGraph()
1304
- edges1 = [
1305
- (1, 2),
1306
- (1, 2),
1307
- (2, 2),
1308
- (2, 3),
1309
- (3, 8),
1310
- (3, 8),
1311
- (3, 4),
1312
- (4, 5),
1313
- (4, 5),
1314
- (4, 5),
1315
- (4, 6),
1316
- (3, 6),
1317
- (3, 6),
1318
- (6, 6),
1319
- (8, 7),
1320
- (7, 7),
1321
- (8, 9),
1322
- (9, 9),
1323
- (8, 9),
1324
- (8, 9),
1325
- (5, 9),
1326
- (10, 11),
1327
- (11, 12),
1328
- (12, 13),
1329
- (11, 13),
1330
- ]
1331
-
1332
- mapped = {
1333
- 1: "n",
1334
- 2: "m",
1335
- 3: "l",
1336
- 4: "j",
1337
- 5: "k",
1338
- 6: "i",
1339
- 7: "g",
1340
- 8: "h",
1341
- 9: "f",
1342
- 10: "b",
1343
- 11: "a",
1344
- 12: "d",
1345
- 13: "e",
1346
- }
1347
-
1348
- G1.add_edges_from(edges1)
1349
- G2 = nx.relabel_nodes(G1, mapped)
1350
-
1351
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
1352
- nx.set_node_attributes(
1353
- G2,
1354
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
1355
- "label",
1356
- )
1357
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1358
- assert m == mapped
1359
-
1360
- # Add extra but corresponding edges to both graphs
1361
- G1.add_edges_from([(2, 2), (2, 3), (2, 8), (3, 4)])
1362
- G2.add_edges_from([("m", "m"), ("m", "l"), ("m", "h"), ("l", "j")])
1363
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1364
- assert m == mapped
1365
-
1366
- # Obtain isomorphic subgraphs
1367
- H1 = nx.MultiGraph(G1.subgraph([2, 3, 4, 6]))
1368
- H2 = nx.MultiGraph(G2.subgraph(["m", "l", "j", "i"]))
1369
-
1370
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1371
- assert m
1372
-
1373
- # Delete the 3-clique, keeping only the path-graph. Also, H1 mirrors H2
1374
- H1.remove_node(4)
1375
- H2.remove_node("j")
1376
- H1.remove_edges_from([(2, 2), (2, 3), (6, 6)])
1377
- H2.remove_edges_from([("l", "i"), ("m", "m"), ("m", "m")])
1378
-
1379
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1380
- assert not m
1381
-
1382
- # Assign the same labels so that mirroring means isomorphic
1383
- for n1, n2 in zip(H1.nodes(), H2.nodes()):
1384
- H1.nodes[n1]["label"] = "red"
1385
- H2.nodes[n2]["label"] = "red"
1386
-
1387
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1388
- assert m
1389
-
1390
- # Leave only one node with self-loop
1391
- H1.remove_nodes_from([3, 6])
1392
- H2.remove_nodes_from(["m", "l"])
1393
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1394
- assert m
1395
-
1396
- # Remove one self-loop from H1
1397
- H1.remove_edge(2, 2)
1398
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1399
- assert not m
1400
-
1401
- # Same for H2
1402
- H2.remove_edge("i", "i")
1403
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1404
- assert m
1405
-
1406
- # Compose H1 with the disconnected sub-graph of G1. Same for H2
1407
- S1 = nx.compose(H1, nx.MultiGraph(G1.subgraph([10, 11, 12, 13])))
1408
- S2 = nx.compose(H2, nx.MultiGraph(G2.subgraph(["a", "b", "d", "e"])))
1409
-
1410
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1411
- assert m
1412
-
1413
- # Connect the two components
1414
- S1.add_edges_from([(13, 13), (13, 13), (2, 13)])
1415
- S2.add_edges_from([("a", "a"), ("a", "a"), ("i", "e")])
1416
- m = vf2pp_isomorphism(H1, H2, node_label="label")
1417
- assert m
1418
-
1419
- def test_custom_multigraph5_same_labels(self):
1420
- # Lots of symmetries
1421
- # 1
1422
- # /|\
1423
- # / 2 \
1424
- # | / \ |
1425
- # 5-6 3-4
1426
- # | \ / |
1427
- # \ 7 /
1428
- # \|/
1429
- # 8
1430
- G1 = nx.MultiGraph()
1431
- edges1 = [
1432
- (1, 5),
1433
- (1, 2),
1434
- (1, 4),
1435
- (2, 3),
1436
- (2, 6),
1437
- (3, 4),
1438
- (3, 7),
1439
- (4, 8),
1440
- (5, 8),
1441
- (5, 6),
1442
- (6, 7),
1443
- (7, 8),
1444
- ]
1445
- mapped = {1: "a", 2: "h", 3: "d", 4: "i", 5: "g", 6: "b", 7: "j", 8: "c"}
1446
-
1447
- G1.add_edges_from(edges1)
1448
- G2 = nx.relabel_nodes(G1, mapped)
1449
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
1450
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
1451
-
1452
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1453
- assert m
1454
-
1455
- # Add multiple edges and self-loops, maintaining isomorphism
1456
- G1.add_edges_from(
1457
- [(1, 2), (1, 2), (3, 7), (8, 8), (8, 8), (7, 8), (2, 3), (5, 6)]
1458
- )
1459
- G2.add_edges_from(
1460
- [
1461
- ("a", "h"),
1462
- ("a", "h"),
1463
- ("d", "j"),
1464
- ("c", "c"),
1465
- ("c", "c"),
1466
- ("j", "c"),
1467
- ("d", "h"),
1468
- ("g", "b"),
1469
- ]
1470
- )
1471
-
1472
- all_self_mappings = list(vf2pp_all_isomorphisms(G1, G1))
1473
- assert len(all_self_mappings) == 1
1474
-
1475
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1476
- assert m
1477
-
1478
- # Make G2 to be the rotated G1
1479
- G2.remove_edges_from(
1480
- [
1481
- ("a", "h"),
1482
- ("a", "h"),
1483
- ("d", "j"),
1484
- ("c", "c"),
1485
- ("c", "c"),
1486
- ("j", "c"),
1487
- ("d", "h"),
1488
- ("g", "b"),
1489
- ]
1490
- )
1491
- G2.add_edges_from(
1492
- [
1493
- ("d", "i"),
1494
- ("a", "h"),
1495
- ("g", "b"),
1496
- ("g", "b"),
1497
- ("i", "i"),
1498
- ("i", "i"),
1499
- ("b", "j"),
1500
- ("d", "j"),
1501
- ]
1502
- )
1503
-
1504
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1505
- assert m
1506
-
1507
- def test_disconnected_multigraph_all_same_labels(self):
1508
- G1 = nx.MultiGraph()
1509
- G1.add_nodes_from(list(range(10)))
1510
- G1.add_edges_from([(i, i) for i in range(10)])
1511
-
1512
- mapped = {0: 9, 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0}
1513
- G2 = nx.relabel_nodes(G1, mapped)
1514
-
1515
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_same))), "label")
1516
- nx.set_node_attributes(G2, dict(zip(G2, it.cycle(labels_same))), "label")
1517
-
1518
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1519
- assert m
1520
-
1521
- # Add self-loops to non-mapped nodes. Should be the same, as the graph is disconnected.
1522
- G1.add_edges_from([(i, i) for i in range(5, 8)] * 3)
1523
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1524
- assert not m
1525
-
1526
- # Compensate in G2
1527
- G2.add_edges_from([(i, i) for i in range(3)] * 3)
1528
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1529
- assert m
1530
-
1531
- # Add one more self-loop in G2
1532
- G2.add_edges_from([(0, 0), (1, 1), (1, 1)])
1533
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1534
- assert not m
1535
-
1536
- # Compensate in G1
1537
- G1.add_edges_from([(5, 5), (7, 7), (7, 7)])
1538
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1539
- assert m
1540
-
1541
- def test_disconnected_multigraph_all_different_labels(self):
1542
- G1 = nx.MultiGraph()
1543
- G1.add_nodes_from(list(range(10)))
1544
- G1.add_edges_from([(i, i) for i in range(10)])
1545
-
1546
- mapped = {0: 9, 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0}
1547
- G2 = nx.relabel_nodes(G1, mapped)
1548
-
1549
- nx.set_node_attributes(G1, dict(zip(G1, it.cycle(labels_many))), "label")
1550
- nx.set_node_attributes(
1551
- G2,
1552
- dict(zip([mapped[n] for n in G1], it.cycle(labels_many))),
1553
- "label",
1554
- )
1555
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1556
- assert m
1557
- assert m == mapped
1558
-
1559
- # Add self-loops to non-mapped nodes. Now it is not the same, as there are different labels
1560
- G1.add_edges_from([(i, i) for i in range(5, 8)] * 3)
1561
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1562
- assert not m
1563
-
1564
- # Add self-loops to non mapped nodes in G2 as well
1565
- G2.add_edges_from([(mapped[i], mapped[i]) for i in range(3)] * 7)
1566
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1567
- assert not m
1568
-
1569
- # Add self-loops to mapped nodes in G2
1570
- G2.add_edges_from([(mapped[i], mapped[i]) for i in range(5, 8)] * 3)
1571
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1572
- assert not m
1573
-
1574
- # Add self-loops to G1 so that they are even in both graphs
1575
- G1.add_edges_from([(i, i) for i in range(3)] * 7)
1576
- m = vf2pp_isomorphism(G1, G2, node_label="label")
1577
- assert m
1578
-
1579
-
1580
- class TestDiGraphISOVF2pp:
1581
- def test_wikipedia_graph(self):
1582
- edges1 = [
1583
- (1, 5),
1584
- (1, 2),
1585
- (1, 4),
1586
- (3, 2),
1587
- (6, 2),
1588
- (3, 4),
1589
- (7, 3),
1590
- (4, 8),
1591
- (5, 8),
1592
- (6, 5),
1593
- (6, 7),
1594
- (7, 8),
1595
- ]
1596
- mapped = {1: "a", 2: "h", 3: "d", 4: "i", 5: "g", 6: "b", 7: "j", 8: "c"}
1597
-
1598
- G1 = nx.DiGraph(edges1)
1599
- G2 = nx.relabel_nodes(G1, mapped)
1600
-
1601
- assert vf2pp_isomorphism(G1, G2) == mapped
1602
-
1603
- # Change the direction of an edge
1604
- G1.remove_edge(1, 5)
1605
- G1.add_edge(5, 1)
1606
- assert vf2pp_isomorphism(G1, G2) is None
1607
-
1608
- def test_non_isomorphic_same_degree_sequence(self):
1609
- r"""
1610
- G1 G2
1611
- 1-------------2 1-------------2
1612
- | \ | | \ |
1613
- | 5-------6 | | 5-------6 |
1614
- | | | | | | | |
1615
- | 8-------7 | | 8-------7 |
1616
- | / | | \ |
1617
- 4-------------3 4-------------3
1618
- """
1619
- edges1 = [
1620
- (1, 5),
1621
- (1, 2),
1622
- (4, 1),
1623
- (3, 2),
1624
- (3, 4),
1625
- (5, 8),
1626
- (6, 5),
1627
- (6, 7),
1628
- (7, 8),
1629
- (4, 8),
1630
- ]
1631
- edges2 = [
1632
- (1, 5),
1633
- (1, 2),
1634
- (4, 1),
1635
- (3, 2),
1636
- (3, 4),
1637
- (5, 8),
1638
- (6, 5),
1639
- (6, 7),
1640
- (7, 8),
1641
- (3, 7),
1642
- ]
1643
-
1644
- G1 = nx.DiGraph(edges1)
1645
- G2 = nx.DiGraph(edges2)
1646
- assert vf2pp_isomorphism(G1, G2) is None
1647
-
1648
-
1649
- def test_isomorphvf2pp_multidigraphs():
1650
- g = nx.MultiDiGraph({0: [1, 1, 2, 2, 3], 1: [2, 3, 3], 2: [3]})
1651
- h = nx.MultiDiGraph({0: [1, 1, 2, 2, 3], 1: [2, 3, 3], 2: [3]})
1652
- assert nx.vf2pp_is_isomorphic(g, h)
1653
-
1654
- h = nx.MultiDiGraph({0: [1, 1, 2, 2, 3], 1: [2, 3, 3], 3: [2]})
1655
- assert not nx.vf2pp_is_isomorphic(g, h)