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