passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

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 (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1673 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ """
3
+ GenericGraph Cython functions
4
+
5
+ AUTHORS:
6
+
7
+ - Robert L. Miller (2007-02-13): initial version
8
+ - Robert W. Bradshaw (2007-03-31): fast spring layout algorithms
9
+ - Nathann Cohen : exhaustive search
10
+ """
11
+
12
+ # ****************************************************************************
13
+ # Copyright (C) 2007 Robert L. Miller <rlmillster@gmail.com>
14
+ # 2007 Robert W. Bradshaw <robertwb@math.washington.edu>
15
+ #
16
+ # This program is free software: you can redistribute it and/or modify
17
+ # it under the terms of the GNU General Public License as published by
18
+ # the Free Software Foundation, either version 2 of the License, or
19
+ # (at your option) any later version.
20
+ # http://www.gnu.org/licenses/
21
+ # ****************************************************************************
22
+
23
+ from cysignals.memory cimport check_allocarray, check_calloc, sig_free
24
+ from cysignals.signals cimport sig_on, sig_off
25
+
26
+ import cython
27
+
28
+ from sage.data_structures.binary_matrix cimport *
29
+ from libc.math cimport sqrt, fabs
30
+ from libc.string cimport memset
31
+ from memory_allocator cimport MemoryAllocator
32
+
33
+ from sage.cpython.string cimport char_to_str
34
+ from sage.libs.gmp.mpz cimport *
35
+ from sage.misc.prandom import random
36
+ from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
37
+ from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
38
+ from sage.graphs.base.static_sparse_graph cimport short_digraph
39
+ from sage.graphs.base.static_sparse_graph cimport init_short_digraph
40
+ from sage.graphs.base.static_sparse_graph cimport init_reverse
41
+ from sage.graphs.base.static_sparse_graph cimport free_short_digraph
42
+ from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge
43
+
44
+
45
+ cdef class GenericGraph_pyx(SageObject):
46
+ pass
47
+
48
+
49
+ def layout_split(layout_function, G, **options):
50
+ """
51
+ Graph each component of ``G`` separately with ``layout_function``,
52
+ placing them adjacent to each other.
53
+
54
+ This is done because several layout methods need the input graph to
55
+ be connected. For instance, on a disconnected graph, the spring
56
+ layout will push components further and further from each other
57
+ without bound, resulting in very tight clumps for each component.
58
+
59
+ .. NOTE::
60
+
61
+ If the axis are scaled to fit the plot in a square, the
62
+ horizontal distance may end up being "squished" due to
63
+ the several adjacent components.
64
+
65
+ EXAMPLES::
66
+
67
+ sage: G = graphs.DodecahedralGraph()
68
+ sage: for i in range(10): G.add_cycle(list(range(100*i, 100*i+3)))
69
+ sage: from sage.graphs.generic_graph_pyx import layout_split, spring_layout_fast
70
+ sage: D = layout_split(spring_layout_fast, G); D # random
71
+ {0: [0.77..., 0.06...],
72
+ ...
73
+ 902: [3.13..., 0.22...]}
74
+
75
+ AUTHOR:
76
+
77
+ Robert Bradshaw
78
+ """
79
+ from copy import copy
80
+ Gs = G.connected_components_subgraphs()
81
+ pos = {}
82
+ left = 0
83
+ buffer = 1/sqrt(len(G))
84
+
85
+ on_embedding = options.get('on_embedding', None)
86
+ forest_roots = options.get('forest_roots', None)
87
+ try:
88
+ forest_roots = list(forest_roots) if forest_roots else None
89
+ except TypeError:
90
+ raise TypeError('forest_roots should be an iterable of vertices')
91
+
92
+ if forest_roots or on_embedding:
93
+ options = copy(options)
94
+ options.pop('forest_roots', None)
95
+ options.pop('on_embedding', None)
96
+
97
+ for g in Gs:
98
+ if on_embedding:
99
+ # Restrict ``on_embedding`` to ``g``
100
+ embedding_g = {v: on_embedding[v] for v in g}
101
+ cur_pos = layout_function(g, on_embedding=embedding_g, **options)
102
+ elif forest_roots:
103
+ # Find a root for ``g`` (if any)
104
+ tree_root = next((v for v in forest_roots if v in g), None)
105
+ cur_pos = layout_function(g, tree_root=tree_root, **options)
106
+ else:
107
+ cur_pos = layout_function(g, **options)
108
+
109
+ xmin = min(x[0] for x in cur_pos.values())
110
+ xmax = max(x[0] for x in cur_pos.values())
111
+ if len(g) > 1:
112
+ buffer = max(1, (xmax - xmin)/sqrt(len(g)))
113
+ for v, loc in cur_pos.items():
114
+ loc[0] += left - xmin + buffer
115
+ pos[v] = loc
116
+ left += xmax - xmin + buffer
117
+
118
+ if options.get('set_embedding', None):
119
+ embedding = dict()
120
+ for g in Gs:
121
+ embedding.update(g.get_embedding())
122
+ G.set_embedding(embedding)
123
+ return pos
124
+
125
+
126
+ def spring_layout_fast(G, iterations=50, int dim=2, vpos=None, bint rescale=True,
127
+ bint height=False, by_component=False, **options):
128
+ """
129
+ Spring force model layout.
130
+
131
+ This function primarily acts as a wrapper around :func:`run_spring`,
132
+ converting to and from raw C types.
133
+
134
+ This kind of speed cannot be achieved by naive Cythonification of the
135
+ function alone, especially if we require a function call (let alone
136
+ an object creation) every time we want to add a pair of doubles.
137
+
138
+ INPUT:
139
+
140
+ - ``by_component`` -- boolean
141
+
142
+ EXAMPLES::
143
+
144
+ sage: G = graphs.DodecahedralGraph()
145
+ sage: for i in range(10): G.add_cycle(list(range(100*i, 100*i+3)))
146
+ sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
147
+ sage: pos = spring_layout_fast(G)
148
+ sage: pos[0] # random
149
+ [0.00..., 0.03...]
150
+ sage: sorted(pos.keys()) == sorted(G)
151
+ True
152
+
153
+ With ``split=True``, each component of G is laid out separately,
154
+ placing them adjacent to each other. This is done because on a
155
+ disconnected graph, the spring layout will push components further
156
+ and further from each other without bound, resulting in very tight
157
+ clumps for each component.
158
+
159
+ If the axis are scaled to fit the plot in a square, the
160
+ horizontal distance may end up being "squished" due to
161
+ the several adjacent components. ::
162
+
163
+ sage: G = graphs.DodecahedralGraph()
164
+ sage: for i in range(10): G.add_cycle(list(range(100*i, 100*i+3)))
165
+ sage: from sage.graphs.generic_graph_pyx import spring_layout_fast
166
+ sage: pos = spring_layout_fast(G, by_component = True)
167
+ sage: pos[0] # random
168
+ [2.21..., -0.00...]
169
+ sage: len(pos) == G.order()
170
+ True
171
+ """
172
+ if by_component:
173
+ return layout_split(spring_layout_fast, G, iterations=iterations,
174
+ dim=dim, vpos=vpos, rescale=rescale,
175
+ height=height, **options)
176
+ elif not G:
177
+ return {}
178
+
179
+ G = G.to_undirected()
180
+ cdef list vlist = list(G) # this defines a consistent order
181
+
182
+ cdef int i, j, x
183
+ cdef int n = G.order()
184
+
185
+ cdef double* pos = NULL # position of each vertex (for dim=2: x1,y1,x2,y2,...)
186
+ cdef int* elist = NULL # lexicographically ordered list of edges (u1,v1,u2,v2,...)
187
+ cdef double* cen = NULL # array of 'dim' doubles
188
+ try:
189
+ elist = <int*>check_allocarray(2*G.size() + 2, sizeof(int))
190
+ pos = <double*>check_allocarray(n*dim, sizeof(double))
191
+ cen = <double*>check_calloc(dim, sizeof(double))
192
+ except MemoryError:
193
+ sig_free(pos)
194
+ sig_free(elist)
195
+ sig_free(cen)
196
+ raise
197
+
198
+ # Initialize the starting positions
199
+ if vpos is None:
200
+ for i in range(n*dim):
201
+ pos[i] = random() # random in 1x1 box
202
+ else:
203
+ for i in range(n):
204
+ loc = vpos[vlist[i]]
205
+ for x in range(dim):
206
+ pos[i*dim + x] = loc[x]
207
+
208
+ # Lexicographically ordered list of edges
209
+ cdef int cur_edge = 0
210
+
211
+ for i in range(n):
212
+ for j in range(i+1, n):
213
+ if G.has_edge(vlist[i], vlist[j]):
214
+ elist[cur_edge] = i
215
+ elist[cur_edge+1] = j
216
+ cur_edge += 2
217
+
218
+ # finish the list with -1, -1 which never gets matched
219
+ # but does get compared against when looking for the "next" edge
220
+ elist[cur_edge] = -1
221
+ elist[cur_edge+1] = -1
222
+
223
+ if dim == 2:
224
+ run_spring(<int> iterations, <D_TWO> NULL, <double*> pos, <int*>elist, <int> n, <int> G.size(), <bint> height)
225
+ elif dim == 3:
226
+ run_spring(<int> iterations, <D_THREE> NULL, <double*> pos, <int*>elist, <int> n, <int> G.size(), <bint> height)
227
+ else:
228
+ raise ValueError("'dim' must be equal to 2 or 3")
229
+
230
+ # recenter
231
+ cdef double r, r2, max_r2 = 0
232
+ if rescale:
233
+ for i in range(n):
234
+ for x in range(dim):
235
+ cen[x] += pos[i*dim + x]
236
+ for x in range(dim):
237
+ cen[x] /= n
238
+ for i in range(n):
239
+ r2 = 0
240
+ for x in range(dim):
241
+ pos[i*dim + x] -= cen[x]
242
+ r2 += pos[i*dim + x] * pos[i*dim + x]
243
+ if r2 > max_r2:
244
+ max_r2 = r2
245
+ r = 1 if max_r2 == 0 else sqrt(max_r2)
246
+ for i in range(n):
247
+ for x in range(dim):
248
+ pos[i*dim + x] /= r
249
+
250
+ # put the data back into a position dictionary
251
+ vpos = {}
252
+ for i in range(n):
253
+ vpos[vlist[i]] = [pos[i*dim + x] for x in range(dim)]
254
+
255
+ sig_free(pos)
256
+ sig_free(elist)
257
+ sig_free(cen)
258
+
259
+ return vpos
260
+
261
+
262
+ @cython.cdivision(True)
263
+ cdef run_spring(int iterations, dimension_t _dim, double* pos, int* edges, int n, int m, bint height):
264
+ r"""
265
+ Find a locally optimal layout for this graph, according to the
266
+ constraints that neighboring nodes want to be a fixed distance
267
+ from each other, and non-neighboring nodes always repel.
268
+
269
+ This is not a true physical model of mutually-repulsive particles
270
+ with springs, rather it is more a model of such things traveling,
271
+ without any inertia, through an (ever thickening) fluid.
272
+
273
+ TODO: The inertial model could be incorporated (with F=ma)
274
+ TODO: Are the hard-coded constants here optimal?
275
+
276
+ INPUT:
277
+
278
+ - ``iterations`` -- number of steps to take
279
+ - ``_dim`` -- number of dimensions of freedom. Provide a value of type `D_TWO`
280
+ for 2 dimensions, or type `D_THREE` for three dimensions. The actual
281
+ value does not matter: only its type is important.
282
+ - ``pos`` -- already initialized initial positions. Each vertex is stored as
283
+ [dim] consecutive doubles. These doubles are then placed consecutively
284
+ in the array. For example, if dim=3, we would have
285
+ pos = [x_1, y_1, z_1, x_2, y_2, z_2, ... , x_n, y_n, z_n]
286
+ - ``edges`` -- List of edges, sorted lexicographically by the first (smallest)
287
+ vertex, terminated by -1, -1. The first two entries represent the first
288
+ edge, and so on.
289
+ - ``n`` -- number of vertices in the graph
290
+ - ``height`` -- if ``True``, do not update the last coordinate ever
291
+
292
+ OUTPUT: modifies contents of pos
293
+
294
+ AUTHOR:
295
+
296
+ Robert Bradshaw
297
+ """
298
+ cdef int dim
299
+ cdef int cur_iter, cur_edge
300
+ cdef int i, j, x
301
+
302
+ if dimension_t is D_TWO:
303
+ dim = 2
304
+ else:
305
+ dim = 3
306
+
307
+ # k -- the equilibrium distance between two adjacent nodes
308
+ cdef double t = 1, dt = t/(1e-20 + iterations), k = sqrt(1.0/n)
309
+ cdef double square_dist, dist, force, scale
310
+ cdef double* disp_i
311
+ cdef double* disp_j
312
+ cdef double delta[3]
313
+ cdef double d_tmp
314
+ cdef double xx, yy, zz
315
+
316
+ cdef double* disp = <double*>check_allocarray(n, dim * sizeof(double))
317
+
318
+ if height:
319
+ update_dim = dim - 1
320
+ else:
321
+ update_dim = dim
322
+
323
+ sig_on()
324
+
325
+ for cur_iter in range(iterations):
326
+ cur_edge = 1 # offset by one for fast checking against 2nd element first
327
+ # zero out the disp vectors
328
+ memset(disp, 0, n * dim * sizeof(double))
329
+ for i in range(n):
330
+ disp_i = disp + (i*dim)
331
+ for j in range(i + 1, n):
332
+ disp_j = disp + (j*dim)
333
+
334
+ for x in range(dim):
335
+ delta[x] = pos[i*dim + x] - pos[j*dim + x]
336
+
337
+ xx = delta[0] * delta[0]
338
+ yy = delta[1] * delta[1]
339
+ if dim == 2:
340
+ square_dist = xx+yy
341
+ else:
342
+ zz = delta[2] * delta[2]
343
+ square_dist = xx+yy+zz
344
+
345
+ if square_dist < 0.0001:
346
+ square_dist = 0.0001
347
+
348
+ # they repel according to the (capped) inverse square law
349
+ force = (k*k)/square_dist
350
+
351
+ # and if they are neighbors, attract according Hooke's law
352
+ if edges[cur_edge] == j and edges[cur_edge - 1] == i:
353
+ if dim == 2:
354
+ dist = sqrt_approx(delta[0], delta[1], xx, yy)
355
+ else:
356
+ dist = sqrt(square_dist)
357
+ force -= dist/k
358
+ cur_edge += 2
359
+
360
+ # add this factor into each of the involved points
361
+ for x in range(dim):
362
+ d_tmp = delta[x] * force
363
+ disp_i[x] += d_tmp
364
+ disp_j[x] -= d_tmp
365
+
366
+ # now update the positions
367
+ for i in range(n):
368
+ disp_i = disp + (i*dim)
369
+
370
+ square_dist = disp_i[0] * disp_i[0]
371
+ for x in range(1, dim):
372
+ square_dist += disp_i[x] * disp_i[x]
373
+
374
+ if square_dist < 0.0001:
375
+ scale = 1
376
+ else:
377
+ scale = t/sqrt(square_dist)
378
+
379
+ for x in range(update_dim):
380
+ pos[i*dim+x] += disp_i[x] * scale
381
+
382
+ t -= dt
383
+
384
+ sig_off()
385
+ sig_free(disp)
386
+
387
+
388
+ @cython.cdivision(True)
389
+ cdef inline double sqrt_approx(double x, double y, double xx, double yy) noexcept:
390
+ r"""
391
+ Approximation of `\sqrt(x^2+y^2)`.
392
+
393
+ Assuming that `x > y > 0`, it is a taylor expansion at `x^2`. To see how
394
+ 'bad' the approximation is::
395
+
396
+ sage: def dist(x, y):
397
+ ....: x = abs(x)
398
+ ....: y = abs(y)
399
+ ....: return max(x,y) + min(x,y)**2/(2*max(x,y))
400
+
401
+ sage: polar_plot([1, lambda x: dist(cos(x), sin(x))], (0, 2*math.pi)) # needs sage.plot sage.symbolic
402
+ Graphics object consisting of 2 graphics primitives
403
+ """
404
+ if xx < yy:
405
+ x, y = y, x
406
+ xx, yy = yy, xx
407
+
408
+ x = fabs(x)
409
+
410
+ return x + yy/(2*x)
411
+
412
+
413
+ def int_to_binary_string(n):
414
+ """
415
+ A quick python int to binary string conversion.
416
+
417
+ INPUT:
418
+
419
+ - ``n`` -- integer
420
+
421
+ EXAMPLES::
422
+
423
+ sage: sage.graphs.generic_graph_pyx.int_to_binary_string(389)
424
+ '110000101'
425
+ sage: Integer(389).binary()
426
+ '110000101'
427
+ sage: sage.graphs.generic_graph_pyx.int_to_binary_string(2007)
428
+ '11111010111'
429
+ """
430
+ cdef mpz_t i
431
+ cdef char* s
432
+ mpz_init(i)
433
+ mpz_set_ui(i, n)
434
+ s = mpz_get_str(NULL, 2, i)
435
+ t = char_to_str(s)
436
+ sig_free(s)
437
+ mpz_clear(i)
438
+ return t
439
+
440
+
441
+ def binary_string_to_graph6(x):
442
+ r"""
443
+ Transform a binary string into its graph6 representation.
444
+
445
+ This helper function is named `R` in [McK2015]_.
446
+
447
+ INPUT:
448
+
449
+ - ``x`` -- a binary string
450
+
451
+ EXAMPLES::
452
+
453
+ sage: from sage.graphs.generic_graph_pyx import binary_string_to_graph6
454
+ sage: binary_string_to_graph6('110111010110110010111000001100000001000000001')
455
+ 'vUqwK@?G'
456
+ """
457
+ # The length of x must be a multiple of 6. We extend it with 0s.
458
+ x += '0' * ((6 - (len(x) % 6)) % 6)
459
+
460
+ # Split into groups of 6, and convert numbers to decimal, adding 63
461
+ six_bits = ''
462
+ cdef int i
463
+ for i in range(len(x)/6):
464
+ six_bits += chr(int(x[6*i:6*(i + 1)], 2) + 63)
465
+ return six_bits
466
+
467
+
468
+ def small_integer_to_graph6(n):
469
+ r"""
470
+ Encode a small integer (i.e. a number of vertices) as a graph6 string.
471
+
472
+ This helper function is named `N` [McK2015]_.
473
+
474
+ INPUT:
475
+
476
+ - ``n`` -- integer
477
+
478
+ EXAMPLES::
479
+
480
+ sage: from sage.graphs.generic_graph_pyx import small_integer_to_graph6
481
+ sage: small_integer_to_graph6(13)
482
+ 'L'
483
+ sage: small_integer_to_graph6(136)
484
+ '~?AG'
485
+ """
486
+ if n < 63:
487
+ return chr(n + 63)
488
+ # get 18-bit rep of n
489
+ n = int_to_binary_string(n)
490
+ n = '0'*(18 - len(n)) + n
491
+ return chr(126) + binary_string_to_graph6(n)
492
+
493
+
494
+ def length_and_string_from_graph6(s):
495
+ r"""
496
+ Return a pair ``(length, graph6_string)`` from a graph6 string of unknown length.
497
+
498
+ This helper function is the inverse of `N` from [McK2015]_.
499
+
500
+ INPUT:
501
+
502
+ - ``s`` -- a graph6 string describing a binary vector (and encoding its
503
+ length)
504
+
505
+ EXAMPLES::
506
+
507
+ sage: from sage.graphs.generic_graph_pyx import length_and_string_from_graph6
508
+ sage: g6 = '~??~?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O?'
509
+ sage: g6 += '?????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?C'
510
+ sage: g6 += 'C?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@E'
511
+ sage: g6 += 'G???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAA'
512
+ sage: g6 += 'Cd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?'
513
+ sage: g6 += 'aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?'
514
+ sage: length_and_string_from_graph6(g6)
515
+ (63, '?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?')
516
+ sage: g6 = '_???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG?'
517
+ sage: g6 += '???I?J??Q??O?_@@??@??????'
518
+ sage: length_and_string_from_graph6(g6)
519
+ (32, '???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????')
520
+ """
521
+ if s[0] == chr(126): # first four bytes are N
522
+ a = int_to_binary_string(ord(s[1]) - 63).zfill(6)
523
+ b = int_to_binary_string(ord(s[2]) - 63).zfill(6)
524
+ c = int_to_binary_string(ord(s[3]) - 63).zfill(6)
525
+ n = int(a + b + c, 2)
526
+ s = s[4:]
527
+ else: # only first byte is N
528
+ o = ord(s[0])
529
+ if o > 126 or o < 63:
530
+ raise RuntimeError("the string seems corrupt: valid characters are \n" + ''.join(chr(i) for i in range(63, 127)))
531
+ n = o - 63
532
+ s = s[1:]
533
+ return n, s
534
+
535
+
536
+ def binary_string_from_graph6(s, n):
537
+ r"""
538
+ Decode a binary string from its graph6 representation.
539
+
540
+ This helper function is the inverse of `R` from [McK2015]_.
541
+
542
+ INPUT:
543
+
544
+ - ``s`` -- a graph6 string
545
+
546
+ - ``n`` -- the length of the binary string encoded by ``s``
547
+
548
+ EXAMPLES::
549
+
550
+ sage: from sage.graphs.generic_graph_pyx import binary_string_from_graph6
551
+ sage: g6 = '?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O?????'
552
+ sage: g6 += '?G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?'
553
+ sage: g6 += 'CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???'
554
+ sage: g6 += 'O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_'
555
+ sage: g6 += 'C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G'
556
+ sage: g6 += '@A??O??_?A?????O@Z?_@M????GQ@_G@?C?'
557
+ sage: binary_string_from_graph6(g6, 63)
558
+ '0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'
559
+ sage: g6 = '???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG??'
560
+ sage: g6 += '??I?J??Q??O?_@@??@??????'
561
+ sage: binary_string_from_graph6(g6, 32)
562
+ '0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'
563
+ """
564
+ cdef list l = []
565
+ cdef int i
566
+ for i in range(len(s)):
567
+ o = ord(s[i])
568
+ if o > 126 or o < 63:
569
+ raise RuntimeError("the string seems corrupt: valid characters are \n" + ''.join(chr(i) for i in range(63, 127)))
570
+ a = int_to_binary_string(o - 63)
571
+ l.append('0'*(6 - len(a)) + a)
572
+ m = "".join(l)
573
+ return m
574
+
575
+
576
+ def binary_string_from_dig6(s, n):
577
+ """
578
+ A helper function for the dig6 format.
579
+
580
+ INPUT:
581
+
582
+ - ``s`` -- a graph6 string
583
+
584
+ - ``n`` -- the length of the binary string encoded by ``s``
585
+
586
+ EXAMPLES::
587
+
588
+ sage: from sage.graphs.generic_graph_pyx import binary_string_from_dig6
589
+ sage: d6 = '?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O?????'
590
+ sage: d6 += '?G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?'
591
+ sage: d6 += 'CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???'
592
+ sage: d6 += 'O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_'
593
+ sage: d6 += 'C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G'
594
+ sage: d6 += '@A??O??_?A?????O@Z?_@M????GQ@_G@?C?'
595
+ sage: binary_string_from_dig6(d6, 63)
596
+ '0000000000000000000000000000001000000000010000000001000010000000000000000000110000000000000000010100000010000000000001000000000010000000000...10000000000000000000000000000000010000000001011011000000100000000001001110000000000000000000000000001000010010000001100000001000000001000000000100000000'
597
+ sage: d6 = '???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG??'
598
+ sage: d6 += '??I?J??Q??O?_@@??@??????'
599
+ sage: binary_string_from_dig6(d6, 32)
600
+ '0000000000000000000001000000000000010000100000100000001000000000000000100000000100000...010000000000000100010000001000000000000000000000000000001010000000001011000000000000010010000000000000010000000000100000000001000001000000000000000001000000000000000000000000000000000000'
601
+ """
602
+ cdef list l = []
603
+ cdef int i
604
+ for i in range(len(s)):
605
+ o = ord(s[i])
606
+ if o > 126 or o < 63:
607
+ raise RuntimeError("the string seems corrupt: valid characters are \n" + ''.join(chr(i) for i in range(63, 127)))
608
+ a = int_to_binary_string(o - 63)
609
+ l.append('0'*(6 - len(a)) + a)
610
+ m = "".join(l)
611
+ return m[:n*n]
612
+
613
+
614
+ # Exhaustive search in graphs
615
+
616
+ cdef class SubgraphSearch:
617
+ r"""
618
+ This class implements methods to exhaustively search for
619
+ copies of a graph `H` in a larger graph `G`.
620
+
621
+ It is possible to look for induced subgraphs instead, and to
622
+ iterate or count the number of their occurrences.
623
+
624
+ ALGORITHM:
625
+
626
+ The algorithm is a brute-force search. Let `V(H) =
627
+ \{h_1,\dots,h_k\}`. It first tries to find in `G` a possible
628
+ representative of `h_1`, then a representative of `h_2` compatible
629
+ with `h_1`, then a representative of `h_3` compatible with the first
630
+ two, etc.
631
+
632
+ This way, most of the time we need to test far less than `k!
633
+ \binom{|V(G)|}{k}` subsets, and hope this brute-force technique
634
+ can sometimes be useful.
635
+
636
+ .. NOTE::
637
+
638
+ This algorithm does not take vertex/edge labels into account.
639
+ """
640
+ def __init__(self, G, H, induced=False):
641
+ r"""
642
+ Constructor.
643
+
644
+ This constructor only checks there is no inconsistency in the
645
+ input : `G` and `H` are both graphs or both digraphs and that `H`
646
+ has order at least 2.
647
+
648
+ EXAMPLES::
649
+
650
+ sage: g = graphs.PetersenGraph()
651
+ sage: g.subgraph_search(graphs.CycleGraph(5)) # needs sage.modules
652
+ Subgraph of (Petersen graph): Graph on 5 vertices
653
+
654
+ TESTS:
655
+
656
+ Test proper initialization and deallocation, see :issue:`14067`.
657
+ We intentionally only create the class without doing any
658
+ computations with it::
659
+
660
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
661
+ sage: SubgraphSearch(Graph(5), Graph(1)) # needs sage.modules
662
+ Traceback (most recent call last):
663
+ ...
664
+ ValueError: searched graph should have at least 2 vertices
665
+ sage: SubgraphSearch(Graph(5), Graph(2)) # needs sage.modules
666
+ <sage.graphs.generic_graph_pyx.SubgraphSearch ...>
667
+ """
668
+ if H.order() <= 1:
669
+ raise ValueError("searched graph should have at least 2 vertices")
670
+
671
+ if G.is_directed() != H.is_directed():
672
+ raise ValueError("one graph cannot be directed while the other is not")
673
+
674
+ G._scream_if_not_simple(allow_loops=True)
675
+ H._scream_if_not_simple(allow_loops=True)
676
+
677
+ self._initialization()
678
+
679
+ def __iter__(self):
680
+ r"""
681
+ Return an iterator over all the labeled subgraphs of `G`
682
+ isomorphic to `H`.
683
+
684
+ EXAMPLES:
685
+
686
+ Iterating through all the `P_3` of `P_5`::
687
+
688
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
689
+ sage: g = graphs.PathGraph(5)
690
+ sage: h = graphs.PathGraph(3)
691
+ sage: S = SubgraphSearch(g, h) # needs sage.modules
692
+ sage: for p in S: # needs sage.modules
693
+ ....: print(p)
694
+ [0, 1, 2]
695
+ [1, 2, 3]
696
+ [2, 1, 0]
697
+ [2, 3, 4]
698
+ [3, 2, 1]
699
+ [4, 3, 2]
700
+ """
701
+ self._initialization()
702
+ return self
703
+
704
+ def cardinality(self):
705
+ r"""
706
+ Return the number of labelled subgraphs of `G` isomorphic to
707
+ `H`.
708
+
709
+ .. NOTE::
710
+
711
+ This method counts the subgraphs by enumerating them all !
712
+ Hence it probably is not a good idea to count their number
713
+ before enumerating them :-)
714
+
715
+ EXAMPLES:
716
+
717
+ Counting the number of labelled `P_3` in `P_5`::
718
+
719
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
720
+ sage: g = graphs.PathGraph(5)
721
+ sage: h = graphs.PathGraph(3)
722
+ sage: S = SubgraphSearch(g, h) # needs sage.modules
723
+ sage: S.cardinality() # needs sage.modules
724
+ 6
725
+
726
+ Check that the method is working even when vertices or edges are of
727
+ incomparable types (see :issue:`35904`)::
728
+
729
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
730
+ sage: G = Graph()
731
+ sage: G.add_cycle(['A', 1, 2, 3, ('a', 1)])
732
+ sage: H = Graph()
733
+ sage: H.add_path("xyz")
734
+ sage: S = SubgraphSearch(G, H) # needs sage.modules
735
+ sage: S.cardinality() # needs sage.modules
736
+ 10
737
+ """
738
+ if self.nh > self.ng:
739
+ return 0
740
+
741
+ self._initialization()
742
+ cdef int i
743
+
744
+ i = 0
745
+ for _ in self:
746
+ i += 1
747
+
748
+ from sage.rings.integer import Integer
749
+ return Integer(i)
750
+
751
+ def _initialization(self):
752
+ r"""
753
+ Initialization of the variables.
754
+
755
+ Once the memory allocation is done in :meth:`__cinit__`,
756
+ several variables need to be set to a default value. As this
757
+ operation needs to be performed before any call to
758
+ :meth:`__iter__` or to :meth:`cardinality`, it is cleaner to
759
+ create a dedicated method.
760
+
761
+ EXAMPLES:
762
+
763
+ Finding two times the first occurrence through the
764
+ re-initialization of the instance ::
765
+
766
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
767
+ sage: g = graphs.PathGraph(5)
768
+ sage: h = graphs.PathGraph(3)
769
+ sage: S = SubgraphSearch(g, h) # needs sage.modules
770
+ sage: S.__next__() # needs sage.modules
771
+ [0, 1, 2]
772
+ sage: S._initialization() # needs sage.modules
773
+ sage: S.__next__() # needs sage.modules
774
+ [0, 1, 2]
775
+
776
+ TESTS:
777
+
778
+ Check that :issue:`21828` is fixed::
779
+
780
+ sage: Poset().is_incomparable_chain_free(1,1) # indirect doctest # needs sage.modules
781
+ True
782
+ """
783
+ cdef int i
784
+
785
+ if self.ng > 0:
786
+ # 0 is the first vertex we use, so it is at first busy
787
+ self.busy[0] = 1
788
+ for i in range(1, self.ng):
789
+ self.busy[i] = 0
790
+ # stack -- list of the vertices which are part of the partial copy of H
791
+ # in G.
792
+ #
793
+ # stack[i] -- the integer corresponding to the vertex of G representing
794
+ # the i-th vertex of H.
795
+ #
796
+ # stack[i] = -1 means that i is not represented
797
+ # ... yet!
798
+
799
+ self.stack[0] = 0
800
+ self.stack[1] = -1
801
+
802
+ # Number of representatives we have already found. Set to 1 as vertex 0
803
+ # is already part of the partial copy of H in G.
804
+ self.active = 1
805
+
806
+ def __cinit__(self, G, H, induced=False):
807
+ r"""
808
+ Cython constructor.
809
+
810
+ This method initializes all the C values.
811
+
812
+ EXAMPLES::
813
+
814
+ sage: g = graphs.PetersenGraph()
815
+ sage: g.subgraph_search(graphs.CycleGraph(5)) # needs sage.modules
816
+ Subgraph of (Petersen graph): Graph on 5 vertices
817
+ """
818
+ self.mem = MemoryAllocator()
819
+
820
+ # Storing the number of vertices
821
+ self.ng = G.order()
822
+ self.nh = H.order()
823
+
824
+ # Storing the list of vertices
825
+ self.g_vertices = list(G)
826
+ cdef list h_vertices = list(H)
827
+
828
+ # Are the graphs directed (in __init__(), we check
829
+ # whether both are of the same type)
830
+ self.directed = G.is_directed()
831
+
832
+ cdef int i, j
833
+
834
+ # A vertex is said to be busy if it is already part of the partial copy
835
+ # of H in G.
836
+ self.busy = <int *> self.mem.allocarray(self.ng, sizeof(int))
837
+ self.tmp_array = <int *> self.mem.allocarray(self.ng, sizeof(int))
838
+ self.stack = <int *> self.mem.allocarray(self.nh, sizeof(int))
839
+ self.vertices = <int *> self.mem.allocarray(self.nh, sizeof(int))
840
+ self.line_h_out = <int **> self.mem.allocarray(self.nh, sizeof(int *))
841
+ self.line_h_in = <int **> self.mem.allocarray(self.nh, sizeof(int *)) if self.directed else NULL
842
+
843
+ self.line_h_out[0] = <int *> self.mem.allocarray(self.nh*self.nh,
844
+ sizeof(int))
845
+ if self.directed:
846
+ self.line_h_in[0] = <int *> self.mem.allocarray(self.nh*self.nh,
847
+ sizeof(int))
848
+
849
+ # Should we look for induced subgraphs ?
850
+ if induced:
851
+ self.is_admissible = vectors_equal
852
+ else:
853
+ self.is_admissible = vectors_inferior
854
+
855
+ # static copies of the two graphs for more efficient operations
856
+ self.g = DenseGraph(self.ng)
857
+ self.h = DenseGraph(self.nh)
858
+
859
+ # copying the adjacency relations in both G and H
860
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(self.g_vertices)}
861
+ cdef bint undirected = not G.is_directed()
862
+ for u, v in G.edge_iterator(labels=False):
863
+ i = vertex_to_int[u]
864
+ j = vertex_to_int[v]
865
+ self.g.add_arc(i, j)
866
+ if undirected:
867
+ self.g.add_arc(j, i)
868
+
869
+ vertex_to_int = {v: i for i, v in enumerate(h_vertices)}
870
+ for u, v in H.edge_iterator(labels=False):
871
+ i = vertex_to_int[u]
872
+ j = vertex_to_int[v]
873
+ self.h.add_arc(i, j)
874
+ if undirected:
875
+ self.h.add_arc(j, i)
876
+
877
+ # vertices is equal to range(nh), as an int *variable
878
+ for i in range(self.nh):
879
+ self.vertices[i] = i
880
+
881
+ # line_h_out[i] represents the adjacency sequence of vertex i
882
+ # in h relative to vertices 0, 1, ..., i-1
883
+ for i in range(self.nh):
884
+ self.line_h_out[i] = self.line_h_out[0] + i*self.nh
885
+ self.h.adjacency_sequence_out(i, self.vertices, i, self.line_h_out[i])
886
+
887
+ # Similarly in the opposite direction (only useful if the
888
+ # graphs are directed)
889
+ if self.directed:
890
+ for i in range(self.nh):
891
+ self.line_h_in[i] = self.line_h_in[0] + i*self.nh
892
+ self.h.adjacency_sequence_in(i, self.vertices, i, self.line_h_in[i])
893
+
894
+ def __next__(self):
895
+ r"""
896
+ Return the next isomorphic subgraph if any, and raises a
897
+ ``StopIteration`` otherwise.
898
+
899
+ EXAMPLES::
900
+
901
+ sage: from sage.graphs.generic_graph_pyx import SubgraphSearch
902
+ sage: g = graphs.PathGraph(5)
903
+ sage: h = graphs.PathGraph(3)
904
+ sage: S = SubgraphSearch(g, h) # needs sage.modules
905
+ sage: S.__next__() # needs sage.modules
906
+ [0, 1, 2]
907
+ """
908
+ if not self.ng:
909
+ raise StopIteration
910
+ sig_on()
911
+ cdef bint is_admissible
912
+ cdef int * tmp_array = self.tmp_array
913
+
914
+ # as long as there is a non-void partial copy of H in G
915
+ while self.active >= 0:
916
+ # If we are here and found nothing yet, we try the next possible
917
+ # vertex as a representative of the active i-th vertex of H.
918
+ self.i = self.stack[self.active] + 1
919
+ # Looking for a vertex that is not busy and compatible with the
920
+ # partial copy we have of H.
921
+ while self.i < self.ng:
922
+ if self.busy[self.i]:
923
+ self.i += 1
924
+ else:
925
+ # Testing whether the vertex we picked is a
926
+ # correct extension by checking the edges from the
927
+ # vertices already selected to self.i satisfy the
928
+ # constraints
929
+ self.g.adjacency_sequence_out(self.active, self.stack, self.i, tmp_array)
930
+ is_admissible = self.is_admissible(self.active, tmp_array, self.line_h_out[self.active])
931
+
932
+ # If G and H are digraphs, we also need to ensure
933
+ # the edges going in the opposite direction
934
+ # satisfy the constraints
935
+ if is_admissible and self.directed:
936
+ self.g.adjacency_sequence_in(self.active, self.stack, self.i, tmp_array)
937
+ is_admissible = is_admissible and self.is_admissible(self.active, tmp_array, self.line_h_in[self.active])
938
+
939
+ if is_admissible:
940
+ break
941
+ else:
942
+ self.i += 1
943
+
944
+ # If we have found a good representative of H's i-th vertex in G
945
+ if self.i < self.ng:
946
+
947
+ # updating the last vertex of the stack
948
+ if self.stack[self.active] != -1:
949
+ self.busy[self.stack[self.active]] = 0
950
+ self.stack[self.active] = self.i
951
+
952
+ # We have found our copy !!!
953
+ if self.active == self.nh-1:
954
+ sig_off()
955
+ return [self.g_vertices[self.stack[l]]
956
+ for l in range(self.nh)]
957
+
958
+ # We are still missing several vertices ...
959
+ else:
960
+ self.busy[self.stack[self.active]] = 1
961
+ self.active += 1
962
+
963
+ # we begin the search of the next vertex at 0
964
+ self.stack[self.active] = -1
965
+
966
+ # If we found no representative for the i-th vertex, it
967
+ # means that we cannot extend the current copy of H so we
968
+ # update the status of stack[active] and prepare to change
969
+ # the previous vertex.
970
+
971
+ else:
972
+ if self.stack[self.active] != -1:
973
+ self.busy[self.stack[self.active]] = 0
974
+ self.stack[self.active] = -1
975
+ self.active -= 1
976
+
977
+ sig_off()
978
+ raise StopIteration
979
+
980
+ cdef inline bint vectors_equal(int n, int *a, int *b) noexcept:
981
+ r"""
982
+ Test whether the two given vectors are equal. Two integer vectors
983
+ `a = (a_1, a_2, \dots, a_n)` and `b = (b_1, b_2, \dots, b_n)` are equal
984
+ iff `a_i = b_i` for all `i = 1, 2, \dots, n`. See the function
985
+ ``_test_vectors_equal_inferior()`` for unit tests.
986
+
987
+ INPUT:
988
+
989
+ - ``n`` -- positive integer; length of the vectors
990
+
991
+ - ``a``, ``b`` -- two vectors of integers
992
+
993
+ OUTPUT: ``True`` if ``a`` and ``b`` are the same vector; ``False`` otherwise
994
+ """
995
+ cdef int i
996
+ for i in range(n):
997
+ if a[i] != b[i]:
998
+ return False
999
+ return True
1000
+
1001
+ cdef inline bint vectors_inferior(int n, int *a, int *b) noexcept:
1002
+ r"""
1003
+ Test whether the second vector of integers is inferior to the first. Let
1004
+ `u = (u_1, u_2, \dots, u_k)` and `v = (v_1, v_2, \dots, v_k)` be two
1005
+ integer vectors of equal length. Then `u` is said to be less than
1006
+ (or inferior to) `v` if `u_i \leq v_i` for all `i = 1, 2, \dots, k`. See
1007
+ the function ``_test_vectors_equal_inferior()`` for unit tests. Given two
1008
+ equal integer vectors `u` and `v`, `u` is inferior to `v` and vice versa.
1009
+ We could also define two vectors `a` and `b` to be equal if `a` is
1010
+ inferior to `b` and `b` is inferior to `a`.
1011
+
1012
+ INPUT:
1013
+
1014
+ - ``n`` -- positive integer; length of the vectors
1015
+
1016
+ - ``a``, ``b`` -- two vectors of integers
1017
+
1018
+ OUTPUT:
1019
+
1020
+ - ``True`` if ``b`` is inferior to (or less than) ``a``; ``False``
1021
+ otherwise.
1022
+ """
1023
+ cdef int i
1024
+ for i in range(n):
1025
+ if a[i] < b[i]:
1026
+ return False
1027
+ return True
1028
+
1029
+
1030
+ ##############################
1031
+ # Further tests. Unit tests for methods, functions, classes defined with cdef.
1032
+ ##############################
1033
+
1034
+ def _test_vectors_equal_inferior():
1035
+ """
1036
+ Unit testing the function ``vectors_equal()``. No output means that no
1037
+ errors were found in the random tests.
1038
+
1039
+ TESTS::
1040
+
1041
+ sage: from sage.graphs.generic_graph_pyx import _test_vectors_equal_inferior
1042
+ sage: _test_vectors_equal_inferior()
1043
+ """
1044
+ from sage.misc.prandom import randint
1045
+ n = randint(500, 10**3)
1046
+ cdef int *u = <int *>check_allocarray(n, sizeof(int))
1047
+ cdef int *v = <int *>check_allocarray(n, sizeof(int))
1048
+ cdef int i
1049
+ # equal vectors: u = v
1050
+ for i in range(n):
1051
+ u[i] = randint(-10**6, 10**6)
1052
+ v[i] = u[i]
1053
+ try:
1054
+ assert vectors_equal(n, u, v)
1055
+ assert vectors_equal(n, v, u)
1056
+ # Since u and v are equal vectors, then u is inferior to v and v is
1057
+ # inferior to u. One could also define u and v as being equal if
1058
+ # u is inferior to v and vice versa.
1059
+ assert vectors_inferior(n, u, v)
1060
+ assert vectors_inferior(n, v, u)
1061
+ except AssertionError:
1062
+ sig_free(u)
1063
+ sig_free(v)
1064
+ raise AssertionError("Vectors u and v should be equal.")
1065
+ # Different vectors: u != v because we have u_j > v_j for some j. Thus,
1066
+ # u_i = v_i for 0 <= i < j and u_j > v_j. For j < k < n - 2, we could have:
1067
+ # (1) u_k = v_k,
1068
+ # (2) u_k < v_k, or
1069
+ # (3) u_k > v_k.
1070
+ # And finally, u_{n-1} < v_{n-1}.
1071
+ cdef int j = randint(1, n//2)
1072
+ cdef int k
1073
+ for i in range(j):
1074
+ u[i] = randint(-10**6, 10**6)
1075
+ v[i] = u[i]
1076
+ u[j] = randint(-10**6, 10**6)
1077
+ v[j] = u[j] - randint(1, 10**6)
1078
+ for k in range(j + 1, n):
1079
+ u[k] = randint(-10**6, 10**6)
1080
+ v[k] = randint(-10**6, 10**6)
1081
+ u[n - 1] = v[n - 1] - randint(1, 10**6)
1082
+ try:
1083
+ assert not vectors_equal(n, u, v)
1084
+ assert not vectors_equal(n, v, u)
1085
+ # u is not inferior to v because at least u_j > v_j
1086
+ assert u[j] > v[j]
1087
+ assert not vectors_inferior(n, v, u)
1088
+ # v is not inferior to u because at least v_{n-1} > u_{n-1}
1089
+ assert v[n - 1] > u[n - 1]
1090
+ assert not vectors_inferior(n, u, v)
1091
+ except AssertionError:
1092
+ sig_free(u)
1093
+ sig_free(v)
1094
+ raise AssertionError("Vectors u and v should not be equal. "
1095
+ "u should not be inferior to v, and vice versa.")
1096
+ # Different vectors: u != v because we have u_j < v_j for some j. Thus,
1097
+ # u_i = v_i for 0 <= i < j and u_j < v_j. For j < k < n - 2, we could have:
1098
+ # (1) u_k = v_k,
1099
+ # (2) u_k < v_k, or
1100
+ # (3) u_k > v_k.
1101
+ # And finally, u_{n-1} > v_{n-1}.
1102
+ j = randint(1, n//2)
1103
+ for i in range(j):
1104
+ u[i] = randint(-10**6, 10**6)
1105
+ v[i] = u[i]
1106
+ u[j] = randint(-10**6, 10**6)
1107
+ v[j] = u[j] + randint(1, 10**6)
1108
+ for j < k < n:
1109
+ u[k] = randint(-10**6, 10**6)
1110
+ v[k] = randint(-10**6, 10**6)
1111
+ u[n - 1] = v[n - 1] + randint(1, 10**6)
1112
+ try:
1113
+ assert not vectors_equal(n, u, v)
1114
+ assert not vectors_equal(n, v, u)
1115
+ # u is not inferior to v because at least u_{n-1} > v_{n-1}
1116
+ assert u[n - 1] > v[n - 1]
1117
+ assert not vectors_inferior(n, v, u)
1118
+ # v is not inferior to u because at least u_j < v_j
1119
+ assert u[j] < v[j]
1120
+ assert not vectors_inferior(n, u, v)
1121
+ except AssertionError:
1122
+ sig_free(u)
1123
+ sig_free(v)
1124
+ raise AssertionError("Vectors u and v should not be equal. "
1125
+ "u should not be inferior to v, and vice versa.")
1126
+ # different vectors u != v
1127
+ # What's the probability of two random vectors being equal?
1128
+ for i in range(n):
1129
+ u[i] = randint(-10**6, 10**6)
1130
+ v[i] = randint(-10**6, 10**6)
1131
+ try:
1132
+ assert not vectors_equal(n, u, v)
1133
+ assert not vectors_equal(n, v, u)
1134
+ except AssertionError:
1135
+ sig_free(u)
1136
+ sig_free(v)
1137
+ raise AssertionError("Vectors u and v should not be equal.")
1138
+ # u is inferior to v, but v is not inferior to u
1139
+ for i in range(n):
1140
+ v[i] = randint(-10**6, 10**6)
1141
+ u[i] = randint(-10**6, 10**6)
1142
+ while u[i] > v[i]:
1143
+ u[i] = randint(-10**6, 10**6)
1144
+ try:
1145
+ assert not vectors_equal(n, u, v)
1146
+ assert not vectors_equal(n, v, u)
1147
+ assert vectors_inferior(n, v, u)
1148
+ assert not vectors_inferior(n, u, v)
1149
+ except AssertionError:
1150
+ raise AssertionError("u should be inferior to v, but v is not inferior to u.")
1151
+ finally:
1152
+ sig_free(u)
1153
+ sig_free(v)
1154
+
1155
+
1156
+ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1157
+ long backtrack_bound=1000, find_path=False):
1158
+ r"""
1159
+ Randomized backtracking for finding Hamiltonian cycles and paths.
1160
+
1161
+ ALGORITHM:
1162
+
1163
+ A path ``P`` is maintained during the execution of the algorithm.
1164
+ Initially the path will contain an edge of the graph. Every 10
1165
+ iterations the path is reversed. Every ``reset_bound`` iterations
1166
+ the path will be cleared and the procedure is restarted. Every
1167
+ ``backtrack_bound`` steps we discard the last five vertices and
1168
+ continue with the procedure. The total number of steps in the
1169
+ algorithm is controlled by ``max_iter``. If a Hamiltonian cycle or
1170
+ Hamiltonian path is found it is returned. If the number of steps
1171
+ reaches ``max_iter`` then a longest path is returned. See OUTPUT
1172
+ for more details.
1173
+
1174
+ INPUT:
1175
+
1176
+ - ``G`` -- graph
1177
+
1178
+ - ``max_iter`` -- maximum number of iterations
1179
+
1180
+ - ``reset_bound`` -- number of iterations before restarting the
1181
+ procedure
1182
+
1183
+ - ``backtrack_bound`` -- number of iterations to elapse before
1184
+ discarding the last 5 vertices of the path
1185
+
1186
+ - ``find_path`` -- boolean (default: ``False``); if set to ``True``, will
1187
+ search a Hamiltonian path. If ``False``, will search for a Hamiltonian
1188
+ cycle.
1189
+
1190
+ OUTPUT:
1191
+
1192
+ A pair ``(B, P)``, where ``B`` is a Boolean and ``P`` is a list
1193
+ of vertices.
1194
+
1195
+ * If ``B`` is ``True`` and ``find_path`` is ``False``, ``P``
1196
+ represents a Hamiltonian cycle.
1197
+
1198
+ * If ``B`` is ``True`` and ``find_path`` is ``True``, ``P``
1199
+ represents a Hamiltonian path.
1200
+
1201
+ * If ``B`` is ``False``, then ``P`` represents the longest path
1202
+ found during the execution of the algorithm.
1203
+
1204
+ .. WARNING::
1205
+
1206
+ May loop endlessly when run on a graph with vertices of degree 1.
1207
+
1208
+ EXAMPLES:
1209
+
1210
+ For demonstration purposes we fix a random seed::
1211
+
1212
+ sage: set_random_seed(0)
1213
+
1214
+ First we try the algorithm in the Dodecahedral graph, which is
1215
+ Hamiltonian, so we are able to find a Hamiltonian cycle and a
1216
+ Hamiltonian path::
1217
+
1218
+ sage: from sage.graphs.generic_graph_pyx import find_hamiltonian as fh
1219
+ sage: G=graphs.DodecahedralGraph()
1220
+ sage: fh(G)
1221
+ (True, [12, 11, 10, 9, 13, 14, 15, 5, 4, 3, 2, 6, 7, 8, 1, 0, 19, 18, 17, 16])
1222
+ sage: fh(G,find_path=True)
1223
+ (True, [10, 0, 19, 3, 4, 5, 15, 16, 17, 18, 11, 12, 13, 9, 8, 1, 2, 6, 7, 14])
1224
+
1225
+ Another test, now in the Möbius-Kantor graph which is also
1226
+ Hamiltonian, as in our previous example, we are able to find a
1227
+ Hamiltonian cycle and path::
1228
+
1229
+ sage: G=graphs.MoebiusKantorGraph()
1230
+ sage: fh(G)
1231
+ (True, [15, 10, 2, 3, 4, 5, 13, 8, 11, 14, 6, 7, 0, 1, 9, 12])
1232
+ sage: fh(G,find_path=True)
1233
+ (True, [10, 15, 7, 6, 5, 4, 12, 9, 14, 11, 3, 2, 1, 0, 8, 13])
1234
+
1235
+ Now, we try the algorithm on a non Hamiltonian graph, the Petersen
1236
+ graph. This graph is known to be hypohamiltonian, so a
1237
+ Hamiltonian path can be found::
1238
+
1239
+ sage: G=graphs.PetersenGraph()
1240
+ sage: fh(G)
1241
+ (False, [9, 4, 0, 1, 6, 8, 5, 7, 2, 3])
1242
+ sage: fh(G,find_path=True)
1243
+ (True, [7, 2, 1, 0, 5, 8, 6, 9, 4, 3])
1244
+
1245
+ We now show the algorithm working on another known hypohamiltonian
1246
+ graph, the generalized Petersen graph with parameters 11 and 2::
1247
+
1248
+ sage: G=graphs.GeneralizedPetersenGraph(11,2)
1249
+ sage: fh(G)
1250
+ (False, [7, 8, 9, 10, 0, 1, 2, 3, 14, 12, 21, 19, 17, 6, 5, 4, 15, 13, 11, 20, 18, 16])
1251
+ sage: fh(G,find_path=True)
1252
+ (True, [2, 1, 12, 21, 10, 0, 11, 13, 15, 17, 19, 8, 7, 6, 5, 4, 3, 14, 16, 18, 20, 9])
1253
+
1254
+ Finally, an example on a graph which does not have a Hamiltonian
1255
+ path::
1256
+
1257
+ sage: G = graphs.HyperStarGraph(5, 2)
1258
+ sage: G.order()
1259
+ 10
1260
+ sage: b, P = fh(G,find_path=False)
1261
+ sage: b, len(P)
1262
+ (False, 9)
1263
+ sage: b, P = fh(G,find_path=True)
1264
+ sage: b, len(P)
1265
+ (False, 9)
1266
+
1267
+ The method can also be used for directed graphs::
1268
+
1269
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 3)])
1270
+ sage: fh(G)
1271
+ (False, [0, 1, 2, 3])
1272
+ sage: G = G.reverse()
1273
+ sage: fh(G)
1274
+ (False, [3, 2, 1, 0])
1275
+ sage: G = DiGraph()
1276
+ sage: G.add_cycle([0, 1, 2, 3, 4, 5])
1277
+ sage: b, P = fh(G)
1278
+ sage: b, len(P)
1279
+ (True, 6)
1280
+
1281
+ TESTS:
1282
+
1283
+ :issue:`10206` -- Hamiltonian cycle in small (di)graphs::
1284
+
1285
+ sage: for n in range(3): # needs nauty
1286
+ ....: for G in graphs(n):
1287
+ ....: print('order {} and size {}: {}'.format(G.order(),G.size(),fh(G, find_path=False)))
1288
+ order 0 and size 0: (False, [])
1289
+ order 1 and size 0: (False, [0])
1290
+ order 2 and size 0: (False, [0])
1291
+ order 2 and size 1: (False, [0, 1])
1292
+ sage: for n in range(3): # needs nauty
1293
+ ....: for G in digraphs(n):
1294
+ ....: print('order {} and size {}: {}'.format(G.order(),G.size(),fh(G, find_path=False)))
1295
+ order 0 and size 0: (False, [])
1296
+ order 1 and size 0: (False, [0])
1297
+ order 2 and size 0: (False, [0])
1298
+ order 2 and size 1: (False, [0, 1])
1299
+ order 2 and size 2: (False, [0, 1])
1300
+
1301
+ :issue:`10206` -- Hamiltonian path in small (di)graphs::
1302
+
1303
+ sage: for n in range(3): # needs nauty
1304
+ ....: for G in graphs(n):
1305
+ ....: print('order {} and size {}: {}'.format(G.order(),G.size(),fh(G, find_path=True)))
1306
+ order 0 and size 0: (False, [])
1307
+ order 1 and size 0: (False, [0])
1308
+ order 2 and size 0: (False, [0])
1309
+ order 2 and size 1: (True, [0, 1])
1310
+ sage: for n in range(3): # needs nauty
1311
+ ....: for G in digraphs(n):
1312
+ ....: print('order {} and size {}: {}'.format(G.order(),G.size(),fh(G, find_path=True)))
1313
+ order 0 and size 0: (False, [])
1314
+ order 1 and size 0: (False, [0])
1315
+ order 2 and size 0: (False, [0])
1316
+ order 2 and size 1: (True, [0, 1])
1317
+ order 2 and size 2: (True, [0, 1])
1318
+
1319
+ :issue:`10206` -- disconnected graphs::
1320
+
1321
+ sage: G = graphs.CompleteGraph(4) + Graph(1)
1322
+ sage: fh(G, find_path=False)
1323
+ (False, [0, 1, 2, 3])
1324
+ sage: fh(G, find_path=True)
1325
+ (False, [0, 1, 2, 3])
1326
+
1327
+ Check that the method is robust to incomparable vertices::
1328
+
1329
+ sage: G = Graph([(1, 'a'), ('a', 2), (2, 3), (3, 1)])
1330
+ sage: b, C = fh(G, find_path=False)
1331
+ sage: b, len(C)
1332
+ (True, 4)
1333
+
1334
+ Immutable graphs::
1335
+
1336
+ sage: G = graphs.PetersenGraph()
1337
+ sage: H = Graph(G, immutable=True)
1338
+ sage: fh(H)
1339
+ (False, [7, 5, 0, 1, 2, 3, 8, 6, 9, 4])
1340
+ sage: fh(H, find_path=True)
1341
+ (True, [5, 0, 1, 6, 8, 3, 2, 7, 9, 4])
1342
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 3)], immutable=True)
1343
+ sage: fh(G)
1344
+ (False, [0, 1, 2, 3])
1345
+ """
1346
+ G._scream_if_not_simple()
1347
+
1348
+ from sage.misc.prandom import randint
1349
+ cdef int n = G.order()
1350
+
1351
+ # Easy cases
1352
+ if n < 2:
1353
+ return False, list(G)
1354
+
1355
+ # To clean the output when find_path is None or a number
1356
+ find_path = (find_path > 0)
1357
+
1358
+ if G.is_clique(induced=False):
1359
+ # We have a hamiltonian path since n >= 2, but we have a hamiltonian
1360
+ # cycle only if n >= 3
1361
+ return find_path or n >= 3, list(G)
1362
+
1363
+ cdef list best_path, p
1364
+ if not G.is_connected():
1365
+ # The (Di)Graph has no hamiltonian path or cycle. We search for the
1366
+ # longest path in its connected components.
1367
+ best_path = []
1368
+ for H in G.connected_components_subgraphs():
1369
+ if H.order() <= len(best_path):
1370
+ continue
1371
+ _, p = find_hamiltonian(H, max_iter=max_iter, reset_bound=reset_bound,
1372
+ backtrack_bound=backtrack_bound, find_path=True)
1373
+ if len(p) > len(best_path):
1374
+ best_path = p
1375
+ return False, best_path
1376
+
1377
+ # Misc variables used below
1378
+ cdef int i, j
1379
+ cdef bint directed = G.is_directed()
1380
+
1381
+ # Initialize the path.
1382
+ cdef MemoryAllocator mem = MemoryAllocator()
1383
+ cdef int *path = <int *>mem.allocarray(n, sizeof(int))
1384
+
1385
+ # Initialize the membership array
1386
+ cdef bint *member = <bint *>mem.allocarray(n, sizeof(int))
1387
+ memset(member, 0, n * sizeof(int))
1388
+
1389
+ # static copy of the graph for more efficient operations
1390
+ cdef list int_to_vertex
1391
+ cdef StaticSparseCGraph cg
1392
+ cdef short_digraph sd
1393
+ if isinstance(G, StaticSparseBackend):
1394
+ cg = <StaticSparseCGraph> G._cg
1395
+ sd = <short_digraph> cg.g
1396
+ int_to_vertex = cg._vertex_to_labels
1397
+ else:
1398
+ int_to_vertex = list(G)
1399
+ init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
1400
+ cdef short_digraph rev_sd
1401
+ cdef bint reverse = False
1402
+ if directed:
1403
+ if isinstance(G, StaticSparseBackend) and cg._directed:
1404
+ rev_sd = <short_digraph> cg.g_rev
1405
+ else:
1406
+ init_reverse(rev_sd, sd)
1407
+
1408
+ # A list to store the available vertices at each step
1409
+ cdef list available_vertices = []
1410
+
1411
+ # We now work towards picking a random edge
1412
+ # First we pick a random vertex u of (out-)degree at least one
1413
+ cdef int u = randint(0, n - 1)
1414
+ while not out_degree(sd, u):
1415
+ u = randint(0, n - 1)
1416
+ # Then we pick at random a neighbor of u
1417
+ cdef int x = randint(0, out_degree(sd, u) - 1)
1418
+ cdef int v = sd.neighbors[u][x]
1419
+ # This will be the first edge in the path
1420
+ cdef int length = 2
1421
+ path[0] = u
1422
+ path[1] = v
1423
+ member[u] = True
1424
+ member[v] = True
1425
+
1426
+ # Initialize all the variables necessary to start iterating
1427
+ cdef bint done = False
1428
+ cdef long counter = 0
1429
+ cdef long bigcount = 0
1430
+ cdef int longest = length
1431
+
1432
+ # Initialize a path to contain the longest path
1433
+ cdef int *longest_path = <int *>mem.allocarray(n, sizeof(int))
1434
+ for i in range(length):
1435
+ longest_path[i] = path[i]
1436
+
1437
+ cdef bint longer = False
1438
+ cdef bint longest_reversed = False
1439
+ cdef bint flag
1440
+
1441
+ while not done:
1442
+ counter = counter + 1
1443
+ if counter % 10 == 0:
1444
+ # Reverse the path
1445
+ for i in range(length//2):
1446
+ t = path[i]
1447
+ path[i] = path[length - i - 1]
1448
+ path[length - i - 1] = t
1449
+
1450
+ if directed:
1451
+ # We now work on the reverse graph
1452
+ reverse = not reverse
1453
+
1454
+ if counter > reset_bound:
1455
+ bigcount = bigcount + 1
1456
+ counter = 1
1457
+
1458
+ # Time to reset the procedure
1459
+ memset(member, 0, n * sizeof(int))
1460
+ if directed and reverse:
1461
+ # We restore the original orientation
1462
+ reverse = False
1463
+
1464
+ # First we pick a random vertex u of (out-)degree at least one
1465
+ u = randint(0, n - 1)
1466
+ while not out_degree(sd, u):
1467
+ u = randint(0, n - 1)
1468
+ # Then we pick at random a neighbor of u
1469
+ x = randint(0, out_degree(sd, u) - 1)
1470
+ v = sd.neighbors[u][x]
1471
+ # This will be the first edge in the path
1472
+ length = 2
1473
+ path[0] = u
1474
+ path[1] = v
1475
+ member[u] = True
1476
+ member[v] = True
1477
+
1478
+ if length > 5 and counter % backtrack_bound == 0:
1479
+ for i in range(5):
1480
+ member[path[length - i - 1]] = False
1481
+ length = length - 5
1482
+ longer = False
1483
+
1484
+ # We search for a possible extension of the path
1485
+ available_vertices = []
1486
+ u = path[length - 1]
1487
+ if directed and reverse:
1488
+ for i in range(out_degree(rev_sd, u)):
1489
+ v = rev_sd.neighbors[u][i]
1490
+ if not member[v]:
1491
+ available_vertices.append(v)
1492
+ else:
1493
+ for i in range(out_degree(sd, u)):
1494
+ v = sd.neighbors[u][i]
1495
+ if not member[v]:
1496
+ available_vertices.append(v)
1497
+
1498
+ if available_vertices:
1499
+ longer = True
1500
+ x = randint(0, len(available_vertices) - 1)
1501
+ v = available_vertices[x]
1502
+ path[length] = v
1503
+ length = length + 1
1504
+ member[v] = True
1505
+
1506
+ if not longer and length > longest:
1507
+ # Store the current best solution
1508
+ for i in range(length):
1509
+ longest_path[i] = path[i]
1510
+
1511
+ longest = length
1512
+ longest_reversed = reverse
1513
+
1514
+ if not directed and not longer and out_degree(sd, path[length - 1]) > 1:
1515
+ # We revert a cycle to change the extremity of the path
1516
+ degree = out_degree(sd, path[length - 1])
1517
+ while True:
1518
+ x = randint(0, degree - 1)
1519
+ u = sd.neighbors[path[length - 1]][x]
1520
+ if u != path[length - 2]:
1521
+ break
1522
+
1523
+ flag = False
1524
+ j = 0
1525
+ for i in range(length):
1526
+ if i > length - j - 1:
1527
+ break
1528
+ if flag:
1529
+ t = path[i]
1530
+ path[i] = path[length - j - 1]
1531
+ path[length - j - 1] = t
1532
+ j += 1
1533
+ if path[i] == u:
1534
+ flag = True
1535
+
1536
+ if length == n:
1537
+ if find_path:
1538
+ done = True
1539
+ elif directed and reverse:
1540
+ done = has_edge(rev_sd, path[0], path[n - 1]) != NULL
1541
+ else:
1542
+ done = has_edge(sd, path[n - 1], path[0]) != NULL
1543
+
1544
+ if bigcount * reset_bound > max_iter:
1545
+ output = [int_to_vertex[longest_path[i]] for i in range(longest)]
1546
+ if not isinstance(G, StaticSparseBackend):
1547
+ free_short_digraph(sd)
1548
+ if directed:
1549
+ if not (isinstance(G, StaticSparseBackend) and cg._directed):
1550
+ free_short_digraph(rev_sd)
1551
+ if longest_reversed:
1552
+ return (False, output[::-1])
1553
+ return (False, output)
1554
+ # #
1555
+ # # Output test
1556
+ # #
1557
+
1558
+ if directed and reverse:
1559
+ # We revert the path to work on sd
1560
+ for i in range(length//2):
1561
+ t = path[i]
1562
+ path[i] = path[length - i - 1]
1563
+ path[length - i - 1] = t
1564
+
1565
+ # Test adjacencies
1566
+ cdef bint good = True
1567
+ for i in range(n - 1):
1568
+ u = path[i]
1569
+ v = path[i + 1]
1570
+ if has_edge(sd, u, v) == NULL:
1571
+ good = False
1572
+ break
1573
+ if good is False:
1574
+ raise RuntimeError(f"vertices {int_to_vertex[u]} and {int_to_vertex[v]}"
1575
+ " are consecutive in the cycle but are not adjacent")
1576
+ if not find_path and has_edge(sd, path[n - 1], path[0]) == NULL:
1577
+ raise RuntimeError(f"vertices {int_to_vertex[path[n - 1]]} and "
1578
+ f"{int_to_vertex[path[0]]} are not adjacent")
1579
+
1580
+ output = [int_to_vertex[path[i]] for i in range(length)]
1581
+ if not isinstance(G, StaticSparseBackend):
1582
+ free_short_digraph(sd)
1583
+ if directed and not (isinstance(G, StaticSparseBackend) and cg._directed):
1584
+ free_short_digraph(rev_sd)
1585
+
1586
+ return (True, output)
1587
+
1588
+
1589
+ def transitive_reduction_acyclic(G, immutable=None):
1590
+ r"""
1591
+ Return the transitive reduction of an acyclic digraph.
1592
+
1593
+ INPUT:
1594
+
1595
+ - ``G`` -- an acyclic digraph
1596
+
1597
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
1598
+ mutable/immutable transitive closure. ``immutable=None`` (default) means
1599
+ that the (di)graph and its transitive closure will behave the same way.
1600
+
1601
+ EXAMPLES::
1602
+
1603
+ sage: from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
1604
+ sage: G = posets.BooleanLattice(4).hasse_diagram()
1605
+ sage: G == transitive_reduction_acyclic(G.transitive_closure())
1606
+ True
1607
+
1608
+ TESTS:
1609
+
1610
+ Check the behavior of parameter ``immutable``::
1611
+
1612
+ sage: G = DiGraph([(0, 1)])
1613
+ sage: transitive_reduction_acyclic(G).is_immutable()
1614
+ False
1615
+ sage: transitive_reduction_acyclic(G, immutable=True).is_immutable()
1616
+ True
1617
+ sage: G = DiGraph([(0, 1)], immutable=True)
1618
+ sage: transitive_reduction_acyclic(G).is_immutable()
1619
+ True
1620
+ """
1621
+ cdef int n = G.order()
1622
+ cdef dict v_to_int = {vv: i for i, vv in enumerate(G)}
1623
+ cdef int u, v, i
1624
+
1625
+ cdef list linear_extension
1626
+
1627
+ is_acyclic, linear_extension = G.is_directed_acyclic(certificate=True)
1628
+ if not is_acyclic:
1629
+ raise ValueError("The graph is not directed acyclic")
1630
+
1631
+ linear_extension.reverse()
1632
+
1633
+ cdef binary_matrix_t closure
1634
+
1635
+ # Build the transitive closure of G
1636
+ #
1637
+ # A point is reachable from u if it is one of its neighbours, or if it is
1638
+ # reachable from one of its neighbours.
1639
+ binary_matrix_init(closure, n, n)
1640
+ for uu in linear_extension:
1641
+ u = v_to_int[uu]
1642
+ for vv in G.neighbors_out(uu):
1643
+ v = v_to_int[vv]
1644
+ binary_matrix_set1(closure, u, v)
1645
+ bitset_or(closure.rows[u], closure.rows[u], closure.rows[v])
1646
+
1647
+ # Build the transitive reduction of G
1648
+ #
1649
+ # An edge uv belongs to the transitive reduction of G if no outneighbor of u
1650
+ # can reach v (except v itself, of course).
1651
+ linear_extension.reverse()
1652
+ cdef list useful_edges = []
1653
+ for uu in linear_extension:
1654
+ u = v_to_int[uu]
1655
+ for vv in G.neighbors_out(uu):
1656
+ v = v_to_int[vv]
1657
+ bitset_difference(closure.rows[u], closure.rows[u], closure.rows[v])
1658
+ for vv in G.neighbors_out(uu):
1659
+ v = v_to_int[vv]
1660
+ if binary_matrix_get(closure, u, v):
1661
+ useful_edges.append((uu, vv))
1662
+
1663
+ if immutable is None:
1664
+ immutable = G.is_immutable()
1665
+
1666
+ from sage.graphs.digraph import DiGraph
1667
+ reduced = DiGraph([linear_extension, useful_edges],
1668
+ format='vertices_and_edges',
1669
+ immutable=immutable)
1670
+
1671
+ binary_matrix_free(closure)
1672
+
1673
+ return reduced