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