passagemath-graphs 10.5.43__cp39-cp39-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 (258) hide show
  1. passagemath_graphs-10.5.43.dist-info/METADATA +293 -0
  2. passagemath_graphs-10.5.43.dist-info/RECORD +258 -0
  3. passagemath_graphs-10.5.43.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.5.43.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 +2552 -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 +125 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1556 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2262 -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 +534 -0
  25. sage/combinat/designs/database.py +5614 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-39-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-39-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-39-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 +548 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2243 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-39-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +966 -0
  44. sage/combinat/designs/resolvable_bibd.py +781 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-39-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/cartesian_product.py +493 -0
  57. sage/combinat/posets/d_complete.py +182 -0
  58. sage/combinat/posets/elements.py +273 -0
  59. sage/combinat/posets/forest.py +30 -0
  60. sage/combinat/posets/hasse_cython.cpython-39-aarch64-linux-gnu.so +0 -0
  61. sage/combinat/posets/hasse_cython.pyx +174 -0
  62. sage/combinat/posets/hasse_diagram.py +3678 -0
  63. sage/combinat/posets/incidence_algebras.py +796 -0
  64. sage/combinat/posets/lattices.py +5119 -0
  65. sage/combinat/posets/linear_extension_iterator.cpython-39-aarch64-linux-gnu.so +0 -0
  66. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  67. sage/combinat/posets/linear_extensions.py +1039 -0
  68. sage/combinat/posets/mobile.py +275 -0
  69. sage/combinat/posets/moebius_algebra.py +776 -0
  70. sage/combinat/posets/poset_examples.py +2131 -0
  71. sage/combinat/posets/posets.py +9169 -0
  72. sage/combinat/rooted_tree.py +1070 -0
  73. sage/combinat/shard_order.py +239 -0
  74. sage/combinat/tamari_lattices.py +384 -0
  75. sage/combinat/yang_baxter_graph.py +923 -0
  76. sage/databases/all__sagemath_graphs.py +1 -0
  77. sage/databases/knotinfo_db.py +1230 -0
  78. sage/ext_data/all__sagemath_graphs.py +1 -0
  79. sage/ext_data/graphs/graph_plot_js.html +330 -0
  80. sage/ext_data/kenzo/CP2.txt +45 -0
  81. sage/ext_data/kenzo/CP3.txt +349 -0
  82. sage/ext_data/kenzo/CP4.txt +4774 -0
  83. sage/ext_data/kenzo/README.txt +49 -0
  84. sage/ext_data/kenzo/S4.txt +20 -0
  85. sage/graphs/all.py +42 -0
  86. sage/graphs/asteroidal_triples.cpython-39-aarch64-linux-gnu.so +0 -0
  87. sage/graphs/asteroidal_triples.pyx +299 -0
  88. sage/graphs/base/all.py +1 -0
  89. sage/graphs/base/boost_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sage/graphs/base/boost_graph.pxd +106 -0
  91. sage/graphs/base/boost_graph.pyx +3045 -0
  92. sage/graphs/base/c_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  93. sage/graphs/base/c_graph.pxd +106 -0
  94. sage/graphs/base/c_graph.pyx +5096 -0
  95. sage/graphs/base/dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  96. sage/graphs/base/dense_graph.pxd +26 -0
  97. sage/graphs/base/dense_graph.pyx +757 -0
  98. sage/graphs/base/graph_backends.cpython-39-aarch64-linux-gnu.so +0 -0
  99. sage/graphs/base/graph_backends.pxd +5 -0
  100. sage/graphs/base/graph_backends.pyx +797 -0
  101. sage/graphs/base/overview.py +85 -0
  102. sage/graphs/base/sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  103. sage/graphs/base/sparse_graph.pxd +90 -0
  104. sage/graphs/base/sparse_graph.pyx +1653 -0
  105. sage/graphs/base/static_dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  106. sage/graphs/base/static_dense_graph.pxd +5 -0
  107. sage/graphs/base/static_dense_graph.pyx +1032 -0
  108. sage/graphs/base/static_sparse_backend.cpython-39-aarch64-linux-gnu.so +0 -0
  109. sage/graphs/base/static_sparse_backend.pxd +27 -0
  110. sage/graphs/base/static_sparse_backend.pyx +1580 -0
  111. sage/graphs/base/static_sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  112. sage/graphs/base/static_sparse_graph.pxd +37 -0
  113. sage/graphs/base/static_sparse_graph.pyx +1304 -0
  114. sage/graphs/bipartite_graph.py +2709 -0
  115. sage/graphs/centrality.cpython-39-aarch64-linux-gnu.so +0 -0
  116. sage/graphs/centrality.pyx +965 -0
  117. sage/graphs/cographs.py +519 -0
  118. sage/graphs/comparability.cpython-39-aarch64-linux-gnu.so +0 -0
  119. sage/graphs/comparability.pyx +813 -0
  120. sage/graphs/connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/connectivity.pxd +157 -0
  122. sage/graphs/connectivity.pyx +4813 -0
  123. sage/graphs/convexity_properties.cpython-39-aarch64-linux-gnu.so +0 -0
  124. sage/graphs/convexity_properties.pxd +16 -0
  125. sage/graphs/convexity_properties.pyx +827 -0
  126. sage/graphs/digraph.py +4410 -0
  127. sage/graphs/digraph_generators.py +1921 -0
  128. sage/graphs/distances_all_pairs.cpython-39-aarch64-linux-gnu.so +0 -0
  129. sage/graphs/distances_all_pairs.pxd +12 -0
  130. sage/graphs/distances_all_pairs.pyx +2938 -0
  131. sage/graphs/domination.py +1363 -0
  132. sage/graphs/dot2tex_utils.py +100 -0
  133. sage/graphs/edge_connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  134. sage/graphs/edge_connectivity.pyx +1215 -0
  135. sage/graphs/generators/all.py +1 -0
  136. sage/graphs/generators/basic.py +1769 -0
  137. sage/graphs/generators/chessboard.py +538 -0
  138. sage/graphs/generators/classical_geometries.py +1611 -0
  139. sage/graphs/generators/degree_sequence.py +235 -0
  140. sage/graphs/generators/distance_regular.cpython-39-aarch64-linux-gnu.so +0 -0
  141. sage/graphs/generators/distance_regular.pyx +2846 -0
  142. sage/graphs/generators/families.py +4749 -0
  143. sage/graphs/generators/intersection.py +565 -0
  144. sage/graphs/generators/platonic_solids.py +262 -0
  145. sage/graphs/generators/random.py +2623 -0
  146. sage/graphs/generators/smallgraphs.py +5741 -0
  147. sage/graphs/generators/world_map.py +724 -0
  148. sage/graphs/generic_graph.py +26395 -0
  149. sage/graphs/generic_graph_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  150. sage/graphs/generic_graph_pyx.pxd +34 -0
  151. sage/graphs/generic_graph_pyx.pyx +1626 -0
  152. sage/graphs/genus.cpython-39-aarch64-linux-gnu.so +0 -0
  153. sage/graphs/genus.pyx +623 -0
  154. sage/graphs/graph.py +9362 -0
  155. sage/graphs/graph_coloring.cpython-39-aarch64-linux-gnu.so +0 -0
  156. sage/graphs/graph_coloring.pyx +2284 -0
  157. sage/graphs/graph_database.py +1122 -0
  158. sage/graphs/graph_decompositions/all.py +1 -0
  159. sage/graphs/graph_decompositions/bandwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  160. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  161. sage/graphs/graph_decompositions/clique_separators.cpython-39-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/clique_separators.pyx +595 -0
  163. sage/graphs/graph_decompositions/cutwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  165. sage/graphs/graph_decompositions/fast_digraph.cpython-39-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  167. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  168. sage/graphs/graph_decompositions/graph_products.cpython-39-aarch64-linux-gnu.so +0 -0
  169. sage/graphs/graph_decompositions/graph_products.pyx +462 -0
  170. sage/graphs/graph_decompositions/modular_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  173. sage/graphs/graph_decompositions/slice_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  174. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.pyx +1080 -0
  176. sage/graphs/graph_decompositions/tree_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  177. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  179. sage/graphs/graph_decompositions/vertex_separation.cpython-39-aarch64-linux-gnu.so +0 -0
  180. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  181. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  182. sage/graphs/graph_editor.py +82 -0
  183. sage/graphs/graph_generators.py +3301 -0
  184. sage/graphs/graph_generators_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  185. sage/graphs/graph_generators_pyx.pyx +95 -0
  186. sage/graphs/graph_input.py +812 -0
  187. sage/graphs/graph_latex.py +2064 -0
  188. sage/graphs/graph_list.py +367 -0
  189. sage/graphs/graph_plot.py +1749 -0
  190. sage/graphs/graph_plot_js.py +338 -0
  191. sage/graphs/hyperbolicity.cpython-39-aarch64-linux-gnu.so +0 -0
  192. sage/graphs/hyperbolicity.pyx +1702 -0
  193. sage/graphs/hypergraph_generators.py +364 -0
  194. sage/graphs/independent_sets.cpython-39-aarch64-linux-gnu.so +0 -0
  195. sage/graphs/independent_sets.pxd +13 -0
  196. sage/graphs/independent_sets.pyx +402 -0
  197. sage/graphs/isgci.py +1033 -0
  198. sage/graphs/isoperimetric_inequalities.cpython-39-aarch64-linux-gnu.so +0 -0
  199. sage/graphs/isoperimetric_inequalities.pyx +453 -0
  200. sage/graphs/line_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/line_graph.pyx +627 -0
  202. sage/graphs/lovasz_theta.py +77 -0
  203. sage/graphs/matching.py +1633 -0
  204. sage/graphs/matching_covered_graph.py +3566 -0
  205. sage/graphs/orientations.py +1504 -0
  206. sage/graphs/partial_cube.py +459 -0
  207. sage/graphs/path_enumeration.cpython-39-aarch64-linux-gnu.so +0 -0
  208. sage/graphs/path_enumeration.pyx +2040 -0
  209. sage/graphs/pq_trees.py +1129 -0
  210. sage/graphs/print_graphs.py +201 -0
  211. sage/graphs/schnyder.py +865 -0
  212. sage/graphs/spanning_tree.cpython-39-aarch64-linux-gnu.so +0 -0
  213. sage/graphs/spanning_tree.pyx +1457 -0
  214. sage/graphs/strongly_regular_db.cpython-39-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/strongly_regular_db.pyx +3340 -0
  216. sage/graphs/traversals.cpython-39-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/traversals.pxd +9 -0
  218. sage/graphs/traversals.pyx +1871 -0
  219. sage/graphs/trees.cpython-39-aarch64-linux-gnu.so +0 -0
  220. sage/graphs/trees.pxd +15 -0
  221. sage/graphs/trees.pyx +310 -0
  222. sage/graphs/tutte_polynomial.py +713 -0
  223. sage/graphs/views.cpython-39-aarch64-linux-gnu.so +0 -0
  224. sage/graphs/views.pyx +794 -0
  225. sage/graphs/weakly_chordal.cpython-39-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/weakly_chordal.pyx +562 -0
  227. sage/groups/all__sagemath_graphs.py +1 -0
  228. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  229. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-39-aarch64-linux-gnu.so +0 -0
  231. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  233. sage/knots/all.py +6 -0
  234. sage/knots/free_knotinfo_monoid.py +507 -0
  235. sage/knots/gauss_code.py +291 -0
  236. sage/knots/knot.py +682 -0
  237. sage/knots/knot_table.py +284 -0
  238. sage/knots/knotinfo.py +2880 -0
  239. sage/knots/link.py +4682 -0
  240. sage/sandpiles/all.py +13 -0
  241. sage/sandpiles/examples.py +225 -0
  242. sage/sandpiles/sandpile.py +6365 -0
  243. sage/topology/all.py +22 -0
  244. sage/topology/cell_complex.py +1214 -0
  245. sage/topology/cubical_complex.py +1977 -0
  246. sage/topology/delta_complex.py +1806 -0
  247. sage/topology/filtered_simplicial_complex.py +744 -0
  248. sage/topology/moment_angle_complex.py +823 -0
  249. sage/topology/simplicial_complex.py +5161 -0
  250. sage/topology/simplicial_complex_catalog.py +86 -0
  251. sage/topology/simplicial_complex_examples.py +1692 -0
  252. sage/topology/simplicial_complex_homset.py +205 -0
  253. sage/topology/simplicial_complex_morphism.py +836 -0
  254. sage/topology/simplicial_set.py +4102 -0
  255. sage/topology/simplicial_set_catalog.py +55 -0
  256. sage/topology/simplicial_set_constructions.py +2954 -0
  257. sage/topology/simplicial_set_examples.py +865 -0
  258. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1977 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # sage.doctest: needs sage.graphs
