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,1806 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # sage.doctest: needs sage.graphs
3
+ r"""
4
+ Finite Delta-complexes
5
+
6
+ AUTHORS:
7
+
8
+ - John H. Palmieri (2009-08)
9
+
10
+ This module implements the basic structure of finite
11
+ `\Delta`-complexes. For full mathematical details, see Hatcher [Hat2002]_,
12
+ especially Section 2.1 and the Appendix on "Simplicial CW Structures".
13
+ As Hatcher points out, `\Delta`-complexes were first introduced by Eilenberg
14
+ and Zilber [EZ1950]_, although they called them "semi-simplicial complexes".
15
+
16
+ A `\Delta`-complex is a generalization of a :mod:`simplicial complex
17
+ <sage.homology.simplicial_complex>`; a `\Delta`-complex `X` consists
18
+ of sets `X_n` for each nonnegative integer `n`, the elements of which
19
+ are called *n-simplices*, along with *face maps* between these sets of
20
+ simplices: for each `n` and for all `0 \leq i \leq n`, there are
21
+ functions `d_i` from `X_n` to `X_{n-1}`, with `d_i(s)` equal to the
22
+ `i`-th face of `s` for each simplex `s \in X_n`. These maps must
23
+ satisfy the *simplicial identity*
24
+
25
+ .. MATH::
26
+
27
+ d_i d_j = d_{j-1} d_i \text{ for all } i<j.
28
+
29
+ Given a `\Delta`-complex, it has a *geometric realization*: a
30
+ topological space built by taking one topological `n`-simplex for each
31
+ element of `X_n`, and gluing them together as determined by the face
32
+ maps.
33
+
34
+ `\Delta`-complexes are an alternative to simplicial complexes. Every
35
+ simplicial complex is automatically a `\Delta`-complex; in the other
36
+ direction, though, it seems in practice that one can often construct
37
+ `\Delta`-complex representations for spaces with many fewer simplices
38
+ than in a simplicial complex representation. For example, the minimal
39
+ triangulation of a torus as a simplicial complex contains 14
40
+ triangles, 21 edges, and 7 vertices, while there is a `\Delta`-complex
41
+ representation of a torus using only 2 triangles, 3 edges, and 1
42
+ vertex.
43
+
44
+ .. NOTE::
45
+
46
+ This class derives from
47
+ :class:`~sage.homology.cell_complex.GenericCellComplex`, and so
48
+ inherits its methods. Some of those methods are not listed here;
49
+ see the :mod:`Generic Cell Complex <sage.homology.cell_complex>`
50
+ page instead.
51
+ """
52
+
53
+ from copy import copy
54
+ from sage.topology.cell_complex import GenericCellComplex
55
+ from sage.rings.integer_ring import ZZ
56
+ from sage.rings.rational_field import QQ
57
+ from sage.rings.integer import Integer
58
+ from .simplicial_complex import Simplex, lattice_paths, SimplicialComplex
59
+ from sage.arith.misc import binomial
60
+ from sage.misc.cachefunc import cached_method
61
+ from sage.misc.lazy_import import lazy_import
62
+
63
+ lazy_import('sage.matrix.constructor', 'matrix')
64
+
65
+
66
+ class DeltaComplex(GenericCellComplex):
67
+ r"""
68
+ Define a `\Delta`-complex.
69
+
70
+ INPUT:
71
+
72
+ - ``data`` -- see below for a description of the options
73
+ - ``check_validity`` -- boolean (default: ``True``); if ``True``, check
74
+ that the simplicial identities hold
75
+
76
+ OUTPUT: a `\Delta`-complex
77
+
78
+ Use ``data`` to define a `\Delta`-complex. It may be in any of
79
+ three forms:
80
+
81
+ - ``data`` may be a dictionary indexed by simplices. The value
82
+ associated to a d-simplex `S` can be any of:
83
+
84
+ - a list or tuple of (d-1)-simplices, where the i-th entry is the
85
+ i-th face of S, given as a simplex,
86
+
87
+ - another d-simplex `T`, in which case the i-th face of `S` is
88
+ declared to be the same as the i-th face of `T`: `S` and `T`
89
+ are glued along their entire boundary,
90
+
91
+ - ``None`` or ``True`` or ``False`` or anything other than the previous two
92
+ options, in which case the faces are just the ordinary faces of `S`.
93
+
94
+ For example, consider the following::
95
+
96
+ sage: n = 5
97
+ sage: S5 = DeltaComplex({Simplex(n):True, Simplex(range(1,n+2)): Simplex(n)})
98
+ sage: S5
99
+ Delta complex with 6 vertices and 65 simplices
100
+
101
+ The first entry in dictionary forming the argument to
102
+ ``DeltaComplex`` says that there is an `n`-dimensional simplex
103
+ with its ordinary boundary. The second entry says that there is
104
+ another simplex whose boundary is glued to that of the first
105
+ one. The resulting `\Delta`-complex is, of course, homeomorphic
106
+ to an `n`-sphere, or actually a 5-sphere, since we defined `n`
107
+ to be 5. (Note that the second simplex here can be any
108
+ `n`-dimensional simplex, as long as it is distinct from
109
+ ``Simplex(n)``.)
110
+
111
+ Let's compute its homology, and also compare it to the simplicial version::
112
+
113
+ sage: S5.homology() # needs sage.modules
114
+ {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z}
115
+ sage: S5.f_vector() # number of simplices in each dimension
116
+ [1, 6, 15, 20, 15, 6, 2]
117
+ sage: simplicial_complexes.Sphere(5).f_vector()
118
+ [1, 7, 21, 35, 35, 21, 7]
119
+
120
+ Both contain a single (-1)-simplex, the empty simplex; other
121
+ than that, the `\Delta`-complex version contains fewer simplices
122
+ than the simplicial one in each dimension.
123
+
124
+ To construct a torus, use::
125
+
126
+ sage: torus_dict = {Simplex([0,1,2]): True,
127
+ ....: Simplex([3,4,5]): (Simplex([0,1]), Simplex([0,2]), Simplex([1,2])),
128
+ ....: Simplex([0,1]): (Simplex(0), Simplex(0)),
129
+ ....: Simplex([0,2]): (Simplex(0), Simplex(0)),
130
+ ....: Simplex([1,2]): (Simplex(0), Simplex(0)),
131
+ ....: Simplex(0): ()}
132
+ sage: T = DeltaComplex(torus_dict); T
133
+ Delta complex with 1 vertex and 7 simplices
134
+ sage: T.cohomology(base_ring=QQ) # needs sage.modules
135
+ {0: Vector space of dimension 0 over Rational Field,
136
+ 1: Vector space of dimension 2 over Rational Field,
137
+ 2: Vector space of dimension 1 over Rational Field}
138
+
139
+ This `\Delta`-complex consists of two triangles (given by
140
+ ``Simplex([0,1,2])`` and ``Simplex([3,4,5])``); the boundary of
141
+ the first is just its usual boundary: the 0th face is obtained
142
+ by omitting the lowest numbered vertex, etc., and so the
143
+ boundary consists of the edges ``[1,2]``, ``[0,2]``, and
144
+ ``[0,1]``, in that order. The boundary of the second is, on the
145
+ one hand, computed the same way: the n-th face is obtained by
146
+ omitting the n-th vertex. On the other hand, the boundary is
147
+ explicitly declared to be edges ``[0,1]``, ``[0,2]``, and
148
+ ``[1,2]``, in that order. This glues the second triangle to the
149
+ first in the prescribed way. The three edges each start and end
150
+ at the single vertex, ``Simplex(0)``.
151
+
152
+ .. image:: ../../media/torus_labelled.png
153
+
154
+ - ``data`` may be nested lists or tuples. The n-th entry in the
155
+ list is a list of the n-simplices in the complex, and each
156
+ n-simplex is encoded as a list, the i-th entry of which is its
157
+ i-th face. Each face is represented by an integer, giving its
158
+ index in the list of (n-1)-faces. For example, consider this::
159
+
160
+ sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)],
161
+ ....: [(1,0,2), (0, 1, 2)] ])
162
+
163
+ The 0th entry in the list is ``[(), ()]``: there are two
164
+ 0-simplices, and their boundaries are empty.
165
+
166
+ The 1st entry in the list is ``[(1,0), (1,0), (0,0)]``: there
167
+ are three 1-simplices. Two of them have boundary ``(1,0)``,
168
+ which means that their 0th face is vertex 1 (in the list of
169
+ vertices), and their 1st face is vertex 0. The other edge has
170
+ boundary ``(0,0)``, so it starts and ends at vertex 0.
171
+
172
+ The 2nd entry in the list is ``[(1,0,2), (0,1,2)]``: there are
173
+ two 2-simplices. The first 2-simplex has boundary ``(1,0,2)``,
174
+ meaning that its 0th face is edge 1 (in the list above), its 1st
175
+ face is edge 0, and its 2nd face is edge 2; similarly for the
176
+ 2nd 2-simplex.
177
+
178
+ If one draws two triangles and identifies them according to this
179
+ description, the result is the real projective plane.
180
+
181
+ .. image:: ../../media/rp2.png
182
+
183
+ ::
184
+
185
+ sage: P.homology(1) # needs sage.modules
186
+ C2
187
+ sage: P.cohomology(2) # needs sage.modules
188
+ C2
189
+
190
+ Closely related to this form for ``data`` is ``X.cells()``
191
+ for a `\Delta`-complex ``X``: this is a dictionary, indexed by
192
+ dimension ``d``, whose ``d``-th entry is a list of the
193
+ ``d``-simplices, as a list::
194
+
195
+ sage: P.cells()
196
+ {-1: ((),),
197
+ 0: ((), ()),
198
+ 1: ((1, 0), (1, 0), (0, 0)),
199
+ 2: ((1, 0, 2), (0, 1, 2))}
200
+
201
+ - ``data`` may be a dictionary indexed by integers. For each
202
+ integer `n`, the entry with key `n` is the list of
203
+ `n`-simplices: this is the same format as is output by the
204
+ :meth:`cells` method. ::
205
+
206
+ sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)],
207
+ ....: [(1,0,2), (0, 1, 2)] ])
208
+ sage: cells_dict = P.cells()
209
+ sage: cells_dict
210
+ {-1: ((),),
211
+ 0: ((), ()),
212
+ 1: ((1, 0), (1, 0), (0, 0)),
213
+ 2: ((1, 0, 2), (0, 1, 2))}
214
+ sage: DeltaComplex(cells_dict)
215
+ Delta complex with 2 vertices and 8 simplices
216
+ sage: P == DeltaComplex(cells_dict)
217
+ True
218
+
219
+ Since `\Delta`-complexes are generalizations of simplicial
220
+ complexes, any simplicial complex may be viewed as a
221
+ `\Delta`-complex::
222
+
223
+ sage: RP2 = simplicial_complexes.RealProjectivePlane()
224
+ sage: RP2_delta = RP2.delta_complex()
225
+ sage: RP2.f_vector()
226
+ [1, 6, 15, 10]
227
+ sage: RP2_delta.f_vector()
228
+ [1, 6, 15, 10]
229
+
230
+ Finally, `\Delta`-complex constructions for several familiar
231
+ spaces are available as follows::
232
+
233
+ sage: delta_complexes.Sphere(4) # the 4-sphere
234
+ Delta complex with 5 vertices and 33 simplices
235
+ sage: delta_complexes.KleinBottle()
236
+ Delta complex with 1 vertex and 7 simplices
237
+ sage: delta_complexes.RealProjectivePlane()
238
+ Delta complex with 2 vertices and 8 simplices
239
+
240
+ Type ``delta_complexes.`` and then hit the :kbd:`Tab` key to get the
241
+ full list.
242
+ """
243
+ def __init__(self, data=None, check_validity=True):
244
+ r"""
245
+ Define a `\Delta`-complex. See :class:`DeltaComplex` for more
246
+ documentation.
247
+
248
+ EXAMPLES::
249
+
250
+ sage: X = DeltaComplex({Simplex(3):True, Simplex(range(1,5)): Simplex(3), Simplex(range(2,6)): Simplex(3)}); X # indirect doctest
251
+ Delta complex with 4 vertices and 18 simplices
252
+ sage: X.homology() # needs sage.modules
253
+ {0: 0, 1: 0, 2: 0, 3: Z x Z}
254
+ sage: X == loads(dumps(X))
255
+ True
256
+ """
257
+ def store_bdry(simplex, faces):
258
+ r"""
259
+ Given a simplex of dimension d and a list of boundaries
260
+ (as other simplices), this stores each boundary face in
261
+ new_data[d-1] if necessary, records the index of each
262
+ boundary face in bdry_list, represents the simplex as
263
+ bdry_list in new_data[d], and returns bdry_list.
264
+
265
+ If the simplex is in the dictionary old_delayed, then it
266
+ is already stored, temporarily, in new_data[d], so replace
267
+ its temporary version with bdry_list.
268
+ """
269
+ bdry_list = []
270
+ d = simplex.dimension()
271
+ if d > 0:
272
+ for f in faces:
273
+ if f in new_data[d-1]:
274
+ bdry_list.append(new_data[d-1].index(f))
275
+ else:
276
+ bdry_list.append(len(new_data[d-1]))
277
+ new_delayed[f] = len(new_data[d-1])
278
+ new_data[d-1].append(f)
279
+ bdry_list = tuple(bdry_list)
280
+ else:
281
+ bdry_list = ()
282
+ if simplex in old_delayed:
283
+ idx = old_delayed[simplex]
284
+ new_data[d][idx] = bdry_list
285
+ else:
286
+ new_data[d].append(bdry_list)
287
+ return bdry_list
288
+
289
+ new_data = {-1: ((),)} # add the empty cell
290
+ if data is None:
291
+ pass
292
+ else:
293
+ if isinstance(data, (list, tuple)):
294
+ dim = 0
295
+ for s in data:
296
+ new_data[dim] = s
297
+ dim += 1
298
+ elif isinstance(data, dict):
299
+ if all(isinstance(a, (int, Integer)) for a in data):
300
+ # a dictionary indexed by integers
301
+ new_data = data
302
+ if -1 not in new_data:
303
+ new_data[-1] = ((),) # add the empty cell
304
+ else:
305
+ # else a dictionary indexed by simplices
306
+ dimension = max([f.dimension() for f in data])
307
+ old_data_by_dim = {}
308
+ for dim in range(dimension + 1):
309
+ old_data_by_dim[dim] = []
310
+ new_data[dim] = []
311
+ for x in data:
312
+ if not isinstance(x, Simplex):
313
+ raise TypeError("each key in the data dictionary must be a simplex")
314
+ old_data_by_dim[x.dimension()].append(x)
315
+ old_delayed = {}
316
+ for dim in range(dimension, -1, -1):
317
+ new_delayed = {}
318
+ current = {}
319
+ for x in old_data_by_dim[dim]:
320
+ if x in data:
321
+ bdry = data[x]
322
+ else:
323
+ bdry = True
324
+ if isinstance(bdry, Simplex):
325
+ # case 1
326
+ # value is a simplex, so x is glued to the old
327
+ # one along its boundary. So the boundary of
328
+ # x is the boundary of the old simplex.
329
+ if bdry in current:
330
+ # if the old simplex is there, copy its boundary
331
+ if x in old_delayed:
332
+ idx = old_delayed[x]
333
+ new_data[dim][idx] = current[bdry]
334
+ else:
335
+ new_data[dim].append(current[bdry])
336
+ elif bdry in data:
337
+ # the old simplex has not yet been added to
338
+ # new_data, but is in the data dictionary. So
339
+ # add it.
340
+ current[bdry] = store_bdry(bdry, bdry.faces())
341
+ new_data[dim].append(current[bdry])
342
+ else:
343
+ raise ValueError("in the data dictionary, there is a value which is a simplex not already in the dictionary")
344
+ elif isinstance(bdry, (list, tuple)):
345
+ # case 2
346
+ # boundary is a list or tuple
347
+ current[x] = store_bdry(x, bdry)
348
+ else:
349
+ # case 3
350
+ # no valid boundary specified, so the default
351
+ # boundary of x should be used
352
+ if x not in current:
353
+ # x hasn't already been added, in case 1
354
+ current[x] = store_bdry(x, x.faces())
355
+ old_delayed = new_delayed
356
+ if dim > 0:
357
+ old_data_by_dim[dim-1].extend(old_delayed.keys())
358
+ else:
359
+ raise ValueError("data is not a list, tuple, or dictionary")
360
+ for n in new_data:
361
+ new_data[n] = tuple(new_data[n])
362
+ # at this point, new_data is a dictionary indexed by
363
+ # dimension, with new_data[d] a list of "simplices" in
364
+ # dimension d
365
+ if check_validity:
366
+ dim = max(new_data)
367
+ for d in range(dim, 1, -1):
368
+ for s in new_data[d]: # s is a d-simplex
369
+ faces = new_data[d-1]
370
+ for j in range(d+1):
371
+ if not all(faces[s[j]][i] == faces[s[i]][j-1] for i in range(j)):
372
+ msg = "simplicial identity d_i d_j = d_{j-1} d_i fails"
373
+ msg += " for j={}, in dimension {}".format(j, d)
374
+ raise ValueError(msg)
375
+ # self._cells_dict: dictionary indexed by dimension d: for
376
+ # each d, have list or tuple of simplices, and for each
377
+ # simplex, have list or tuple with its boundary (as the index
378
+ # of an element in the list of (d-1)-simplices).
379
+ self._cells_dict = new_data
380
+ # self._is_subcomplex_of: if self is a subcomplex of another
381
+ # Delta complex, record that other complex here, along with
382
+ # data relating the cells in self to the cells in the
383
+ # containing complex: for each dimension, a list of indices
384
+ # specifying, for each cell in self, which cell it corresponds
385
+ # to in the containing complex.
386
+ self._is_subcomplex_of = None
387
+ # self._complex: dictionary indexed by dimension d, base_ring,
388
+ # etc.: differential from dim d to dim d-1 in the associated
389
+ # chain complex. thus to get the differential in the cochain
390
+ # complex from dim d-1 to dim d, take the transpose of this
391
+ # one.
392
+ # self._complex = {}
393
+
394
+ def subcomplex(self, data):
395
+ r"""
396
+ Create a subcomplex.
397
+
398
+ INPUT:
399
+
400
+ - ``data`` -- dictionary indexed by dimension or a list (or
401
+ tuple); in either case, data[n] should be the list (or tuple
402
+ or set) of the indices of the simplices to be included in
403
+ the subcomplex
404
+
405
+ This automatically includes all faces of the simplices in
406
+ ``data``, so you only have to specify the simplices which are
407
+ maximal with respect to inclusion.
408
+
409
+ EXAMPLES::
410
+
411
+ sage: X = delta_complexes.Torus()
412
+ sage: A = X.subcomplex({2: [0]}) # one of the triangles of X
413
+ sage: X.homology(subcomplex=A) # needs sage.modules
414
+ {0: 0, 1: 0, 2: Z}
415
+
416
+ In the following, ``line`` is a line segment and ``ends`` is
417
+ the complex consisting of its two endpoints, so the relative
418
+ homology of the two is isomorphic to the homology of a circle::
419
+
420
+ sage: line = delta_complexes.Simplex(1) # an edge
421
+ sage: line.cells()
422
+ {-1: ((),), 0: ((), ()), 1: ((0, 1),)}
423
+ sage: ends = line.subcomplex({0: (0, 1)})
424
+ sage: ends.cells()
425
+ {-1: ((),), 0: ((), ())}
426
+ sage: line.homology(subcomplex=ends) # needs sage.modules
427
+ {0: 0, 1: Z}
428
+ """
429
+ if isinstance(data, (list, tuple)):
430
+ data = dict(zip(range(len(data)), data))
431
+
432
+ # new_dict: dictionary for constructing the subcomplex
433
+ new_dict = {}
434
+ # new_data: dictionary of all cells in the subcomplex: store
435
+ # this with the subcomplex to make it fast to list the cells
436
+ # in self which are not in the subcomplex.
437
+ new_data = {}
438
+ # max_dim: maximum dimension of cells being added
439
+ max_dim = max(data.keys())
440
+ # cells_to_add: in each dimension, add these cells to
441
+ # new_dict. start with the cells given in new_data and add
442
+ # faces of cells one dimension higher.
443
+ cells_to_add = data[max_dim]
444
+ cells = self.cells()
445
+ for d in range(max_dim, -1, -1):
446
+ # cells_to_add is the set of indices of d-cells in self to
447
+ # add to new_dict.
448
+ cells_to_add = sorted(cells_to_add)
449
+ # we add only these cells, so we need to translate their
450
+ # indices from, for example, (0, 1, 4, 5) to (0, 1, 2, 3).
451
+ # That is, when they appear as boundaries of (d+1)-cells,
452
+ # we need to translate their indices in each (d+1)-cell.
453
+ # Here is the key for that translation:
454
+ translate = dict(zip(cells_to_add, range(len(cells_to_add))))
455
+ new_dict[d] = []
456
+ d_cells = cells_to_add
457
+ new_data[d] = cells_to_add
458
+ try:
459
+ cells_to_add = set(new_data[d-1]) # begin to populate the (d-1)-cells
460
+ except KeyError:
461
+ cells_to_add = set()
462
+ for x in d_cells:
463
+ if d+1 in new_dict:
464
+ old = new_dict[d+1]
465
+ new_dict[d+1] = []
466
+ for f in old:
467
+ new_dict[d+1].append(tuple([translate[n] for n in f]))
468
+ new_dict[d].append(cells[d][x])
469
+ cells_to_add.update(cells[d][x])
470
+ new_cells = [new_dict[n] for n in range(max_dim + 1)]
471
+ sub = DeltaComplex(new_cells)
472
+ sub._is_subcomplex_of = {self: new_data}
473
+ return sub
474
+
475
+ def __hash__(self):
476
+ r"""
477
+ TESTS::
478
+
479
+ sage: hash(delta_complexes.Sphere(2)) == hash(delta_complexes.Sphere(2))
480
+ True
481
+ sage: hash(delta_complexes.Sphere(4)) == hash(delta_complexes.Sphere(4))
482
+ True
483
+ """
484
+ return hash(frozenset(self._cells_dict.items()))
485
+
486
+ def __eq__(self, right):
487
+ r"""
488
+ Two `\Delta`-complexes are equal, according to this, if they have
489
+ the same ``_cells_dict``.
490
+
491
+ EXAMPLES::
492
+
493
+ sage: S4 = delta_complexes.Sphere(4)
494
+ sage: S2 = delta_complexes.Sphere(2)
495
+ sage: S4 == S2
496
+ False
497
+ sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)})
498
+ sage: newS2 == S2
499
+ True
500
+ """
501
+ return self._cells_dict == right._cells_dict
502
+
503
+ def __ne__(self, other):
504
+ r"""
505
+ Return ``True`` if ``self`` and ``other`` are not equal.
506
+
507
+ EXAMPLES::
508
+
509
+ sage: S4 = delta_complexes.Sphere(4)
510
+ sage: S2 = delta_complexes.Sphere(2)
511
+ sage: S4 != S2
512
+ True
513
+ sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)})
514
+ sage: newS2 != S2
515
+ False
516
+ """
517
+ return not self.__eq__(other)
518
+
519
+ def cells(self, subcomplex=None):
520
+ r"""
521
+ The cells of this `\Delta`-complex.
522
+
523
+ INPUT:
524
+
525
+ - ``subcomplex`` -- a subcomplex of this complex (default: ``None``)
526
+
527
+ The cells of this `\Delta`-complex, in the form of a dictionary:
528
+ the keys are integers, representing dimension, and the value
529
+ associated to an integer d is the list of d-cells. Each
530
+ d-cell is further represented by a list, the i-th entry of
531
+ which gives the index of its i-th face in the list of
532
+ (d-1)-cells.
533
+
534
+ If the optional argument ``subcomplex`` is present, then
535
+ "return only the faces which are *not* in the subcomplex". To
536
+ preserve the indexing, which is necessary to compute the
537
+ relative chain complex, this actually replaces the faces in
538
+ ``subcomplex`` with ``None``.
539
+
540
+ EXAMPLES::
541
+
542
+ sage: S2 = delta_complexes.Sphere(2)
543
+ sage: S2.cells()
544
+ {-1: ((),),
545
+ 0: ((), (), ()),
546
+ 1: ((0, 1), (0, 2), (1, 2)),
547
+ 2: ((0, 1, 2), (0, 1, 2))}
548
+ sage: A = S2.subcomplex({1: [0,2]}) # one edge
549
+ sage: S2.cells(subcomplex=A)
550
+ {-1: (None,),
551
+ 0: (None, None, None),
552
+ 1: (None, (0, 2), None),
553
+ 2: ((0, 1, 2), (0, 1, 2))}
554
+ """
555
+ cells = self._cells_dict.copy()
556
+ if subcomplex is None:
557
+ return cells
558
+ if subcomplex._is_subcomplex_of is None or self not in subcomplex._is_subcomplex_of:
559
+ if subcomplex == self:
560
+ for d in range(-1, max(cells.keys())+1):
561
+ l = len(cells[d])
562
+ cells[d] = [None]*l # get rid of all cells
563
+ return cells
564
+ else:
565
+ raise ValueError("this is not a subcomplex of self")
566
+ else:
567
+ subcomplex_cells = subcomplex._is_subcomplex_of[self]
568
+ for d in range(max(subcomplex_cells.keys()) + 1):
569
+ L = list(cells[d])
570
+ for c in subcomplex_cells[d]:
571
+ L[c] = None
572
+ cells[d] = tuple(L)
573
+ cells[-1] = (None,)
574
+ return cells
575
+
576
+ def chain_complex(self, subcomplex=None, augmented=False,
577
+ verbose=False, check=False, dimensions=None,
578
+ base_ring=ZZ, cochain=False):
579
+ r"""
580
+ The chain complex associated to this `\Delta`-complex.
581
+
582
+ INPUT:
583
+
584
+ - ``dimensions`` -- if ``None``, compute the chain complex in all
585
+ dimensions. If a list or tuple of integers, compute the
586
+ chain complex in those dimensions, setting the chain groups
587
+ in all other dimensions to zero. NOT IMPLEMENTED YET: this
588
+ function always returns the entire chain complex
589
+ - ``base_ring`` -- commutative ring (default: ``ZZ``)
590
+ - ``subcomplex`` -- a subcomplex of this simplicial complex (default:
591
+ empty). Compute the chain complex relative to this subcomplex.
592
+ - ``augmented`` -- boolean (default: ``False``); if ``True``, return the
593
+ augmented chain complex (that is, include a class in dimension `-1`
594
+ corresponding to the empty cell). This is ignored if ``dimensions``
595
+ is specified or if ``subcomplex`` is nonempty.
596
+ - ``cochain`` -- boolean (default: ``False``); if ``True``, return the
597
+ cochain complex (that is, the dual of the chain complex)
598
+ - ``verbose`` -- boolean (default: ``False``); if ``True``, print some
599
+ messages as the chain complex is computed
600
+ - ``check`` -- boolean (default: ``False``); if ``True``, make sure that
601
+ the chain complex is actually a chain complex: the differentials are
602
+ composable and their product is zero
603
+
604
+ .. NOTE::
605
+
606
+ If subcomplex is nonempty, then the argument ``augmented``
607
+ has no effect: the chain complex relative to a nonempty
608
+ subcomplex is zero in dimension `-1`.
609
+
610
+ EXAMPLES::
611
+
612
+ sage: # needs sage.modules
613
+ sage: circle = delta_complexes.Sphere(1)
614
+ sage: circle.chain_complex()
615
+ Chain complex with at most 2 nonzero terms over Integer Ring
616
+ sage: circle.chain_complex()._latex_()
617
+ '\\Bold{Z}^{1} \\xrightarrow{d_{1}} \\Bold{Z}^{1}'
618
+ sage: circle.chain_complex(base_ring=QQ, augmented=True)
619
+ Chain complex with at most 3 nonzero terms over Rational Field
620
+ sage: circle.homology(dim=1)
621
+ Z
622
+ sage: circle.cohomology(dim=1)
623
+ Z
624
+ sage: T = delta_complexes.Torus()
625
+ sage: T.chain_complex(subcomplex=T)
626
+ Trivial chain complex over Integer Ring
627
+ sage: T.homology(subcomplex=T)
628
+ {0: 0, 1: 0, 2: 0}
629
+ sage: A = T.subcomplex({2: [1]}) # one of the two triangles forming T
630
+ sage: T.chain_complex(subcomplex=A)
631
+ Chain complex with at most 1 nonzero terms over Integer Ring
632
+ sage: T.homology(subcomplex=A)
633
+ {0: 0, 1: 0, 2: Z}
634
+ """
635
+ from sage.homology.chain_complex import ChainComplex
636
+
637
+ if subcomplex is not None:
638
+ # relative chain complex, so don't augment the chain complex
639
+ augmented = False
640
+
641
+ differentials = {}
642
+ if augmented:
643
+ empty_simplex = 1 # number of (-1)-dimensional simplices
644
+ else:
645
+ empty_simplex = 0
646
+ vertices = self.n_cells(0, subcomplex=subcomplex)
647
+ old = vertices
648
+ old_real = [x for x in old if x is not None] # remove faces not in subcomplex
649
+ n = len(old_real)
650
+ differentials[0] = matrix(base_ring, empty_simplex, n, n*empty_simplex*[1])
651
+ # current is list of simplices in dimension dim
652
+ # current_real is list of simplices in dimension dim, with None filtered out
653
+ # old is list of simplices in dimension dim-1
654
+ # old_real is list of simplices in dimension dim-1, with None filtered out
655
+ for dim in range(1, self.dimension()+1):
656
+ current = list(self.n_cells(dim, subcomplex=subcomplex))
657
+ current_real = [x for x in current if x is not None]
658
+ i = 0
659
+ i_real = 0
660
+ translate = {}
661
+ for s in old:
662
+ if s is not None:
663
+ translate[i] = i_real
664
+ i_real += 1
665
+ i += 1
666
+ mat_dict = {}
667
+ col = 0
668
+ for s in current_real:
669
+ sign = 1
670
+ for row in s:
671
+ if old[row] is not None:
672
+ actual_row = translate[row]
673
+ if (actual_row, col) in mat_dict:
674
+ mat_dict[(actual_row, col)] += sign
675
+ else:
676
+ mat_dict[(actual_row, col)] = sign
677
+ sign *= -1
678
+ col += 1
679
+ differentials[dim] = matrix(base_ring, len(old_real), len(current_real), mat_dict)
680
+ old = current
681
+ old_real = current_real
682
+ if cochain:
683
+ cochain_diffs = {}
684
+ for dim in differentials:
685
+ cochain_diffs[dim-1] = differentials[dim].transpose()
686
+ return ChainComplex(data=cochain_diffs, degree=1,
687
+ base_ring=base_ring, check=check)
688
+ else:
689
+ return ChainComplex(data=differentials, degree=-1,
690
+ base_ring=base_ring, check=check)
691
+
692
+ def alexander_whitney(self, cell, dim_left):
693
+ r"""
694
+ Subdivide ``cell`` in this `\Delta`-complex into a pair of
695
+ simplices.
696
+
697
+ For an abstract simplex with vertices `v_0`, `v_1`, ...,
698
+ `v_n`, then subdivide it into simplices `(v_0, v_1, ...,
699
+ v_{dim_left})` and `(v_{dim_left}, v_{dim_left + 1}, ...,
700
+ v_n)`. In a `\Delta`-complex, instead take iterated faces:
701
+ take top faces to get the left factor, take bottom faces to
702
+ get the right factor.
703
+
704
+ INPUT:
705
+
706
+ - ``cell`` -- a simplex in this complex, given as a pair
707
+ ``(idx, tuple)``, where ``idx`` is its index in the list of
708
+ cells in the given dimension, and ``tuple`` is the tuple of
709
+ its faces
710
+
711
+ - ``dim_left`` -- integer between 0 and one more than the
712
+ dimension of this simplex
713
+
714
+ OUTPUT: list containing just the triple ``(1, left,
715
+ right)``, where ``left`` and ``right`` are the two cells
716
+ described above, each given as pairs ``(idx, tuple)``.
717
+
718
+ EXAMPLES::
719
+
720
+ sage: X = delta_complexes.Torus()
721
+ sage: X.n_cells(2)
722
+ [(1, 2, 0), (0, 2, 1)]
723
+ sage: X.alexander_whitney((0, (1, 2, 0)), 1)
724
+ [(1, (0, (0, 0)), (1, (0, 0)))]
725
+ sage: X.alexander_whitney((0, (1, 2, 0)), 0)
726
+ [(1, (0, ()), (0, (1, 2, 0)))]
727
+ sage: X.alexander_whitney((1, (0, 2, 1)), 2)
728
+ [(1, (1, (0, 2, 1)), (0, ()))]
729
+ """
730
+ dim = len(cell[1]) - 1
731
+ left_cell = cell[1]
732
+ idx_l = cell[0]
733
+ for i in range(dim, dim_left, -1):
734
+ idx_l = left_cell[i]
735
+ left_cell = self.n_cells(i-1)[idx_l]
736
+ right_cell = cell[1]
737
+ idx_r = cell[0]
738
+ for i in range(dim, dim - dim_left, -1):
739
+ idx_r = right_cell[0]
740
+ right_cell = self.n_cells(i-1)[idx_r]
741
+ return [(ZZ.one(), (idx_l, left_cell), (idx_r, right_cell))]
742
+
743
+ def n_skeleton(self, n):
744
+ r"""
745
+ The n-skeleton of this `\Delta`-complex.
746
+
747
+ - ``n`` -- nonnegative integer; dimension
748
+
749
+ EXAMPLES::
750
+
751
+ sage: S3 = delta_complexes.Sphere(3)
752
+ sage: S3.n_skeleton(1) # 1-skeleton of a tetrahedron
753
+ Delta complex with 4 vertices and 11 simplices
754
+ sage: S3.n_skeleton(1).dimension()
755
+ 1
756
+ sage: S3.n_skeleton(1).homology() # needs sage.modules
757
+ {0: 0, 1: Z x Z x Z}
758
+ """
759
+ if n >= self.dimension():
760
+ return self
761
+ data = [self._cells_dict[d] for d in range(n + 1)]
762
+ return DeltaComplex(data)
763
+
764
+ def graph(self):
765
+ r"""
766
+ The 1-skeleton of this `\Delta`-complex as a graph.
767
+
768
+ EXAMPLES::
769
+
770
+ sage: T = delta_complexes.Torus()
771
+ sage: T.graph()
772
+ Looped multi-graph on 1 vertex
773
+ sage: S = delta_complexes.Sphere(2)
774
+ sage: S.graph()
775
+ Graph on 3 vertices
776
+ sage: delta_complexes.Simplex(4).graph() == graphs.CompleteGraph(5)
777
+ True
778
+ """
779
+ from sage.graphs.graph import Graph
780
+
781
+ data = {}
782
+ for vertex in range(len(self.n_cells(0))):
783
+ data[vertex] = []
784
+ for edge in self.n_cells(1):
785
+ data[edge[0]].append(edge[1])
786
+ return Graph(data)
787
+
788
+ def join(self, other):
789
+ r"""
790
+ The join of this `\Delta`-complex with another one.
791
+
792
+ INPUT:
793
+
794
+ - ``other`` -- another `\Delta`-complex (the right-hand factor)
795
+
796
+ OUTPUT: the join ``self * other``
797
+
798
+ The join of two `\Delta`-complexes `S` and `T` is the
799
+ `\Delta`-complex `S*T` with simplices of the form `[v_0, ...,
800
+ v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in
801
+ `S` and `[w_0, ..., w_n]` in `T`. The faces are computed
802
+ accordingly: the i-th face of such a simplex is either `(d_i S)
803
+ * T` if `i \leq k`, or `S * (d_{i-k-1} T)` if `i > k`.
804
+
805
+ EXAMPLES::
806
+
807
+ sage: T = delta_complexes.Torus()
808
+ sage: S0 = delta_complexes.Sphere(0)
809
+ sage: T.join(S0) # the suspension of T
810
+ Delta complex with 3 vertices and 21 simplices
811
+
812
+ Compare to simplicial complexes::
813
+
814
+ sage: K = delta_complexes.KleinBottle()
815
+ sage: T_simp = simplicial_complexes.Torus()
816
+ sage: K_simp = simplicial_complexes.KleinBottle()
817
+ sage: T.join(K).homology()[3] == T_simp.join(K_simp).homology()[3] # long time (3 seconds), needs sage.modules
818
+ True
819
+
820
+ The notation '*' may be used, as well::
821
+
822
+ sage: S1 = delta_complexes.Sphere(1)
823
+ sage: X = S1 * S1 # X is a 3-sphere
824
+ sage: X.homology() # needs sage.modules
825
+ {0: 0, 1: 0, 2: 0, 3: Z}
826
+ """
827
+ data = []
828
+ # vertices of the join: the union of the vertices. put the
829
+ # vertices of self first, then the vertices of right.
830
+ data.append(self.n_cells(0) + other.n_cells(0))
831
+ bdries = {}
832
+ for l_idx in range(len(self.n_cells(0))):
833
+ bdries[(0, l_idx, -1, 0)] = l_idx
834
+ for r_idx in range(len(other.n_cells(0))):
835
+ bdries[(-1, 0, 0, r_idx)] = len(self.n_cells(0)) + r_idx
836
+ # dimension of the join:
837
+ maxdim = self.dimension() + other.dimension() + 1
838
+ # now for the d-cells, d>0:
839
+ for d in range(1, maxdim+1):
840
+ d_cells = []
841
+ positions = {}
842
+ new_idx = 0
843
+ for k in range(-1, d+1):
844
+ n = d-1-k
845
+ # d=n+k. need a k-cell from self and an n-cell from other
846
+ if k == -1:
847
+ left = [()]
848
+ else:
849
+ left = self.n_cells(k)
850
+ l_idx = 0
851
+ if n == -1:
852
+ right = [()]
853
+ else:
854
+ right = other.n_cells(n)
855
+ for l in left:
856
+ r_idx = 0
857
+ for r in right:
858
+ # store index of the new simplex in positions
859
+ positions[(k, l_idx, n, r_idx)] = new_idx
860
+ # form boundary of l*r and store it in d_cells
861
+ bdry = []
862
+ # first faces come from left-hand factor
863
+ if k == 0:
864
+ bdry.append(bdries[(-1, 0, n, r_idx)])
865
+ else:
866
+ for i in range(k+1):
867
+ bdry.append(bdries[(k-1, l[i], n, r_idx)])
868
+ # remaining faces come from right-hand factor
869
+ if n == 0:
870
+ bdry.append(bdries[(k, l_idx, -1, 0)])
871
+ else:
872
+ for i in range(n+1):
873
+ bdry.append(bdries[(k, l_idx, n-1, r[i])])
874
+ d_cells.append(tuple(bdry))
875
+ r_idx += 1
876
+ new_idx += 1
877
+ l_idx += 1
878
+ data.append(d_cells)
879
+ bdries = positions
880
+ return DeltaComplex(data)
881
+
882
+ # Use * to mean 'join':
883
+ __mul__ = join
884
+
885
+ def cone(self):
886
+ r"""
887
+ The cone on this `\Delta`-complex.
888
+
889
+ The cone is the complex formed by adding a new vertex `C` and
890
+ simplices of the form `[C, v_0, ..., v_k]` for every simplex
891
+ `[v_0, ..., v_k]` in the original complex. That is, the cone
892
+ is the join of the original complex with a one-point complex.
893
+
894
+ EXAMPLES::
895
+
896
+ sage: K = delta_complexes.KleinBottle()
897
+ sage: K.cone()
898
+ Delta complex with 2 vertices and 14 simplices
899
+ sage: K.cone().homology() # needs sage.modules
900
+ {0: 0, 1: 0, 2: 0, 3: 0}
901
+ """
902
+ return self.join(delta_complexes.Simplex(0))
903
+
904
+ def suspension(self, n=1):
905
+ r"""
906
+ The suspension of this `\Delta`-complex.
907
+
908
+ - ``n`` -- positive integer (default: 1); suspend this many times
909
+
910
+ The suspension is the complex formed by adding two new
911
+ vertices `S_0` and `S_1` and simplices of the form `[S_0, v_0,
912
+ ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex `[v_0,
913
+ ..., v_k]` in the original complex. That is, the suspension
914
+ is the join of the original complex with a two-point complex
915
+ (the 0-sphere).
916
+
917
+ EXAMPLES::
918
+
919
+ sage: S = delta_complexes.Sphere(0)
920
+ sage: S3 = S.suspension(3) # the 3-sphere
921
+ sage: S3.homology() # needs sage.modules
922
+ {0: 0, 1: 0, 2: 0, 3: Z}
923
+ """
924
+ if n < 0:
925
+ raise ValueError("n must be nonnegative")
926
+ if n == 0:
927
+ return self
928
+ if n == 1:
929
+ return self.join(delta_complexes.Sphere(0))
930
+ return self.suspension().suspension(int(n-1))
931
+
932
+ def product(self, other):
933
+ r"""
934
+ The product of this `\Delta`-complex with another one.
935
+
936
+ INPUT:
937
+
938
+ - ``other`` -- another `\Delta`-complex (the right-hand factor)
939
+
940
+ OUTPUT: the product ``self x other``
941
+
942
+ .. WARNING::
943
+
944
+ If ``X`` and ``Y`` are `\Delta`-complexes, then ``X*Y``
945
+ returns their join, not their product.
946
+
947
+ EXAMPLES::
948
+
949
+ sage: K = delta_complexes.KleinBottle()
950
+ sage: X = K.product(K)
951
+
952
+ sage: # needs sage.modules
953
+ sage: X.homology(1)
954
+ Z x Z x C2 x C2
955
+ sage: X.homology(2)
956
+ Z x C2 x C2 x C2
957
+ sage: X.homology(3)
958
+ C2
959
+ sage: X.homology(4)
960
+ 0
961
+ sage: X.homology(base_ring=GF(2))
962
+ {0: Vector space of dimension 0 over Finite Field of size 2,
963
+ 1: Vector space of dimension 4 over Finite Field of size 2,
964
+ 2: Vector space of dimension 6 over Finite Field of size 2,
965
+ 3: Vector space of dimension 4 over Finite Field of size 2,
966
+ 4: Vector space of dimension 1 over Finite Field of size 2}
967
+
968
+ sage: S1 = delta_complexes.Sphere(1)
969
+ sage: K.product(S1).homology() == S1.product(K).homology() # needs sage.modules
970
+ True
971
+ sage: S1.product(S1) == delta_complexes.Torus()
972
+ True
973
+ """
974
+ data = []
975
+ bdries = {}
976
+ # vertices: the vertices in the product are of the form (v,w)
977
+ # for v a vertex in self, w a vertex in other
978
+ vertices = []
979
+ l_idx = 0
980
+ for v in self.n_cells(0):
981
+ r_idx = 0
982
+ for w in other.n_cells(0):
983
+ # one vertex for each pair (v,w)
984
+ # store its indices in bdries; store its boundary in vertices
985
+ bdries[(0, l_idx, 0, r_idx, ((0, 0),))] = len(vertices)
986
+ vertices.append(()) # add new vertex (simplex with empty bdry)
987
+ r_idx += 1
988
+ l_idx += 1
989
+ data.append(tuple(vertices))
990
+ # dim of the product:
991
+ maxdim = self.dimension() + other.dimension()
992
+ # d-cells, d>0: these are obtained by taking products of cells
993
+ # of dimensions k and n, where n+k >= d and n <= d, k <= d.
994
+ simplices = []
995
+ new = {}
996
+ for d in range(1, maxdim+1):
997
+ for k in range(d+1):
998
+ for n in range(d-k, d+1):
999
+ k_idx = 0
1000
+ for k_cell in self.n_cells(k):
1001
+ n_idx = 0
1002
+ for n_cell in other.n_cells(n):
1003
+ # find d-dimensional faces in product of
1004
+ # k_cell and n_cell. to avoid repetition,
1005
+ # only look for faces which use all
1006
+ # vertices of each factor: the 'path'
1007
+ # corresponding to each d-cell must hit
1008
+ # every row and every column in the
1009
+ # lattice. (See the 'product' method for
1010
+ # Simplex, as well as the function
1011
+ # 'lattice_paths', in
1012
+ # simplicial_complex.py.)
1013
+ for _path in lattice_paths(list(range(k + 1)),
1014
+ list(range(n + 1)),
1015
+ length=d+1):
1016
+ path = tuple(_path)
1017
+ new[(k, k_idx, n, n_idx, path)] = len(simplices)
1018
+ bdry_list = []
1019
+ for i in range(d+1):
1020
+ face_path = path[:i] + path[i+1:]
1021
+ if ((i < d and path[i][0] == path[i+1][0]) or
1022
+ (i > 0 and path[i][0] == path[i-1][0])):
1023
+ # this k-simplex
1024
+ k_face_idx = k_idx
1025
+ k_face_dim = k
1026
+ else:
1027
+ # face of this k-simplex
1028
+ k_face_idx = k_cell[path[i][0]]
1029
+ k_face_dim = k-1
1030
+ tail = []
1031
+ for j in range(i, d):
1032
+ tail.append((face_path[j][0]-1,
1033
+ face_path[j][1]))
1034
+ face_path = face_path[:i] + tuple(tail)
1035
+ if ((i < d and path[i][1] == path[i+1][1]) or
1036
+ (i > 0 and path[i][1] == path[i-1][1])):
1037
+ # this n-simplex
1038
+ n_face_idx = n_idx
1039
+ n_face_dim = n
1040
+ else:
1041
+ # face of this n-simplex
1042
+ n_face_idx = n_cell[path[i][1]]
1043
+ n_face_dim = n-1
1044
+ tail = []
1045
+ for j in range(i, d):
1046
+ tail.append((face_path[j][0],
1047
+ face_path[j][1]-1))
1048
+ face_path = face_path[:i] + tuple(tail)
1049
+ bdry_list.append(bdries[(k_face_dim, k_face_idx,
1050
+ n_face_dim, n_face_idx,
1051
+ face_path)])
1052
+ simplices.append(tuple(bdry_list))
1053
+ n_idx += 1
1054
+ k_idx += 1
1055
+ # add d-simplices to data, store d-simplices in bdries,
1056
+ # reset simplices
1057
+ data.append(tuple(simplices))
1058
+ bdries = new
1059
+ new = {}
1060
+ simplices = []
1061
+ return DeltaComplex(data)
1062
+
1063
+ def disjoint_union(self, right):
1064
+ r"""
1065
+ The disjoint union of this `\Delta`-complex with another one.
1066
+
1067
+ INPUT:
1068
+
1069
+ - ``right`` -- the other `\Delta`-complex (the right-hand factor)
1070
+
1071
+ EXAMPLES::
1072
+
1073
+ sage: S1 = delta_complexes.Sphere(1)
1074
+ sage: S2 = delta_complexes.Sphere(2)
1075
+ sage: S1.disjoint_union(S2).homology() # needs sage.modules
1076
+ {0: Z, 1: Z, 2: Z}
1077
+ """
1078
+ dim = max(self.dimension(), right.dimension())
1079
+ data = {}
1080
+ # in dimension n, append simplices of self with simplices of
1081
+ # right, but translate each entry of each right simplex: add
1082
+ # len(self.n_cells(n-1)) to it
1083
+ for n in range(dim, 0, -1):
1084
+ data[n] = list(self.n_cells(n))
1085
+ translate = len(self.n_cells(n-1))
1086
+ for f in right.n_cells(n):
1087
+ data[n].append(tuple([a+translate for a in f]))
1088
+ data[0] = self.n_cells(0) + right.n_cells(0)
1089
+ return DeltaComplex(data)
1090
+
1091
+ def wedge(self, right):
1092
+ r"""
1093
+ The wedge (one-point union) of this `\Delta`-complex with
1094
+ another one.
1095
+
1096
+ - ``right`` -- the other `\Delta`-complex (the right-hand factor)
1097
+
1098
+ .. NOTE::
1099
+
1100
+ This operation is not well-defined if ``self`` or
1101
+ ``other`` is not path-connected.
1102
+
1103
+ EXAMPLES::
1104
+
1105
+ sage: S1 = delta_complexes.Sphere(1)
1106
+ sage: S2 = delta_complexes.Sphere(2)
1107
+ sage: S1.wedge(S2).homology() # needs sage.modules
1108
+ {0: 0, 1: Z, 2: Z}
1109
+ """
1110
+ data = self.disjoint_union(right).cells()
1111
+ left_verts = len(self.n_cells(0))
1112
+ translate = {i: i for i in range(left_verts)}
1113
+ translate[left_verts] = 0
1114
+ for i in range(left_verts + 1, left_verts + len(right.n_cells(0))):
1115
+ translate[i] = i - 1
1116
+ data[0] = data[0][:-1]
1117
+ edges = [[translate[a] for a in e] for e in data[1]]
1118
+ data[1] = edges
1119
+ return DeltaComplex(data)
1120
+
1121
+ def connected_sum(self, other):
1122
+ r"""
1123
+ Return the connected sum of ``self`` with ``other``.
1124
+
1125
+ INPUT:
1126
+
1127
+ - ``other`` -- another `\Delta`-complex
1128
+
1129
+ OUTPUT: the connected sum ``self # other``
1130
+
1131
+ .. warning::
1132
+
1133
+ This does not check that ``self`` and ``other`` are manifolds.
1134
+ It doesn't even check that their facets all have the same
1135
+ dimension. It just chooses top-dimensional simplices from
1136
+ each complex, checks that they have the same dimension,
1137
+ removes them, and glues the remaining pieces together.
1138
+ Since a (more or less) random facet is chosen from each
1139
+ complex, this method may return random results if applied
1140
+ to non-manifolds, depending on which facet is chosen.
1141
+
1142
+ ALGORITHM:
1143
+
1144
+ Pick a top-dimensional simplex from each complex. Check to
1145
+ see if there are any identifications on either simplex, using
1146
+ the :meth:`_is_glued` method. If there are no
1147
+ identifications, remove the simplices and glue the remaining
1148
+ parts of complexes along their boundary. If there are
1149
+ identifications on a simplex, subdivide it repeatedly (using
1150
+ :meth:`elementary_subdivision`) until some piece has no
1151
+ identifications.
1152
+
1153
+ EXAMPLES::
1154
+
1155
+ sage: # needs sage.modules
1156
+ sage: T = delta_complexes.Torus()
1157
+ sage: S2 = delta_complexes.Sphere(2)
1158
+ sage: T.connected_sum(S2).cohomology() == T.cohomology()
1159
+ True
1160
+ sage: RP2 = delta_complexes.RealProjectivePlane()
1161
+ sage: T.connected_sum(RP2).homology(1)
1162
+ Z x Z x C2
1163
+ sage: T.connected_sum(RP2).homology(2)
1164
+ 0
1165
+ sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1)
1166
+ Z x Z x C2
1167
+ """
1168
+ if not self.dimension() == other.dimension():
1169
+ raise ValueError("complexes are not of the same dimension")
1170
+ dim = self.dimension()
1171
+ # Look at the last simplex in the list of top-dimensional
1172
+ # simplices for each complex. If there are identifications on
1173
+ # either of these simplices, subdivide until there are no more
1174
+ # identifications.
1175
+ Left = self
1176
+ while Left._is_glued():
1177
+ Left = Left.elementary_subdivision()
1178
+ Right = other
1179
+ while Right._is_glued():
1180
+ Right = Right.elementary_subdivision()
1181
+ # remove last top-dimensional face from each one and glue.
1182
+ data = {}
1183
+ for n in Left.cells():
1184
+ data[n] = list(Left.cells()[n])
1185
+ right_cells = Right.cells()
1186
+ data[dim] = data[dim][:-1]
1187
+ left_simplex = Left.n_cells(dim)[-1]
1188
+ right_simplex = Right.n_cells(dim)[-1]
1189
+ # renaming: dictionary for translating all simplices of Right
1190
+ renaming = dict(zip(right_simplex, left_simplex))
1191
+ # process_now: cells to be reindexed and added to data
1192
+ process_now = right_cells[dim][:-1]
1193
+ for n in range(dim, 0, -1):
1194
+ # glued: dictionary of just the simplices being glued
1195
+ glued = copy(renaming)
1196
+ # process_later: cells one dim lower to be added to data
1197
+ process_later = []
1198
+ old_idx = 0
1199
+ new_idx = len(data[n-1])
1200
+ # build 'renaming'
1201
+ for s in right_cells[n-1]:
1202
+ if old_idx not in renaming:
1203
+ process_later.append(s)
1204
+ renaming[old_idx] = new_idx
1205
+ new_idx += 1
1206
+ old_idx += 1
1207
+ # reindex all simplices to be processed and add them to data
1208
+ for s in process_now:
1209
+ data[n].append(tuple([renaming[i] for i in s]))
1210
+ # set up for next loop, one dimension down
1211
+ renaming = {}
1212
+ process_now = process_later
1213
+ for f in glued:
1214
+ renaming.update(dict(zip(right_cells[n-1][f], data[n-1][glued[f]])))
1215
+ # deal with vertices separately. we just need to add enough
1216
+ # vertices: all the vertices from Right, minus the number
1217
+ # being glued, which should be dim+1, the number of vertices
1218
+ # in the simplex of dimension dim being glued.
1219
+ for i in range(len(right_cells[0]) - dim - 1):
1220
+ data[0].append(())
1221
+ return DeltaComplex(data)
1222
+
1223
+ def elementary_subdivision(self, idx=-1):
1224
+ r"""
1225
+ Perform an "elementary subdivision" on a top-dimensional
1226
+ simplex in this `\Delta`-complex. If the optional argument
1227
+ ``idx`` is present, it specifies the index (in the list of
1228
+ top-dimensional simplices) of the simplex to subdivide. If
1229
+ not present, subdivide the last entry in this list.
1230
+
1231
+ INPUT:
1232
+
1233
+ - ``idx`` -- integer (default: -1); index specifying which simplex to
1234
+ subdivide
1235
+
1236
+ OUTPUT: `\Delta`-complex with one simplex subdivided
1237
+
1238
+ *Elementary subdivision* of a simplex means replacing that
1239
+ simplex with the cone on its boundary. That is, given a
1240
+ `\Delta`-complex containing a `d`-simplex `S` with vertices
1241
+ `v_0`, ..., `v_d`, form a new `\Delta`-complex by
1242
+
1243
+ - removing `S`
1244
+ - adding a vertex `w` (thought of as being in the interior of `S`)
1245
+ - adding all simplices with vertices `v_{i_0}`, ...,
1246
+ `v_{i_k}`, `w`, preserving any identifications present
1247
+ along the boundary of `S`
1248
+
1249
+ The algorithm for achieving this uses
1250
+ :meth:`_epi_from_standard_simplex` to keep track of simplices
1251
+ (with multiplicity) and what their faces are: this method
1252
+ defines a surjection `\pi` from the standard `d`-simplex to
1253
+ `S`. So first remove `S` and add a new vertex `w`, say at the
1254
+ end of the old list of vertices. Then for each vertex `v` in
1255
+ the standard `d`-simplex, add an edge from `\pi(v)` to `w`;
1256
+ for each edge `(v_0, v_1)` in the standard `d`-simplex, add a
1257
+ triangle `(\pi(v_0), \pi(v_1), w)`, etc.
1258
+
1259
+ Note that given an `n`-simplex `(v_0, v_1, ..., v_n)` in the
1260
+ standard `d`-simplex, the faces of the new `(n+1)`-simplex are
1261
+ given by removing vertices, one at a time, from `(\pi(v_0),
1262
+ ..., \pi(v_n), w)`. These are either the image of the old
1263
+ `n`-simplex (if `w` is removed) or the various new
1264
+ `n`-simplices added in the previous dimension. So keep track
1265
+ of what's added in dimension `n` for use in computing the
1266
+ faces in dimension `n+1`.
1267
+
1268
+ In contrast with barycentric subdivision, note that only the
1269
+ interior of `S` has been changed; this allows for subdivision
1270
+ of a single top-dimensional simplex without subdividing every
1271
+ simplex in the complex.
1272
+
1273
+ The term "elementary subdivision" is taken from p. 112 in John
1274
+ M. Lee's book [Lee2011]_.
1275
+
1276
+ EXAMPLES::
1277
+
1278
+ sage: T = delta_complexes.Torus()
1279
+ sage: T.n_cells(2)
1280
+ [(1, 2, 0), (0, 2, 1)]
1281
+ sage: T.elementary_subdivision(0) # subdivide first triangle
1282
+ Delta complex with 2 vertices and 13 simplices
1283
+ sage: X = T.elementary_subdivision(); X # subdivide last triangle
1284
+ Delta complex with 2 vertices and 13 simplices
1285
+ sage: X.elementary_subdivision()
1286
+ Delta complex with 3 vertices and 19 simplices
1287
+ sage: X.homology() == T.homology() # needs sage.modules
1288
+ True
1289
+ """
1290
+ pi = self._epi_from_standard_simplex(idx=idx)
1291
+ cells_dict = {}
1292
+ old_cells = self.cells()
1293
+ for n in old_cells:
1294
+ cells_dict[n] = list(old_cells[n])
1295
+ dim = self.dimension()
1296
+ # cells of standard simplex of dimension dim
1297
+ std_cells = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True).cells()
1298
+ # adjust zero-cells so they're distinct
1299
+ std_cells[0] = tuple([[n] for n in range(dim + 1)])
1300
+ # remove the cell being subdivided
1301
+ cells_dict[dim].pop(idx)
1302
+ # add the new vertex "w"
1303
+ cells_dict[0].append(())
1304
+ # added_cells: dict indexed by (n-1)-cells, with value the
1305
+ # corresponding new n-cell.
1306
+ added_cells = {(): len(cells_dict[0])-1}
1307
+ for n in range(dim):
1308
+ new_cells = {}
1309
+ # for each n-cell in the standard simplex, add an
1310
+ # (n+1)-cell to the subdivided complex.
1311
+ try:
1312
+ simplices = sorted(pi[n])
1313
+ except TypeError:
1314
+ simplices = pi[n]
1315
+ for simplex in simplices:
1316
+ # compute the faces of the new (n+1)-cell.
1317
+ cell = []
1318
+ for i in simplex:
1319
+ if n > 0:
1320
+ bdry = tuple(std_cells[n-1][i])
1321
+ else:
1322
+ bdry = ()
1323
+ cell.append(added_cells[bdry])
1324
+ # last face is the image of the old simplex)
1325
+ cell.append(pi[n][simplex])
1326
+ cell = tuple(cell)
1327
+ cells_dict[n+1].append(cell)
1328
+ new_cells[simplex] = len(cells_dict[n+1])-1
1329
+ added_cells = new_cells
1330
+ return DeltaComplex(cells_dict)
1331
+
1332
+ def _epi_from_standard_simplex(self, idx=-1, dim=None):
1333
+ r"""
1334
+ Construct an epimorphism from a standard simplex to a
1335
+ top-dimensional simplex in this `\Delta`-complex.
1336
+
1337
+ If the optional argument ``dim`` is not ``None``, then
1338
+ construct the map to a simplex with this dimension. If the
1339
+ optional argument ``idx`` is present, it specifies which
1340
+ simplex to use by giving its index in the list of simplices of
1341
+ the appropriate dimension; if not present, use the last
1342
+ simplex in this list.
1343
+
1344
+ This is used by :meth:`elementary_subdivision`.
1345
+
1346
+ INPUT:
1347
+
1348
+ - ``idx`` -- integer (default: -1); index specifying which simplex to
1349
+ examine
1350
+ - ``dim`` -- integer (default: dimension of complex); dimension of simplex
1351
+ to consider
1352
+
1353
+ OUTPUT: boolean; whether the boundary of the simplex has any
1354
+ identifications
1355
+
1356
+ Suppose that the dimension is `d`. The map is given by a
1357
+ dictionary indexed by dimension: in dimension `i`, its value
1358
+ is a dictionary specifying, for each `i`-simplex in the
1359
+ domain, the corresponding `i`-simplex in the codomain. The
1360
+ vertices are specified as their indices in the lists of
1361
+ simplices in each complex; the same goes for all of the
1362
+ simplices in the codomain. The simplices of dimension 1 or
1363
+ higher in the domain are listed explicitly (in the form of
1364
+ entries from the output of :meth:`cells`).
1365
+
1366
+ In this function, the "standard simplex" is defined to be
1367
+ ``simplicial_complexes.Simplex(d).delta_complex(sort_simplices=True)``.
1368
+
1369
+ EXAMPLES:
1370
+
1371
+ The `\Delta`-complex model for a torus has two triangles and
1372
+ three edges, but only one vertex. So a surjection from the
1373
+ standard 2-simplex to either of the triangles is a bijection
1374
+ in dimension 1, but in dimension 0, sends all three vertices
1375
+ to the same place::
1376
+
1377
+ sage: T = delta_complexes.Torus()
1378
+ sage: sorted(T._epi_from_standard_simplex()[1].items())
1379
+ [((1, 0), 1), ((2, 0), 2), ((2, 1), 0)]
1380
+ sage: sorted(T._epi_from_standard_simplex()[0].items())
1381
+ [((0,), 0), ((1,), 0), ((2,), 0)]
1382
+ """
1383
+ if dim is None:
1384
+ dim = self.dimension()
1385
+ # the output is easier to read if the entries are nonnegative.
1386
+ if idx == -1:
1387
+ idx = len(self.n_cells(dim)) - 1
1388
+ simplex = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True)
1389
+ simplex_cells = simplex.cells()
1390
+ self_cells = self.cells()
1391
+ if dim > 0:
1392
+ mapping = {dim: {tuple(simplex_cells[dim][0]): idx}}
1393
+ else:
1394
+ mapping = {dim: {(0,): idx}}
1395
+ faces_dict = mapping[dim]
1396
+ for n in range(dim, 0, -1):
1397
+ n_cells = faces_dict
1398
+ faces_dict = {}
1399
+ for cell in n_cells:
1400
+ if n > 1:
1401
+ faces = [tuple(simplex_cells[n-1][cell[j]]) for j in range(n+1)]
1402
+ one_cell = dict(zip(faces, self_cells[n][n_cells[cell]]))
1403
+ else:
1404
+ temp = dict(zip(cell, self_cells[n][n_cells[cell]]))
1405
+ one_cell = {}
1406
+ for j in temp:
1407
+ one_cell[(j,)] = temp[j]
1408
+ for j in one_cell:
1409
+ if j not in faces_dict:
1410
+ faces_dict[j] = one_cell[j]
1411
+ mapping[n-1] = faces_dict
1412
+ return mapping
1413
+
1414
+ def _is_glued(self, idx=-1, dim=None):
1415
+ r"""
1416
+ Return ``True`` if there is any gluing along the boundary of a
1417
+ top-dimensional simplex in this `\Delta`-complex.
1418
+
1419
+ If the optional argument ``idx`` is present, it specifies
1420
+ which simplex to consider by giving its index in the list of
1421
+ top-dimensional simplices; if not present, look at the last
1422
+ simplex in this list. If the optional argument ``dim`` is
1423
+ present, it specifies the dimension of the simplex to
1424
+ consider; if not present, look at a top-dimensional simplex.
1425
+
1426
+ This is used by :meth:`connected_sum`.
1427
+
1428
+ INPUT:
1429
+
1430
+ - ``idx`` -- integer (default: -1); index specifying which simplex to
1431
+ examine
1432
+ - ``dim`` -- integer (default: dimension of complex); dimension of simplex
1433
+ to consider
1434
+
1435
+ OUTPUT: boolean; whether the boundary of the simplex has any
1436
+ identifications
1437
+
1438
+ EXAMPLES::
1439
+
1440
+ sage: T = delta_complexes.Torus()
1441
+ sage: T._is_glued()
1442
+ True
1443
+ sage: S = delta_complexes.Simplex(3)
1444
+ sage: S._is_glued()
1445
+ False
1446
+ """
1447
+ if dim is None:
1448
+ dim = self.dimension()
1449
+
1450
+ simplex = self.n_cells(dim)[idx]
1451
+ i = self.dimension() - 1
1452
+ i_faces = set(simplex)
1453
+ # if there are enough i_faces, then no gluing is evident so far
1454
+ not_glued = (len(i_faces) == binomial(dim+1, i+1))
1455
+ while not_glued and i > 0:
1456
+ # count the (i-1) cells and compare to (n+1) choose i.
1457
+ old_faces = i_faces
1458
+ i_faces = set()
1459
+ all_cells = self.n_cells(i)
1460
+ for face in old_faces:
1461
+ i_faces.update(all_cells[face])
1462
+ not_glued = (len(i_faces) == binomial(dim+1, i))
1463
+ i = i-1
1464
+ return not not_glued
1465
+
1466
+ def face_poset(self):
1467
+ r"""
1468
+ The face poset of this `\Delta`-complex, the poset of
1469
+ nonempty cells, ordered by inclusion.
1470
+
1471
+ EXAMPLES::
1472
+
1473
+ sage: T = delta_complexes.Torus()
1474
+ sage: T.face_poset()
1475
+ Finite poset containing 6 elements
1476
+ """
1477
+ from sage.combinat.posets.posets import Poset
1478
+ # given the structure of self.cells(), it's easier to compute
1479
+ # the dual poset, then reverse it at the end.
1480
+ dim = self.dimension()
1481
+ covers = {}
1482
+ # store each n-simplex as a pair (n, idx).
1483
+ for n in range(dim, 0, -1):
1484
+ idx = 0
1485
+ for s in self.n_cells(n):
1486
+ covers[(n, idx)] = list({(n-1, i) for i in s})
1487
+ idx += 1
1488
+ # deal with vertices separately: they have no covers (in the
1489
+ # dual poset).
1490
+ idx = 0
1491
+ for s in self.n_cells(0):
1492
+ covers[(0, idx)] = []
1493
+ idx += 1
1494
+ return Poset(Poset(covers).hasse_diagram().reverse())
1495
+
1496
+ # implement using the definition? the simplices are obtained by
1497
+ # taking chains of inclusions of simplices, etc. have to work out
1498
+ # the faces and identifications.
1499
+ def barycentric_subdivision(self):
1500
+ r"""
1501
+ Not implemented.
1502
+
1503
+ EXAMPLES::
1504
+
1505
+ sage: K = delta_complexes.KleinBottle()
1506
+ sage: K.barycentric_subdivision()
1507
+ Traceback (most recent call last):
1508
+ ...
1509
+ NotImplementedError: barycentric subdivisions are not implemented for Delta complexes
1510
+ """
1511
+ raise NotImplementedError("barycentric subdivisions are not implemented for Delta complexes")
1512
+
1513
+ def n_chains(self, n, base_ring=None, cochains=False):
1514
+ r"""
1515
+ Return the free module of chains in degree ``n`` over ``base_ring``.
1516
+
1517
+ INPUT:
1518
+
1519
+ - ``n`` -- integer
1520
+ - ``base_ring`` -- ring (default: `\ZZ`)
1521
+ - ``cochains`` -- boolean (default: ``False``); if
1522
+ ``True``, return cochains instead
1523
+
1524
+ Since the list of `n`-cells for a `\Delta`-complex may have
1525
+ some ambiguity -- for example, the list of edges may look like
1526
+ ``[(0, 0), (0, 0), (0, 0)]`` if each edge starts and ends at
1527
+ vertex 0 -- we record the indices of the cells along with
1528
+ their tuples. So the basis of chains in such a case would look
1529
+ like ``[(0, (0, 0)), (1, (0, 0)), (2, (0, 0))]``.
1530
+
1531
+ The only difference between chains and cochains is notation:
1532
+ the dual cochain to the chain basis element ``b`` is written
1533
+ as ``\chi_b``.
1534
+
1535
+ EXAMPLES::
1536
+
1537
+ sage: T = delta_complexes.Torus()
1538
+ sage: T.n_chains(1, QQ) # needs sage.modules
1539
+ Free module generated by {(0, (0, 0)), (1, (0, 0)), (2, (0, 0))}
1540
+ over Rational Field
1541
+ sage: list(T.n_chains(1, QQ, cochains=False).basis()) # needs sage.modules
1542
+ [(0, (0, 0)), (1, (0, 0)), (2, (0, 0))]
1543
+ sage: list(T.n_chains(1, QQ, cochains=True).basis()) # needs sage.modules
1544
+ [\chi_(0, (0, 0)), \chi_(1, (0, 0)), \chi_(2, (0, 0))]
1545
+ """
1546
+ from sage.homology.chains import Chains, Cochains
1547
+
1548
+ n_cells = tuple(enumerate(self.n_cells(n)))
1549
+ if cochains:
1550
+ return Cochains(self, n, n_cells, base_ring)
1551
+ else:
1552
+ return Chains(self, n, n_cells, base_ring)
1553
+
1554
+ # the second barycentric subdivision is a simplicial complex. implement this somehow?
1555
+ # def simplicial_complex(self):
1556
+ # X = self.barycentric_subdivision().barycentric_subdivision()
1557
+ # find facets of X and return SimplicialComplex(facets)
1558
+
1559
+ # This is cached for speed reasons: it can be very slow to run
1560
+ # this function.
1561
+ @cached_method
1562
+ def algebraic_topological_model(self, base_ring=None):
1563
+ r"""
1564
+ Algebraic topological model for this `\Delta`-complex with
1565
+ coefficients in ``base_ring``.
1566
+
1567
+ The term "algebraic topological model" is defined by Pilarczyk
1568
+ and Réal [PR2015]_.
1569
+
1570
+ INPUT:
1571
+
1572
+ - ``base_ring`` -- coefficient ring (default: ``QQ``); must be a field
1573
+
1574
+ Denote by `C` the chain complex associated to this
1575
+ `\Delta`-complex. The algebraic topological model is a chain complex
1576
+ `M` with zero differential, with the same homology as `C`,
1577
+ along with chain maps `\pi: C \to M` and `\iota: M \to C`
1578
+ satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic
1579
+ to `1_C`. The chain homotopy `\phi` must satisfy
1580
+
1581
+ - `\phi \phi = 0`,
1582
+ - `\pi \phi = 0`,
1583
+ - `\phi \iota = 0`.
1584
+
1585
+ Such a chain homotopy is called a *chain contraction*.
1586
+
1587
+ OUTPUT: a pair consisting of
1588
+
1589
+ - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and
1590
+ `\iota`
1591
+ - the chain complex `M`
1592
+
1593
+ Note that from the chain contraction ``phi``, one can recover the
1594
+ chain maps `\pi` and `\iota` via ``phi.pi()`` and
1595
+ ``phi.iota()``. Then one can recover `C` and `M` from, for
1596
+ example, ``phi.pi().domain()`` and ``phi.pi().codomain()``,
1597
+ respectively.
1598
+
1599
+ EXAMPLES::
1600
+
1601
+ sage: # needs sage.modules
1602
+ sage: RP2 = delta_complexes.RealProjectivePlane()
1603
+ sage: phi, M = RP2.algebraic_topological_model(GF(2))
1604
+ sage: M.homology()
1605
+ {0: Vector space of dimension 1 over Finite Field of size 2,
1606
+ 1: Vector space of dimension 1 over Finite Field of size 2,
1607
+ 2: Vector space of dimension 1 over Finite Field of size 2}
1608
+ sage: T = delta_complexes.Torus()
1609
+ sage: phi, M = T.algebraic_topological_model(QQ)
1610
+ sage: M.homology()
1611
+ {0: Vector space of dimension 1 over Rational Field,
1612
+ 1: Vector space of dimension 2 over Rational Field,
1613
+ 2: Vector space of dimension 1 over Rational Field}
1614
+ """
1615
+ from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex
1616
+ if base_ring is None:
1617
+ base_ring = QQ
1618
+ return algebraic_topological_model_delta_complex(self, base_ring)
1619
+
1620
+ def _string_constants(self):
1621
+ r"""
1622
+ Tuple containing the name of the type of complex, and the
1623
+ singular and plural of the name of the cells from which it is
1624
+ built. This is used in constructing the string representation.
1625
+
1626
+ EXAMPLES::
1627
+
1628
+ sage: T = delta_complexes.Torus()
1629
+ sage: T._string_constants()
1630
+ ('Delta', 'simplex', 'simplices')
1631
+ """
1632
+ return ('Delta', 'simplex', 'simplices')
1633
+
1634
+
1635
+ class DeltaComplexExamples:
1636
+ r"""
1637
+ Some examples of `\Delta`-complexes.
1638
+
1639
+ Here are the available examples; you can also type
1640
+ ``delta_complexes.`` and hit TAB to get a list::
1641
+
1642
+ Sphere
1643
+ Torus
1644
+ RealProjectivePlane
1645
+ KleinBottle
1646
+ Simplex
1647
+ SurfaceOfGenus
1648
+
1649
+ EXAMPLES::
1650
+
1651
+ sage: S = delta_complexes.Sphere(6) # the 6-sphere
1652
+ sage: S.dimension()
1653
+ 6
1654
+ sage: S.cohomology(6) # needs sage.modules
1655
+ Z
1656
+ sage: delta_complexes.Torus() == delta_complexes.Sphere(3)
1657
+ False
1658
+ """
1659
+
1660
+ def Sphere(self, n):
1661
+ r"""
1662
+ A `\Delta`-complex representation of the `n`-dimensional sphere,
1663
+ formed by gluing two `n`-simplices along their boundary,
1664
+ except in dimension 1, in which case it is a single 1-simplex
1665
+ starting and ending at the same vertex.
1666
+
1667
+ INPUT:
1668
+
1669
+ - ``n`` -- dimension of the sphere
1670
+
1671
+ EXAMPLES::
1672
+
1673
+ sage: delta_complexes.Sphere(4).cohomology(4, base_ring=GF(3)) # needs sage.modules
1674
+ Vector space of dimension 1 over Finite Field of size 3
1675
+ """
1676
+ if n == 1:
1677
+ return DeltaComplex([[()], [(0, 0)]])
1678
+ return DeltaComplex({Simplex(n): True, Simplex(range(1, n+2)): Simplex(n)})
1679
+
1680
+ def Torus(self):
1681
+ r"""
1682
+ A `\Delta`-complex representation of the torus, consisting of one
1683
+ vertex, three edges, and two triangles.
1684
+
1685
+ .. image:: ../../media/torus.png
1686
+
1687
+ EXAMPLES::
1688
+
1689
+ sage: delta_complexes.Torus().homology(1) # needs sage.modules sage.rings.finite_rings
1690
+ Z x Z
1691
+ """
1692
+ return DeltaComplex((((),), ((0, 0), (0, 0), (0, 0)),
1693
+ ((1, 2, 0), (0, 2, 1))))
1694
+
1695
+ def RealProjectivePlane(self):
1696
+ r"""
1697
+ A `\Delta`-complex representation of the real projective plane,
1698
+ consisting of two vertices, three edges, and two triangles.
1699
+
1700
+ .. image:: ../../media/rp2.png
1701
+
1702
+ EXAMPLES::
1703
+
1704
+ sage: # needs sage.modules
1705
+ sage: P = delta_complexes.RealProjectivePlane()
1706
+ sage: P.cohomology(1)
1707
+ 0
1708
+ sage: P.cohomology(2)
1709
+ C2
1710
+ sage: P.cohomology(dim=1, base_ring=GF(2))
1711
+ Vector space of dimension 1 over Finite Field of size 2
1712
+ sage: P.cohomology(dim=2, base_ring=GF(2))
1713
+ Vector space of dimension 1 over Finite Field of size 2
1714
+ """
1715
+ return DeltaComplex((((), ()), ((1, 0), (1, 0), (0, 0)),
1716
+ ((1, 0, 2), (0, 1, 2))))
1717
+
1718
+ def KleinBottle(self):
1719
+ r"""
1720
+ A `\Delta`-complex representation of the Klein bottle, consisting
1721
+ of one vertex, three edges, and two triangles.
1722
+
1723
+ .. image:: ../../media/klein.png
1724
+
1725
+ EXAMPLES::
1726
+
1727
+ sage: delta_complexes.KleinBottle()
1728
+ Delta complex with 1 vertex and 7 simplices
1729
+ """
1730
+ return DeltaComplex((((),), ((0, 0), (0, 0), (0, 0)),
1731
+ ((1, 2, 0), (0, 1, 2))))
1732
+
1733
+ def Simplex(self, n):
1734
+ r"""
1735
+ A `\Delta`-complex representation of an `n`-simplex,
1736
+ consisting of a single `n`-simplex and its faces. (This is
1737
+ the same as the simplicial complex representation available by
1738
+ using ``simplicial_complexes.Simplex(n)``.)
1739
+
1740
+ EXAMPLES::
1741
+
1742
+ sage: delta_complexes.Simplex(3)
1743
+ Delta complex with 4 vertices and 16 simplices
1744
+ """
1745
+ return DeltaComplex({Simplex(n): True})
1746
+
1747
+ def SurfaceOfGenus(self, g, orientable=True):
1748
+ r"""
1749
+ A surface of genus g as a `\Delta`-complex.
1750
+
1751
+ INPUT:
1752
+
1753
+ - ``g`` -- nonnegative integer; the genus
1754
+ - ``orientable`` -- boolean (default: ``True``); whether the surface
1755
+ should be orientable
1756
+
1757
+ In the orientable case, return a sphere if `g` is zero, and
1758
+ otherwise return a `g`-fold connected sum of a torus with
1759
+ itself.
1760
+
1761
+ In the non-orientable case, raise an error if `g` is zero. If
1762
+ `g` is positive, return a `g`-fold connected sum of a
1763
+ real projective plane with itself.
1764
+
1765
+ EXAMPLES::
1766
+
1767
+ sage: delta_complexes.SurfaceOfGenus(1, orientable=False)
1768
+ Delta complex with 2 vertices and 8 simplices
1769
+ sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(1) # needs sage.modules
1770
+ Z x Z x C2
1771
+ sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(2) # needs sage.modules
1772
+ 0
1773
+
1774
+ Compare to simplicial complexes::
1775
+
1776
+ sage: delta_g4 = delta_complexes.SurfaceOfGenus(4)
1777
+ sage: delta_g4.f_vector()
1778
+ [1, 3, 27, 18]
1779
+ sage: simpl_g4 = simplicial_complexes.SurfaceOfGenus(4)
1780
+ sage: simpl_g4.f_vector()
1781
+ [1, 19, 75, 50]
1782
+ sage: delta_g4.homology() == simpl_g4.homology() # needs sage.modules
1783
+ True
1784
+ """
1785
+ try:
1786
+ g = Integer(g)
1787
+ except TypeError:
1788
+ raise ValueError("genus must be a nonnegative integer")
1789
+ if g < 0:
1790
+ raise ValueError("genus must be a nonnegative integer")
1791
+ if g == 0:
1792
+ if not orientable:
1793
+ raise ValueError("no non-orientable surface of genus zero")
1794
+ else:
1795
+ return delta_complexes.Sphere(2)
1796
+ if orientable:
1797
+ X = delta_complexes.Torus()
1798
+ else:
1799
+ X = delta_complexes.RealProjectivePlane()
1800
+ S = X
1801
+ for i in range(g - 1):
1802
+ S = S.connected_sum(X)
1803
+ return S
1804
+
1805
+
1806
+ delta_complexes = DeltaComplexExamples()