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