3
+ r"""
4
+ Finite cubical complexes
5
+
6
+ AUTHORS:
7
+
8
+ - John H. Palmieri (2009-08)
9
+
10
+ This module implements the basic structure of finite cubical
11
+ complexes. For full mathematical details, see Kaczynski, Mischaikow,
12
+ and Mrozek [KMM2004]_, for example.
13
+
14
+ Cubical complexes are topological spaces built from gluing together
15
+ cubes of various dimensions; the collection of cubes must be closed
16
+ under taking faces, just as with a simplicial complex. In this
17
+ context, a "cube" means a product of intervals of length 1 or length 0
18
+ (degenerate intervals), with integer endpoints, and its faces are
19
+ obtained by using the nondegenerate intervals: if `C` is a cube -- a
20
+ product of degenerate and nondegenerate intervals -- and if `[i,i+1]`
21
+ is the `k`-th nondegenerate factor, then `C` has two faces indexed by
22
+ `k`: the cubes obtained by replacing `[i, i+1]` with `[i, i]` or
23
+ `[i+1, i+1]`.
24
+
25
+ So to construct a space homeomorphic to a circle as a cubical complex,
26
+ we could take for example the four line segments in the plane from
27
+ `(0,2)` to `(0,3)` to `(1,3)` to `(1,2)` to `(0,2)`. In Sage, this is
28
+ done with the following command::
29
+
30
+ sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]),
31
+ ....: ([0,1], [2,2]), ([1,1], [2,3])]); S1
32
+ Cubical complex with 4 vertices and 8 cubes
33
+
34
+ The argument to ``CubicalComplex`` is a list of the maximal "cubes" in
35
+ the complex. Each "cube" can be an instance of the class ``Cube`` or
36
+ a list (or tuple) of "intervals", and an "interval" is a pair of
37
+ integers, of one of the two forms `[i, i]` or `[i, i+1]`. So the
38
+ cubical complex ``S1`` above has four maximal cubes::
39
+
40
+ sage: len(S1.maximal_cells())
41
+ 4
42
+ sage: sorted(S1.maximal_cells())
43
+ [[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]]
44
+
45
+ The first of these, for instance, is the product of the degenerate
46
+ interval `[0,0]` with the unit interval `[2,3]`: this is the line
47
+ segment in the plane from `(0,2)` to `(0,3)`. We could form a
48
+ topologically equivalent space by inserting some degenerate simplices::
49
+
50
+ sage: S1.homology() # needs sage.modules
51
+ {0: 0, 1: Z}
52
+ sage: X = CubicalComplex([([0,0], [2,3], [2]), ([0,1], [3,3], [2]),
53
+ ....: ([0,1], [2,2], [2]), ([1,1], [2,3], [2])])
54
+ sage: X.homology() # needs sage.modules
55
+ {0: 0, 1: Z}
56
+
57
+ Topologically, the cubical complex ``X`` consists of four edges of a
58
+ square in `\RR^3`: the same unit square as ``S1``, but embedded in
59
+ `\RR^3` with `z`-coordinate equal to 2. Thus ``X`` is homeomorphic to
60
+ ``S1`` (in fact, they're "cubically equivalent"), and this is
61
+ reflected in the fact that they have isomorphic homology groups.
62
+
63
+ .. NOTE::
64
+
65
+ This class derives from
66
+ :class:`~sage.homology.cell_complex.GenericCellComplex`, and so
67
+ inherits its methods. Some of those methods are not listed here;
68
+ see the :mod:`Generic Cell Complex <sage.homology.cell_complex>`
69
+ page instead.
70
+ """
71
+
72
+ from copy import copy
73
+ from .cell_complex import GenericCellComplex
74
+ from sage.structure.sage_object import SageObject
75
+ from sage.rings.integer import Integer
76
+ from sage.sets.set import Set
77
+ from sage.rings.integer_ring import ZZ
78
+ from sage.rings.rational_field import QQ
79
+ from sage.misc.cachefunc import cached_method
80
+ from sage.misc.lazy_import import lazy_import
81
+ from sage.misc.superseded import deprecation
82
+ from functools import total_ordering
83
+
84
+ lazy_import('sage.matrix.constructor', 'matrix')
85
+
86
+
87
+ @total_ordering
88
+ class Cube(SageObject):
89
+ r"""
90
+ Define a cube for use in constructing a cubical complex.
91
+
92
+ "Elementary cubes" are products of intervals with integer
93
+ endpoints, each of which is either a unit interval or a degenerate
94
+ (length 0) interval; for example,
95
+
96
+ .. MATH::
97
+
98
+ [0,1] \times [3,4] \times [2,2] \times [1,2]
99
+
100
+ is a 3-dimensional cube (since one of the intervals is degenerate)
101
+ embedded in `\RR^4`.
102
+
103
+ INPUT:
104
+
105
+ - ``data`` -- list or tuple of terms of the form ``(i,i+1)`` or
106
+ ``(i,i)`` or ``(i,)``; the last two are degenerate intervals
107
+
108
+ OUTPUT: an elementary cube
109
+
110
+ Each cube is stored in a standard form: a tuple of tuples, with a
111
+ nondegenerate interval ``[j,j]`` represented by ``(j,j)``, not
112
+ ``(j,)``. (This is so that for any interval ``I``, ``I[1]`` will
113
+ produce a value, not an :exc:`IndexError`.)
114
+
115
+ EXAMPLES::
116
+
117
+ sage: from sage.topology.cubical_complex import Cube
118
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C
119
+ [1,2] x [5,5] x [6,7] x [-1,0]
120
+ sage: C.dimension() # number of nondegenerate intervals
121
+ 3
122
+ sage: C.nondegenerate_intervals() # indices of these intervals
123
+ [0, 2, 3]
124
+ sage: C.face(1, upper=False)
125
+ [1,2] x [5,5] x [6,6] x [-1,0]
126
+ sage: C.face(1, upper=True)
127
+ [1,2] x [5,5] x [7,7] x [-1,0]
128
+ sage: Cube(()).dimension() # empty cube has dimension -1
129
+ -1
130
+ """
131
+ def __init__(self, data):
132
+ """
133
+ Define a cube for use in constructing a cubical complex.
134
+
135
+ See ``Cube`` for more information.
136
+
137
+ EXAMPLES::
138
+
139
+ sage: from sage.topology.cubical_complex import Cube
140
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C # indirect doctest
141
+ [1,2] x [5,5] x [6,7] x [-1,0]
142
+ sage: C == loads(dumps(C))
143
+ True
144
+ """
145
+ if isinstance(data, Cube):
146
+ data = tuple(data)
147
+ new_data = []
148
+ nondegenerate = []
149
+ i = 0
150
+ for x in data:
151
+ if len(x) == 2:
152
+ try:
153
+ Integer(x[0])
154
+ except TypeError:
155
+ raise ValueError("the interval %s is not of the correct form" % x)
156
+ if x[0] + 1 == x[1]:
157
+ nondegenerate.append(i)
158
+ elif x[0] != x[1]:
159
+ raise ValueError("the interval %s is not of the correct form" % x)
160
+ new_data.append(tuple(x))
161
+ elif len(x) == 1:
162
+ y = tuple(x)
163
+ new_data.append(y+y)
164
+ elif len(x) != 1:
165
+ raise ValueError("the interval %s is not of the correct form" % x)
166
+ i += 1
167
+ self.__tuple = tuple(new_data)
168
+ self.__nondegenerate = nondegenerate
169
+
170
+ def tuple(self):
171
+ """
172
+ The tuple attached to this cube.
173
+
174
+ EXAMPLES::
175
+
176
+ sage: from sage.topology.cubical_complex import Cube
177
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
178
+ sage: C.tuple()
179
+ ((1, 2), (5, 5), (6, 7), (-1, 0))
180
+ """
181
+ return self.__tuple
182
+
183
+ def is_face(self, other):
184
+ """
185
+ Return ``True`` iff this cube is a face of other.
186
+
187
+ EXAMPLES::
188
+
189
+ sage: from sage.topology.cubical_complex import Cube
190
+ sage: C1 = Cube([[1,2], [5,], [6,7], [-1, 0]])
191
+ sage: C2 = Cube([[1,2], [5,], [6,], [-1, 0]])
192
+ sage: C1.is_face(C2)
193
+ False
194
+ sage: C1.is_face(C1)
195
+ True
196
+ sage: C2.is_face(C1)
197
+ True
198
+ """
199
+ def is_subinterval(i1, i2):
200
+ return ((i1[0] == i2[0] and i1[1] == i2[1]) or
201
+ (i1[0] == i2[0] and i1[1] == i2[0]) or
202
+ (i1[0] == i2[1] and i1[1] == i2[1]))
203
+
204
+ t = self.tuple()
205
+ u = other.tuple()
206
+ if len(t) == len(u):
207
+ # these must be equal for self to be a face of other
208
+ return all(is_subinterval(ti, ui) for ti, ui in zip(t, u))
209
+ else:
210
+ return False
211
+
212
+ def _translate(self, vec):
213
+ """
214
+ Translate ``self`` by ``vec``.
215
+
216
+ INPUT:
217
+
218
+ - ``vec`` -- anything which can be converted to a tuple of integers
219
+
220
+ OUTPUT: cube; the translation of ``self`` by ``vec``
221
+
222
+ If ``vec`` is shorter than the list of intervals forming the
223
+ cube, pad with zeroes, and similarly if the cube's defining
224
+ tuple is too short.
225
+
226
+ EXAMPLES::
227
+
228
+ sage: from sage.topology.cubical_complex import Cube
229
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
230
+ sage: C._translate((-12,))
231
+ [-11,-10] x [5,5] x [6,7] x [-1,0]
232
+ sage: C._translate((0, 0, 0, 0, 0, 5))
233
+ [1,2] x [5,5] x [6,7] x [-1,0] x [0,0] x [5,5]
234
+ """
235
+ t = self.__tuple
236
+ embed = max(len(t), len(vec))
237
+ t = t + ((0, 0),) * (embed-len(t))
238
+ vec = tuple(vec) + (0,) * (embed-len(vec))
239
+ new = []
240
+ for (a, b) in zip(t, vec):
241
+ new.append([a[0] + b, a[1] + b])
242
+ return Cube(new)
243
+
244
+ def __getitem__(self, n):
245
+ """
246
+ Return the `n`-th interval in this cube.
247
+
248
+ INPUT:
249
+
250
+ - ``n`` -- integer
251
+
252
+ OUTPUT: tuple representing the `n`-th interval in the cube
253
+
254
+ EXAMPLES::
255
+
256
+ sage: from sage.topology.cubical_complex import Cube
257
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
258
+ sage: C[2]
259
+ (6, 7)
260
+ sage: C[1]
261
+ (5, 5)
262
+ """
263
+ return self.__tuple[n]
264
+
265
+ def __iter__(self):
266
+ """
267
+ Iterator for the intervals of this cube.
268
+
269
+ EXAMPLES::
270
+
271
+ sage: from sage.topology.cubical_complex import Cube
272
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
273
+ sage: [x[0] for x in C]
274
+ [1, 5, 6, -1]
275
+ """
276
+ return iter(self.__tuple)
277
+
278
+ def __add__(self, other):
279
+ """
280
+ Cube obtained by concatenating the underlying tuples of the
281
+ two arguments.
282
+
283
+ INPUT:
284
+
285
+ - ``other`` -- another cube
286
+
287
+ OUTPUT: the product of ``self`` and ``other``, as a Cube
288
+
289
+ EXAMPLES::
290
+
291
+ sage: from sage.topology.cubical_complex import Cube
292
+ sage: C = Cube([[1,2], [3,]])
293
+ sage: D = Cube([[4], [0,1]])
294
+ sage: C.product(D)
295
+ [1,2] x [3,3] x [4,4] x [0,1]
296
+
297
+ You can also use ``__add__`` or ``+`` or ``__mul__`` or ``*``::
298
+
299
+ sage: D * C
300
+ [4,4] x [0,1] x [1,2] x [3,3]
301
+ sage: D + C * C
302
+ [4,4] x [0,1] x [1,2] x [3,3] x [1,2] x [3,3]
303
+ """
304
+ return Cube(self.__tuple + other.__tuple)
305
+
306
+ # the __add__ operation actually produces the product of the two cubes
307
+ __mul__ = __add__
308
+ product = __add__
309
+
310
+ def nondegenerate_intervals(self):
311
+ """
312
+ The list of indices of nondegenerate intervals of this cube.
313
+
314
+ EXAMPLES::
315
+
316
+ sage: from sage.topology.cubical_complex import Cube
317
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
318
+ sage: C.nondegenerate_intervals()
319
+ [0, 2, 3]
320
+ sage: C = Cube([[1,], [5,], [6,], [-1,]])
321
+ sage: C.nondegenerate_intervals()
322
+ []
323
+ """
324
+ return self.__nondegenerate
325
+
326
+ def dimension(self):
327
+ """
328
+ The dimension of this cube: the number of its nondegenerate intervals.
329
+
330
+ EXAMPLES::
331
+
332
+ sage: from sage.topology.cubical_complex import Cube
333
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]])
334
+ sage: C.dimension()
335
+ 3
336
+ sage: C = Cube([[1,], [5,], [6,], [-1,]])
337
+ sage: C.dimension()
338
+ 0
339
+ sage: Cube([]).dimension() # empty cube has dimension -1
340
+ -1
341
+ """
342
+ if len(self.__tuple) == 0: # empty cube
343
+ return -1
344
+ return len(self.nondegenerate_intervals())
345
+
346
+ def face(self, n, upper=True):
347
+ """
348
+ The `n`-th primary face of this cube.
349
+
350
+ INPUT:
351
+
352
+ - ``n`` -- integer between 0 and one less than the dimension
353
+ of this cube
354
+ - ``upper`` -- boolean (default=True);if ``True``, return the "upper"
355
+ `n`-th primary face; otherwise, return the "lower" `n`-th primary
356
+ face
357
+
358
+ OUTPUT: the cube obtained by replacing the `n`-th non-degenerate
359
+ interval with either its upper or lower endpoint.
360
+
361
+ EXAMPLES::
362
+
363
+ sage: from sage.topology.cubical_complex import Cube
364
+ sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C
365
+ [1,2] x [5,5] x [6,7] x [-1,0]
366
+ sage: C.face(0)
367
+ [2,2] x [5,5] x [6,7] x [-1,0]
368
+ sage: C.face(0, upper=False)
369
+ [1,1] x [5,5] x [6,7] x [-1,0]
370
+ sage: C.face(1)
371
+ [1,2] x [5,5] x [7,7] x [-1,0]
372
+ sage: C.face(2, upper=False)
373
+ [1,2] x [5,5] x [6,7] x [-1,-1]
374
+ sage: C.face(3)
375
+ Traceback (most recent call last):
376
+ ...
377
+ ValueError: can only compute the n-th face if 0 <= n < dim
378
+ """
379
+ if n < 0 or n >= self.dimension():
380
+ raise ValueError("can only compute the n-th face if 0 <= n < dim")
381
+ idx = self.nondegenerate_intervals()[n]
382
+ t = self.__tuple
383
+ if upper:
384
+ new = t[idx][1]
385
+ else:
386
+ new = t[idx][0]
387
+ return Cube(t[0:idx] + ((new, new),) + t[idx+1:])
388
+
389
+ def faces(self):
390
+ """
391
+ The list of faces (of codimension 1) of this cube.
392
+
393
+ EXAMPLES::
394
+
395
+ sage: from sage.topology.cubical_complex import Cube
396
+ sage: C = Cube([[1,2], [3,4]])
397
+ sage: C.faces()
398
+ [[2,2] x [3,4], [1,2] x [4,4], [1,1] x [3,4], [1,2] x [3,3]]
399
+ """
400
+ upper = [self.face(i, True) for i in range(self.dimension())]
401
+ lower = [self.face(i, False) for i in range(self.dimension())]
402
+ return upper + lower
403
+
404
+ def faces_as_pairs(self):
405
+ """
406
+ The list of faces (of codimension 1) of this cube, as pairs
407
+ (upper, lower).
408
+
409
+ EXAMPLES::
410
+
411
+ sage: from sage.topology.cubical_complex import Cube
412
+ sage: C = Cube([[1,2], [3,4]])
413
+ sage: C.faces_as_pairs()
414
+ [([2,2] x [3,4], [1,1] x [3,4]), ([1,2] x [4,4], [1,2] x [3,3])]
415
+ """
416
+ upper = [self.face(i, True) for i in range(self.dimension())]
417
+ lower = [self.face(i, False) for i in range(self.dimension())]
418
+ return list(zip(upper, lower))
419
+
420
+ def _compare_for_gluing(self, other):
421
+ r"""
422
+ Given two cubes ``self`` and ``other``, describe how to
423
+ transform them so that they become equal.
424
+
425
+ INPUT:
426
+
427
+ - ``other`` -- a cube of the same dimension as ``self``
428
+
429
+ OUTPUT: a triple ``(insert_self, insert_other, translate)``.
430
+ ``insert_self`` is a tuple with entries ``(index, (list of
431
+ degenerate intervals))``. ``insert_other`` is similar.
432
+ ``translate`` is a tuple of integers, suitable as a second
433
+ argument for the ``_translate`` method.
434
+
435
+ To do this, ``self`` and ``other`` must have the same
436
+ dimension; degenerate intervals from ``other`` are added to
437
+ ``self``, and vice versa. Intervals in ``other`` are
438
+ translated so that they coincide with the intervals in
439
+ ``self``. The output is a triple, as noted above: in the
440
+ tuple ``insert_self``, an entry like ``(3, (3, 4, 0))`` means
441
+ that in position 3 in ``self``, insert the degenerate
442
+ intervals ``[3,3]``, ``[4,4]``, and ``[0,0]``. The same goes
443
+ for ``insert_other``. After applying the translations to the
444
+ cube ``other``, call ``_translate`` with argument the tuple
445
+ ``translate``.
446
+
447
+ This is used in forming connected sums of cubical complexes:
448
+ the two complexes are modified, via this method, so that they
449
+ have a cube which matches up, then those matching cubes are
450
+ removed.
451
+
452
+ In the example below, this method is called with arguments
453
+ ``C1`` and ``C2``, where
454
+
455
+ .. MATH::
456
+
457
+ C1 = [0,1] \times [3] \times [4] \times [6,7] \\
458
+ C2 = [2] \times [7,8] \times [9] \times [1,2] \times [0] \times [5]
459
+
460
+ To C1, we need to add [2] in position 0 and [0] and [5] in
461
+ position 5. To C2, we need to add [4] in position 3. Once
462
+ this has been done, we need to translate the new C2 by the
463
+ vector ``(0, -7, -6, 0, 5, 0, 0)``.
464
+
465
+ EXAMPLES::
466
+
467
+ sage: from sage.topology.cubical_complex import Cube
468
+ sage: C1 = Cube([[0,1], [3], [4], [6,7]])
469
+ sage: C2 = Cube([[2], [7,8], [9], [1,2], [0], [5]])
470
+ sage: C1._compare_for_gluing(C2)
471
+ ([(0, ((2, 2),)), (5, ((0, 0), (5, 5)))], [(3, ((4, 4),))], [0, -7, -6, 0, 5, 0, 0])
472
+
473
+ sage: C1 = Cube([[1,1], [0,1]])
474
+ sage: C2 = Cube([[2,3], [4,4], [5,5]])
475
+ sage: C1._compare_for_gluing(C2)
476
+ ([(2, ((4, 4), (5, 5)))], [(0, ((1, 1),))], [0, -2, 0, 0])
477
+ """
478
+ d = self.dimension()
479
+ if d != other.dimension():
480
+ raise ValueError("cubes must be of the same dimension")
481
+ insert_self = []
482
+ insert_other = []
483
+ translate = []
484
+ self_tuple = self.tuple()
485
+ other_tuple = other.tuple()
486
+ nondegen = (list(zip(self.nondegenerate_intervals(),
487
+ other.nondegenerate_intervals()))
488
+ + [(len(self_tuple), len(other_tuple))])
489
+ old = (-1, -1)
490
+ self_added = 0
491
+ other_added = 0
492
+
493
+ for current in nondegen:
494
+ # number of positions between nondegenerate intervals:
495
+ self_diff = current[0] - old[0]
496
+ other_diff = current[1] - old[1]
497
+ diff = self_diff - other_diff
498
+
499
+ if diff < 0:
500
+ insert_self.append((old[0] + self_diff + self_added,
501
+ other.tuple()[current[1]+diff:current[1]]))
502
+ common_terms = self_diff
503
+ diff = -diff
504
+ self_added += diff
505
+ elif diff > 0:
506
+ insert_other.append((old[1] + other_diff + other_added,
507
+ self.tuple()[current[0]-diff:current[0]]))
508
+ common_terms = other_diff
509
+ other_added += diff
510
+ else:
511
+ common_terms = other_diff
512
+
513
+ if old[0] > -1:
514
+ translate.extend([self_tuple[old[0]+idx][0] -
515
+ other_tuple[old[1]+idx][0] for idx in
516
+ range(common_terms)])
517
+ translate.extend(diff*[0])
518
+ old = current
519
+
520
+ return (insert_self, insert_other, translate)
521
+
522
+ def _triangulation_(self):
523
+ r"""
524
+ Triangulate this cube by "pulling vertices," as described by
525
+ Hetyei. Return a list of simplices which triangulate
526
+ ``self``.
527
+
528
+ ALGORITHM:
529
+
530
+ If the cube is given by
531
+
532
+ .. MATH::
533
+
534
+ C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k]
535
+
536
+ let `v_1` be the "upper" corner of `C`: `v` is the point
537
+ `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval
538
+ `[i_n, j_n]` is non-degenerate and form `v_2` by replacing
539
+ `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex
540
+ so defined will be `(i_1, ..., i_k)`. These vertices define a
541
+ simplex, as do the vertices obtained by making different
542
+ choices at each stage. Return the list of such simplices;
543
+ thus if `C` is `n`-dimensional, then it is subdivided into
544
+ `n!` simplices.
545
+
546
+ REFERENCES:
547
+
548
+ - G. Hetyei, "On the Stanley ring of a cubical complex",
549
+ Discrete Comput. Geom. 14 (1995), 305-330.
550
+
551
+ EXAMPLES::
552
+
553
+ sage: from sage.topology.cubical_complex import Cube
554
+ sage: C = Cube([[1,2], [3,4]]); C
555
+ [1,2] x [3,4]
556
+ sage: C._triangulation_()
557
+ [((1, 3), (1, 4), (2, 4)), ((1, 3), (2, 3), (2, 4))]
558
+ sage: C = Cube([[1,2], [3,4], [8,9]])
559
+ sage: len(C._triangulation_())
560
+ 6
561
+ """
562
+ from .simplicial_complex import Simplex
563
+ if self.dimension() < 0: # the empty cube
564
+ return [Simplex(())] # the empty simplex
565
+ v = tuple([max(j) for j in self.tuple()])
566
+ Sv = Simplex((v,))
567
+ if self.dimension() == 0: # just v
568
+ return [Sv]
569
+ return [S.join(Sv, rename_vertices=False)
570
+ for i in range(self.dimension())
571
+ for S in self.face(i, upper=False)._triangulation_()]
572
+
573
+ def alexander_whitney(self, dim):
574
+ r"""
575
+ Subdivide this cube into pairs of cubes.
576
+
577
+ This provides a cubical approximation for the diagonal map
578
+ `K \to K \times K`.
579
+
580
+ INPUT:
581
+
582
+ - ``dim`` -- integer between 0 and one more than the
583
+ dimension of this cube
584
+
585
+ OUTPUT:
586
+
587
+ - a list containing triples ``(coeff, left, right)``
588
+
589
+ This uses the algorithm described by Pilarczyk and Réal [PR2015]_
590
+ on p. 267; the formula is originally due to Serre. Calling
591
+ this method ``alexander_whitney`` is an abuse of notation,
592
+ since the actual Alexander-Whitney map goes from `C(K \times
593
+ L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated
594
+ chain complex, but this subdivision of cubes is at the heart
595
+ of it.
596
+
597
+ EXAMPLES::
598
+
599
+ sage: from sage.topology.cubical_complex import Cube
600
+ sage: C1 = Cube([[0,1], [3,4]])
601
+ sage: C1.alexander_whitney(0)
602
+ [(1, [0,0] x [3,3], [0,1] x [3,4])]
603
+ sage: C1.alexander_whitney(1)
604
+ [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])]
605
+ sage: C1.alexander_whitney(2)
606
+ [(1, [0,1] x [3,4], [1,1] x [4,4])]
607
+ """
608
+ from sage.sets.set import Set
609
+ N = Set(self.nondegenerate_intervals())
610
+ result = []
611
+ for J in N.subsets(dim):
612
+ Jprime = N.difference(J)
613
+ nu = 0
614
+ for i in J:
615
+ for j in Jprime:
616
+ if j < i:
617
+ nu += 1
618
+ t = self.tuple()
619
+ left = []
620
+ right = []
621
+ for j in range(len(t)):
622
+ if j in Jprime:
623
+ left.append((t[j][0], t[j][0]))
624
+ right.append(t[j])
625
+ elif j in J:
626
+ left.append(t[j])
627
+ right.append((t[j][1], t[j][1]))
628
+ else:
629
+ left.append(t[j])
630
+ right.append(t[j])
631
+ result.append(((-1)**nu, Cube(left), Cube(right)))
632
+ return result
633
+
634
+ def __eq__(self, other):
635
+ """
636
+ Return ``True`` iff this cube is the same as ``other``: that is,
637
+ if they are the product of the same intervals in the same
638
+ order.
639
+
640
+ INPUT:
641
+
642
+ - ``other`` -- another cube
643
+
644
+ EXAMPLES::
645
+
646
+ sage: from sage.topology.cubical_complex import Cube
647
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
648
+ sage: C2 = Cube([[1], [2,3], [4,5]])
649
+ sage: C3 = Cube([[0], [2,3], [4,5]])
650
+ sage: C1 == C2 # indirect doctest
651
+ True
652
+ sage: C1 == C3 # indirect doctest
653
+ False
654
+ """
655
+ return tuple(self) == tuple(other)
656
+
657
+ def __ne__(self, other):
658
+ """
659
+ Return ``True`` iff this cube is not equal to ``other``.
660
+
661
+ INPUT:
662
+
663
+ - ``other`` -- another cube
664
+
665
+ EXAMPLES::
666
+
667
+ sage: from sage.topology.cubical_complex import Cube
668
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
669
+ sage: C2 = Cube([[1], [2,3], [4,5]])
670
+ sage: C3 = Cube([[0], [2,3], [4,5]])
671
+ sage: C1 != C2 # indirect doctest
672
+ False
673
+ sage: C1 != C3 # indirect doctest
674
+ True
675
+ """
676
+ return not self == other
677
+
678
+ def __lt__(self, other):
679
+ """
680
+ Return ``True`` iff the tuple for this cube is less than that for
681
+ ``other``.
682
+
683
+ INPUT:
684
+
685
+ - ``other`` -- another cube
686
+
687
+ EXAMPLES::
688
+
689
+ sage: from sage.topology.cubical_complex import Cube
690
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
691
+ sage: C2 = Cube([[1], [2,3], [4,5]])
692
+ sage: C3 = Cube([[0], [2,3], [4,5]])
693
+ sage: C1 < C1
694
+ False
695
+ sage: C1 < C3
696
+ False
697
+ sage: C3 < C1
698
+ True
699
+
700
+ Test ``@total_ordering`` by testing other comparisons::
701
+
702
+ sage: C1 <= C1
703
+ True
704
+ sage: C1 <= C2
705
+ True
706
+ sage: C1 >= C2
707
+ True
708
+ sage: C1 > C2
709
+ False
710
+ sage: C3 <= C1
711
+ True
712
+ sage: C1 > C3
713
+ True
714
+ """
715
+ return tuple(self) < tuple(other)
716
+
717
+ def __hash__(self):
718
+ """
719
+ Hash value for this cube. This computes the hash value of the
720
+ underlying tuple, since this is what's important when testing
721
+ equality.
722
+
723
+ EXAMPLES::
724
+
725
+ sage: from sage.topology.cubical_complex import Cube
726
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
727
+ sage: hash(C1) == hash(((1,1),(2,3),(4,5)))
728
+ True
729
+ """
730
+ return hash(self.__tuple)
731
+
732
+ def _repr_(self):
733
+ """
734
+ Print representation of a cube.
735
+
736
+ EXAMPLES::
737
+
738
+ sage: from sage.topology.cubical_complex import Cube
739
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
740
+ sage: C1
741
+ [1,1] x [2,3] x [4,5]
742
+ sage: C1._repr_()
743
+ '[1,1] x [2,3] x [4,5]'
744
+ """
745
+ s = ("[{},{}]".format(str(x), str(y)) for x, y in self.__tuple)
746
+ return " x ".join(s)
747
+
748
+ def _latex_(self):
749
+ r"""
750
+ LaTeX representation of a cube..
751
+
752
+ EXAMPLES::
753
+
754
+ sage: from sage.topology.cubical_complex import Cube
755
+ sage: C1 = Cube([[1,1], [2,3], [4,5]])
756
+ sage: latex(C1)
757
+ [1,1] \times [2,3] \times [4,5]
758
+ sage: C1._latex_()
759
+ '[1,1] \\times [2,3] \\times [4,5]'
760
+ """
761
+ return self._repr_().replace('x', r'\times')
762
+
763
+
764
+ class CubicalComplex(GenericCellComplex):
765
+ r"""
766
+ Define a cubical complex.
767
+
768
+ INPUT:
769
+
770
+ - ``maximal_faces`` -- set of maximal faces
771
+ - ``maximality_check`` -- boolean (default: ``True``); see below
772
+
773
+ OUTPUT: a cubical complex
774
+
775
+ ``maximal_faces`` should be a list or tuple or set (or anything
776
+ which may be converted to a set) of "cubes": instances of the
777
+ class :class:`Cube`, or lists or tuples suitable for conversion to
778
+ cubes. These cubes are the maximal cubes in the complex.
779
+
780
+ In addition, ``maximal_faces`` may be a cubical complex, in which
781
+ case that complex is returned. Also, ``maximal_faces`` may
782
+ instead be any object which has a ``_cubical_`` method (e.g., a
783
+ simplicial complex); then that method is used to convert the
784
+ object to a cubical complex.
785
+
786
+ If ``maximality_check`` is True, check that each maximal face is,
787
+ in fact, maximal. In this case, when producing the internal
788
+ representation of the cubical complex, omit those that are not.
789
+ It is highly recommended that this be True; various methods for
790
+ this class may fail if faces which are claimed to be maximal are
791
+ in fact not.
792
+
793
+ EXAMPLES:
794
+
795
+ The empty complex, consisting of one cube, the empty cube::
796
+
797
+ sage: CubicalComplex()
798
+ Cubical complex with 0 vertices and 1 cube
799
+
800
+ A "circle" (four edges connecting the vertices (0,2), (0,3),
801
+ (1,2), and (1,3))::
802
+
803
+ sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]),
804
+ ....: ([0,1], [2,2]), ([1,1], [2,3])]); S1
805
+ Cubical complex with 4 vertices and 8 cubes
806
+ sage: S1.homology() # needs sage.modules
807
+ {0: 0, 1: Z}
808
+
809
+ A set of five points and its product with ``S1``::
810
+
811
+ sage: pts = CubicalComplex([([0],), ([3],), ([6],), ([-12],), ([5],)])
812
+ sage: pts
813
+ Cubical complex with 5 vertices and 5 cubes
814
+ sage: pts.homology() # needs sage.modules
815
+ {0: Z x Z x Z x Z}
816
+ sage: X = S1.product(pts); X
817
+ Cubical complex with 20 vertices and 40 cubes
818
+ sage: X.homology() # needs sage.modules
819
+ {0: Z x Z x Z x Z, 1: Z^5}
820
+
821
+ Converting a simplicial complex to a cubical complex::
822
+
823
+ sage: S2 = simplicial_complexes.Sphere(2)
824
+ sage: C2 = CubicalComplex(S2)
825
+ sage: all(C2.homology(n) == S2.homology(n) for n in range(3)) # needs sage.modules
826
+ True
827
+
828
+ You can get the set of maximal cells or a dictionary of all cells::
829
+
830
+ sage: X.maximal_cells() # random: order may depend on the version of Python
831
+ {[0,0] x [2,3] x [-12,-12], [0,1] x [3,3] x [5,5], [0,1] x [2,2] x [3,3], [0,1] x [2,2] x [0,0], [0,1] x [3,3] x [6,6], [1,1] x [2,3] x [0,0], [0,1] x [2,2] x [-12,-12], [0,0] x [2,3] x [6,6], [1,1] x [2,3] x [-12,-12], [1,1] x [2,3] x [5,5], [0,1] x [2,2] x [5,5], [0,1] x [3,3] x [3,3], [1,1] x [2,3] x [3,3], [0,0] x [2,3] x [5,5], [0,1] x [3,3] x [0,0], [1,1] x [2,3] x [6,6], [0,1] x [2,2] x [6,6], [0,0] x [2,3] x [0,0], [0,0] x [2,3] x [3,3], [0,1] x [3,3] x [-12,-12]}
832
+ sage: sorted(X.maximal_cells())
833
+ [[0,0] x [2,3] x [-12,-12],
834
+ [0,0] x [2,3] x [0,0],
835
+ [0,0] x [2,3] x [3,3],
836
+ [0,0] x [2,3] x [5,5],
837
+ [0,0] x [2,3] x [6,6],
838
+ [0,1] x [2,2] x [-12,-12],
839
+ [0,1] x [2,2] x [0,0],
840
+ [0,1] x [2,2] x [3,3],
841
+ [0,1] x [2,2] x [5,5],
842
+ [0,1] x [2,2] x [6,6],
843
+ [0,1] x [3,3] x [-12,-12],
844
+ [0,1] x [3,3] x [0,0],
845
+ [0,1] x [3,3] x [3,3],
846
+ [0,1] x [3,3] x [5,5],
847
+ [0,1] x [3,3] x [6,6],
848
+ [1,1] x [2,3] x [-12,-12],
849
+ [1,1] x [2,3] x [0,0],
850
+ [1,1] x [2,3] x [3,3],
851
+ [1,1] x [2,3] x [5,5],
852
+ [1,1] x [2,3] x [6,6]]
853
+ sage: S1.cells()
854
+ {-1: set(),
855
+ 0: {[0,0] x [2,2], [0,0] x [3,3], [1,1] x [2,2], [1,1] x [3,3]},
856
+ 1: {[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]}}
857
+
858
+ Chain complexes, homology, and cohomology::
859
+
860
+ sage: T = S1.product(S1); T
861
+ Cubical complex with 16 vertices and 64 cubes
862
+ sage: T.chain_complex() # needs sage.modules
863
+ Chain complex with at most 3 nonzero terms over Integer Ring
864
+ sage: T.homology(base_ring=QQ) # needs sage.modules
865
+ {0: Vector space of dimension 0 over Rational Field,
866
+ 1: Vector space of dimension 2 over Rational Field,
867
+ 2: Vector space of dimension 1 over Rational Field}
868
+ sage: RP2 = cubical_complexes.RealProjectivePlane()
869
+ sage: RP2.cohomology(dim=[1, 2], base_ring=GF(2)) # needs sage.modules
870
+ {1: Vector space of dimension 1 over Finite Field of size 2,
871
+ 2: Vector space of dimension 1 over Finite Field of size 2}
872
+
873
+ Joins are not implemented::
874
+
875
+ sage: S1.join(S1)
876
+ Traceback (most recent call last):
877
+ ...
878
+ NotImplementedError: joins are not implemented for cubical complexes
879
+
880
+ Therefore, neither are cones or suspensions.
881
+ """
882
+ def __init__(self, maximal_faces=None, maximality_check=True):
883
+ r"""
884
+ Define a cubical complex. See ``CubicalComplex`` for more
885
+ documentation.
886
+
887
+ EXAMPLES::
888
+
889
+ sage: X = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]); X
890
+ Cubical complex with 4 vertices and 8 cubes
891
+ sage: X == loads(dumps(X))
892
+ True
893
+ """
894
+ if maximal_faces is None:
895
+ maximal_faces = []
896
+ C = None
897
+ if isinstance(maximal_faces, CubicalComplex):
898
+ C = maximal_faces
899
+ try:
900
+ C = maximal_faces._cubical_()
901
+ except AttributeError:
902
+ pass
903
+ if C is not None:
904
+ self._facets = copy(C._facets)
905
+ self._cells = copy(C._cells)
906
+ self._complex = copy(C._complex)
907
+ return
908
+
909
+ good_faces = []
910
+ maximal_cubes = [Cube(f) for f in maximal_faces]
911
+ for face in maximal_cubes:
912
+ # check whether each given face is actually maximal
913
+ face_is_maximal = True
914
+ if maximality_check:
915
+ faces_to_be_removed = []
916
+ for other in good_faces:
917
+ if other.is_face(face):
918
+ faces_to_be_removed.append(other)
919
+ elif face_is_maximal:
920
+ face_is_maximal = not face.is_face(other)
921
+ for x in faces_to_be_removed:
922
+ good_faces.remove(x)
923
+ if face_is_maximal:
924
+ good_faces += [face]
925
+ # if no maximal faces, add the empty face as a facet
926
+ if len(maximal_cubes) == 0:
927
+ good_faces.append(Cube(()))
928
+ # self._facets: tuple of facets
929
+ self._facets = tuple(good_faces)
930
+ # self._cells: dictionary of dictionaries of faces. The main
931
+ # dictionary is keyed by subcomplexes, and each value is a
932
+ # dictionary keyed by dimension. This should be empty until
933
+ # needed -- that is, until the faces method is called
934
+ self._cells = {}
935
+ # self._complex: dictionary indexed by dimension d, base_ring,
936
+ # etc.: differential from dim d to dim d-1 in the associated
937
+ # chain complex. thus to get the differential in the cochain
938
+ # complex from dim d-1 to dim d, take the transpose of this
939
+ # one.
940
+ self._complex = {}
941
+
942
+ def maximal_cells(self):
943
+ """
944
+ The set of maximal cells (with respect to inclusion) of this
945
+ cubical complex.
946
+
947
+ OUTPUT: set of maximal cells
948
+
949
+ This just returns the set of cubes used in defining the
950
+ cubical complex, so if the complex was defined with no
951
+ maximality checking, none is done here, either.
952
+
953
+ EXAMPLES::
954
+
955
+ sage: interval = cubical_complexes.Cube(1)
956
+ sage: interval
957
+ Cubical complex with 2 vertices and 3 cubes
958
+ sage: interval.maximal_cells()
959
+ {[0,1]}
960
+ sage: interval.product(interval).maximal_cells()
961
+ {[0,1] x [0,1]}
962
+ """
963
+ return Set(self._facets)
964
+
965
+ def __eq__(self, other):
966
+ r"""
967
+ Return ``True`` if the set of maximal cells is the same for
968
+ ``self`` and ``other``.
969
+
970
+ INPUT:
971
+
972
+ - ``other`` -- another cubical complex
973
+
974
+ EXAMPLES::
975
+
976
+ sage: I1 = cubical_complexes.Cube(1)
977
+ sage: I2 = cubical_complexes.Cube(1)
978
+ sage: I1.product(I2) == I2.product(I1)
979
+ True
980
+ sage: I1.product(I2.product(I2)) == I2.product(I1.product(I1))
981
+ True
982
+ sage: S1 = cubical_complexes.Sphere(1)
983
+ sage: I1.product(S1) == S1.product(I1)
984
+ False
985
+ """
986
+ return self.maximal_cells() == other.maximal_cells()
987
+
988
+ def __ne__(self, other):
989
+ r"""
990
+ Return ``True`` if ``self`` and ``other`` are not equal.
991
+
992
+ INPUT:
993
+
994
+ - ``other`` -- another cubical complex
995
+
996
+ EXAMPLES::
997
+
998
+ sage: I1 = cubical_complexes.Cube(1)
999
+ sage: I2 = cubical_complexes.Cube(1)
1000
+ sage: I1.product(I2) != I2.product(I1)
1001
+ False
1002
+ sage: I1.product(I2.product(I2)) != I2.product(I1.product(I1))
1003
+ False
1004
+ sage: S1 = cubical_complexes.Sphere(1)
1005
+ sage: I1.product(S1) != S1.product(I1)
1006
+ True
1007
+ """
1008
+ return not self.__eq__(other)
1009
+
1010
+ def __hash__(self):
1011
+ r"""
1012
+ TESTS::
1013
+
1014
+ sage: I1 = cubical_complexes.Cube(1)
1015
+ sage: I2 = cubical_complexes.Cube(1)
1016
+ sage: hash(I1) == hash(I2)
1017
+ True
1018
+ sage: hash(I1.product(I1)) == hash(I2.product(I1))
1019
+ True
1020
+ """
1021
+ return hash(frozenset(self._facets))
1022
+
1023
+ def is_subcomplex(self, other):
1024
+ r"""
1025
+ Return ``True`` if ``self`` is a subcomplex of ``other``.
1026
+
1027
+ INPUT:
1028
+
1029
+ - ``other`` -- a cubical complex
1030
+
1031
+ Each maximal cube of ``self`` must be a face of a maximal cube
1032
+ of ``other`` for this to be True.
1033
+
1034
+ EXAMPLES::
1035
+
1036
+ sage: S1 = cubical_complexes.Sphere(1)
1037
+ sage: C0 = cubical_complexes.Cube(0)
1038
+ sage: C1 = cubical_complexes.Cube(1)
1039
+ sage: cyl = S1.product(C1)
1040
+ sage: end = S1.product(C0)
1041
+ sage: end.is_subcomplex(cyl)
1042
+ True
1043
+ sage: cyl.is_subcomplex(end)
1044
+ False
1045
+
1046
+ The embedding of the cubical complex is important here::
1047
+
1048
+ sage: C2 = cubical_complexes.Cube(2)
1049
+ sage: C1.is_subcomplex(C2)
1050
+ False
1051
+ sage: C1.product(C0).is_subcomplex(C2)
1052
+ True
1053
+
1054
+ ``C1`` is not a subcomplex of ``C2`` because it's not embedded
1055
+ in `\RR^2`. On the other hand, ``C1 x C0`` is a face of
1056
+ ``C2``. Look at their maximal cells::
1057
+
1058
+ sage: C1.maximal_cells()
1059
+ {[0,1]}
1060
+ sage: C2.maximal_cells()
1061
+ {[0,1] x [0,1]}
1062
+ sage: C1.product(C0).maximal_cells()
1063
+ {[0,1] x [0,0]}
1064
+ """
1065
+ other_facets = other.maximal_cells()
1066
+ return all(any(cube.is_face(other_cube)
1067
+ for other_cube in other_facets)
1068
+ for cube in self.maximal_cells())
1069
+
1070
+ def cells(self, subcomplex=None):
1071
+ """
1072
+ The cells of this cubical complex, in the form of a dictionary:
1073
+ the keys are integers, representing dimension, and the value
1074
+ associated to an integer d is the list of d-cells.
1075
+
1076
+ If the optional argument ``subcomplex`` is present, then
1077
+ return only the faces which are *not* in the subcomplex.
1078
+
1079
+ INPUT:
1080
+
1081
+ - ``subcomplex`` -- a subcomplex of this cubical complex (default:
1082
+ ``None``)
1083
+
1084
+ OUTPUT: dictionary; the cells of this complex not contained in
1085
+ ``subcomplex``
1086
+
1087
+ EXAMPLES::
1088
+
1089
+ sage: S2 = cubical_complexes.Sphere(2)
1090
+ sage: sorted(S2.cells()[2])
1091
+ [[0,0] x [0,1] x [0,1],
1092
+ [0,1] x [0,0] x [0,1],
1093
+ [0,1] x [0,1] x [0,0],
1094
+ [0,1] x [0,1] x [1,1],
1095
+ [0,1] x [1,1] x [0,1],
1096
+ [1,1] x [0,1] x [0,1]]
1097
+ """
1098
+ if subcomplex not in self._cells:
1099
+ if subcomplex is not None and subcomplex.dimension() > -1:
1100
+ if not subcomplex.is_subcomplex(self):
1101
+ raise ValueError("the 'subcomplex' is not actually a subcomplex")
1102
+ # Cells is the dictionary of cells in self but not in
1103
+ # subcomplex, indexed by dimension
1104
+ Cells = {}
1105
+ # sub_facets is the dictionary of facets in the subcomplex
1106
+ sub_facets = {}
1107
+ dimension = max([cube.dimension() for cube in self._facets])
1108
+ # initialize the lists: add each maximal cube to Cells and sub_facets
1109
+ for i in range(-1, dimension+1):
1110
+ Cells[i] = set()
1111
+ sub_facets[i] = set()
1112
+ for f in self._facets:
1113
+ Cells[f.dimension()].add(f)
1114
+ if subcomplex is not None:
1115
+ for g in subcomplex._facets:
1116
+ dim = g.dimension()
1117
+ Cells[dim].discard(g)
1118
+ sub_facets[dim].add(g)
1119
+ # bad_faces is the set of faces in the subcomplex in the
1120
+ # current dimension
1121
+ bad_faces = sub_facets[dimension]
1122
+ for dim in range(dimension, -1, -1):
1123
+ # bad_bdries = boundaries of bad_faces: things to be
1124
+ # discarded in dim-1
1125
+ bad_bdries = sub_facets[dim-1]
1126
+ for f in bad_faces:
1127
+ bad_bdries.update(f.faces())
1128
+ for f in Cells[dim]:
1129
+ Cells[dim-1].update(set(f.faces()).difference(bad_bdries))
1130
+ bad_faces = bad_bdries
1131
+ self._cells[subcomplex] = Cells
1132
+ return self._cells[subcomplex]
1133
+
1134
+ def n_cubes(self, n, subcomplex=None):
1135
+ """
1136
+ The set of cubes of dimension n of this cubical complex.
1137
+ If the optional argument ``subcomplex`` is present, then
1138
+ return the ``n``-dimensional cubes which are *not* in the
1139
+ subcomplex.
1140
+
1141
+ INPUT:
1142
+
1143
+ - ``n`` -- integer; dimension
1144
+ - ``subcomplex`` -- a subcomplex of this cubical complex (default:
1145
+ ``None``)
1146
+
1147
+ OUTPUT: set; cells in dimension ``n``
1148
+
1149
+ EXAMPLES::
1150
+
1151
+ sage: C = cubical_complexes.Cube(3)
1152
+ sage: C.n_cubes(3)
1153
+ {[0,1] x [0,1] x [0,1]}
1154
+ sage: sorted(C.n_cubes(2))
1155
+ [[0,0] x [0,1] x [0,1],
1156
+ [0,1] x [0,0] x [0,1],
1157
+ [0,1] x [0,1] x [0,0],
1158
+ [0,1] x [0,1] x [1,1],
1159
+ [0,1] x [1,1] x [0,1],
1160
+ [1,1] x [0,1] x [0,1]]
1161
+ """
1162
+ return set(self.n_cells(n, subcomplex))
1163
+
1164
+ def chain_complex(self, subcomplex=None, augmented=False,
1165
+ verbose=False, check=False, dimensions=None,
1166
+ base_ring=ZZ, cochain=False):
1167
+ r"""
1168
+ The chain complex associated to this cubical complex.
1169
+
1170
+ INPUT:
1171
+
1172
+ - ``dimensions`` -- if ``None``, compute the chain complex in all
1173
+ dimensions. If a list or tuple of integers, compute the
1174
+ chain complex in those dimensions, setting the chain groups
1175
+ in all other dimensions to zero. NOT IMPLEMENTED YET: this
1176
+ function always returns the entire chain complex
1177
+ - ``base_ring`` -- commutative ring (default: ZZ)
1178
+ - ``subcomplex`` -- a subcomplex of this cubical complex (default: empty).
1179
+ Compute the chain complex relative to this subcomplex.
1180
+ - ``augmented`` -- boolean (default: ``False``); if ``True``, return
1181
+ the augmented chain complex (that is, include a class in dimension
1182
+ `-1` corresponding to the empty cell). This is ignored if
1183
+ ``dimensions`` is specified.
1184
+ - ``cochain`` -- boolean (default: ``False``); if ``True``, return the
1185
+ cochain complex (that is, the dual of the chain complex).
1186
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, print some
1187
+ messages as the chain complex is computed.
1188
+ - ``check`` -- boolean (default: ``False``); if ``True``, make sure
1189
+ that the chain complex is actually a chain complex: the differentials
1190
+ are composable and their product is zero.
1191
+
1192
+ .. NOTE::
1193
+
1194
+ If subcomplex is nonempty, then the argument ``augmented``
1195
+ has no effect: the chain complex relative to a nonempty
1196
+ subcomplex is zero in dimension `-1`.
1197
+
1198
+ EXAMPLES::
1199
+
1200
+ sage: # needs sage.modules
1201
+ sage: S2 = cubical_complexes.Sphere(2)
1202
+ sage: S2.chain_complex()
1203
+ Chain complex with at most 3 nonzero terms over Integer Ring
1204
+ sage: Prod = S2.product(S2); Prod
1205
+ Cubical complex with 64 vertices and 676 cubes
1206
+ sage: Prod.chain_complex()
1207
+ Chain complex with at most 5 nonzero terms over Integer Ring
1208
+ sage: Prod.chain_complex(base_ring=QQ)
1209
+ Chain complex with at most 5 nonzero terms over Rational Field
1210
+ sage: C1 = cubical_complexes.Cube(1)
1211
+ sage: S0 = cubical_complexes.Sphere(0)
1212
+ sage: C1.chain_complex(subcomplex=S0)
1213
+ Chain complex with at most 1 nonzero terms over Integer Ring
1214
+ sage: C1.homology(subcomplex=S0)
1215
+ {0: 0, 1: Z}
1216
+
1217
+ Check that :issue:`32203` has been fixed::
1218
+
1219
+ sage: # needs sage.modules
1220
+ sage: Square = CubicalComplex([([0,1],[0,1])])
1221
+ sage: EdgesLTR = CubicalComplex([([0,0],[0,1]),([0,1],[1,1]),([1,1],[0,1])])
1222
+ sage: EdgesLBR = CubicalComplex([([0,0],[0,1]),([0,1],[0,0]),([1,1],[0,1])])
1223
+ sage: Square.homology(subcomplex=EdgesLTR)[2] == Square.homology(subcomplex=EdgesLBR)[2]
1224
+ True
1225
+ """
1226
+ from sage.homology.chain_complex import ChainComplex
1227
+
1228
+ # initialize subcomplex
1229
+ if subcomplex is None:
1230
+ subcomplex = CubicalComplex()
1231
+ else:
1232
+ # subcomplex is not empty, so don't augment the chain complex
1233
+ augmented = False
1234
+ differentials = {}
1235
+ if augmented:
1236
+ empty_cell = 1 # number of (-1)-dimensional cubes
1237
+ else:
1238
+ empty_cell = 0
1239
+ vertices = self._n_cells_sorted(0, subcomplex=subcomplex)
1240
+ n = len(vertices)
1241
+ mat = matrix(base_ring, empty_cell, n, n*empty_cell*[1])
1242
+ if cochain:
1243
+ differentials[-1] = mat.transpose()
1244
+ else:
1245
+ differentials[0] = mat
1246
+ current = vertices
1247
+ # now loop from 1 to dimension of the complex
1248
+ for dim in range(1, self.dimension()+1):
1249
+ if verbose:
1250
+ print(" starting dimension %s" % dim)
1251
+ if (dim, subcomplex) in self._complex:
1252
+ if cochain:
1253
+ differentials[dim-1] = self._complex[(dim, subcomplex)].transpose().change_ring(base_ring)
1254
+ mat = differentials[dim-1]
1255
+ else:
1256
+ differentials[dim] = self._complex[(dim, subcomplex)].change_ring(base_ring)
1257
+ mat = differentials[dim]
1258
+ if verbose:
1259
+ print(" boundary matrix (cached): it's {} by {}.".format(mat.nrows(), mat.ncols()))
1260
+ else:
1261
+ # 'current' is the list of cells in dimension n
1262
+ #
1263
+ # 'old' is a dictionary, with keys the cells in the
1264
+ # previous dimension, values the integers 0, 1, 2,
1265
+ # ... (the index of the face). finding an entry in a
1266
+ # dictionary seems to be faster than finding the index
1267
+ # of an entry in a list.
1268
+ old = dict(zip(current, range(len(current))))
1269
+ current = self._n_cells_sorted(dim, subcomplex=subcomplex)
1270
+ # construct matrix. it is easiest to construct it as
1271
+ # a sparse matrix, specifying which entries are
1272
+ # nonzero via a dictionary.
1273
+ matrix_data = {}
1274
+ col = 0
1275
+ if len(old) and len(current):
1276
+ for cube in current:
1277
+ faces = cube.faces_as_pairs()
1278
+ sign = 1
1279
+ for (upper, lower) in faces:
1280
+ # trac 32203: use two "try/except" loops
1281
+ # in case lower is in old but upper is not.
1282
+ try:
1283
+ matrix_data[(old[upper], col)] = sign
1284
+ except KeyError:
1285
+ pass
1286
+ try:
1287
+ matrix_data[(old[lower], col)] = -1*sign
1288
+ except KeyError:
1289
+ pass
1290
+ # The signs in the boundary alternate as
1291
+ # we iterate through the faces.
1292
+ sign *= -1
1293
+ col += 1
1294
+ mat = matrix(ZZ, len(old), len(current), matrix_data)
1295
+ self._complex[(dim, subcomplex)] = mat
1296
+ if cochain:
1297
+ differentials[dim-1] = mat.transpose().change_ring(base_ring)
1298
+ else:
1299
+ differentials[dim] = mat.change_ring(base_ring)
1300
+ if verbose:
1301
+ print(" boundary matrix computed: it's {} by {}.".format(mat.nrows(), mat.ncols()))
1302
+ # finally, return the chain complex
1303
+ if cochain:
1304
+ return ChainComplex(data=differentials, base_ring=base_ring,
1305
+ degree=1, check=check)
1306
+ else:
1307
+ return ChainComplex(data=differentials, base_ring=base_ring,
1308
+ degree=-1, check=check)
1309
+
1310
+ def alexander_whitney(self, cube, dim_left):
1311
+ r"""
1312
+ Subdivide ``cube`` in this cubical complex into pairs of cubes.
1313
+
1314
+ See :meth:`Cube.alexander_whitney` for more details. This
1315
+ method just calls that one.
1316
+
1317
+ INPUT:
1318
+
1319
+ - ``cube`` -- a cube in this cubical complex
1320
+ - ``dim`` -- integer between 0 and one more than the
1321
+ dimension of this cube
1322
+
1323
+ OUTPUT: list containing triples ``(coeff, left, right)``
1324
+
1325
+ EXAMPLES::
1326
+
1327
+ sage: C = cubical_complexes.Cube(3)
1328
+ sage: c = list(C.n_cubes(3))[0]; c
1329
+ [0,1] x [0,1] x [0,1]
1330
+ sage: C.alexander_whitney(c, 1)
1331
+ [(1, [0,1] x [0,0] x [0,0], [1,1] x [0,1] x [0,1]),
1332
+ (-1, [0,0] x [0,1] x [0,0], [0,1] x [1,1] x [0,1]),
1333
+ (1, [0,0] x [0,0] x [0,1], [0,1] x [0,1] x [1,1])]
1334
+ """
1335
+ return cube.alexander_whitney(dim_left)
1336
+
1337
+ def n_skeleton(self, n):
1338
+ r"""
1339
+ The n-skeleton of this cubical complex.
1340
+
1341
+ INPUT:
1342
+
1343
+ - ``n`` -- nonnegative integer; dimension
1344
+
1345
+ OUTPUT: cubical complex
1346
+
1347
+ EXAMPLES::
1348
+
1349
+ sage: S2 = cubical_complexes.Sphere(2)
1350
+ sage: C3 = cubical_complexes.Cube(3)
1351
+ sage: S2 == C3.n_skeleton(2)
1352
+ True
1353
+ """
1354
+ if n >= self.dimension():
1355
+ return self
1356
+ else:
1357
+ data = []
1358
+ for d in range(n+1):
1359
+ data.extend(list(self.cells()[d]))
1360
+ return CubicalComplex(data)
1361
+
1362
+ def graph(self):
1363
+ """
1364
+ The 1-skeleton of this cubical complex, as a graph.
1365
+
1366
+ EXAMPLES::
1367
+
1368
+ sage: cubical_complexes.Sphere(2).graph()
1369
+ Graph on 8 vertices
1370
+ """
1371
+ from sage.graphs.graph import Graph
1372
+
1373
+ data = {}
1374
+ vertex_dict = {}
1375
+ i = 0
1376
+ for vertex in self.n_cells(0):
1377
+ vertex_dict[vertex] = i
1378
+ data[i] = []
1379
+ i += 1
1380
+ for edge in self.n_cells(1):
1381
+ start = edge.face(0, False)
1382
+ end = edge.face(0, True)
1383
+ data[vertex_dict[start]].append(vertex_dict[end])
1384
+ return Graph(data)
1385
+
1386
+ def is_pure(self):
1387
+ """
1388
+ Return ``True`` iff this cubical complex is pure: that is,
1389
+ all of its maximal faces have the same dimension.
1390
+
1391
+ .. WARNING::
1392
+
1393
+ This may give the wrong answer if the cubical complex
1394
+ was constructed with ``maximality_check`` set to False.
1395
+
1396
+ EXAMPLES::
1397
+
1398
+ sage: S4 = cubical_complexes.Sphere(4)
1399
+ sage: S4.is_pure()
1400
+ True
1401
+ sage: C = CubicalComplex([([0,0], [3,3]), ([1,2], [4,5])])
1402
+ sage: C.is_pure()
1403
+ False
1404
+ """
1405
+ dims = [face.dimension() for face in self._facets]
1406
+ return max(dims) == min(dims)
1407
+
1408
+ def join(self, other):
1409
+ r"""
1410
+ The join of this cubical complex with another one.
1411
+
1412
+ NOT IMPLEMENTED.
1413
+
1414
+ INPUT:
1415
+
1416
+ - ``other`` -- another cubical complex
1417
+
1418
+ EXAMPLES::
1419
+
1420
+ sage: C1 = cubical_complexes.Cube(1)
1421
+ sage: C1.join(C1)
1422
+ Traceback (most recent call last):
1423
+ ...
1424
+ NotImplementedError: joins are not implemented for cubical complexes
1425
+ """
1426
+ raise NotImplementedError("joins are not implemented for cubical complexes")
1427
+
1428
+ # Use * to mean 'join':
1429
+ # __mul__ = join
1430
+
1431
+ def cone(self):
1432
+ r"""
1433
+ The cone on this cubical complex.
1434
+
1435
+ NOT IMPLEMENTED
1436
+
1437
+ The cone is the complex formed by taking the join of the
1438
+ original complex with a one-point complex (that is, a
1439
+ 0-dimensional cube). Since joins are not implemented for
1440
+ cubical complexes, neither are cones.
1441
+
1442
+ EXAMPLES::
1443
+
1444
+ sage: C1 = cubical_complexes.Cube(1)
1445
+ sage: C1.cone()
1446
+ Traceback (most recent call last):
1447
+ ...
1448
+ NotImplementedError: cones are not implemented for cubical complexes
1449
+ """
1450
+ # return self.join(cubical_complexes.Cube(0))
1451
+ raise NotImplementedError("cones are not implemented for cubical complexes")
1452
+
1453
+ def suspension(self, n=1):
1454
+ r"""
1455
+ The suspension of this cubical complex.
1456
+
1457
+ NOT IMPLEMENTED
1458
+
1459
+ INPUT:
1460
+
1461
+ - ``n`` -- positive integer (default: 1); suspend this many times
1462
+
1463
+ The suspension is the complex formed by taking the join of the
1464
+ original complex with a two-point complex (the 0-sphere).
1465
+ Since joins are not implemented for cubical complexes, neither
1466
+ are suspensions.
1467
+
1468
+ EXAMPLES::
1469
+
1470
+ sage: C1 = cubical_complexes.Cube(1)
1471
+ sage: C1.suspension()
1472
+ Traceback (most recent call last):
1473
+ ...
1474
+ NotImplementedError: suspensions are not implemented for cubical complexes
1475
+ """
1476
+ # if n < 0:
1477
+ # raise ValueError("n must be nonnegative")
1478
+ # if n == 0:
1479
+ # return self
1480
+ # if n == 1:
1481
+ # return self.join(cubical_complexes.Sphere(0))
1482
+ # return self.suspension().suspension(int(n-1))
1483
+ raise NotImplementedError("suspensions are not implemented for cubical complexes")
1484
+
1485
+ def product(self, other):
1486
+ r"""
1487
+ Return the product of this cubical complex with another one.
1488
+
1489
+ INPUT:
1490
+
1491
+ - ``other`` -- another cubical complex
1492
+
1493
+ EXAMPLES::
1494
+
1495
+ sage: RP2 = cubical_complexes.RealProjectivePlane()
1496
+ sage: S1 = cubical_complexes.Sphere(1)
1497
+ sage: RP2.product(S1).homology()[1] # long time: 5 seconds
1498
+ Z x C2
1499
+ """
1500
+ facets = [f.product(g) for f in self._facets for g in other._facets]
1501
+ return CubicalComplex(facets)
1502
+
1503
+ def disjoint_union(self, other):
1504
+ """
1505
+ The disjoint union of this cubical complex with another one.
1506
+
1507
+ INPUT:
1508
+
1509
+ - ``right`` -- the other cubical complex (the right-hand factor)
1510
+
1511
+ Algorithm: first embed both complexes in d-dimensional
1512
+ Euclidean space. Then embed in (1+d)-dimensional space,
1513
+ calling the new axis `x`, and putting the first complex at
1514
+ `x=0`, the second at `x=1`.
1515
+
1516
+ EXAMPLES::
1517
+
1518
+ sage: S1 = cubical_complexes.Sphere(1)
1519
+ sage: S2 = cubical_complexes.Sphere(2)
1520
+ sage: S1.disjoint_union(S2).homology() # needs sage.modules
1521
+ {0: Z, 1: Z, 2: Z}
1522
+ """
1523
+ embedded_left = len(tuple(self.maximal_cells()[0]))
1524
+ embedded_right = len(tuple(other.maximal_cells()[0]))
1525
+ zero = [0] * max(embedded_left, embedded_right)
1526
+ C00 = Cube([[0, 0]])
1527
+ facets = [C00.product(f._translate(zero))
1528
+ for f in self.maximal_cells()]
1529
+ C11 = Cube([[1, 1]])
1530
+ facets.extend(C11.product(f._translate(zero))
1531
+ for f in other.maximal_cells())
1532
+ return CubicalComplex(facets)
1533
+
1534
+ def wedge(self, other):
1535
+ """
1536
+ The wedge (one-point union) of this cubical complex with
1537
+ another one.
1538
+
1539
+ INPUT:
1540
+
1541
+ - ``right`` -- the other cubical complex (the right-hand factor)
1542
+
1543
+ Algorithm: if ``self`` is embedded in `d` dimensions and
1544
+ ``other`` in `n` dimensions, embed them in `d+n` dimensions:
1545
+ ``self`` using the first `d` coordinates, ``other`` using the
1546
+ last `n`, translating them so that they have the origin as a
1547
+ common vertex.
1548
+
1549
+ .. NOTE::
1550
+
1551
+ This operation is not well-defined if ``self`` or
1552
+ ``other`` is not path-connected.
1553
+
1554
+ EXAMPLES::
1555
+
1556
+ sage: S1 = cubical_complexes.Sphere(1)
1557
+ sage: S2 = cubical_complexes.Sphere(2)
1558
+ sage: S1.wedge(S2).homology() # needs sage.modules
1559
+ {0: 0, 1: Z, 2: Z}
1560
+ """
1561
+ embedded_left = len(tuple(self.maximal_cells()[0]))
1562
+ embedded_right = len(tuple(other.maximal_cells()[0]))
1563
+ translate_left = [-a[0] for a in self.maximal_cells()[0]] + [0] * embedded_right
1564
+ translate_right = [-a[0] for a in other.maximal_cells()[0]]
1565
+ point_right = Cube([[0, 0]] * embedded_left)
1566
+
1567
+ facets = [f._translate(translate_left) for f in self.maximal_cells()]
1568
+ facets.extend(point_right.product(f._translate(translate_right))
1569
+ for f in other.maximal_cells())
1570
+ return CubicalComplex(facets)
1571
+
1572
+ def connected_sum(self, other):
1573
+ """
1574
+ Return the connected sum of ``self`` with ``other``.
1575
+
1576
+ INPUT:
1577
+
1578
+ - ``other`` -- another cubical complex
1579
+
1580
+ OUTPUT: the connected sum ``self # other``
1581
+
1582
+ .. warning::
1583
+
1584
+ This does not check that ``self`` and ``other`` are manifolds, only
1585
+ that their facets all have the same dimension. Since a
1586
+ (more or less) random facet is chosen from each complex and
1587
+ then glued together, this method may return random
1588
+ results if applied to non-manifolds, depending on which
1589
+ facet is chosen.
1590
+
1591
+ EXAMPLES::
1592
+
1593
+ sage: T = cubical_complexes.Torus()
1594
+ sage: S2 = cubical_complexes.Sphere(2)
1595
+ sage: T.connected_sum(S2).cohomology() == T.cohomology() # needs sage.modules
1596
+ True
1597
+ sage: RP2 = cubical_complexes.RealProjectivePlane()
1598
+ sage: T.connected_sum(RP2).homology(1) # needs sage.modules
1599
+ Z x Z x C2
1600
+ sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1) # needs sage.modules
1601
+ Z x Z x C2
1602
+ """
1603
+ # connected_sum: first check whether the complexes are pure
1604
+ # and of the same dimension. Then insert degenerate intervals
1605
+ # and translate them so that they have a common cube C. Add one
1606
+ # more dimension, embedding the first complex as (..., 0) and
1607
+ # the second as (..., 1). Keep all of the other facets, but remove
1608
+ # C x 0 and C x 1, putting in its place (its boundary) x (0,1).
1609
+ if not (self.is_pure() and other.is_pure() and
1610
+ self.dimension() == other.dimension()):
1611
+ raise ValueError("complexes are not pure of the same dimension")
1612
+
1613
+ self_facets = list(self.maximal_cells())
1614
+ other_facets = list(other.maximal_cells())
1615
+
1616
+ C1 = self_facets.pop()
1617
+ C2 = other_facets.pop()
1618
+ (insert_self, insert_other, translate) = C1._compare_for_gluing(C2)
1619
+
1620
+ CL = list(C1.tuple())
1621
+ for (idx, L) in insert_self:
1622
+ CL[idx:idx] = L
1623
+ removed = Cube(CL)
1624
+
1625
+ # start assembling the facets in the connected sum: first, the
1626
+ # cylinder on the removed face.
1627
+ new_facets = []
1628
+ cylinder = removed.product(Cube([[0, 1]]))
1629
+ # don't want to include the ends of the cylinder, so don't
1630
+ # include the last pair of faces. therefore, choose faces up
1631
+ # to removed.dimension(), not cylinder.dimension().
1632
+ for n in range(removed.dimension()):
1633
+ new_facets.append(cylinder.face(n, upper=False))
1634
+ new_facets.append(cylinder.face(n, upper=True))
1635
+
1636
+ for cube in self_facets:
1637
+ CL = list(cube.tuple())
1638
+ for (idx, L) in insert_self:
1639
+ CL[idx:idx] = L
1640
+ CL.append((0, 0))
1641
+ new_facets.append(Cube(CL))
1642
+ for cube in other_facets:
1643
+ CL = list(cube.tuple())
1644
+ for (idx, L) in insert_other:
1645
+ CL[idx:idx] = L
1646
+ CL.append((1, 1))
1647
+ new_facets.append(Cube(CL)._translate(translate))
1648
+ return CubicalComplex(new_facets)
1649
+
1650
+ def _translate(self, vec):
1651
+ """
1652
+ Translate ``self`` by ``vec``.
1653
+
1654
+ INPUT:
1655
+
1656
+ - ``vec`` -- anything which can be converted to a tuple of integers
1657
+
1658
+ OUTPUT: cubical complex; the translation of ``self`` by ``vec``
1659
+
1660
+ If ``vec`` is shorter than the list of intervals forming the
1661
+ complex, pad with zeroes, and similarly if the complexes
1662
+ defining tuples are too short.
1663
+
1664
+ EXAMPLES::
1665
+
1666
+ sage: C1 = cubical_complexes.Cube(1)
1667
+ sage: C1.maximal_cells()
1668
+ {[0,1]}
1669
+ sage: C1._translate([2,6]).maximal_cells()
1670
+ {[2,3] x [6,6]}
1671
+ """
1672
+ return CubicalComplex([f._translate(vec) for f in self.maximal_cells()])
1673
+
1674
+ # This is cached for speed reasons: it can be very slow to run
1675
+ # this function.
1676
+ @cached_method
1677
+ def algebraic_topological_model(self, base_ring=None):
1678
+ r"""
1679
+ Algebraic topological model for this cubical complex with
1680
+ coefficients in ``base_ring``.
1681
+
1682
+ The term "algebraic topological model" is defined by Pilarczyk
1683
+ and Réal [PR2015]_.
1684
+
1685
+ INPUT:
1686
+
1687
+ - ``base_ring`` -- coefficient ring (default: ``QQ``); must be a field
1688
+
1689
+ Denote by `C` the chain complex associated to this cubical
1690
+ complex. The algebraic topological model is a chain complex
1691
+ `M` with zero differential, with the same homology as `C`,
1692
+ along with chain maps `\pi: C \to M` and `\iota: M \to C`
1693
+ satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic
1694
+ to `1_C`. The chain homotopy `\phi` must satisfy
1695
+
1696
+ - `\phi \phi = 0`,
1697
+ - `\pi \phi = 0`,
1698
+ - `\phi \iota = 0`.
1699
+
1700
+ Such a chain homotopy is called a *chain contraction*.
1701
+
1702
+ OUTPUT: a pair consisting of
1703
+
1704
+ - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and
1705
+ `\iota`
1706
+ - the chain complex `M`
1707
+
1708
+ Note that from the chain contraction ``phi``, one can recover the
1709
+ chain maps `\pi` and `\iota` via ``phi.pi()`` and
1710
+ ``phi.iota()``. Then one can recover `C` and `M` from, for
1711
+ example, ``phi.pi().domain()`` and ``phi.pi().codomain()``,
1712
+ respectively.
1713
+
1714
+ EXAMPLES::
1715
+
1716
+ sage: # needs sage.modules
1717
+ sage: RP2 = cubical_complexes.RealProjectivePlane()
1718
+ sage: phi, M = RP2.algebraic_topological_model(GF(2))
1719
+ sage: M.homology()
1720
+ {0: Vector space of dimension 1 over Finite Field of size 2,
1721
+ 1: Vector space of dimension 1 over Finite Field of size 2,
1722
+ 2: Vector space of dimension 1 over Finite Field of size 2}
1723
+ sage: T = cubical_complexes.Torus()
1724
+ sage: phi, M = T.algebraic_topological_model(QQ)
1725
+ sage: M.homology()
1726
+ {0: Vector space of dimension 1 over Rational Field,
1727
+ 1: Vector space of dimension 2 over Rational Field,
1728
+ 2: Vector space of dimension 1 over Rational Field}
1729
+ """
1730
+ from sage.homology.algebraic_topological_model import algebraic_topological_model
1731
+ if base_ring is None:
1732
+ base_ring = QQ
1733
+ return algebraic_topological_model(self, base_ring)
1734
+
1735
+ def _simplicial_(self):
1736
+ r"""
1737
+ Simplicial complex constructed from ``self``.
1738
+
1739
+ ALGORITHM:
1740
+
1741
+ This is constructed as described by Hetyei: choose a total
1742
+ ordering of the vertices of the cubical complex. Then for
1743
+ each maximal face
1744
+
1745
+ .. MATH::
1746
+
1747
+ C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k]
1748
+
1749
+ let `v_1` be the "upper" corner of `C`: `v` is the point
1750
+ `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval
1751
+ `[i_n, j_n]` is non-degenerate and form `v_2` by replacing
1752
+ `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex
1753
+ so defined will be `(i_1, ..., i_k)`. These vertices define a
1754
+ simplex, and do the vertices obtained by making different
1755
+ choices at each stage. Thus each `n`-cube is subdivided into
1756
+ `n!` simplices.
1757
+
1758
+ REFERENCES:
1759
+
1760
+ - G. Hetyei, "On the Stanley ring of a cubical complex",
1761
+ Discrete Comput. Geom. 14 (1995), 305-330.
1762
+
1763
+ EXAMPLES::
1764
+
1765
+ sage: T = cubical_complexes.Torus(); T
1766
+ Cubical complex with 16 vertices and 64 cubes
1767
+ sage: len(T.maximal_cells())
1768
+ 16
1769
+
1770
+ When this is triangulated, each maximal 2-dimensional cube
1771
+ gets turned into a pair of triangles. Since there were 16
1772
+ maximal cubes, this results in 32 facets in the simplicial
1773
+ complex::
1774
+
1775
+ sage: Ts = T._simplicial_(); Ts
1776
+ Simplicial complex with 16 vertices and 32 facets
1777
+ sage: T.homology() == Ts.homology() # needs sage.modules
1778
+ True
1779
+
1780
+ Each `n`-dimensional cube produces `n!` `n`-simplices::
1781
+
1782
+ sage: S4 = cubical_complexes.Sphere(4)
1783
+ sage: len(S4.maximal_cells())
1784
+ 10
1785
+ sage: SimplicialComplex(S4) # calls S4._simplicial_()
1786
+ Simplicial complex with 32 vertices and 240 facets
1787
+ """
1788
+ from .simplicial_complex import SimplicialComplex
1789
+ simplices = []
1790
+ for C in self.maximal_cells():
1791
+ simplices.extend(C._triangulation_())
1792
+ return SimplicialComplex(simplices)
1793
+
1794
+ def _string_constants(self):
1795
+ """
1796
+ Tuple containing the name of the type of complex, and the
1797
+ singular and plural of the name of the cells from which it is
1798
+ built. This is used in constructing the string representation.
1799
+
1800
+ EXAMPLES::
1801
+
1802
+ sage: S3 = cubical_complexes.Sphere(3)
1803
+ sage: S3._string_constants()
1804
+ ('Cubical', 'cube', 'cubes')
1805
+ sage: S3._repr_() # indirect doctest
1806
+ 'Cubical complex with 16 vertices and 80 cubes'
1807
+ """
1808
+ return ('Cubical', 'cube', 'cubes')
1809
+
1810
+
1811
+ class CubicalComplexExamples:
1812
+ r"""
1813
+ Some examples of cubical complexes.
1814
+
1815
+ Here are the available examples; you can also type
1816
+ "cubical_complexes." and hit TAB to get a list::
1817
+
1818
+ Sphere
1819
+ Torus
1820
+ RealProjectivePlane
1821
+ KleinBottle
1822
+ SurfaceOfGenus
1823
+ Cube
1824
+
1825
+ EXAMPLES::
1826
+
1827
+ sage: cubical_complexes.Torus() # indirect doctest
1828
+ Cubical complex with 16 vertices and 64 cubes
1829
+ sage: cubical_complexes.Cube(7)
1830
+ Cubical complex with 128 vertices and 2187 cubes
1831
+ sage: cubical_complexes.Sphere(7)
1832
+ Cubical complex with 256 vertices and 6560 cubes
1833
+ """
1834
+
1835
+ def Sphere(self, n):
1836
+ r"""
1837
+ A cubical complex representation of the `n`-dimensional sphere,
1838
+ formed by taking the boundary of an `(n+1)`-dimensional cube.
1839
+
1840
+ INPUT:
1841
+
1842
+ - ``n`` -- nonnegative integer; the dimension of the sphere
1843
+
1844
+ EXAMPLES::
1845
+
1846
+ sage: cubical_complexes.Sphere(7)
1847
+ Cubical complex with 256 vertices and 6560 cubes
1848
+ """
1849
+ return CubicalComplex(Cube([[0, 1]]*(n+1)).faces())
1850
+
1851
+ def Torus(self):
1852
+ r"""
1853
+ A cubical complex representation of the torus, obtained by
1854
+ taking the product of the circle with itself.
1855
+
1856
+ EXAMPLES::
1857
+
1858
+ sage: cubical_complexes.Torus()
1859
+ Cubical complex with 16 vertices and 64 cubes
1860
+ """
1861
+ S1 = cubical_complexes.Sphere(1)
1862
+ return S1.product(S1)
1863
+
1864
+ def RealProjectivePlane(self):
1865
+ r"""
1866
+ A cubical complex representation of the real projective plane.
1867
+ This is taken from the examples from CHomP, the Computational
1868
+ Homology Project: http://chomp.rutgers.edu/.
1869
+
1870
+ EXAMPLES::
1871
+
1872
+ sage: cubical_complexes.RealProjectivePlane()
1873
+ Cubical complex with 21 vertices and 81 cubes
1874
+ """
1875
+ return CubicalComplex([
1876
+ ([0, 1], [0], [0], [0, 1], [0]),
1877
+ ([0, 1], [0], [0], [0], [0, 1]),
1878
+ ([0], [0, 1], [0, 1], [0], [0]),
1879
+ ([0], [0, 1], [0], [0, 1], [0]),
1880
+ ([0], [0], [0, 1], [0], [0, 1]),
1881
+ ([0, 1], [0, 1], [1], [0], [0]),
1882
+ ([0, 1], [1], [0, 1], [0], [0]),
1883
+ ([1], [0, 1], [0, 1], [0], [0]),
1884
+ ([0, 1], [0, 1], [0], [0], [1]),
1885
+ ([0, 1], [1], [0], [0], [0, 1]),
1886
+ ([1], [0, 1], [0], [0], [0, 1]),
1887
+ ([0, 1], [0], [0, 1], [1], [0]),
1888
+ ([0, 1], [0], [1], [0, 1], [0]),
1889
+ ([1], [0], [0, 1], [0, 1], [0]),
1890
+ ([0], [0, 1], [0], [0, 1], [1]),
1891
+ ([0], [0, 1], [0], [1], [0, 1]),
1892
+ ([0], [1], [0], [0, 1], [0, 1]),
1893
+ ([0], [0], [0, 1], [0, 1], [1]),
1894
+ ([0], [0], [0, 1], [1], [0, 1]),
1895
+ ([0], [0], [1], [0, 1], [0, 1])])
1896
+
1897
+ def KleinBottle(self):
1898
+ r"""
1899
+ A cubical complex representation of the Klein bottle, formed
1900
+ by taking the connected sum of the real projective plane with
1901
+ itself.
1902
+
1903
+ EXAMPLES::
1904
+
1905
+ sage: cubical_complexes.KleinBottle()
1906
+ Cubical complex with 42 vertices and 168 cubes
1907
+ """
1908
+ RP2 = cubical_complexes.RealProjectivePlane()
1909
+ return RP2.connected_sum(RP2)
1910
+
1911
+ def SurfaceOfGenus(self, g, orientable=True):
1912
+ """
1913
+ A surface of genus `g` as a cubical complex.
1914
+
1915
+ INPUT:
1916
+
1917
+ - ``g`` -- nonnegative integer; the genus
1918
+ - ``orientable`` -- boolean (default: ``True``); whether the surface
1919
+ should be orientable
1920
+
1921
+ In the orientable case, return a sphere if `g` is zero, and
1922
+ otherwise return a `g`-fold connected sum of a torus with
1923
+ itself.
1924
+
1925
+ In the non-orientable case, raise an error if `g` is zero. If
1926
+ `g` is positive, return a `g`-fold connected sum of a
1927
+ real projective plane with itself.
1928
+
1929
+ EXAMPLES::
1930
+
1931
+ sage: cubical_complexes.SurfaceOfGenus(2)
1932
+ Cubical complex with 32 vertices and 134 cubes
1933
+ sage: cubical_complexes.SurfaceOfGenus(1, orientable=False)
1934
+ Cubical complex with 21 vertices and 81 cubes
1935
+ """
1936
+ try:
1937
+ g = Integer(g)
1938
+ except TypeError:
1939
+ raise ValueError("genus must be a nonnegative integer")
1940
+ if g < 0:
1941
+ raise ValueError("genus must be a nonnegative integer")
1942
+ if g == 0:
1943
+ if not orientable:
1944
+ raise ValueError("no non-orientable surface of genus zero")
1945
+ else:
1946
+ return cubical_complexes.Sphere(2)
1947
+ if orientable:
1948
+ T = cubical_complexes.Torus()
1949
+ else:
1950
+ T = cubical_complexes.RealProjectivePlane()
1951
+ S = T
1952
+ for i in range(g-1):
1953
+ S = S.connected_sum(T)
1954
+ return S
1955
+
1956
+ def Cube(self, n):
1957
+ r"""
1958
+ A cubical complex representation of an `n`-dimensional cube.
1959
+
1960
+ INPUT:
1961
+
1962
+ - ``n`` -- nonnegative integer; the dimension
1963
+
1964
+ EXAMPLES::
1965
+
1966
+ sage: cubical_complexes.Cube(0)
1967
+ Cubical complex with 1 vertex and 1 cube
1968
+ sage: cubical_complexes.Cube(3)
1969
+ Cubical complex with 8 vertices and 27 cubes
1970
+ """
1971
+ if n == 0:
1972
+ return CubicalComplex([Cube([[0]])])
1973
+ else:
1974
+ return CubicalComplex([Cube([[0, 1]] * n)])
1975
+
1976
+
1977
+ cubical_complexes = CubicalComplexExamples()