okstra 0.113.0 → 0.115.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 (348) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/docs/for-ai/README.md +2 -2
  4. package/docs/for-ai/skills/{okstra-brief.md → okstra-brief-gen.md} +3 -3
  5. package/docs/for-ai/skills/okstra-inspect.md +1 -1
  6. package/docs/kr/architecture.md +11 -11
  7. package/docs/kr/cli.md +1 -1
  8. package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +51 -0
  9. package/docs/project-structure-overview.md +2 -2
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/workers/report-writer-worker.md +1 -0
  13. package/runtime/prompts/lead/context-loader.md +2 -2
  14. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  15. package/runtime/prompts/profiles/_common-contract.md +3 -3
  16. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  17. package/runtime/prompts/profiles/requirements-discovery.md +2 -2
  18. package/runtime/python/okstra_ctl/fix_cycles.py +1 -1
  19. package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
  20. package/runtime/python/okstra_ctl/report_views.py +151 -12
  21. package/runtime/python/okstra_ctl/run.py +24 -24
  22. package/runtime/python/okstra_ctl/wizard.py +3 -3
  23. package/runtime/python/okstra_ctl/worktree.py +1 -1
  24. package/runtime/python/okstra_token_usage/claude.py +58 -19
  25. package/runtime/python/okstra_token_usage/collect.py +3 -1
  26. package/runtime/python/okstra_vendor/__init__.py +3 -0
  27. package/runtime/schemas/final-report-v1.0.schema.json +20 -0
  28. package/runtime/skills/{okstra-brief → okstra-brief-gen}/SKILL.md +6 -6
  29. package/runtime/skills/{okstra-brief → okstra-brief-gen}/references/reporter-confirmations.md +1 -1
  30. package/runtime/skills/{okstra-brief → okstra-brief-gen}/references/tracker-recursion.md +1 -1
  31. package/runtime/skills/okstra-inspect/SKILL.md +2 -2
  32. package/runtime/templates/reports/brief.template.md +2 -2
  33. package/runtime/templates/reports/final-report.template.md +14 -0
  34. package/runtime/templates/reports/i18n/en.json +23 -12
  35. package/runtime/templates/reports/i18n/ko.json +23 -12
  36. package/runtime/templates/reports/report.css +55 -0
  37. package/runtime/templates/reports/report.js +53 -1
  38. package/runtime/validators/validate-brief.py +5 -5
  39. package/runtime/validators/validate-brief.sh +1 -1
  40. package/src/commands/lifecycle/uninstall.mjs +2 -1
  41. package/src/lib/skill-catalog.mjs +2 -1
  42. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  43. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
  44. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
  45. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
  46. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
  47. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
  48. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
  49. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
  50. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
  51. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
  52. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
  53. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
  54. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
  55. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
  56. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
  57. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  58. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
  59. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
  60. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
  61. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
  62. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
  63. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
  64. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  65. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
  66. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
  67. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
  68. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
  69. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
  70. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
  71. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
  72. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
  73. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
  74. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
  75. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
  76. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
  77. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
  78. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  79. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
  80. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
  81. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
  82. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
  83. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
  84. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
  85. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
  86. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
  87. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
  88. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
  89. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
  90. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
  91. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
  92. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
  93. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
  94. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
  95. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
  96. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
  97. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
  98. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
  99. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  100. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
  101. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  102. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
  103. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
  104. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
  105. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
  106. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
  107. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
  108. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
  109. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
  110. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
  111. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
  112. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
  113. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
  114. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
  115. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  116. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
  117. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
  118. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
  119. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
  120. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
  121. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
  122. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
  124. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
  125. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
  126. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
  127. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
  128. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
  129. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
  130. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
  131. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
  136. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
  137. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
  138. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
  139. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
  140. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  141. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  142. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  143. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  144. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  146. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
  147. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
  148. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
  149. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
  150. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
  151. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
  152. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
  153. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
  154. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
  155. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  156. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
  157. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
  158. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
  159. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  160. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
  161. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
  162. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
  163. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
  164. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  165. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
  166. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
  167. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
  168. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
  169. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
  170. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
  171. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  172. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
  173. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
  174. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
  175. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
  176. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
  177. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
  178. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
  179. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
  180. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
  181. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
  182. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
  183. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
  184. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
  185. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
  186. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
  187. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
  188. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
  189. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
  190. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
  191. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
  192. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
  193. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
  194. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
  195. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
  196. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
  197. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
  198. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
  199. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
  200. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
  201. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
  202. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
  203. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
  204. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
  205. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
  206. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
  207. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
  208. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
  209. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
  210. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
  211. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
  212. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
  213. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
  214. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
  215. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
  216. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
  217. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
  218. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
  219. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
  220. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
  221. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
  222. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
  223. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
  224. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
  225. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
  226. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
  227. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
  228. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
  229. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  230. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
  231. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
  232. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
  233. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
  234. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
  235. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  236. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
  237. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
  238. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
  239. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
  240. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
  241. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
  242. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
  243. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  244. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
  245. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
  246. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
  247. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
  248. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
  249. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
  250. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
  251. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
  252. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
  253. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
  254. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
  255. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
  256. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
  257. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
  258. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
  259. package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
  260. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  261. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  262. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  263. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  264. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  265. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  266. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  267. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
  268. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
  269. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
  270. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
  271. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
  272. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
  273. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  274. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
  275. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
  276. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
  277. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
  278. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
  279. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
  280. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
  281. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
  282. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
  283. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
  284. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
  285. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
  286. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
  287. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
  288. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
  289. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
  290. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
  291. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
  292. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
  293. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
  294. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
  295. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
  296. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
  297. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
  298. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
  299. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
  300. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
  301. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
  302. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  303. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
  304. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
  305. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
  306. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
  307. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
  308. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
  309. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
  310. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  311. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
  312. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
  313. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
  314. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
  315. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  316. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
  317. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
  318. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
  319. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
  320. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
  321. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
  322. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
  323. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
  324. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
  325. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
  326. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
  327. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  328. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
  329. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
  330. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
  331. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
  332. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
  333. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
  334. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
  335. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
  336. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
  337. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
  338. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  339. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
  340. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
  341. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
  342. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
  343. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
  344. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
  345. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
  346. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
  347. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
  348. 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)