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.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- 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()
|