Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/homology/simplicial_complex.py
8818 views
1
r"""
2
Finite simplicial complexes
3
4
AUTHORS:
5
6
- John H. Palmieri (2009-04)
7
8
- D. Benjamin Antieau (2009-06) - added is_connected, generated_subcomplex,
9
remove_facet, and is_flag_complex methods;
10
cached the output of the graph() method.
11
12
- Travis Scrimshaw (2012-08-17): Made :class:`SimplicialComplex` have an
13
immutable option, and added ``__hash__()`` function which checks to make
14
sure it is immutable. Made :meth:`SimplicialComplex.remove_face()` into a
15
mutator. Deprecated the ``vertex_set`` parameter.
16
17
- Christian Stump (2011-06) - implementation of is_cohen_macaulay
18
19
- Travis Scrimshaw (2013-02-16): Allowed :class:`SimplicialComplex` to make
20
mutable copies.
21
22
This module implements the basic structure of finite simplicial
23
complexes. Given a set `V` of "vertices", a simplicial complex on `V`
24
is a collection `K` of subsets of `V` satisfying the condition that if
25
`S` is one of the subsets in `K`, then so is every subset of `S`. The
26
subsets `S` are called the 'simplices' of `K`.
27
28
A simplicial complex `K` can be viewed as a purely combinatorial
29
object, as described above, but it also gives rise to a topological
30
space `|K|` (its *geometric realization*) as follows: first, the
31
points of `V` should be in general position in euclidean space. Next,
32
if `\{v\}` is in `K`, then the vertex `v` is in `|K|`. If `\{v, w\}`
33
is in `K`, then the line segment from `v` to `w` is in `|K|`. If `\{u,
34
v, w\}` is in `K`, then the triangle with vertices `u`, `v`, and `w`
35
is in `|K|`. In general, `|K|` is the union of the convex hulls of
36
simplices of `K`. Frequently, one abuses notation and uses `K` to
37
denote both the simplicial complex and the associated topological
38
space.
39
40
.. image:: ../../media/simplices.png
41
42
For any simplicial complex `K` and any commutative ring `R` there is
43
an associated chain complex, with differential of degree `-1`. The
44
`n^{th}` term is the free `R`-module with basis given by the
45
`n`-simplices of `K`. The differential is determined by its value on
46
any simplex: on the `n`-simplex with vertices `(v_0, v_1, ..., v_n)`,
47
the differential is the alternating sum with `i^{th}` summand `(-1)^i`
48
multiplied by the `(n-1)`-simplex obtained by omitting vertex `v_i`.
49
50
In the implementation here, the vertex set must be finite. To define a
51
simplicial complex, specify its vertex set: this should be a list,
52
tuple, or set, or it can be a non-negative integer `n`, in which case
53
the vertex set is `(0, ..., n)`. Also specify the facets: the maximal
54
faces.
55
56
.. NOTE::
57
58
The elements of the vertex set are not automatically contained in
59
the simplicial complex: each one is only included if and only if it
60
is a vertex of at least one of the specified facets.
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
EXAMPLES::
71
72
sage: SimplicialComplex([[1], [3, 7]])
73
Simplicial complex with vertex set (1, 3, 7) and facets {(3, 7), (1,)}
74
sage: SimplicialComplex() # the empty simplicial complex
75
Simplicial complex with vertex set () and facets {()}
76
sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]])
77
sage: X
78
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (2, 3), (0, 3), (0, 1)}
79
sage: X.stanley_reisner_ring()
80
Quotient of Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring by the ideal (x1*x3, x0*x2)
81
sage: X.is_pure()
82
True
83
84
Sage can perform a number of operations on simplicial complexes, such
85
as the join and the product, and it can also compute homology::
86
87
sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle
88
sage: T = S.product(S) # torus
89
sage: T
90
Simplicial complex with 9 vertices and 18 facets
91
sage: T.homology() # this computes reduced homology
92
{0: 0, 1: Z x Z, 2: Z}
93
sage: T.euler_characteristic()
94
0
95
96
Sage knows about some basic combinatorial data associated to a
97
simplicial complex::
98
99
sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]])
100
sage: X.f_vector()
101
[1, 4, 4]
102
sage: X.face_poset()
103
Finite poset containing 8 elements
104
sage: X.stanley_reisner_ring()
105
Quotient of Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring by the ideal (x1*x3, x0*x2)
106
107
Mutability (see :trac:`12587`)::
108
109
sage: S = SimplicialComplex([[1,4], [2,4]])
110
sage: S.add_face([1,3])
111
sage: S.remove_face([1,3]); S
112
Simplicial complex with vertex set (1, 2, 3, 4) and facets {(2, 4), (1, 4), (3,)}
113
sage: hash(S)
114
Traceback (most recent call last):
115
...
116
ValueError: This simplicial complex must be immutable. Call set_immutable().
117
sage: S = SimplicialComplex([[1,4], [2,4]])
118
sage: S.set_immutable()
119
sage: S.add_face([1,3])
120
Traceback (most recent call last):
121
...
122
ValueError: This simplicial complex is not mutable
123
sage: S.remove_face([1,3])
124
Traceback (most recent call last):
125
...
126
ValueError: This simplicial complex is not mutable
127
sage: hash(S) == hash(S)
128
True
129
130
sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False)
131
sage: hash(S2) == hash(S)
132
True
133
134
We can also make mutable copies of an immutable simplicial complex
135
(see :trac:`14142`)::
136
137
sage: S = SimplicialComplex([[1,4], [2,4]])
138
sage: S.set_immutable()
139
sage: T = copy(S)
140
sage: T.is_mutable()
141
True
142
sage: S == T
143
True
144
"""
145
146
# possible future directions for SimplicialComplex:
147
#
148
# make compatible with GAP (see http://linalg.org/gap.html)
149
# compare to and make compatible with polymake
150
# see Macaulay: http://www.math.uiuc.edu/Macaulay2/doc/Macaulay2-1.1/share/doc/Macaulay2/SimplicialComplexes/html/___Simplicial__Complex.html; compare performance and make compatible
151
# should + have any meaning?
152
# cohomology: compute cup products (and Massey products?)
153
154
from copy import copy
155
from sage.homology.cell_complex import GenericCellComplex
156
from sage.structure.sage_object import SageObject
157
from sage.rings.integer import Integer
158
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
159
from sage.sets.set import Set
160
from sage.rings.integer_ring import ZZ
161
from sage.structure.parent_gens import normalize_names
162
from sage.misc.latex import latex
163
from sage.matrix.constructor import matrix
164
from sage.homology.chain_complex import ChainComplex
165
from sage.graphs.graph import Graph
166
167
def lattice_paths(t1, t2, length=None):
168
"""
169
Given lists (or tuples or ...) ``t1`` and ``t2``, think of them as
170
labelings for vertices: ``t1`` labeling points on the x-axis,
171
``t2`` labeling points on the y-axis, both increasing. Return the
172
list of rectilinear paths along the grid defined by these points
173
in the plane, starting from ``(t1[0], t2[0])``, ending at
174
``(t1[last], t2[last])``, and at each grid point, going either
175
right or up. See the examples.
176
177
:param t1: labeling for vertices
178
:param t2: labeling for vertices
179
:param length: if not ``None``, then an integer, the length of the desired
180
path.
181
:type length: integer or ``None``; optional, default ``None``
182
:type t1: tuple, list, other iterable
183
:type t2: tuple, list, other iterable
184
:return: list of lists of vertices making up the paths as described above
185
:rtype: list of lists
186
187
This is used when triangulating the product of simplices. The
188
optional argument ``length`` is used for `\Delta`-complexes, to
189
specify all simplices in a product: in the triangulation of a
190
product of two simplices, there is a `d`-simplex for every path of
191
length `d+1` in the lattice. The path must start at the bottom
192
left and end at the upper right, and it must use at least one
193
point in each row and in each column, so if ``length`` is too
194
small, there will be no paths.
195
196
EXAMPLES::
197
198
sage: from sage.homology.simplicial_complex import lattice_paths
199
sage: lattice_paths([0,1,2], [0,1,2])
200
[[(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)],
201
[(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)],
202
[(0, 0), (1, 0), (1, 1), (1, 2), (2, 2)],
203
[(0, 0), (0, 1), (1, 1), (2, 1), (2, 2)],
204
[(0, 0), (1, 0), (1, 1), (2, 1), (2, 2)],
205
[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)]]
206
sage: lattice_paths(('a', 'b', 'c'), (0, 3, 5))
207
[[('a', 0), ('a', 3), ('a', 5), ('b', 5), ('c', 5)],
208
[('a', 0), ('a', 3), ('b', 3), ('b', 5), ('c', 5)],
209
[('a', 0), ('b', 0), ('b', 3), ('b', 5), ('c', 5)],
210
[('a', 0), ('a', 3), ('b', 3), ('c', 3), ('c', 5)],
211
[('a', 0), ('b', 0), ('b', 3), ('c', 3), ('c', 5)],
212
[('a', 0), ('b', 0), ('c', 0), ('c', 3), ('c', 5)]]
213
sage: lattice_paths(range(3), range(3), length=2)
214
[]
215
sage: lattice_paths(range(3), range(3), length=3)
216
[[(0, 0), (1, 1), (2, 2)]]
217
sage: lattice_paths(range(3), range(3), length=4)
218
[[(0, 0), (1, 1), (1, 2), (2, 2)],
219
[(0, 0), (0, 1), (1, 2), (2, 2)],
220
[(0, 0), (1, 1), (2, 1), (2, 2)],
221
[(0, 0), (1, 0), (2, 1), (2, 2)],
222
[(0, 0), (0, 1), (1, 1), (2, 2)],
223
[(0, 0), (1, 0), (1, 1), (2, 2)]]
224
"""
225
if length is None:
226
# 0 x n (or k x 0) rectangle:
227
if len(t1) == 0 or len(t2) == 0:
228
return [[]]
229
# 1 x n (or k x 1) rectangle:
230
elif len(t1) == 1:
231
return [[(t1[0], w) for w in t2]]
232
elif len(t2) == 1:
233
return [[(v, t2[0]) for v in t1]]
234
else:
235
# recursive: paths in rectangle with either one fewer row
236
# or column, plus the upper right corner
237
return ([path + [(t1[-1], t2[-1])] for path
238
in lattice_paths(t1[:-1], t2)] +
239
[path + [(t1[-1], t2[-1])] for path
240
in lattice_paths(t1, t2[:-1])])
241
else:
242
if length > len(t1) + len(t2) - 1:
243
return []
244
# as above, except make sure that lengths are correct. if
245
# not, return an empty list.
246
#
247
# 0 x n (or k x 0) rectangle:
248
elif len(t1) == 0 or len(t2) == 0:
249
if length == 0:
250
return [[]]
251
else:
252
return []
253
# 1 x n (or k x 1) rectangle:
254
elif len(t1) == 1:
255
if length == len(t2):
256
return [[(t1[0], w) for w in t2]]
257
else:
258
return []
259
elif len(t2) == 1:
260
if length == len(t1):
261
return [[(v, t2[0]) for v in t1]]
262
else:
263
return []
264
else:
265
# recursive: paths of length one fewer in rectangle with
266
# either one fewer row, one fewer column, or one fewer of
267
# each, and then plus the upper right corner
268
return ([path + [(t1[-1], t2[-1])] for path
269
in lattice_paths(t1[:-1], t2, length=length-1)] +
270
[path + [(t1[-1], t2[-1])] for path
271
in lattice_paths(t1, t2[:-1], length=length-1)] +
272
[path + [(t1[-1], t2[-1])] for path
273
in lattice_paths(t1[:-1], t2[:-1], length=length-1)])
274
275
def rename_vertex(n, keep, left = True):
276
"""
277
Rename a vertex: the vertices from the list ``keep`` get
278
relabeled 0, 1, 2, ..., in order. Any other vertex (e.g. 4) gets
279
renamed to by prepending an 'L' or an 'R' (thus to either 'L4' or
280
'R4'), depending on whether the argument left is ``True`` or ``False``.
281
282
:param n: a 'vertex': either an integer or a string
283
:param keep: a list of three vertices
284
:param left: if ``True``, rename for use in left factor
285
:type left: boolean; optional, default ``True``
286
287
This is used by the :meth:`~SimplicialComplex.connected_sum` method for
288
simplicial complexes.
289
290
EXAMPLES::
291
292
sage: from sage.homology.simplicial_complex import rename_vertex
293
sage: rename_vertex(6, [5, 6, 7])
294
1
295
sage: rename_vertex(3, [5, 6, 7, 8, 9])
296
'L3'
297
sage: rename_vertex(3, [5, 6, 7], left=False)
298
'R3'
299
"""
300
lookup = dict(zip(keep, range(len(keep))))
301
try:
302
return lookup[n]
303
except KeyError:
304
if left:
305
return "L" + str(n)
306
else:
307
return "R" + str(n)
308
309
class Simplex(SageObject):
310
"""
311
Define a simplex.
312
313
Topologically, a simplex is the convex hull of a collection of
314
vertices in general position. Combinatorially, it is defined just
315
by specifying a set of vertices. It is represented in Sage by the
316
tuple of the vertices.
317
318
:param X: set of vertices
319
:type X: integer or list, tuple, or other iterable
320
:return: simplex with those vertices
321
322
``X`` may be a non-negative integer `n`, in which case the
323
simplicial complex will have `n+1` vertices `(0, 1, ..., n)`, or
324
it may be anything which may be converted to a tuple, in which
325
case the vertices will be that tuple. In the second case, each
326
vertex must be hashable, so it should be a number, a string, or a
327
tuple, for instance, but not a list.
328
329
.. WARNING::
330
331
The vertices should be distinct, and no error checking is done
332
to make sure this is the case.
333
334
EXAMPLES::
335
336
sage: Simplex(4)
337
(0, 1, 2, 3, 4)
338
sage: Simplex([3, 4, 1])
339
(3, 4, 1)
340
sage: X = Simplex((3, 'a', 'vertex')); X
341
(3, 'a', 'vertex')
342
sage: X == loads(dumps(X))
343
True
344
345
Vertices may be tuples but not lists::
346
347
sage: Simplex([(1,2), (3,4)])
348
((1, 2), (3, 4))
349
sage: Simplex([[1,2], [3,4]])
350
Traceback (most recent call last):
351
...
352
TypeError: unhashable type: 'list'
353
"""
354
355
def __init__(self, X):
356
"""
357
Define a simplex. See :class:`Simplex` for full documentation.
358
359
EXAMPLES::
360
361
sage: Simplex(2)
362
(0, 1, 2)
363
sage: Simplex(('a', 'b', 'c'))
364
('a', 'b', 'c')
365
"""
366
try:
367
N = int(X) + 1
368
self.__tuple = tuple(range(N))
369
except TypeError:
370
self.__tuple = tuple(X)
371
self.__set = frozenset(self.__tuple)
372
373
def tuple(self):
374
"""
375
The tuple attached to this simplex.
376
377
EXAMPLES::
378
379
sage: Simplex(3).tuple()
380
(0, 1, 2, 3)
381
382
Although simplices are printed as if they were tuples, they
383
are not the same type::
384
385
sage: type(Simplex(3).tuple())
386
<type 'tuple'>
387
sage: type(Simplex(3))
388
<class 'sage.homology.simplicial_complex.Simplex'>
389
"""
390
return self.__tuple
391
392
def set(self):
393
"""
394
The frozenset attached to this simplex.
395
396
EXAMPLES::
397
398
sage: Simplex(3).set()
399
frozenset([0, 1, 2, 3])
400
"""
401
return self.__set
402
403
def is_face(self, other):
404
"""
405
Return ``True`` iff this simplex is a face of other.
406
407
EXAMPLES::
408
409
sage: Simplex(3).is_face(Simplex(5))
410
True
411
sage: Simplex(5).is_face(Simplex(2))
412
False
413
sage: Simplex(['a', 'b', 'c']).is_face(Simplex(8))
414
False
415
"""
416
return self.__set.issubset(other.__set)
417
418
def __contains__(self, x):
419
"""
420
Return ``True`` iff ``x`` is a vertex of this simplex.
421
422
EXAMPLES::
423
424
sage: 3 in Simplex(5)
425
True
426
sage: 3 in Simplex(2)
427
False
428
"""
429
return self.__set.__contains__(x)
430
431
def __getitem__(self, n):
432
"""
433
Return the `n`-th vertex in this simplex.
434
435
EXAMPLES::
436
437
sage: Simplex(5)[2]
438
2
439
sage: Simplex(['a', 'b', 'c'])[1]
440
'b'
441
"""
442
return self.__tuple.__getitem__(n)
443
444
def __iter__(self):
445
"""
446
Iterator for the vertices of this simplex.
447
448
EXAMPLES::
449
450
sage: [v**2 for v in Simplex(3)]
451
[0, 1, 4, 9]
452
"""
453
return self.__tuple.__iter__()
454
455
def __add__(self, other):
456
"""
457
Simplex obtained by concatenating the underlying tuples of the
458
two arguments.
459
460
:param other: another simplex
461
462
EXAMPLES::
463
464
sage: Simplex((1,2,3)) + Simplex((5,6))
465
(1, 2, 3, 5, 6)
466
"""
467
return Simplex(self.__tuple.__add__(other.__tuple))
468
469
def face(self, n):
470
"""
471
The `n`-th face of this simplex.
472
473
:param n: an integer between 0 and the dimension of this simplex
474
:type n: integer
475
:return: the simplex obtained by removing the `n`-th vertex from this
476
simplex
477
478
EXAMPLES::
479
480
sage: S = Simplex(4)
481
sage: S.face(0)
482
(1, 2, 3, 4)
483
sage: S.face(3)
484
(0, 1, 2, 4)
485
"""
486
if n >= 0 and n <= self.dimension():
487
return Simplex(self.__tuple[:n] + self.__tuple[n+1:])
488
else:
489
raise IndexError, "%s does not have an nth face for n=%s." % (self, n)
490
491
def faces(self):
492
"""
493
The list of faces (of codimension 1) of this simplex.
494
495
EXAMPLES::
496
497
sage: S = Simplex(4)
498
sage: S.faces()
499
[(1, 2, 3, 4), (0, 2, 3, 4), (0, 1, 3, 4), (0, 1, 2, 4), (0, 1, 2, 3)]
500
sage: len(Simplex(10).faces())
501
11
502
"""
503
return [self.face(i) for i in range(self.dimension()+1)]
504
505
def dimension(self):
506
"""
507
The dimension of this simplex.
508
509
The dimension of a simplex is the number of vertices minus 1.
510
511
EXAMPLES::
512
513
sage: Simplex(5).dimension() == 5
514
True
515
sage: Simplex(5).face(1).dimension()
516
4
517
"""
518
return len(self.__tuple) - 1
519
520
def is_empty(self):
521
"""
522
Return ``True`` iff this simplex is the empty simplex.
523
524
EXAMPLES::
525
526
sage: [Simplex(n).is_empty() for n in range(-1,4)]
527
[True, False, False, False, False]
528
"""
529
return self.dimension() < 0
530
531
def join(self, right, rename_vertices=True):
532
"""
533
The join of this simplex with another one.
534
535
The join of two simplices `[v_0, ..., v_k]` and `[w_0, ...,
536
w_n]` is the simplex `[v_0, ..., v_k, w_0, ..., w_n]`.
537
538
:param right: the other simplex (the right-hand factor)
539
540
:param rename_vertices: If this is ``True``, the vertices in the
541
join will be renamed by this formula: vertex "v" in the
542
left-hand factor --> vertex "Lv" in the join, vertex "w"
543
in the right-hand factor --> vertex "Rw" in the join. If
544
this is false, this tries to construct the join without
545
renaming the vertices; this may cause problems if the two
546
factors have any vertices with names in common.
547
548
:type rename_vertices: boolean; optional, default ``True``
549
550
EXAMPLES::
551
552
sage: Simplex(2).join(Simplex(3))
553
('L0', 'L1', 'L2', 'R0', 'R1', 'R2', 'R3')
554
sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z']))
555
('La', 'Lb', 'Rx', 'Ry', 'Rz')
556
sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z']), rename_vertices=False)
557
('a', 'b', 'x', 'y', 'z')
558
"""
559
if rename_vertices:
560
vertex_set = (["L" + str(v) for v in self]
561
+ ["R" + str(w) for w in right])
562
else:
563
vertex_set = self.__tuple + right.__tuple
564
return Simplex(vertex_set)
565
566
def product(self, other, rename_vertices=True):
567
r"""
568
The product of this simplex with another one, as a list of simplices.
569
570
:param other: the other simplex
571
572
:param rename_vertices: If this is ``False``, then the vertices in
573
the product are the set of ordered pairs `(v,w)` where `v`
574
is a vertex in the left-hand factor (``self``) and `w` is
575
a vertex in the right-hand factor (``other``). If this is
576
``True``, then the vertices are renamed as "LvRw" (e.g., the
577
vertex (1,2) would become "L1R2"). This is useful if you
578
want to define the Stanley-Reisner ring of the complex:
579
vertex names like (0,1) are not suitable for that, while
580
vertex names like "L0R1" are.
581
582
:type rename_vertices: boolean; optional, default ``True``
583
584
Algorithm: see Hatcher, p. 277-278 [Hat]_ (who in turn refers to
585
Eilenberg-Steenrod, p. 68): given ``S = Simplex(m)`` and
586
``T = Simplex(n)``, then `S \times T` can be
587
triangulated as follows: for each path `f` from `(0,0)` to
588
`(m,n)` along the integer grid in the plane, going up or right
589
at each lattice point, associate an `(m+n)`-simplex with
590
vertices `v_0`, `v_1`, ..., where `v_k` is the `k^{th}` vertex
591
in the path `f`.
592
593
Note that there are `m+n` choose `n` such paths. Note also
594
that each vertex in the product is a pair of vertices `(v,w)`
595
where `v` is a vertex in the left-hand factor and `w`
596
is a vertex in the right-hand factor.
597
598
.. NOTE::
599
600
This produces a list of simplices -- not a :class:`Simplex`, not
601
a :class:`SimplicialComplex`.
602
603
EXAMPLES::
604
605
sage: len(Simplex(2).product(Simplex(2)))
606
6
607
sage: Simplex(1).product(Simplex(1))
608
[('L0R0', 'L0R1', 'L1R1'), ('L0R0', 'L1R0', 'L1R1')]
609
sage: Simplex(1).product(Simplex(1), rename_vertices=False)
610
[((0, 0), (0, 1), (1, 1)), ((0, 0), (1, 0), (1, 1))]
611
"""
612
if not rename_vertices:
613
return [Simplex(x) for x in lattice_paths(self.tuple(), other.tuple())]
614
615
answer = []
616
for x in lattice_paths(self.tuple(), other.tuple()):
617
new = tuple(["L" + str(v) + "R" + str(w) for (v,w) in x])
618
answer.append(Simplex(new))
619
return answer
620
621
def __cmp__(self, other):
622
"""
623
Return ``True`` iff this simplex is the same as ``other``: that
624
is, if the vertices of the two are the same, even with a
625
different ordering
626
627
:param other: the other simplex
628
629
EXAMPLES::
630
631
sage: Simplex([0,1,2]) == Simplex([0,2,1])
632
True
633
sage: Simplex([0,1,2]) == Simplex(['a','b','c'])
634
False
635
sage: Simplex([1]) < Simplex([2])
636
True
637
sage: Simplex([1]) > Simplex([2])
638
False
639
"""
640
if not isinstance(other, Simplex):
641
return -1
642
if self.__set == other.__set:
643
return 0
644
return cmp(sorted(tuple(self.__set)), sorted(tuple(other.__set)))
645
646
def __hash__(self):
647
"""
648
Hash value for this simplex. This computes the hash value of
649
the Python frozenset of the underlying tuple, since this is
650
what's important when testing equality.
651
652
EXAMPLES::
653
654
sage: Simplex([1,2,0]).__hash__() == Simplex(2).__hash__()
655
True
656
sage: Simplex([1,2,0,1,1,2]).__hash__() == Simplex(2).__hash__()
657
True
658
"""
659
return hash(self.__set)
660
661
def _repr_(self):
662
"""
663
Print representation.
664
665
EXAMPLES::
666
667
sage: S = Simplex(5)
668
sage: S._repr_()
669
'(0, 1, 2, 3, 4, 5)'
670
"""
671
return self.__tuple.__repr__()
672
673
def _latex_(self):
674
r"""
675
LaTeX representation.
676
677
EXAMPLES::
678
679
sage: Simplex(3)._latex_()
680
\left(0,
681
1,
682
2,
683
3\right)
684
"""
685
return latex(self.__tuple)
686
687
class SimplicialComplex(GenericCellComplex):
688
r"""
689
Define a simplicial complex.
690
691
:param maximal_faces: set of maximal faces
692
:param maximality_check: see below
693
:type maximality_check: boolean; optional, default ``True``
694
:param sort_facets: see below
695
:type sort_facets: boolean; optional, default ``True``
696
:param name_check: see below
697
:type name_check: boolean; optional, default ``False``
698
:param is_mutable: Set to ``False`` to make this immutable
699
:type is_mutable: boolean; optional, default ``True``
700
:return: a simplicial complex
701
702
``maximal_faces`` should be a list or tuple or set (indeed,
703
anything which may be converted to a set) whose elements are lists
704
(or tuples, etc.) of vertices. Maximal faces are also known as
705
'facets'.
706
707
If ``maximality_check`` is ``True``, check that each maximal face is,
708
in fact, maximal. In this case, when producing the internal
709
representation of the simplicial complex, omit those that are not.
710
It is highly recommended that this be ``True``; various methods for
711
this class may fail if faces which are claimed to be maximal are
712
in fact not.
713
714
If ``sort_facets`` is ``True``, sort the vertices in each facet. If
715
the vertices in different facets are not ordered compatibly (e.g.,
716
if you have facets ``(1, 3, 5)`` and ``(5, 3, 8)``), then homology
717
calculations may have unpredictable results.
718
719
If ``name_check`` is ``True``, check the names of the vertices to see
720
if they can be easily converted to generators of a polynomial ring
721
-- use this if you plan to use the Stanley-Reisner ring for the
722
simplicial complex.
723
724
.. WARNING::
725
726
Earlier versions of Sage supported a ``vertex_set`` argument
727
to specify the vertices. This is now deprecated -- see
728
:trac:`12587` -- the set of vertices is determined from the
729
maximal faces.
730
731
EXAMPLES::
732
733
sage: SimplicialComplex([[1,2], [1,4]])
734
Simplicial complex with vertex set (1, 2, 4) and facets {(1, 2), (1, 4)}
735
sage: SimplicialComplex([[0,2], [0,3], [0]])
736
Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)}
737
sage: SimplicialComplex([[0,2], [0,3], [0]], maximality_check=False)
738
Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3), (0,)}
739
sage: S = SimplicialComplex((('a', 'b'), ['a', 'c'], ('b', 'c')))
740
sage: S
741
Simplicial complex with vertex set ('a', 'b', 'c') and facets {('b', 'c'), ('a', 'c'), ('a', 'b')}
742
743
Finally, if there is only one argument and it is a
744
simplicial complex, return that complex. If it is an object with
745
a built-in conversion to simplicial complexes (via a
746
``_simplicial_`` method), then the resulting simplicial complex is
747
returned::
748
749
sage: S = SimplicialComplex([[0,2], [0,3], [0,6]])
750
sage: SimplicialComplex(S) == S
751
True
752
sage: Tc = cubical_complexes.Torus(); Tc
753
Cubical complex with 16 vertices and 64 cubes
754
sage: Ts = SimplicialComplex(Tc); Ts
755
Simplicial complex with 16 vertices and 32 facets
756
sage: Ts.homology()
757
{0: 0, 1: Z x Z, 2: Z}
758
759
TESTS:
760
761
Check that we can make mutable copies (see :trac:`14142`)::
762
763
sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False)
764
sage: S.is_mutable()
765
False
766
sage: C = copy(S)
767
sage: C.is_mutable()
768
True
769
sage: SimplicialComplex(S, is_mutable=True).is_mutable()
770
True
771
sage: SimplicialComplex(S, is_immutable=False).is_mutable()
772
True
773
"""
774
775
def __init__(self, vertex_set=None, maximal_faces=None, **kwds):
776
"""
777
Define a simplicial complex. See ``SimplicialComplex`` for more
778
documentation.
779
780
.. WARNING::
781
782
We are deprecating the option ``vertex_set`` in :trac:`12587`.
783
784
EXAMPLES::
785
786
sage: SimplicialComplex([[0,2], [0,3], [0]])
787
Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)}
788
sage: SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')))
789
Simplicial complex with vertex set ('a', 'b', 'c') and facets {('b', 'c'), ('a', 'c'), ('a', 'b')}
790
791
TESTS::
792
793
sage: S = SimplicialComplex([[1,4], [2,4]])
794
sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False)
795
sage: S == S2
796
True
797
sage: S3 = SimplicialComplex(maximal_faces=[[1,4], [2,4]])
798
sage: S == S3
799
True
800
801
sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')))
802
sage: S == loads(dumps(S))
803
True
804
805
sage: Y = SimplicialComplex([1,2,3,4], [[1,2], [2,3], [3,4]])
806
doctest:1: DeprecationWarning: vertex_set is deprecated.
807
See http://trac.sagemath.org/12587 for details.
808
sage: Y = SimplicialComplex([1,2,3,4], [[1,2], [2,3], [3,4]], vertex_check=False)
809
doctest:1: DeprecationWarning: vertex_check is deprecated.
810
See http://trac.sagemath.org/12587 for details.
811
"""
812
from sage.misc.misc import union
813
# process kwds
814
sort_facets = kwds.get('sort_facets', True)
815
maximality_check = kwds.get('maximality_check', True)
816
name_check = kwds.get('name_check', False)
817
# done with kwds except mutability
818
819
# For deprecation #12587
820
if maximal_faces is None:
821
maximal_faces = vertex_set
822
elif vertex_set is not None:
823
# We've passed in both vertex_set and maximal_faces
824
from sage.misc.superseded import deprecation
825
deprecation(12587, "vertex_set is deprecated.")
826
827
if 'vertex_check' in kwds:
828
from sage.misc.superseded import deprecation
829
deprecation(12587, "vertex_check is deprecated.")
830
831
C = None
832
if maximal_faces is None:
833
vertex_set = []
834
maximal_faces = []
835
elif isinstance(maximal_faces, SimplicialComplex):
836
C = maximal_faces
837
else:
838
try:
839
C = maximal_faces._simplicial_()
840
except AttributeError:
841
if not isinstance(maximal_faces, (list, tuple, Simplex)):
842
# Convert it into a list (in case it is an iterable)
843
maximal_faces = list(maximal_faces)
844
if len(maximal_faces) != 0:
845
vertex_set = reduce(union, maximal_faces)
846
if C is not None:
847
self._vertex_set = copy(C.vertices())
848
self._facets = list(C.facets())
849
self._faces = copy(C._faces)
850
self._gen_dict = copy(C._gen_dict)
851
self._complex = copy(C._complex)
852
self.__contractible = copy(C.__contractible)
853
self.__enlarged = copy(C.__enlarged)
854
self._graph = copy(C._graph)
855
self._is_mutable = True
856
return
857
858
if sort_facets:
859
try: # vertex_set is an iterable
860
vertices = Simplex(sorted(vertex_set))
861
except TypeError: # vertex_set is an integer
862
vertices = Simplex(vertex_set)
863
else:
864
vertices = Simplex(vertex_set)
865
gen_dict = {}
866
for v in vertices:
867
if name_check:
868
try:
869
if int(v) < 0:
870
raise ValueError("The vertex %s does not have an appropriate name."%v)
871
except ValueError: # v is not an integer
872
try:
873
normalize_names(1, v)
874
except ValueError:
875
raise ValueError("The vertex %s does not have an appropriate name."%v)
876
# build dictionary of generator names
877
try:
878
gen_dict[v] = 'x%s'%int(v)
879
except Exception:
880
gen_dict[v] = v
881
# build set of facets
882
good_faces = []
883
maximal_simplices = [Simplex(f) for f in maximal_faces]
884
for face in maximal_simplices:
885
# check whether each given face is actually maximal
886
face_is_maximal = True
887
if maximality_check:
888
faces_to_be_removed = []
889
for other in good_faces:
890
if other.is_face(face):
891
faces_to_be_removed.append(other)
892
elif face_is_maximal:
893
face_is_maximal = not face.is_face(other)
894
for x in faces_to_be_removed:
895
good_faces.remove(x)
896
if sort_facets:
897
face = Simplex(sorted(face.tuple()))
898
if face_is_maximal:
899
good_faces += [face]
900
# if no maximal faces, add the empty face as a facet
901
if len(maximal_simplices) == 0:
902
good_faces.append(Simplex(-1))
903
# now record the attributes for self
904
# self._vertex_set: the Simplex formed by the vertices
905
self._vertex_set = vertices
906
# self._facets: list of facets
907
self._facets = good_faces
908
# self._sorted: True if the vertex set should be sorted. This
909
# gets used by the add_faces method.
910
self._sorted = sort_facets
911
# self._faces: dictionary of dictionaries of faces. The main
912
# dictionary is keyed by subcomplexes, and each value is a
913
# dictionary keyed by dimension. This should be empty until
914
# needed -- that is, until the faces method is called
915
self._faces = {}
916
# self._gen_dict: dictionary of names for the polynomial
917
# generators of the Stanley-Reisner ring
918
self._gen_dict = gen_dict
919
# self._complex: dictionary indexed by dimension d, subcomplex,
920
# etc.: differential from dim d to dim d-1 in the associated
921
# chain complex. thus to get the differential in the cochain
922
# complex from dim d-1 to dim d, take the transpose of this
923
# one.
924
self._complex = {}
925
# self.__contractible: if not None, a contractible subcomplex
926
# of self, as found by the _contractible_subcomplex method.
927
self.__contractible = None
928
# self.__enlarged: dictionary of enlarged subcomplexes,
929
# indexed by subcomplexes. For use in the _enlarge_subcomplex
930
# method.
931
self.__enlarged = {}
932
# initialize self._graph to None.
933
self._graph = None
934
935
# Handle mutability keywords
936
self._is_mutable = True
937
if not kwds.get('is_mutable', True) or kwds.get('is_immutable', False):
938
self.set_immutable()
939
940
def __hash__(self):
941
"""
942
Compute the hash value of ``self``.
943
944
If this simplicial complex is immutable, it computes the hash value
945
based upon the facets. Otherwise it raises a ``ValueError``.
946
947
EXAMPLES::
948
949
sage: S = SimplicialComplex([[1,4], [2,4]])
950
sage: hash(S)
951
Traceback (most recent call last):
952
...
953
ValueError: This simplicial complex must be immutable. Call set_immutable().
954
sage: S.set_immutable()
955
sage: hash(S) == hash(S)
956
True
957
sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False)
958
sage: S == S2
959
True
960
sage: hash(S) == hash(S2)
961
True
962
"""
963
if self._is_mutable:
964
raise ValueError("This simplicial complex must be immutable. Call set_immutable().")
965
return hash(self._facets)
966
967
def __cmp__(self,right):
968
"""
969
Two simplicial complexes are equal iff their vertex sets are
970
equal and their sets of facets are equal.
971
972
EXAMPLES::
973
974
sage: SimplicialComplex([[1,2], [2,3], [4]]) == SimplicialComplex([[4], [2,3], [3], [2,1]])
975
True
976
sage: X = SimplicialComplex()
977
sage: X.add_face([1,3])
978
sage: X == SimplicialComplex([[1,3]])
979
True
980
"""
981
if set(self._facets) == set(right._facets):
982
return 0
983
else:
984
return -1
985
986
def __copy__(self):
987
"""
988
Return a mutable copy of ``self``.
989
990
EXAMPLES::
991
992
sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False)
993
sage: S.is_mutable()
994
False
995
sage: C = copy(S)
996
sage: C.is_mutable()
997
True
998
sage: C == S
999
True
1000
sage: S.is_mutable()
1001
False
1002
sage: T = copy(C)
1003
sage: T == C
1004
True
1005
"""
1006
return SimplicialComplex(self, is_mutable=True)
1007
1008
def vertices(self):
1009
"""
1010
The vertex set of this simplicial complex.
1011
1012
EXAMPLES::
1013
1014
sage: S = SimplicialComplex([[i] for i in range(16)] + [[0,1], [1,2]])
1015
sage: S
1016
Simplicial complex with 16 vertices and 15 facets
1017
sage: S.vertices()
1018
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
1019
1020
Note that this actually returns a simplex::
1021
1022
sage: type(S.vertices())
1023
<class 'sage.homology.simplicial_complex.Simplex'>
1024
"""
1025
return self._vertex_set
1026
1027
def maximal_faces(self):
1028
"""
1029
The maximal faces (a.k.a. facets) of this simplicial complex.
1030
1031
This just returns the set of facets used in defining the
1032
simplicial complex, so if the simplicial complex was defined
1033
with no maximality checking, none is done here, either.
1034
1035
EXAMPLES::
1036
1037
sage: Y = SimplicialComplex([[0,2], [1,4]])
1038
sage: Y.maximal_faces()
1039
{(1, 4), (0, 2)}
1040
1041
``facets`` is a synonym for ``maximal_faces``::
1042
1043
sage: S = SimplicialComplex([[0,1], [0,1,2]])
1044
sage: S.facets()
1045
{(0, 1, 2)}
1046
"""
1047
return Set(self._facets)
1048
1049
facets = maximal_faces
1050
1051
def faces(self, subcomplex=None):
1052
"""
1053
The faces of this simplicial complex, in the form of a
1054
dictionary of sets keyed by dimension. If the optional
1055
argument ``subcomplex`` is present, then return only the
1056
faces which are *not* in the subcomplex.
1057
1058
:param subcomplex: a subcomplex of this simplicial complex.
1059
Return faces which are not in this subcomplex.
1060
1061
:type subcomplex: optional, default ``None``
1062
1063
EXAMPLES::
1064
1065
sage: Y = SimplicialComplex([[1,2], [1,4]])
1066
sage: Y.faces()
1067
{0: set([(4,), (2,), (1,)]), 1: set([(1, 2), (1, 4)]), -1: set([()])}
1068
sage: L = SimplicialComplex([[1,2]])
1069
sage: Y.faces(subcomplex=L)
1070
{0: set([(4,)]), 1: set([(1, 4)]), -1: set([])}
1071
"""
1072
# Make the subcomplex immutable if it is not
1073
if subcomplex is not None and subcomplex._is_mutable:
1074
subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False,
1075
sort_facets=False, is_mutable=False)
1076
1077
if subcomplex not in self._faces:
1078
# Faces is the dictionary of faces in self but not in
1079
# subcomplex, indexed by dimension
1080
Faces = {}
1081
# sub_facets is the dictionary of facets in the subcomplex
1082
sub_facets = {}
1083
dimension = max([face.dimension() for face in self._facets])
1084
for i in range(-1,dimension+1):
1085
Faces[i] = set([])
1086
sub_facets[i] = set([])
1087
for f in self._facets:
1088
dim = f.dimension()
1089
Faces[dim].add(f)
1090
if subcomplex is not None:
1091
for g in subcomplex._facets:
1092
dim = g.dimension()
1093
Faces[dim].discard(g)
1094
sub_facets[dim].add(g)
1095
# bad_faces is the set of faces in the subcomplex in the
1096
# current dimension
1097
bad_faces = sub_facets[dimension]
1098
for dim in range(dimension, -1, -1):
1099
# bad_bdries = boundaries of bad_faces: things to be
1100
# discarded in dim-1
1101
bad_bdries = sub_facets[dim-1]
1102
for f in bad_faces:
1103
bad_bdries.update(f.faces())
1104
for f in Faces[dim]:
1105
Faces[dim-1].update(set(f.faces()).difference(bad_bdries))
1106
bad_faces = bad_bdries
1107
self._faces[subcomplex] = Faces
1108
return self._faces[subcomplex]
1109
1110
def face_iterator(self, increasing=True):
1111
"""
1112
An iterator for the faces in this simplicial complex.
1113
1114
INPUTS:
1115
1116
- ``increasing`` -- (optional, default ``True``) if ``True``, return
1117
faces in increasing order of dimension, thus starting with
1118
the empty face. Otherwise it returns faces in decreasing order of
1119
dimension.
1120
1121
EXAMPLES::
1122
1123
sage: S1 = simplicial_complexes.Sphere(1)
1124
sage: [f for f in S1.face_iterator()]
1125
[(), (2,), (0,), (1,), (1, 2), (0, 2), (0, 1)]
1126
"""
1127
Fs = self.faces()
1128
dim_index = xrange(-1,self.dimension()+1)
1129
if not increasing:
1130
dim_index = reversed(dim_index)
1131
for i in dim_index:
1132
for F in Fs[i]:
1133
yield F
1134
1135
cells = faces
1136
1137
def n_faces(self, n, subcomplex=None):
1138
"""
1139
The set of simplices of dimension ``n`` of this simplicial complex.
1140
If the optional argument ``subcomplex`` is present, then
1141
return the ``n``-dimensional faces which are *not* in the
1142
subcomplex.
1143
1144
:param n: non-negative integer
1145
:param subcomplex: a subcomplex of this simplicial complex.
1146
Return ``n``-dimensional faces which are not in this
1147
subcomplex.
1148
:type subcomplex: optional, default ``None``
1149
1150
EXAMPLES::
1151
1152
sage: S = Set(range(1,5))
1153
sage: Z = SimplicialComplex(S.subsets())
1154
sage: Z
1155
Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)}
1156
sage: Z.n_faces(2)
1157
set([(1, 3, 4), (1, 2, 3), (2, 3, 4), (1, 2, 4)])
1158
sage: K = SimplicialComplex([[1,2,3], [2,3,4]])
1159
sage: Z.n_faces(2, subcomplex=K)
1160
set([(1, 3, 4), (1, 2, 4)])
1161
"""
1162
if n in self.faces(subcomplex):
1163
return self.faces(subcomplex)[n]
1164
else:
1165
return set([])
1166
1167
def is_pure(self):
1168
"""
1169
Return ``True`` iff this simplicial complex is pure.
1170
1171
A simplicial complex is pure if and only if all of its maximal faces
1172
have the same dimension.
1173
1174
.. WARNING::
1175
1176
This may give the wrong answer if the simplicial complex
1177
was constructed with ``maximality_check`` set to ``False``.
1178
1179
EXAMPLES::
1180
1181
sage: U = SimplicialComplex([[1,2], [1, 3, 4]])
1182
sage: U.is_pure()
1183
False
1184
sage: X = SimplicialComplex([[0,1], [0,2], [1,2]])
1185
sage: X.is_pure()
1186
True
1187
1188
Demonstration of the warning::
1189
1190
sage: S = SimplicialComplex([[0,1], [0]], maximality_check=False)
1191
sage: S.is_pure()
1192
False
1193
"""
1194
dims = [face.dimension() for face in self._facets]
1195
return max(dims) == min(dims)
1196
1197
def h_vector(self):
1198
r"""
1199
The `h`-vector of this simplicial complex.
1200
1201
If the complex has dimension `d` and `(f_{-1}, f_0, f_1, ...,
1202
f_d)` is its `f`-vector (with `f_{-1} = 1`, representing the
1203
empy simplex), then the `h`-vector `(h_0, h_1, ..., h_d,
1204
h_{d+1})` is defined by
1205
1206
.. MATH::
1207
1208
\sum_{i=0}^{d+1} h_i x^{d+1-i} = \sum_{i=0}^{d+1} f_{i-1} (x-1)^{d+1-i}.
1209
1210
Alternatively,
1211
1212
.. MATH::
1213
1214
h_j = \sum_{i=-1}^{j-1} (-1)^{j-i-1} \binom{d-i}{j-i-1} f_i.
1215
1216
EXAMPLES:
1217
1218
The `f`- and `h`-vectors of the boundary of an octahedron are
1219
computed in Wikipedia's page on simplicial complexes,
1220
http://en.wikipedia.org/wiki/Simplicial_complex::
1221
1222
sage: square = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]])
1223
sage: S0 = SimplicialComplex([[0], [1]])
1224
sage: octa = square.join(S0) # boundary of an octahedron
1225
sage: octa.f_vector()
1226
[1, 6, 12, 8]
1227
sage: octa.h_vector()
1228
[1, 3, 3, 1]
1229
"""
1230
from sage.rings.arith import binomial
1231
d = self.dimension()
1232
f = self.f_vector() # indexed starting at 0, since it's a Python list
1233
h = []
1234
for j in range(0, d+2):
1235
s = 0
1236
for i in range(-1, j):
1237
s += (-1)**(j-i-1) * binomial(d-i, j-i-1) * f[i+1]
1238
h.append(s)
1239
return h
1240
1241
def g_vector(self):
1242
r"""
1243
The `g`-vector of this simplicial complex.
1244
1245
If the `h`-vector of the complex is `(h_0, h_1, ..., h_d,
1246
h_{d+1})` -- see :meth:`h_vector` -- then its `g`-vector
1247
`(g_0, g_1, ..., g_{[(d+1)/2]})` is defined by `g_0 = 1` and
1248
`g_i = h_i - h_{i-1}` for `i > 0`.
1249
1250
EXAMPLES::
1251
1252
sage: S3 = simplicial_complexes.Sphere(3).barycentric_subdivision()
1253
sage: S3.f_vector()
1254
[1, 30, 150, 240, 120]
1255
sage: S3.h_vector()
1256
[1, 26, 66, 26, 1]
1257
sage: S3.g_vector()
1258
[1, 25, 40]
1259
"""
1260
from sage.functions.other import floor
1261
d = self.dimension()
1262
h = self.h_vector()
1263
g = [1]
1264
for i in range(1, floor((d+1)/2) + 1):
1265
g.append(h[i] - h[i-1])
1266
return g
1267
1268
def flip_graph(self):
1269
"""
1270
If ``self`` is pure, then it returns the the flip graph of ``self``,
1271
otherwise, it returns ``None``.
1272
1273
The flip graph of a pure simplicial complex is the (undirected) graph
1274
with vertices being the facets, such that two facets are joined by
1275
an edge if they meet in a codimension `1` face.
1276
1277
The flip graph is used to detect if ``self`` is a pseudomanifold.
1278
1279
EXAMPLES::
1280
1281
sage: S0 = simplicial_complexes.Sphere(0)
1282
sage: G = S0.flip_graph()
1283
sage: G.vertices(); G.edges(labels=False)
1284
[(0,), (1,)]
1285
[((0,), (1,))]
1286
1287
sage: G = (S0.wedge(S0)).flip_graph()
1288
sage: G.vertices(); G.edges(labels=False)
1289
[(0,), ('L1',), ('R1',)]
1290
[((0,), ('L1',)), ((0,), ('R1',)), (('L1',), ('R1',))]
1291
1292
sage: S1 = simplicial_complexes.Sphere(1)
1293
sage: S2 = simplicial_complexes.Sphere(2)
1294
sage: G = (S1.wedge(S1)).flip_graph()
1295
sage: G.vertices(); G.edges(labels=False)
1296
[(0, 'L1'), (0, 'L2'), (0, 'R1'), (0, 'R2'), ('L1', 'L2'), ('R1', 'R2')]
1297
[((0, 'L1'), (0, 'L2')),
1298
((0, 'L1'), (0, 'R1')),
1299
((0, 'L1'), (0, 'R2')),
1300
((0, 'L1'), ('L1', 'L2')),
1301
((0, 'L2'), (0, 'R1')),
1302
((0, 'L2'), (0, 'R2')),
1303
((0, 'L2'), ('L1', 'L2')),
1304
((0, 'R1'), (0, 'R2')),
1305
((0, 'R1'), ('R1', 'R2')),
1306
((0, 'R2'), ('R1', 'R2'))]
1307
1308
sage: (S1.wedge(S2)).flip_graph() is None
1309
True
1310
1311
sage: G = S2.flip_graph()
1312
sage: G.vertices(); G.edges(labels=False)
1313
[(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]
1314
[((0, 1, 2), (0, 1, 3)),
1315
((0, 1, 2), (0, 2, 3)),
1316
((0, 1, 2), (1, 2, 3)),
1317
((0, 1, 3), (0, 2, 3)),
1318
((0, 1, 3), (1, 2, 3)),
1319
((0, 2, 3), (1, 2, 3))]
1320
1321
sage: T = simplicial_complexes.Torus()
1322
sage: G = T.suspension(4).flip_graph()
1323
sage: len(G.vertices()); len(G.edges(labels=False))
1324
46
1325
161
1326
"""
1327
from collections import defaultdict
1328
if not self.is_pure():
1329
return None
1330
d = self.dimension()
1331
Fs = self.facets()
1332
flipG = Graph()
1333
flipG.add_vertices(Fs)
1334
edges = defaultdict(list)
1335
# go through all codim 1 faces to build the edge
1336
for F in Fs:
1337
F_tuple = sorted(F._Simplex__set)
1338
for i in range(d+1):
1339
coF = tuple(F_tuple[:i]+F_tuple[i+1:])
1340
if coF in edges:
1341
for G in edges[coF]:
1342
flipG.add_edge((F,G))
1343
edges[coF].append(F)
1344
return flipG
1345
1346
def is_pseudomanifold(self):
1347
"""
1348
Return True if self is a pseudomanifold.
1349
1350
A pseudomanifold is a simplicial complex with the following properties:
1351
1352
- it is pure of some dimension `d` (all of its facets are `d`-dimensional)
1353
- every `(d-1)`-dimensional simplex is the face of exactly two facets
1354
- for every two facets `S` and `T`, there is a sequence of
1355
facets
1356
1357
.. math::
1358
1359
S = f_0, f_1, ..., f_n = T
1360
1361
such that for each `i`, `f_i` and `f_{i-1}` intersect in a
1362
`(d-1)`-simplex.
1363
1364
By convention, `S^0` is the only 0-dimensional pseudomanifold.
1365
1366
EXAMPLES::
1367
1368
sage: S0 = simplicial_complexes.Sphere(0)
1369
sage: S0.is_pseudomanifold()
1370
True
1371
sage: (S0.wedge(S0)).is_pseudomanifold()
1372
False
1373
sage: S1 = simplicial_complexes.Sphere(1)
1374
sage: S2 = simplicial_complexes.Sphere(2)
1375
sage: (S1.wedge(S1)).is_pseudomanifold()
1376
False
1377
sage: (S1.wedge(S2)).is_pseudomanifold()
1378
False
1379
sage: S2.is_pseudomanifold()
1380
True
1381
sage: T = simplicial_complexes.Torus()
1382
sage: T.suspension(4).is_pseudomanifold()
1383
True
1384
"""
1385
if not self.is_pure():
1386
return False
1387
d = self.dimension()
1388
if d == 0:
1389
return len(self.facets()) == 2
1390
F = self.facets()
1391
X = self.n_faces(d-1)
1392
# is each (d-1)-simplex is the face of exactly two facets?
1393
for s in X:
1394
if len([a for a in [s.is_face(f) for f in F] if a]) != 2:
1395
return False
1396
# construct a graph with one vertex for each facet, one edge
1397
# when two facets intersect in a (d-1)-simplex, and see
1398
# whether that graph is connected.
1399
V = [f.set() for f in self.facets()]
1400
E = (lambda a,b: len(a.intersection(b)) == d)
1401
g = Graph([V,E])
1402
return g.is_connected()
1403
1404
def product(self, right, rename_vertices=True, is_mutable=True):
1405
"""
1406
The product of this simplicial complex with another one.
1407
1408
:param right: the other simplicial complex (the right-hand
1409
factor)
1410
1411
:param rename_vertices: If this is False, then the vertices in
1412
the product are the set of ordered pairs `(v,w)` where `v`
1413
is a vertex in ``self`` and `w` is a vertex in
1414
``right``. If this is ``True``, then the vertices are renamed
1415
as "LvRw" (e.g., the vertex (1,2) would become "L1R2").
1416
This is useful if you want to define the Stanley-Reisner
1417
ring of the complex: vertex names like (0,1) are not
1418
suitable for that, while vertex names like "L0R1" are.
1419
1420
:type rename_vertices: boolean; optional, default ``True``
1421
1422
:param is_mutable: Determines if the output is mutable
1423
:type is_mutable: boolean; optional, default ``True``
1424
1425
The vertices in the product will be the set of ordered pairs
1426
`(v,w)` where `v` is a vertex in self and `w` is a vertex in
1427
right.
1428
1429
.. WARNING::
1430
1431
If ``X`` and ``Y`` are simplicial complexes, then ``X*Y``
1432
returns their join, not their product.
1433
1434
EXAMPLES::
1435
1436
sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle
1437
sage: K = SimplicialComplex([[0,1]]) # edge
1438
sage: S.product(K).vertices() # cylinder
1439
('L0R0', 'L0R1', 'L1R0', 'L1R1', 'L2R0', 'L2R1')
1440
sage: S.product(K, rename_vertices=False).vertices()
1441
((0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1))
1442
sage: T = S.product(S) # torus
1443
sage: T
1444
Simplicial complex with 9 vertices and 18 facets
1445
sage: T.homology()
1446
{0: 0, 1: Z x Z, 2: Z}
1447
1448
These can get large pretty quickly::
1449
1450
sage: T = simplicial_complexes.Torus(); T
1451
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 14 facets
1452
sage: K = simplicial_complexes.KleinBottle(); K
1453
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 16 facets
1454
sage: T.product(K) # long time: 5 or 6 seconds
1455
Simplicial complex with 56 vertices and 1344 facets
1456
"""
1457
facets = []
1458
for f in self._facets:
1459
for g in right._facets:
1460
facets.extend(f.product(g, rename_vertices))
1461
return SimplicialComplex(facets, is_mutable=is_mutable)
1462
1463
def join(self, right, rename_vertices=True, is_mutable=True):
1464
"""
1465
The join of this simplicial complex with another one.
1466
1467
The join of two simplicial complexes `S` and `T` is the
1468
simplicial complex `S*T` with simplices of the form `[v_0,
1469
..., v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in
1470
`S` and `[w_0, ..., w_n]` in `T`.
1471
1472
:param right: the other simplicial complex (the right-hand factor)
1473
1474
:param rename_vertices: If this is True, the vertices in the
1475
join will be renamed by the formula: vertex "v" in the
1476
left-hand factor --> vertex "Lv" in the join, vertex "w" in
1477
the right-hand factor --> vertex "Rw" in the join. If this
1478
is false, this tries to construct the join without renaming
1479
the vertices; this will cause problems if the two factors
1480
have any vertices with names in common.
1481
1482
:type rename_vertices: boolean; optional, default ``True``
1483
1484
:param is_mutable: Determines if the output is mutable
1485
:type is_mutable: boolean; optional, default ``True``
1486
1487
EXAMPLES::
1488
1489
sage: S = SimplicialComplex([[0], [1]])
1490
sage: T = SimplicialComplex([[2], [3]])
1491
sage: S.join(T)
1492
Simplicial complex with vertex set ('L0', 'L1', 'R2', 'R3') and 4 facets
1493
sage: S.join(T, rename_vertices=False)
1494
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 3), (1, 2), (0, 2), (0, 3)}
1495
1496
The notation '*' may be used, as well::
1497
1498
sage: S * S
1499
Simplicial complex with vertex set ('L0', 'L1', 'R0', 'R1') and 4 facets
1500
sage: S * S * S * S * S * S * S * S
1501
Simplicial complex with 16 vertices and 256 facets
1502
"""
1503
facets = []
1504
for f in self._facets:
1505
for g in right._facets:
1506
facets.append(f.join(g, rename_vertices))
1507
return SimplicialComplex(facets, is_mutable=is_mutable)
1508
1509
# Use * to mean 'join':
1510
__mul__ = join
1511
1512
def cone(self, is_mutable=True):
1513
"""
1514
The cone on this simplicial complex.
1515
1516
:param is_mutable: Determines if the output is mutable
1517
:type is_mutable: boolean; optional, default ``True``
1518
1519
The cone is the simplicial complex formed by adding a new
1520
vertex `C` and simplices of the form `[C, v_0, ..., v_k]` for
1521
every simplex `[v_0, ..., v_k]` in the original simplicial
1522
complex. That is, the cone is the join of the original
1523
complex with a one-point simplicial complex.
1524
1525
EXAMPLES::
1526
1527
sage: S = SimplicialComplex([[0], [1]])
1528
sage: S.cone()
1529
Simplicial complex with vertex set ('L0', 'L1', 'R0') and facets {('L0', 'R0'), ('L1', 'R0')}
1530
"""
1531
return self.join(SimplicialComplex([["0"]], is_mutable=is_mutable),
1532
rename_vertices = True)
1533
1534
def suspension(self, n=1, is_mutable=True):
1535
r"""
1536
The suspension of this simplicial complex.
1537
1538
:param n: positive integer -- suspend this many times.
1539
1540
:type n: optional, default 1
1541
1542
:param is_mutable: Determines if the output is mutable
1543
:type is_mutable: boolean; optional, default ``True``
1544
1545
The suspension is the simplicial complex formed by adding two
1546
new vertices `S_0` and `S_1` and simplices of the form `[S_0,
1547
v_0, ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex
1548
`[v_0, ..., v_k]` in the original simplicial complex. That
1549
is, the suspension is the join of the original complex with a
1550
two-point simplicial complex.
1551
1552
If the simplicial complex `M` happens to be a pseudomanifold
1553
(see :meth:`is_pseudomanifold`), then this instead constructs
1554
Datta's one-point suspension (see p. 434 in the cited
1555
article): choose a vertex `u` in `M` and choose a new vertex
1556
`w` to add. Denote the join of simplices by "`*`". The
1557
facets in the one-point suspension are of the two forms
1558
1559
- `u * \alpha` where `\alpha` is a facet of `M` not containing
1560
`u`
1561
1562
- `w * \beta` where `\beta` is any facet of `M`.
1563
1564
REFERENCES:
1565
1566
- Basudeb Datta, "Minimal triangulations of manifolds",
1567
J. Indian Inst. Sci. 87 (2007), no. 4, 429-449.
1568
1569
EXAMPLES::
1570
1571
sage: S0 = SimplicialComplex([[0], [1]])
1572
sage: S0.suspension() == simplicial_complexes.Sphere(1)
1573
True
1574
sage: S3 = S0.suspension(3) # the 3-sphere
1575
sage: S3.homology()
1576
{0: 0, 1: 0, 2: 0, 3: Z}
1577
1578
For pseudomanifolds, the complex constructed here will be
1579
smaller than that obtained by taking the join with the
1580
0-sphere: the join adds two vertices, while this construction
1581
only adds one. ::
1582
1583
sage: T = simplicial_complexes.Torus()
1584
sage: T.join(S0).vertices() # 9 vertices
1585
('L0', 'L1', 'L2', 'L3', 'L4', 'L5', 'L6', 'R0', 'R1')
1586
sage: T.suspension().vertices() # 8 vertices
1587
(0, 1, 2, 3, 4, 5, 6, 7)
1588
"""
1589
if n<0:
1590
raise ValueError, "n must be non-negative."
1591
if n==0:
1592
return self
1593
if n==1:
1594
if self.is_pseudomanifold():
1595
# Use one-point compactification of Datta. The
1596
# construction is a bit slower, but the resulting
1597
# complex is smaller.
1598
V = self.vertices()
1599
u = V[0]
1600
w = 0
1601
while w in V:
1602
w += 1
1603
w = Simplex([w])
1604
new_facets = []
1605
for f in self.facets():
1606
if u not in f:
1607
new_facets.append(f.join(Simplex([u]), rename_vertices=False))
1608
new_facets.append(f.join(w, rename_vertices=False))
1609
return SimplicialComplex(new_facets)
1610
else:
1611
return self.join(SimplicialComplex([["0"], ["1"]], is_mutable=is_mutable),
1612
rename_vertices = True)
1613
return self.suspension(1, is_mutable).suspension(int(n-1), is_mutable)
1614
1615
def disjoint_union(self, right, rename_vertices=True, is_mutable=True):
1616
"""
1617
The disjoint union of this simplicial complex with another one.
1618
1619
:param right: the other simplicial complex (the right-hand factor)
1620
1621
:param rename_vertices: If this is True, the vertices in the
1622
disjoint union will be renamed by the formula: vertex "v"
1623
in the left-hand factor --> vertex "Lv" in the disjoint
1624
union, vertex "w" in the right-hand factor --> vertex "Rw"
1625
in the disjoint union. If this is false, this tries to
1626
construct the disjoint union without renaming the vertices;
1627
this will cause problems if the two factors have any
1628
vertices with names in common.
1629
1630
:type rename_vertices: boolean; optional, default True
1631
1632
EXAMPLES::
1633
1634
sage: S1 = simplicial_complexes.Sphere(1)
1635
sage: S2 = simplicial_complexes.Sphere(2)
1636
sage: S1.disjoint_union(S2).homology()
1637
{0: Z, 1: Z, 2: Z}
1638
"""
1639
facets = []
1640
for f in self._facets:
1641
facets.append(tuple(["L" + str(v) for v in f]))
1642
for f in right._facets:
1643
facets.append(tuple(["R" + str(v) for v in f]))
1644
return SimplicialComplex(facets, is_mutable=is_mutable)
1645
1646
def wedge(self, right, rename_vertices=True, is_mutable=True):
1647
"""
1648
The wedge (one-point union) of this simplicial complex with
1649
another one.
1650
1651
:param right: the other simplicial complex (the right-hand factor)
1652
1653
:param rename_vertices: If this is ``True``, the vertices in the
1654
wedge will be renamed by the formula: first vertex in each
1655
are glued together and called "0". Otherwise, each vertex
1656
"v" in the left-hand factor --> vertex "Lv" in the wedge,
1657
vertex "w" in the right-hand factor --> vertex "Rw" in the
1658
wedge. If this is ``False``, this tries to construct the wedge
1659
without renaming the vertices; this will cause problems if
1660
the two factors have any vertices with names in common.
1661
1662
:type rename_vertices: boolean; optional, default ``True``
1663
1664
:param is_mutable: Determines if the output is mutable
1665
:type is_mutable: boolean; optional, default ``True``
1666
1667
.. NOTE::
1668
1669
This operation is not well-defined if ``self`` or
1670
``other`` is not path-connected.
1671
1672
EXAMPLES::
1673
1674
sage: S1 = simplicial_complexes.Sphere(1)
1675
sage: S2 = simplicial_complexes.Sphere(2)
1676
sage: S1.wedge(S2).homology()
1677
{0: 0, 1: Z, 2: Z}
1678
"""
1679
left_vertices = list(self.vertices())
1680
left_0 = left_vertices.pop(0)
1681
right_vertices = list(right.vertices())
1682
right_0 = right_vertices.pop(0)
1683
left_dict = {left_0: 0}
1684
right_dict = {right_0: 0}
1685
if rename_vertices:
1686
facets = []
1687
for v in left_vertices:
1688
left_dict[v] = "L" + str(v)
1689
for v in right_vertices:
1690
right_dict[v] = "R" + str(v)
1691
1692
for f in self._facets:
1693
facets.append(tuple([left_dict[v] for v in f]))
1694
for f in right._facets:
1695
facets.append(tuple([right_dict[v] for v in f]))
1696
else:
1697
facets = self._facets + right._facets
1698
return SimplicialComplex(facets, is_mutable=is_mutable)
1699
1700
def chain_complex(self, **kwds):
1701
"""
1702
The chain complex associated to this simplicial complex.
1703
1704
:param dimensions: if ``None``, compute the chain complex in all
1705
dimensions. If a list or tuple of integers, compute the
1706
chain complex in those dimensions, setting the chain groups
1707
in all other dimensions to zero.
1708
:param base_ring: commutative ring
1709
:type base_ring: optional, default ``ZZ``
1710
:param subcomplex: a subcomplex of this simplicial complex.
1711
Compute the chain complex relative to this subcomplex.
1712
:type subcomplex: optional, default empty
1713
:param augmented: If ``True``, return the augmented chain complex
1714
(that is, include a class in dimension `-1` corresponding
1715
to the empty cell). This is ignored if ``dimensions`` is
1716
specified.
1717
:type augmented: boolean; optional, default ``False``
1718
:param cochain: If ``True``, return the cochain complex (that is,
1719
the dual of the chain complex).
1720
:type cochain: boolean; optional, default ``False``
1721
:param verbose: If ``True``, print some messages as the chain
1722
complex is computed.
1723
:type verbose: boolean; optional, default ``False``
1724
:param check_diffs: If ``True``, make sure that the chain complex
1725
is actually a chain complex: the differentials are
1726
composable and their product is zero.
1727
:type check_diffs: boolean; optional, default ``False``
1728
1729
.. NOTE::
1730
1731
If subcomplex is nonempty, then the argument ``augmented``
1732
has no effect: the chain complex relative to a nonempty
1733
subcomplex is zero in dimension `-1`.
1734
1735
EXAMPLES::
1736
1737
sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]])
1738
sage: circle.chain_complex()
1739
Chain complex with at most 2 nonzero terms over Integer Ring
1740
sage: circle.chain_complex()._latex_()
1741
'\\Bold{Z}^{3} \\xrightarrow{d_{1}} \\Bold{Z}^{3}'
1742
sage: circle.chain_complex(base_ring=QQ, augmented=True)
1743
Chain complex with at most 3 nonzero terms over Rational Field
1744
"""
1745
augmented = kwds.get('augmented', False)
1746
cochain = kwds.get('cochain', False)
1747
verbose = kwds.get('verbose', False)
1748
check_diffs = kwds.get('check_diffs', False)
1749
base_ring = kwds.get('base_ring', ZZ)
1750
dimensions = kwds.get('dimensions', None)
1751
subcomplex = kwds.get('subcomplex', None)
1752
1753
# initialize subcomplex
1754
if subcomplex is None:
1755
subcomplex = SimplicialComplex(is_mutable=False)
1756
else:
1757
# subcomplex is not empty, so don't augment the chain complex
1758
augmented = False
1759
# Use an immutable copy of the subcomplex
1760
if not subcomplex._is_mutable:
1761
subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False,
1762
sort_facets=False, is_mutable=False)
1763
# now construct the range of dimensions in which to compute
1764
if dimensions is None:
1765
dimensions = range(0, self.dimension()+1)
1766
first = 0
1767
else:
1768
augmented = False
1769
first = dimensions[0]
1770
differentials = {}
1771
# in the chain complex, compute the first dimension by hand,
1772
# and don't cache it: it may be differ from situation to
1773
# situation because of boundary effects.
1774
current = None
1775
current_dim = None
1776
if augmented: # then first == 0
1777
current = list(self.n_faces(0, subcomplex=subcomplex))
1778
current_dim = 0
1779
if cochain:
1780
differentials[-1] = matrix(base_ring, len(current), 1,
1781
[1]*len(current))
1782
else:
1783
differentials[0] = matrix(base_ring, 1, len(current),
1784
[1]*len(current))
1785
elif first == 0 and not augmented:
1786
current = list(self.n_faces(0, subcomplex=subcomplex))
1787
current_dim = 0
1788
if not cochain:
1789
differentials[0] = matrix(base_ring, 0, len(current))
1790
else: # first > 0
1791
current = list(self.n_faces(first, subcomplex=subcomplex))
1792
current_dim = first
1793
if not cochain:
1794
differentials[first] = matrix(base_ring, 0, len(current))
1795
for n in dimensions[1:]:
1796
if verbose:
1797
print " starting dimension %s" % n
1798
if (n, subcomplex) in self._complex:
1799
if cochain:
1800
differentials[n-1] = self._complex[(n, subcomplex)].transpose().change_ring(base_ring)
1801
mat = differentials[n-1]
1802
else:
1803
differentials[n] = self._complex[(n, subcomplex)].change_ring(base_ring)
1804
mat = differentials[n]
1805
if verbose:
1806
print " boundary matrix (cached): it's %s by %s." % (mat.nrows(), mat.ncols())
1807
else:
1808
# 'current' is the list of faces in dimension n
1809
#
1810
# 'old' is a dictionary, with keys the faces in the
1811
# previous dimension (dim n-1 for the chain complex,
1812
# n+1 for the cochain complex), values the integers 0,
1813
# 1, 2, ... (the index of the face). finding an entry
1814
# in a dictionary seems to be faster than finding the
1815
# index of an entry in a list.
1816
if current_dim == n-1:
1817
old = dict(zip(current, range(len(current))))
1818
else:
1819
set_of_faces = list(self.n_faces(n-1, subcomplex=subcomplex))
1820
old = dict(zip(set_of_faces, range(len(set_of_faces))))
1821
current = list(self.n_faces(n, subcomplex=subcomplex))
1822
current_dim = n
1823
# construct matrix. it is easiest to construct it as
1824
# a sparse matrix, specifying which entries are
1825
# nonzero via a dictionary.
1826
matrix_data = {}
1827
col = 0
1828
if len(old) and len(current):
1829
for simplex in current:
1830
for i in range(n+1):
1831
face_i = simplex.face(i)
1832
try:
1833
matrix_data[(old[face_i], col)] = (-1)**i
1834
except KeyError:
1835
pass
1836
col += 1
1837
mat = matrix(ZZ, len(old), len(current), matrix_data)
1838
if cochain:
1839
self._complex[(n, subcomplex)] = mat
1840
differentials[n-1] = mat.transpose().change_ring(base_ring)
1841
else:
1842
self._complex[(n, subcomplex)] = mat
1843
differentials[n] = mat.change_ring(base_ring)
1844
if verbose:
1845
print " boundary matrix computed: it's %s by %s." % (mat.nrows(), mat.ncols())
1846
# now for the cochain complex, compute the last dimension by
1847
# hand, and don't cache it.
1848
if cochain:
1849
n = dimensions[-1] + 1
1850
if current_dim != n-1:
1851
current = list(self.n_faces(n-1, subcomplex=subcomplex))
1852
differentials[n-1] = matrix(base_ring, 0, len(current))
1853
# finally, return the chain complex
1854
if cochain:
1855
return ChainComplex(data=differentials, degree=1, **kwds)
1856
else:
1857
return ChainComplex(data=differentials, degree=-1, **kwds)
1858
1859
def _homology_(self, dim=None, **kwds):
1860
"""
1861
The reduced homology of this simplicial complex.
1862
1863
:param dim: If ``None``, then return the homology in every
1864
dimension. If ``dim`` is an integer or list, return the
1865
homology in the given dimensions. (Actually, if ``dim`` is
1866
a list, return the homology in the range from ``min(dim)``
1867
to ``max(dim)``.)
1868
1869
:type dim: integer or list of integers or ``None``; optional,
1870
default ``None``
1871
1872
:param base_ring: commutative ring. Must be ``ZZ`` or a field.
1873
1874
:type base_ring: optional, default ``ZZ``
1875
1876
:param subcomplex: a subcomplex of this simplicial complex.
1877
Compute homology relative to this subcomplex.
1878
1879
:type subcomplex: optional, default ``None``
1880
1881
:param cohomology: If ``True``, compute cohomology rather than
1882
homology.
1883
1884
:type cohomology: boolean; optional, default ``False``
1885
1886
:param enlarge: If ``True``, find a new subcomplex homotopy
1887
equivalent to, and probably larger than, the given one.
1888
1889
:type enlarge: boolean; optional, default ``True``
1890
1891
:param algorithm: The options are ``'auto'``, ``'dhsw'``,
1892
``'pari'`` or ``'no_chomp'``. If ``'auto'``, first try CHomP,
1893
then use the Dumas, Heckenbach, Saunders, and Welker elimination
1894
algorithm for large matrices, Pari for small ones. If
1895
``'no_chomp'``, then don't try CHomP, but behave the same
1896
otherwise. If ``'pari'``, then compute elementary divisors
1897
using Pari. If ``'dhsw'``, then use the DHSW algorithm to
1898
compute elementary divisors. (As of this writing, CHomP is
1899
by far the fastest option, followed by the ``'auto'`` or
1900
``'no_chomp'`` setting of using DHSW for large matrices and
1901
Pari for small ones.)
1902
1903
:type algorithm: string; optional, default ``'auto'``
1904
1905
:param verbose: If ``True``, print some messages as the homology
1906
is computed.
1907
1908
:type verbose: boolean; optional, default ``False``
1909
1910
Algorithm: if ``subcomplex`` is ``None``, replace it with a facet
1911
-- a contractible subcomplex of the original complex. Then no
1912
matter what ``subcomplex`` is, replace it with a subcomplex
1913
`L` which is homotopy equivalent and as large as possible.
1914
Compute the homology of the original complex relative to `L`:
1915
if `L` is large, then the relative chain complex will be small
1916
enough to speed up computations considerably.
1917
1918
EXAMPLES::
1919
1920
sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]])
1921
sage: circle._homology_()
1922
{0: 0, 1: Z}
1923
sage: sphere = SimplicialComplex([[0,1,2,3]])
1924
sage: sphere.remove_face([0,1,2,3])
1925
sage: sphere
1926
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}
1927
sage: sphere._homology_()
1928
{0: 0, 1: 0, 2: Z}
1929
1930
Another way to get a two-sphere: take a two-point space and take its
1931
three-fold join with itself::
1932
1933
sage: S = SimplicialComplex([[0], [1]])
1934
sage: (S*S*S)._homology_(dim=2, cohomology=True)
1935
Z
1936
1937
Relative homology::
1938
1939
sage: T = SimplicialComplex([[0,1,2]])
1940
sage: U = SimplicialComplex([[0,1], [1,2], [0,2]])
1941
sage: T._homology_(subcomplex=U)
1942
{0: 0, 1: 0, 2: Z}
1943
"""
1944
from sage.modules.all import VectorSpace
1945
from sage.homology.homology_group import HomologyGroup
1946
1947
base_ring = kwds.get('base_ring', ZZ)
1948
cohomology = kwds.get('cohomology', False)
1949
enlarge = kwds.get('enlarge', True)
1950
verbose = kwds.get('verbose', False)
1951
subcomplex = kwds.get('subcomplex', None)
1952
1953
if dim is not None:
1954
if isinstance(dim, (list, tuple)):
1955
low = min(dim) - 1
1956
high = max(dim) + 2
1957
else:
1958
low = dim - 1
1959
high = dim + 2
1960
dims = range(low, high)
1961
else:
1962
dims = None
1963
1964
if verbose:
1965
print "starting calculation of the homology of this",
1966
print "%s-dimensional simplicial complex" % self.dimension()
1967
if subcomplex is None:
1968
if enlarge:
1969
if verbose:
1970
print "Constructing contractible subcomplex..."
1971
L = self._contractible_subcomplex(verbose=verbose)
1972
if verbose:
1973
print "Done finding contractible subcomplex."
1974
vec = [len(self.n_faces(n-1, subcomplex=L)) for n in range(self.dimension()+2)]
1975
print "The difference between the f-vectors is:"
1976
print " %s" % vec
1977
else:
1978
L = SimplicialComplex([[self.vertices().tuple()[0]]])
1979
else:
1980
if enlarge:
1981
if verbose:
1982
print "Enlarging subcomplex..."
1983
L = self._enlarge_subcomplex(subcomplex, verbose=verbose)
1984
if verbose:
1985
print "Done enlarging subcomplex:"
1986
else:
1987
L = subcomplex
1988
L.set_immutable()
1989
1990
if verbose:
1991
print "Computing the chain complex..."
1992
kwds['subcomplex']=L
1993
C = self.chain_complex(dimensions=dims, augmented=True,
1994
cochain=cohomology, **kwds)
1995
if verbose:
1996
print " Done computing the chain complex. "
1997
print "Now computing homology..."
1998
if 'subcomplex' in kwds:
1999
del kwds['subcomplex']
2000
answer = C.homology(**kwds)
2001
2002
if dim is None:
2003
dim = range(self.dimension()+1)
2004
zero = HomologyGroup(0, base_ring)
2005
if isinstance(dim, (list, tuple)):
2006
return dict([d, answer.get(d, zero)] for d in dim)
2007
return answer.get(dim, zero)
2008
2009
def add_face(self, face):
2010
"""
2011
Add a face to this simplicial complex
2012
2013
:param face: a subset of the vertex set
2014
2015
This *changes* the simplicial complex, adding a new face and all
2016
of its subfaces.
2017
2018
EXAMPLES::
2019
2020
sage: X = SimplicialComplex([[0,1], [0,2]])
2021
sage: X.add_face([0,1,2,]); X
2022
Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
2023
sage: Y = SimplicialComplex(); Y
2024
Simplicial complex with vertex set () and facets {()}
2025
sage: Y.add_face([0,1])
2026
sage: Y.add_face([1,2,3])
2027
sage: Y
2028
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2, 3), (0, 1)}
2029
2030
If you add a face which is already present, there is no effect::
2031
2032
sage: Y.add_face([1,3]); Y
2033
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2, 3), (0, 1)}
2034
2035
Check that the bug reported at :trac:`14354` has been fixed::
2036
2037
sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1)
2038
sage: T.homology()
2039
{0: 0, 1: Z x Z x Z}
2040
sage: T.add_face([1,2,3])
2041
sage: T.homology()
2042
{0: 0, 1: Z x Z, 2: 0}
2043
2044
Check we've fixed the bug reported at :trac:`14578`::
2045
2046
sage: t0 = SimplicialComplex()
2047
sage: t0.add_face(('a', 'b'))
2048
sage: t0.add_face(('c', 'd', 'e'))
2049
sage: t0.add_face(('e', 'f', 'c'))
2050
sage: t0.homology()
2051
{0: Z, 1: 0, 2: 0}
2052
"""
2053
if not self._is_mutable:
2054
raise ValueError("This simplicial complex is not mutable")
2055
2056
if self._sorted:
2057
new_face = Simplex(sorted(face))
2058
else:
2059
new_face = Simplex(face)
2060
2061
face_is_maximal = True
2062
for other in self._facets:
2063
if face_is_maximal:
2064
face_is_maximal = not new_face.is_face(other)
2065
if face_is_maximal:
2066
# remove any old facets which are no longer maximal
2067
Facets = list(self._facets)
2068
for old_face in self._facets:
2069
if old_face.is_face(new_face):
2070
Facets.remove(old_face)
2071
# add new_face to facet list
2072
Facets.append(new_face)
2073
self._facets = Facets
2074
2075
# Update the vertex set
2076
from sage.misc.misc import union
2077
2078
if self._sorted:
2079
self._vertex_set = Simplex(sorted(reduce(union, [self._vertex_set, new_face])))
2080
else:
2081
self._vertex_set = Simplex(reduce(union, [self._vertex_set, new_face]))
2082
2083
# update self._faces if necessary
2084
if None in self._faces:
2085
all_new_faces = SimplicialComplex([new_face]).faces()
2086
for dim in range(0, new_face.dimension()+1):
2087
if dim in self._faces[None]:
2088
self._faces[None][dim] = self._faces[None][dim].union(all_new_faces[dim])
2089
else:
2090
self._faces[None][dim] = all_new_faces[dim]
2091
# update self._graph if necessary
2092
if self._graph is not None:
2093
d = new_face.dimension()+1
2094
for i in range(d):
2095
for j in range(i+1,d):
2096
self._graph.add_edge(new_face[i],new_face[j])
2097
self._complex = {}
2098
self.__contractible = None
2099
self.__enlarged = {}
2100
2101
def remove_face(self, face):
2102
"""
2103
Remove a face from this simplicial complex and return the
2104
resulting simplicial complex.
2105
2106
:param face: a face of the simplicial complex
2107
2108
This *changes* the simplicial complex.
2109
2110
ALGORITHM:
2111
2112
The facets of the new simplicial complex are
2113
the facets of the original complex not containing ``face``,
2114
together with those of ``link(face)*boundary(face)``.
2115
2116
EXAMPLES::
2117
2118
sage: S = range(1,5)
2119
sage: Z = SimplicialComplex([S]); Z
2120
Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)}
2121
sage: Z.remove_face([1,2])
2122
sage: Z
2123
Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 3, 4), (2, 3, 4)}
2124
2125
sage: S = SimplicialComplex([[0,1,2],[2,3]])
2126
sage: S
2127
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (2, 3)}
2128
sage: S.remove_face([0,1,2])
2129
sage: S
2130
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (2, 3), (0, 2), (0, 1)}
2131
"""
2132
if not self._is_mutable:
2133
raise ValueError("This simplicial complex is not mutable")
2134
2135
simplex = Simplex(face)
2136
facets = self.facets()
2137
if all([not simplex.is_face(F) for F in facets]):
2138
# face is not in self: nothing to remove
2139
return self
2140
link = self.link(simplex)
2141
join_facets = []
2142
for f in simplex.faces():
2143
for g in link.facets():
2144
join_facets.append(f.join(g, rename_vertices=False))
2145
# join_facets is the list of facets in the join bdry(face) * link(face)
2146
remaining = join_facets + [elem for elem in facets if not simplex.is_face(elem)]
2147
2148
# Check to see if there are any non-maximial faces
2149
# build set of facets
2150
self._facets = []
2151
for f in remaining:
2152
face = Simplex(f)
2153
face_is_maximal = True
2154
faces_to_be_removed = []
2155
for other in self._facets:
2156
if other.is_face(face):
2157
faces_to_be_removed.append(other)
2158
elif face_is_maximal:
2159
face_is_maximal = not face.is_face(other)
2160
for x in faces_to_be_removed:
2161
self._facets.remove(x)
2162
face = Simplex(sorted(face.tuple()))
2163
if face_is_maximal:
2164
self._facets.append(face)
2165
# if no maximal faces, add the empty face as a facet
2166
if len(remaining) == 0:
2167
self._facets.append(Simplex(-1))
2168
2169
# Recreate the vertex set
2170
from sage.misc.misc import union
2171
if self._sorted:
2172
self._vertex_set = Simplex(sorted(reduce(union, self._facets)))
2173
else:
2174
self._vertex_set = Simplex(reduce(union, self._facets))
2175
2176
# Update self._faces and self._graph if necessary
2177
if None in self._faces:
2178
self._faces = {}
2179
self.faces()
2180
if self._graph is not None:
2181
# Only if removing a 1 or 2 dim face will the graph be affected
2182
if len(face) == 1:
2183
self._graph.delete_vertex(face[0])
2184
self._graph.add_vertex(face[0])
2185
elif len(face) == 2:
2186
self._graph.delete_edge(face[0], face[1])
2187
self._complex = {}
2188
self.__contractible = None
2189
self.__enlarged = {}
2190
2191
def connected_sum(self, other, is_mutable=True):
2192
"""
2193
The connected sum of this simplicial complex with another one.
2194
2195
:param other: another simplicial complex
2196
:param is_mutable: Determines if the output is mutable
2197
:type is_mutable: boolean; optional, default ``True``
2198
:return: the connected sum ``self # other``
2199
2200
.. WARNING::
2201
2202
This does not check that ``self`` and ``other`` are manifolds,
2203
only that their facets all have the same dimension. Since a
2204
(more or less) random facet is chosen from each complex and
2205
then glued together, this method may return random
2206
results if applied to non-manifolds, depending on which
2207
facet is chosen.
2208
2209
Algorithm: a facet is chosen from each surface, and removed.
2210
The vertices of these two facets are relabeled to
2211
``(0,1,...,dim)``. Of the remaining vertices, the ones from
2212
the left-hand factor are renamed by prepending an "L", and
2213
similarly the remaining vertices in the right-hand factor are
2214
renamed by prepending an "R".
2215
2216
EXAMPLES::
2217
2218
sage: S1 = simplicial_complexes.Sphere(1)
2219
sage: S1.connected_sum(S1.connected_sum(S1)).homology()
2220
{0: 0, 1: Z}
2221
sage: P = simplicial_complexes.RealProjectivePlane(); P
2222
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and 10 facets
2223
sage: P.connected_sum(P) # the Klein bottle
2224
Simplicial complex with 9 vertices and 18 facets
2225
2226
The notation '+' may be used for connected sum, also::
2227
2228
sage: P + P # the Klein bottle
2229
Simplicial complex with 9 vertices and 18 facets
2230
sage: (P + P).homology()[1]
2231
Z x C2
2232
"""
2233
if not (self.is_pure() and other.is_pure() and
2234
self.dimension() == other.dimension()):
2235
raise ValueError, "Complexes are not pure of the same dimension."
2236
# first find a top-dimensional simplex to remove from each surface
2237
keep_left = self._facets[0]
2238
keep_right = other._facets[0]
2239
# construct the set of vertices:
2240
left = set(self.vertices()).difference(set(keep_left))
2241
right = set(other.vertices()).difference(set(keep_right))
2242
# construct the set of facets:
2243
left = set(self._facets).difference(set([keep_left]))
2244
right = set(other._facets).difference(set([keep_right]))
2245
facet_set = ([[rename_vertex(v, keep=list(keep_left))
2246
for v in face] for face in left]
2247
+ [[rename_vertex(v, keep=list(keep_right), left=False)
2248
for v in face] for face in right])
2249
# return the new surface
2250
return SimplicialComplex(facet_set, is_mutable=is_mutable)
2251
2252
__add__ = connected_sum
2253
2254
def link(self, simplex, is_mutable=True):
2255
"""
2256
The link of a simplex in this simplicial complex.
2257
2258
The link of a simplex `F` is the simplicial complex formed by
2259
all simplices `G` which are disjoint from `F` but for which `F
2260
\cup G` is a simplex.
2261
2262
:param simplex: a simplex in this simplicial complex.
2263
:param is_mutable: Determines if the output is mutable
2264
:type is_mutable: boolean; optional, default ``True``
2265
2266
EXAMPLES::
2267
2268
sage: X = SimplicialComplex([[0,1,2], [1,2,3]])
2269
sage: X.link(Simplex([0]))
2270
Simplicial complex with vertex set (1, 2) and facets {(1, 2)}
2271
sage: X.link([1,2])
2272
Simplicial complex with vertex set (0, 3) and facets {(3,), (0,)}
2273
sage: Y = SimplicialComplex([[0,1,2,3]])
2274
sage: Y.link([1])
2275
Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2, 3)}
2276
"""
2277
faces = []
2278
s = Simplex(simplex)
2279
for f in self._facets:
2280
if s.is_face(f):
2281
faces.append(Simplex(list(f.set().difference(s.set()))))
2282
return SimplicialComplex(faces, is_mutable=is_mutable)
2283
2284
def is_cohen_macaulay(self, ncpus=0):
2285
r"""
2286
Returns True if ``self`` is Cohen-Macaulay, i.e., if
2287
`\tilde{H}_i(\operatorname{lk}_\Delta(F);\ZZ) = 0` for all
2288
`F \in \Delta` and `i < \operatorname{dim}\operatorname{lk}_\Delta(F)`.
2289
Here, `\Delta` is ``self``, and `\operatorname{lk}` denotes the
2290
link operator on ``self``.
2291
2292
INPUT:
2293
2294
- ``ncpus`` -- (default: 0) number of cpus used for the
2295
computation. If this is 0, determine the number of cpus
2296
automatically based on the hardware being used.
2297
2298
For finite simplicial complexes, this is equivalent to the
2299
statement that the Stanley-Reisner ring of ``self`` is
2300
Cohen-Macaulay.
2301
2302
EXAMPLES:
2303
2304
Spheres are Cohen-Macaulay::
2305
2306
sage: S = SimplicialComplex([[1,2],[2,3],[3,1]])
2307
sage: S.is_cohen_macaulay(ncpus=3)
2308
True
2309
2310
The following example is taken from Bruns, Herzog - Cohen-Macaulay
2311
rings, Figure 5.3::
2312
2313
sage: S = SimplicialComplex([[1,2,3],[1,4,5]])
2314
sage: S.is_cohen_macaulay(ncpus=3)
2315
...
2316
False
2317
"""
2318
from sage.parallel.decorate import parallel
2319
from sage.rings.rational_field import QQ
2320
2321
if ncpus == 0:
2322
import os
2323
try:
2324
ncpus = int(os.environ['SAGE_NUM_THREADS'])
2325
except KeyError:
2326
ncpus = 1
2327
2328
facs = [ x for x in self.face_iterator() ]
2329
n = len(facs)
2330
facs_divided = [ [] for i in range(ncpus) ]
2331
for i in range(n):
2332
facs_divided[i%ncpus].append(facs[i])
2333
2334
def all_homologies_vanish(F):
2335
S = self.link(F)
2336
H = S.homology(base_ring=QQ)
2337
return all( H[j].dimension() == 0 for j in xrange(S.dimension()) )
2338
2339
@parallel(ncpus=ncpus)
2340
def all_homologies_in_list_vanish(Fs):
2341
return all( all_homologies_vanish(F) for F in Fs )
2342
2343
return all( answer[1] for answer in all_homologies_in_list_vanish(facs_divided) )
2344
2345
def effective_vertices(self):
2346
"""
2347
The set of vertices belonging to some face. Returns the list of
2348
vertices.
2349
2350
.. WARNING::
2351
2352
This method is deprecated. See :trac:`12587`.
2353
2354
EXAMPLES::
2355
2356
sage: S = SimplicialComplex([[0,1,2,3],[6,7]])
2357
sage: S
2358
Simplicial complex with vertex set (0, 1, 2, 3, 6, 7) and facets {(6, 7), (0, 1, 2, 3)}
2359
sage: S.effective_vertices()
2360
doctest:1: DeprecationWarning: effective_vertices is deprecated. Use vertices instead
2361
See http://trac.sagemath.org/12587 for details.
2362
(0, 1, 2, 3, 6, 7)
2363
"""
2364
from sage.misc.superseded import deprecation
2365
deprecation(12587, "effective_vertices is deprecated. Use vertices instead")
2366
return self._vertex_set
2367
2368
def generated_subcomplex(self,sub_vertex_set, is_mutable=True):
2369
"""
2370
Returns the largest sub-simplicial complex of ``self`` containing
2371
exactly ``sub_vertex_set`` as vertices.
2372
2373
:param sub_vertex_set: The sub-vertex set.
2374
:param is_mutable: Determines if the output is mutable
2375
:type is_mutable: boolean; optional, default ``True``
2376
2377
EXAMPLES::
2378
2379
sage: S = simplicial_complexes.Sphere(2)
2380
sage: S
2381
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}
2382
sage: S.generated_subcomplex([0,1,2])
2383
Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)}
2384
2385
"""
2386
if not self.vertices().set().issuperset(sub_vertex_set):
2387
raise ValueError, "input must be a subset of the vertex set."
2388
faces = []
2389
for i in range(self.dimension()+1):
2390
for j in self.faces()[i]:
2391
if j.set().issubset(sub_vertex_set):
2392
faces.append(j)
2393
return SimplicialComplex(faces, maximality_check=True,
2394
is_mutable=is_mutable)
2395
2396
def _complement(self, simplex):
2397
"""
2398
Return the complement of a simplex in the vertex set of this
2399
simplicial complex.
2400
2401
:param simplex: a simplex (need not be in the simplicial complex)
2402
2403
OUTPUT: its complement: the simplex formed by the vertices not
2404
contained in ``simplex``.
2405
2406
Note that this only depends on the vertex set of the
2407
simplicial complex, not on its simplices.
2408
2409
EXAMPLES::
2410
2411
sage: X = SimplicialComplex([[0,1,2,3,4,5]])
2412
sage: X._complement([1,2,3])
2413
(0, 4, 5)
2414
sage: X._complement([0,1,3,4])
2415
(2, 5)
2416
sage: X._complement([0,4,1,3])
2417
(2, 5)
2418
"""
2419
return Simplex(set(self.vertices()).difference(simplex))
2420
2421
def _transpose_simplices(self, *simplices):
2422
"""
2423
Given tuple ``L`` of simplices, returns new list, where each
2424
simplex is formed by taking a vertex from each simplex from
2425
``L``.
2426
2427
:param simplices: a bunch of simplices
2428
2429
If ``simplices`` consists of `(f_0, f_1, f_2, ...)`, then the
2430
output consists of all possible simplices of the form `(v_0,
2431
v_1, v_2, ...)`, where `v_i` is a vertex of `f_i`. If a
2432
vertex appears more than once in such a simplex, remove all
2433
but one of its appearances. If such a simplex contains others
2434
already produced, then ignore that larger simplex -- the
2435
output should be a list of minimal simplices constructed in
2436
this way.
2437
2438
This is used in computing the minimal nonfaces and hence the
2439
Stanley-Reisner ring.
2440
2441
Note that this only depends on the vertex set of the
2442
simplicial complex, not on its simplices.
2443
2444
I don't know if there is a standard name for this, but it
2445
looked sort of like the transpose of a matrix; hence the name
2446
for this method.
2447
2448
EXAMPLES::
2449
2450
sage: X = SimplicialComplex()
2451
sage: X._transpose_simplices([1,2])
2452
[(1,), (2,)]
2453
sage: X._transpose_simplices([1,2], [3,4])
2454
[(1, 3), (1, 4), (2, 3), (2, 4)]
2455
2456
In the following example, one can construct the simplices
2457
``(1,2)`` and ``(1,3)``, but you can also construct ``(1,1) = (1,)``,
2458
which is a face of both of the others. So the answer omits
2459
``(1,2)`` and ``(1,3)``::
2460
2461
sage: X._transpose_simplices([1,2], [1,3])
2462
[(1,), (2, 3)]
2463
"""
2464
answer = []
2465
if len(simplices) == 1:
2466
answer = [Simplex((v,)) for v in simplices[0]]
2467
elif len(simplices) > 1:
2468
face = simplices[0]
2469
rest = simplices[1:]
2470
for v in face:
2471
for partial in self._transpose_simplices(*rest):
2472
if v not in partial:
2473
L = [v] + list(partial)
2474
L.sort()
2475
simplex = Simplex(L)
2476
else:
2477
simplex = partial
2478
add_simplex = True
2479
simplices_to_delete = []
2480
for already in answer:
2481
if add_simplex:
2482
if already.is_face(simplex):
2483
add_simplex = False
2484
if add_simplex and simplex.is_face(already):
2485
simplices_to_delete.append(already)
2486
if add_simplex:
2487
answer.append(simplex)
2488
for x in simplices_to_delete:
2489
answer.remove(x)
2490
return answer
2491
2492
def minimal_nonfaces(self):
2493
"""
2494
Set consisting of the minimal subsets of the vertex set of
2495
this simplicial complex which do not form faces.
2496
2497
Algorithm: first take the complement (within the vertex set)
2498
of each facet, obtaining a set `(f_1, f_2, ...)` of simplices.
2499
Now form the set of all simplices of the form `(v_1, v_2,
2500
...)` where vertex `v_i` is in face `f_i`. This set will
2501
contain the minimal nonfaces and may contain some non-minimal
2502
nonfaces also, so loop through the set to find the minimal
2503
ones. (The last two steps are taken care of by the
2504
``_transpose_simplices`` routine.)
2505
2506
This is used in computing the
2507
:meth:`Stanley-Reisner ring<stanley_reisner_ring>` and the
2508
:meth:`Alexander dual<alexander_dual>`.
2509
2510
EXAMPLES::
2511
2512
sage: X = SimplicialComplex([[1,3],[1,2]])
2513
sage: X.minimal_nonfaces()
2514
{(2, 3)}
2515
sage: Y = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]])
2516
sage: Y.minimal_nonfaces()
2517
{(1, 3), (0, 2)}
2518
"""
2519
complements = [self._complement(facet) for facet in self._facets]
2520
return Set(self._transpose_simplices(*complements))
2521
2522
def _stanley_reisner_base_ring(self, base_ring=ZZ):
2523
"""
2524
The polynomial algebra of which the Stanley-Reisner ring is a
2525
quotient.
2526
2527
:param base_ring: a commutative ring
2528
:type base_ring: optional, default ``ZZ``
2529
:return: a polynomial algebra with coefficients in base_ring,
2530
with one generator for each vertex in the simplicial complex.
2531
2532
See the documentation for :meth:`stanley_reisner_ring` for a
2533
warning about the names of the vertices.
2534
2535
EXAMPLES::
2536
2537
sage: X = SimplicialComplex([[1,2], [0], [3]])
2538
sage: X._stanley_reisner_base_ring()
2539
Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring
2540
sage: Y = SimplicialComplex([['a', 'b', 'c']])
2541
sage: Y._stanley_reisner_base_ring(base_ring=QQ)
2542
Multivariate Polynomial Ring in a, c, b over Rational Field
2543
"""
2544
return PolynomialRing(base_ring, self._gen_dict.values())
2545
2546
def stanley_reisner_ring(self, base_ring=ZZ):
2547
"""
2548
The Stanley-Reisner ring of this simplicial complex.
2549
2550
:param base_ring: a commutative ring
2551
:type base_ring: optional, default ``ZZ``
2552
:return: a quotient of a polynomial algebra with coefficients
2553
in ``base_ring``, with one generator for each vertex in the
2554
simplicial complex, by the ideal generated by the products
2555
of those vertices which do not form faces in it.
2556
2557
Thus the ideal is generated by the products corresponding to
2558
the minimal nonfaces of the simplicial complex.
2559
2560
.. WARNING::
2561
2562
This may be quite slow!
2563
2564
Also, this may behave badly if the vertices have the
2565
'wrong' names. To avoid this, define the simplicial complex
2566
at the start with the flag ``name_check`` set to ``True``.
2567
2568
More precisely, this is a quotient of a polynomial ring
2569
with one generator for each vertex. If the name of a
2570
vertex is a non-negative integer, then the corresponding
2571
polynomial generator is named ``'x'`` followed by that integer
2572
(e.g., ``'x2'``, ``'x3'``, ``'x5'``, ...). Otherwise, the
2573
polynomial generators are given the same names as the vertices.
2574
Thus if the vertex set is ``(2, 'x2')``, there will be problems.
2575
2576
EXAMPLES::
2577
2578
sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]])
2579
sage: X.stanley_reisner_ring()
2580
Quotient of Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring by the ideal (x1*x3, x0*x2)
2581
sage: Y = SimplicialComplex([[0,1,2,3,4]]); Y
2582
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2, 3, 4)}
2583
sage: Y.add_face([0,1,2,3,4])
2584
sage: Y.stanley_reisner_ring(base_ring=QQ)
2585
Multivariate Polynomial Ring in x0, x1, x2, x3, x4 over Rational Field
2586
"""
2587
R = self._stanley_reisner_base_ring(base_ring)
2588
products = []
2589
for f in self.minimal_nonfaces():
2590
prod = 1
2591
for v in f:
2592
prod *= R(self._gen_dict[v])
2593
products.append(prod)
2594
return R.quotient(products)
2595
2596
def alexander_dual(self, is_mutable=True):
2597
"""
2598
The Alexander dual of this simplicial complex: according to
2599
the Macaulay2 documentation, this is the simplicial complex
2600
whose faces are the complements of its nonfaces.
2601
2602
Thus find the minimal nonfaces and take their complements to
2603
find the facets in the Alexander dual.
2604
2605
:param is_mutable: Determines if the output is mutable
2606
:type is_mutable: boolean; optional, default ``True``
2607
2608
EXAMPLES::
2609
2610
sage: Y = SimplicialComplex([[i] for i in range(5)]); Y
2611
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(4,), (2,), (3,), (0,), (1,)}
2612
sage: Y.alexander_dual()
2613
Simplicial complex with vertex set (0, 1, 2, 3, 4) and 10 facets
2614
sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]])
2615
sage: X.alexander_dual()
2616
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 3), (0, 2)}
2617
"""
2618
nonfaces = self.minimal_nonfaces()
2619
return SimplicialComplex([self._complement(f) for f in nonfaces], is_mutable=is_mutable)
2620
2621
def barycentric_subdivision(self):
2622
"""
2623
The barycentric subdivision of this simplicial complex.
2624
2625
See http://en.wikipedia.org/wiki/Barycentric_subdivision for a
2626
definition.
2627
2628
EXAMPLES::
2629
2630
sage: triangle = SimplicialComplex([[0,1], [1,2], [0, 2]])
2631
sage: hexagon = triangle.barycentric_subdivision()
2632
sage: hexagon
2633
Simplicial complex with 6 vertices and 6 facets
2634
sage: hexagon.homology(1) == triangle.homology(1)
2635
True
2636
2637
Barycentric subdivisions can get quite large, since each
2638
`n`-dimensional facet in the original complex produces
2639
`(n+1)!` facets in the subdivision::
2640
2641
sage: S4 = simplicial_complexes.Sphere(4)
2642
sage: S4
2643
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and 6 facets
2644
sage: S4.barycentric_subdivision()
2645
Simplicial complex with 62 vertices and 720 facets
2646
"""
2647
return self.face_poset().order_complex()
2648
2649
def graph(self):
2650
"""
2651
The 1-skeleton of this simplicial complex, as a graph.
2652
2653
.. WARNING::
2654
2655
This may give the wrong answer if the simplicial complex
2656
was constructed with ``maximality_check`` set to ``False``.
2657
2658
EXAMPLES::
2659
2660
sage: S = SimplicialComplex([[0,1,2,3]])
2661
sage: G = S.graph(); G
2662
Graph on 4 vertices
2663
sage: G.edges()
2664
[(0, 1, None), (0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None), (2, 3, None)]
2665
"""
2666
if self._graph is None:
2667
edges = self.n_faces(1)
2668
vertices = map(min, filter(lambda f: f.dimension() == 0, self._facets))
2669
used_vertices = [] # vertices which are in an edge
2670
d = {}
2671
for e in edges:
2672
v = min(e)
2673
if v in d:
2674
d[v].append(max(e))
2675
else:
2676
d[v] = [max(e)]
2677
used_vertices.extend(list(e))
2678
for v in vertices:
2679
if v not in used_vertices:
2680
d[v] = []
2681
self._graph = Graph(d)
2682
return self._graph
2683
2684
def delta_complex(self, sort_simplices=False):
2685
r"""
2686
Returns ``self`` as a `\Delta`-complex. The `\Delta`-complex
2687
is essentially identical to the simplicial complex: it has
2688
same simplices with the same boundaries.
2689
2690
:param sort_simplices: if ``True``, sort the list of simplices in
2691
each dimension
2692
:type sort_simplices: boolean; optional, default ``False``
2693
2694
EXAMPLES::
2695
2696
sage: T = simplicial_complexes.Torus()
2697
sage: Td = T.delta_complex()
2698
sage: Td
2699
Delta complex with 7 vertices and 43 simplices
2700
sage: T.homology() == Td.homology()
2701
True
2702
"""
2703
from delta_complex import DeltaComplex
2704
data = {}
2705
dim = self.dimension()
2706
n_cells = self.n_cells(dim)
2707
if sort_simplices:
2708
n_cells.sort()
2709
for n in range(dim, -1, -1):
2710
bdries = self.n_cells(n-1)
2711
if sort_simplices:
2712
bdries.sort()
2713
data[n] = []
2714
for f in n_cells:
2715
data[n].append([bdries.index(f.face(i)) for i in range(n+1)])
2716
n_cells = bdries
2717
return DeltaComplex(data)
2718
2719
def is_flag_complex(self):
2720
"""
2721
Returns ``True`` if and only if ``self`` is a flag complex.
2722
2723
A flag complex is a simplicial complex that is the largest simplicial
2724
complex on its 1-skeleton. Thus a flag complex is the clique complex
2725
of its graph.
2726
2727
EXAMPLES::
2728
2729
sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]})
2730
sage: x = h.clique_complex()
2731
sage: x
2732
Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)}
2733
sage: x.is_flag_complex()
2734
True
2735
2736
sage: X = simplicial_complexes.ChessboardComplex(3,3)
2737
sage: X.is_flag_complex()
2738
True
2739
"""
2740
return self == self.graph().clique_complex()
2741
2742
def is_connected(self):
2743
"""
2744
Returns ``True`` if and only if ``self`` is connected.
2745
2746
.. WARNING::
2747
2748
This may give the wrong answer if the simplicial complex
2749
was constructed with ``maximality_check`` set to ``False``.
2750
2751
EXAMPLES::
2752
2753
sage: V = SimplicialComplex([[0,1,2],[3]])
2754
sage: V
2755
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (3,)}
2756
sage: V.is_connected()
2757
False
2758
2759
sage: X = SimplicialComplex([[0,1,2]])
2760
sage: X.is_connected()
2761
True
2762
2763
sage: U = simplicial_complexes.ChessboardComplex(3,3)
2764
sage: U.is_connected()
2765
True
2766
2767
sage: W = simplicial_complexes.Sphere(3)
2768
sage: W.is_connected()
2769
True
2770
2771
sage: S = SimplicialComplex([[0,1],[2,3]])
2772
sage: S.is_connected()
2773
False
2774
"""
2775
return self.graph().is_connected()
2776
2777
def n_skeleton(self, n):
2778
"""
2779
The `n`-skeleton of this simplicial complex.
2780
2781
The `n`-skeleton of a simplicial complex is obtained by discarding
2782
all of the simplices in dimensions larger than `n`.
2783
2784
:param n: non-negative integer
2785
2786
EXAMPLES::
2787
2788
sage: X = SimplicialComplex([[0,1], [1,2,3], [0,2,3]])
2789
sage: X.n_skeleton(1)
2790
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 2), (1, 3), (1, 2), (0, 3), (0, 1)}
2791
sage: X.set_immutable()
2792
sage: X.n_skeleton(2)
2793
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (1, 2, 3), (0, 1)}
2794
"""
2795
# make sure it's a list (it will be a tuple if immutable)
2796
facets = list(filter(lambda f: f.dimension()<n, self._facets))
2797
facets.extend(self.n_faces(n))
2798
return SimplicialComplex(facets, is_mutable=self._is_mutable)
2799
2800
def _contractible_subcomplex(self, verbose=False):
2801
"""
2802
Find a contractible subcomplex `L` of this simplicial complex,
2803
preferably one which is as large as possible.
2804
2805
:param verbose: If ``True``, print some messages as the simplicial
2806
complex is computed.
2807
:type verbose: boolean; optional, default ``False``
2808
2809
Motivation: if `K` is the original complex and if `L` is
2810
contractible, then the relative homology `H_*(K,L)` is
2811
isomorphic to the reduced homology of `K`. If `L` is large,
2812
then the relative chain complex will be a good deal smaller
2813
than the augmented chain complex for `K`, and this leads to a
2814
speed improvement for computing the homology of `K`.
2815
2816
This just passes an immutable subcomplex consisting of a facet to the
2817
method ``_enlarge_subcomplex``.
2818
2819
.. NOTE::
2820
2821
Thus when the simplicial complex is empty, so is the
2822
resulting 'contractible subcomplex', which is therefore not
2823
technically contractible. In this case, that doesn't
2824
matter because the homology is computed correctly anyway.
2825
2826
EXAMPLES::
2827
2828
sage: sphere = SimplicialComplex([[0,1,2,3]])
2829
sage: sphere.remove_face([0,1,2,3])
2830
sage: sphere
2831
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}
2832
sage: L = sphere._contractible_subcomplex(); L
2833
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (1, 2, 3), (0, 1, 3)}
2834
sage: L.homology()
2835
{0: 0, 1: 0, 2: 0}
2836
"""
2837
facets = [self._facets[0]]
2838
return self._enlarge_subcomplex(SimplicialComplex(facets, is_mutable=False), verbose=verbose)
2839
2840
def _enlarge_subcomplex(self, subcomplex, verbose=False):
2841
"""
2842
Given a subcomplex `S` of this simplicial complex `K`, find a
2843
subcomplex `L`, as large as possible, containing `S` which is
2844
homotopy equivalent to `S` (so that `H_{*}(K,S)` is isomorphic
2845
to `H_{*}(K,L)`). This way, the chain complex for computing
2846
`H_{*}(K,L)` will be smaller than that for computing
2847
`H_{*}(K,S)`, so the computations should be faster.
2848
2849
:param subcomplex: a subcomplex of this simplicial complex
2850
:param verbose: If ``True``, print some messages as the simplicial
2851
complex is computed.
2852
:type verbose: boolean; optional, default ``False``
2853
:return: a complex `L` containing ``subcomplex`` and contained
2854
in ``self``, homotopy equivalent to ``subcomplex``.
2855
2856
Algorithm: start with the subcomplex `S` and loop through the
2857
facets of `K` which are not in `S`. For each one, see whether
2858
its intersection with `S` is contractible, and if so, add it.
2859
This is recursive: testing for contractibility calls this
2860
routine again, via ``_contractible_subcomplex``.
2861
2862
EXAMPLES::
2863
2864
sage: T = simplicial_complexes.Torus(); T
2865
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 14 facets
2866
2867
Inside the torus, define a subcomplex consisting of a loop::
2868
2869
sage: S = SimplicialComplex([[0,1], [1,2], [0,2]], is_mutable=False)
2870
sage: S.homology()
2871
{0: 0, 1: Z}
2872
sage: L = T._enlarge_subcomplex(S)
2873
sage: L
2874
Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 8 facets
2875
sage: L.facets()
2876
{(0, 1, 5), (1, 3, 6), (1, 2), (1, 2, 4), (1, 3, 4), (0, 2), (1, 5, 6), (0, 1)}
2877
sage: L.homology()[1]
2878
Z
2879
"""
2880
# Make the subcomplex immutable if not
2881
if subcomplex is not None and subcomplex._is_mutable:
2882
subcomplex = SimplicialComplex(subcomplex._facets,
2883
maximality_check=False,
2884
sort_facets=False,
2885
is_mutable=False)
2886
2887
if subcomplex in self.__enlarged:
2888
return self.__enlarged[subcomplex]
2889
faces = filter(lambda x: x not in subcomplex._facets, list(self._facets))
2890
done = False
2891
new_facets = list(subcomplex._facets)
2892
while not done:
2893
done = True
2894
remove_these = []
2895
if verbose:
2896
print " looping through %s facets" % len(faces)
2897
for f in faces:
2898
f_set = f.set()
2899
int_facets = set( a.set().intersection(f_set) for a in new_facets )
2900
intersection = SimplicialComplex(int_facets)
2901
if not intersection._facets[0].is_empty():
2902
if (len(intersection._facets) == 1 or
2903
intersection == intersection._contractible_subcomplex()):
2904
new_facets.append(f)
2905
remove_these.append(f)
2906
done = False
2907
if verbose and not done:
2908
print " added %s facets" % len(remove_these)
2909
for f in remove_these:
2910
faces.remove(f)
2911
if verbose:
2912
print " now constructing a simplicial complex with %s vertices and %s facets" % (self.vertices().dimension()+1, len(new_facets))
2913
L = SimplicialComplex(new_facets, maximality_check=False,
2914
sort_facets=False, is_mutable=self._is_mutable)
2915
self.__enlarged[subcomplex] = L
2916
return L
2917
2918
def _cubical_(self):
2919
r"""
2920
Cubical complex constructed from ``self``.
2921
2922
ALGORITHM:
2923
2924
The algorithm comes from a paper by Shtan'ko and Shtogrin, as
2925
reported by Bukhshtaber and Panov. Let `I^m` denote the unit
2926
`m`-cube, viewed as a cubical complex. Let `[m] = \{1, 2,
2927
..., m\}`; then each face of `I^m` has the following form, for
2928
subsets `I \subset J \subset [m]`:
2929
2930
.. MATH::
2931
2932
F_{I \subset J} = \{ (y_1,...,y_m) \in I^m \,:\, y_i =0 \text{
2933
for } i \in I, y_j = 1 \text{ for } j \not \in J\}.
2934
2935
If `K` is a simplicial complex on vertex set `[m]` and if `I
2936
\subset [m]`, write `I \in K` if `I` is a simplex of `K`.
2937
Then we associate to `K` the cubical subcomplex of `I^m` with
2938
faces
2939
2940
.. MATH::
2941
2942
\{F_{I \subset J} \,:\, J \in K, I \neq \emptyset \}
2943
2944
The geometric realization of this cubical complex is
2945
homeomorphic to the geometric realization of the original
2946
simplicial complex.
2947
2948
REFERENCES:
2949
2950
.. [BP2000] V. M. Bukhshtaber and T. E. Panov, "Moment-angle complexes
2951
and combinatorics of simplicial manifolds," *Uspekhi
2952
Mat. Nauk* 55 (2000), 171--172.
2953
2954
.. [SS1992] M. A. Shtan'ko and and M. I. Shtogrin, "Embedding cubic
2955
manifolds and complexes into a cubic lattice", *Uspekhi
2956
Mat. Nauk* 47 (1992), 219-220.
2957
2958
EXAMPLES::
2959
2960
sage: T = simplicial_complexes.Torus()
2961
sage: T.homology()
2962
{0: 0, 1: Z x Z, 2: Z}
2963
sage: Tc = T._cubical_()
2964
sage: Tc
2965
Cubical complex with 42 vertices and 168 cubes
2966
sage: Tc.homology()
2967
{0: 0, 1: Z x Z, 2: Z}
2968
"""
2969
from sage.homology.cubical_complex import CubicalComplex
2970
V = self.vertices()
2971
embed = V.dimension() + 1
2972
# dictionary to translate vertices to the numbers 1, ..., embed
2973
vd = dict(zip(V, range(1, embed + 1)))
2974
cubes = []
2975
for JJ in self.facets():
2976
J = [vd[i] for i in JJ]
2977
for i in J:
2978
# loop over indices from 1 to embed. if equal to i,
2979
# set to 0. if not in J, set to 1. Otherwise, range
2980
# from 0 to 1
2981
cube = []
2982
for n in range(1, embed+1):
2983
if n == i:
2984
cube.append([0,])
2985
elif n not in J:
2986
cube.append([1,])
2987
else:
2988
cube.append([0,1])
2989
cubes.append(cube)
2990
return CubicalComplex(cubes)
2991
2992
def connected_component(self, simplex=None):
2993
"""
2994
Return the connected component of this simplicial complex
2995
containing ``simplex``. If ``simplex`` is omitted, then return
2996
the connected component containing the zeroth vertex in the
2997
vertex list. (If the simplicial complex is empty, raise an
2998
error.)
2999
3000
EXAMPLES::
3001
3002
sage: S1 = simplicial_complexes.Sphere(1)
3003
sage: S1 == S1.connected_component()
3004
True
3005
sage: X = S1.disjoint_union(S1)
3006
sage: X == X.connected_component()
3007
False
3008
sage: v0 = X.vertices()[0]
3009
sage: v1 = X.vertices()[-1]
3010
sage: X.connected_component(Simplex([v0])) == X.connected_component(Simplex([v1]))
3011
False
3012
3013
sage: S0 = simplicial_complexes.Sphere(0)
3014
sage: S0.vertices()
3015
(0, 1)
3016
sage: S0.connected_component()
3017
Simplicial complex with vertex set (0,) and facets {(0,)}
3018
sage: S0.connected_component(Simplex((1,)))
3019
Simplicial complex with vertex set (1,) and facets {(1,)}
3020
3021
sage: SimplicialComplex([[]]).connected_component()
3022
Traceback (most recent call last):
3023
...
3024
ValueError: the empty simplicial complex has no connected components.
3025
"""
3026
if self.dimension() == -1:
3027
raise ValueError("the empty simplicial complex has no connected components.")
3028
if simplex is None:
3029
v = self.vertices()[0]
3030
else:
3031
v = simplex[0]
3032
vertices = self.graph().connected_component_containing_vertex(v)
3033
facets = [f for f in self.facets() if f.is_face(Simplex(vertices))]
3034
return SimplicialComplex(facets)
3035
3036
def fundamental_group(self, base_point=None, simplify=True):
3037
r"""
3038
Return the fundamental group of this simplicial complex.
3039
3040
INPUT:
3041
3042
- ``base_point`` (optional, default None) -- if this complex is
3043
not path-connected, then specify a vertex; the fundamental
3044
group is computed with that vertex as a base point. If the
3045
complex is path-connected, then you may specify a vertex or
3046
leave this as its default setting of ``None``. (If this
3047
complex is path-connected, then this argument is ignored.)
3048
3049
- ``simplify`` (bool, optional True) -- if False, then return a
3050
presentation of the group in terms of generators and
3051
relations. If True, the default, simplify as much as GAP is
3052
able to.
3053
3054
Algorithm: we compute the edge-path group -- see
3055
:wikipedia:`Fundamental_group`. Choose a spanning tree for the
3056
1-skeleton, and then the group's generators are given by the
3057
edges in the 1-skeleton; there are two types of relations:
3058
`e=1` if `e` is in the spanning tree, and for every 2-simplex,
3059
if its edges are `e_0`, `e_1`, and `e_2`, then we impose the
3060
relation `e_0 e_1^{-1} e_2 = 1`.
3061
3062
EXAMPLES::
3063
3064
sage: S1 = simplicial_complexes.Sphere(1)
3065
sage: S1.fundamental_group()
3066
Finitely presented group < e | >
3067
3068
If we pass the argument ``simplify=False``, we get generators and
3069
relations in a form which is not usually very helpful. Here is the
3070
cyclic group of order 2, for instance::
3071
3072
sage: RP2 = simplicial_complexes.RealProjectiveSpace(2)
3073
sage: C2 = RP2.fundamental_group(simplify=False)
3074
sage: C2
3075
Finitely presented group < e0, e1, e2, e3, e4, e5, e6, e7, e8, e9 | e6, e5, e3, e9, e4*e7^-1*e6, e9*e7^-1*e0, e0*e1^-1*e2, e5*e1^-1*e8, e4*e3^-1*e8, e2 >
3076
sage: C2.simplified()
3077
Finitely presented group < e0 | e0^2 >
3078
3079
This is the same answer given if the argument ``simplify`` is True
3080
(the default)::
3081
3082
sage: RP2.fundamental_group()
3083
Finitely presented group < e0 | e0^2 >
3084
3085
You must specify a base point to compute the fundamental group
3086
of a non-connected complex::
3087
3088
sage: K = S1.disjoint_union(RP2)
3089
sage: K.fundamental_group()
3090
Traceback (most recent call last):
3091
...
3092
ValueError: this complex is not connected, so you must specify a base point.
3093
sage: v0 = list(K.vertices())[0]
3094
sage: K.fundamental_group(base_point=v0)
3095
Finitely presented group < e | >
3096
sage: v1 = list(K.vertices())[-1]
3097
sage: K.fundamental_group(base_point=v1)
3098
Finitely presented group < e0 | e0^2 >
3099
3100
Some other examples::
3101
3102
sage: S1.wedge(S1).fundamental_group()
3103
Finitely presented group < e0, e1 | >
3104
sage: simplicial_complexes.Torus().fundamental_group()
3105
Finitely presented group < e0, e3 | e0*e3^-1*e0^-1*e3 >
3106
sage: simplicial_complexes.MooreSpace(5).fundamental_group()
3107
Finitely presented group < e1 | e1^5 >
3108
"""
3109
if not self.is_connected():
3110
if base_point is None:
3111
raise ValueError("this complex is not connected, so you must specify a base point.")
3112
return self.connected_component(Simplex([base_point])).fundamental_group(simplify=simplify)
3113
3114
from sage.groups.free_group import FreeGroup
3115
from sage.interfaces.gap import gap
3116
spanning_tree = [e[:2] for e in self.graph().min_spanning_tree()]
3117
gens = [tuple(e) for e in self.n_cells(1) if tuple(e) not in spanning_tree]
3118
3119
if len(gens) == 0:
3120
return gap.TrivialGroup()
3121
3122
gens_dict = dict(zip(gens, range(len(gens))))
3123
FG = FreeGroup(len(gens), 'e')
3124
rels = []
3125
for f in self.n_cells(2):
3126
bdry = [tuple(e) for e in f.faces()]
3127
z = dict()
3128
for i in range(3):
3129
if bdry[i] in spanning_tree:
3130
z[i] = FG.one()
3131
else:
3132
z[i] = FG.gen(gens_dict[bdry[i]])
3133
rels.append(z[0]*z[1].inverse()*z[2])
3134
if simplify:
3135
return FG.quotient(rels).simplified()
3136
else:
3137
return FG.quotient(rels)
3138
3139
def category(self):
3140
"""
3141
Return the category to which this simplicial complex belongs: the
3142
category of all simplicial complexes.
3143
3144
EXAMPLES::
3145
3146
sage: SimplicialComplex([[0,1], [1,2,3,4,5]]).category()
3147
Category of simplicial complexes
3148
"""
3149
import sage.categories.all
3150
return sage.categories.all.SimplicialComplexes()
3151
3152
def is_isomorphic(self,other, certify = False):
3153
r"""
3154
Checks whether two simplicial complexes are isomorphic
3155
3156
INPUT:
3157
3158
- ``certify`` - if ``True``, then output is ``(a,b)``, where ``a``
3159
is a boolean and ``b`` is either a map or ``None``.
3160
3161
This is done by creating two graphs and checking whether they
3162
are isomorphic.
3163
3164
EXAMPLES::
3165
3166
sage: Z1 = SimplicialComplex([[0,1],[1,2],[2,3,4],[4,5]])
3167
sage: Z2 = SimplicialComplex([['a','b'],['b','c'],['c','d','e'],['e','f']])
3168
sage: Z3 = SimplicialComplex([[1,2,3]])
3169
sage: Z1.is_isomorphic(Z2)
3170
True
3171
sage: Z1.is_isomorphic(Z2, certify=True)
3172
(True, {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f'})
3173
sage: Z3.is_isomorphic(Z2)
3174
False
3175
"""
3176
g1 = Graph()
3177
g2 = Graph()
3178
g1.add_edges((v,f) for f in self.facets() for v in f)
3179
g2.add_edges((v,f) for f in other.facets() for v in f)
3180
g1.add_edges(("fake_vertex",v,"special_edge") for v in self.vertices())
3181
g2.add_edges(("fake_vertex",v,"special_edge") for v in other.vertices())
3182
if not certify:
3183
return g1.is_isomorphic(g2)
3184
isisom, tr = g1.is_isomorphic(g2, certify = True)
3185
3186
if isisom:
3187
for f in self.facets():
3188
tr.pop(f)
3189
tr.pop("fake_vertex")
3190
3191
return isisom,tr
3192
3193
def automorphism_group(self):
3194
r"""
3195
Returns the automorphism group of the simplicial complex
3196
3197
This is done by creating a bipartite graph, whose vertices are
3198
vertices and facets of the simplicial complex, and computing
3199
its automorphism group.
3200
3201
.. WARNING::
3202
3203
Since :trac:`14319` the domain of the automorphism group is equal to
3204
the graph's vertex set, and the ``translation`` argument has become
3205
useless.
3206
3207
EXAMPLES::
3208
3209
sage: S = simplicial_complexes.Simplex(3)
3210
sage: S.automorphism_group().is_isomorphic(SymmetricGroup(4))
3211
True
3212
3213
sage: P = simplicial_complexes.RealProjectivePlane()
3214
sage: P.automorphism_group().is_isomorphic(AlternatingGroup(5))
3215
True
3216
3217
sage: Z = SimplicialComplex([['1','2'],['2','3','a']])
3218
sage: Z.automorphism_group().is_isomorphic(CyclicPermutationGroup(2))
3219
True
3220
sage: group = Z.automorphism_group()
3221
sage: group.domain()
3222
{'1', '2', '3', 'a'}
3223
"""
3224
from sage.groups.perm_gps.permgroup import PermutationGroup
3225
3226
G = Graph()
3227
G.add_vertices(self.vertices())
3228
G.add_edges((f.tuple(),v) for f in self.facets() for v in f)
3229
groupe = G.automorphism_group(partition=[list(self.vertices()),
3230
[f.tuple() for f in self.facets()]])
3231
3232
3233
gens = [ [tuple(c) for c in g.cycle_tuples() if not isinstance(c[0],tuple)]
3234
for g in groupe.gens()]
3235
3236
permgroup = PermutationGroup(gens = gens, domain = self.vertices())
3237
3238
return permgroup
3239
3240
def _Hom_(self, other, category=None):
3241
"""
3242
Return the set of simplicial maps between simplicial complexes
3243
``self`` and ``other``.
3244
3245
EXAMPLES::
3246
3247
sage: S = simplicial_complexes.Sphere(1)
3248
sage: T = simplicial_complexes.Sphere(2)
3249
sage: H = Hom(S,T) # indirect doctest
3250
sage: H
3251
Set of Morphisms from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)} in Category of simplicial complexes
3252
sage: f = {0:0,1:1,2:3}
3253
sage: x = H(f)
3254
sage: x
3255
Simplicial complex morphism {0: 0, 1: 1, 2: 3} from Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 2), (0, 1)} to Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2, 3), (0, 1, 2), (1, 2, 3), (0, 1, 3)}
3256
"""
3257
from sage.homology.simplicial_complex_homset import SimplicialComplexHomset
3258
return SimplicialComplexHomset(self, other)
3259
3260
# @cached_method when we switch to immutable SimplicialComplex
3261
def _is_numeric(self):
3262
"""
3263
Test whether all vertices are labeled by integers
3264
3265
OUTPUT:
3266
3267
Boolean. Whether all vertices are labeled by (not necessarily
3268
consecutive) integers.
3269
3270
EXAMPLES::
3271
3272
sage: s = SimplicialComplex()
3273
sage: s._is_numeric()
3274
True
3275
sage: s.add_face(['a', 'b', 123])
3276
sage: s._is_numeric()
3277
False
3278
"""
3279
return all([isinstance(v, (int, Integer, long)) for v in self._vertex_set])
3280
3281
# @cached_method when we switch to immutable SimplicialComplex
3282
def _translation_to_numeric(self):
3283
"""
3284
Return a dictionary enumerating the vertices
3285
3286
See also :meth:`_translation_from_numeric`, which returns the
3287
inverse map.
3288
3289
OUTPUT:
3290
3291
A dictionary. The keys are the vertices, and the associated
3292
values are integers from 0 to number of vertices - 1.
3293
3294
EXAMPLES::
3295
3296
sage: s = SimplicialComplex()
3297
sage: s._translation_to_numeric()
3298
{}
3299
sage: s.add_face(['a', 'b', 123])
3300
sage: s._translation_to_numeric() # random output
3301
{'a': 1, 123: 0, 'b': 2}
3302
sage: set(s._translation_to_numeric().keys()) == set(['a', 'b', 123])
3303
True
3304
sage: sorted(s._translation_to_numeric().values())
3305
[0, 1, 2]
3306
"""
3307
return dict((vertex, i) for i, vertex in enumerate(self._vertex_set))
3308
3309
# @cached_method when we switch to immutable SimplicialComplex
3310
def _translation_from_numeric(self):
3311
"""
3312
Return a dictionary mapping vertex indices to vertices
3313
3314
See also :meth:`_translation_to_numeric`, which returns the
3315
inverse map.
3316
3317
OUTPUT:
3318
3319
A dictionary. The keys are integers from 0 to the number of
3320
vertices - 1. The associated values are the vertices.
3321
3322
EXAMPLES::
3323
3324
sage: s = SimplicialComplex()
3325
sage: s._translation_from_numeric()
3326
{}
3327
sage: s.add_face(['a', 'b', 123])
3328
sage: s._translation_from_numeric() # random output
3329
{0: 123, 1: 'a', 2: 'b'}
3330
sage: sorted(s._translation_from_numeric().keys())
3331
[0, 1, 2]
3332
sage: set(s._translation_from_numeric().values()) == set(['a', 'b', 123])
3333
True
3334
"""
3335
return dict(enumerate(self._vertex_set))
3336
3337
def _chomp_repr_(self):
3338
r"""
3339
String representation of ``self`` suitable for use by the CHomP
3340
program. This lists each facet on its own line, and makes
3341
sure vertices are listed as numbers.
3342
3343
EXAMPLES::
3344
3345
sage: S = SimplicialComplex([(0,1,2), (2,3,5)])
3346
sage: print S._chomp_repr_()
3347
(2, 3, 5)
3348
(0, 1, 2)
3349
3350
A simplicial complex whose vertices are tuples, not integers::
3351
3352
sage: S = SimplicialComplex([[(0,1), (1,2), (3,4)]])
3353
sage: S._chomp_repr_()
3354
'(0, 1, 2)\n'
3355
"""
3356
s = ""
3357
numeric = self._is_numeric()
3358
if not numeric:
3359
d = self._translation_to_numeric()
3360
for f in self.facets():
3361
if numeric:
3362
s += str(f)
3363
else:
3364
s += '(' + ', '.join(str(d[a]) for a in f) + ')'
3365
s += '\n'
3366
return s
3367
3368
# this function overrides the standard one for GenericCellComplex,
3369
# because it lists the maximal faces, not the total number of faces.
3370
def _repr_(self):
3371
"""
3372
Print representation.
3373
3374
If there are only a few vertices or faces, they are listed. If
3375
there are lots, the number is given.
3376
3377
EXAMPLES::
3378
3379
sage: X = SimplicialComplex([[0,1], [1,2]])
3380
sage: X._repr_()
3381
'Simplicial complex with vertex set (0, 1, 2) and facets {(1, 2), (0, 1)}'
3382
sage: SimplicialComplex([[i for i in range(16)]])
3383
Simplicial complex with 16 vertices and 1 facets
3384
"""
3385
vertex_limit = 45
3386
facet_limit = 55
3387
vertices = self.vertices()
3388
facets = Set(self._facets)
3389
vertex_string = "with vertex set %s" % vertices
3390
if len(vertex_string) > vertex_limit:
3391
vertex_string = "with %s vertices" % str(1+vertices.dimension())
3392
facet_string = "facets %s" % facets
3393
if len(facet_string) > facet_limit:
3394
facet_string = "%s facets" % len(facets)
3395
return "Simplicial complex " + vertex_string + " and " + facet_string
3396
3397
def set_immutable(self):
3398
"""
3399
Make this simplicial complex immutable.
3400
3401
EXAMPLES::
3402
3403
sage: S = SimplicialComplex([[1,4], [2,4]])
3404
sage: S.is_mutable()
3405
True
3406
sage: S.set_immutable()
3407
sage: S.is_mutable()
3408
False
3409
"""
3410
self._is_mutable = False
3411
self._facets = tuple(self._facets)
3412
3413
def is_mutable(self):
3414
"""
3415
Return ``True`` if mutable.
3416
3417
EXAMPLES::
3418
3419
sage: S = SimplicialComplex([[1,4], [2,4]])
3420
sage: S.is_mutable()
3421
True
3422
sage: S.set_immutable()
3423
sage: S.is_mutable()
3424
False
3425
sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False)
3426
sage: S2.is_mutable()
3427
False
3428
sage: S3 = SimplicialComplex([[1,4], [2,4]], is_mutable=False)
3429
sage: S3.is_mutable()
3430
False
3431
"""
3432
return self._is_mutable
3433
3434
def is_immutable(self):
3435
"""
3436
Return ``True`` if immutable.
3437
3438
EXAMPLES::
3439
3440
sage: S = SimplicialComplex([[1,4], [2,4]])
3441
sage: S.is_immutable()
3442
False
3443
sage: S.set_immutable()
3444
sage: S.is_immutable()
3445
True
3446
"""
3447
return not self._is_mutable
3448
3449
3450