Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/geometry/triangulation/point_configuration.py
8817 views
1
r"""
2
Triangulations of a point configuration
3
4
A point configuration is a finite set of points in Euclidean space or,
5
more generally, in projective space. A triangulation is a simplicial
6
decomposition of the convex hull of a given point configuration such
7
that all vertices of the simplices end up lying on points of the
8
configuration. That is, there are no new vertices apart from the
9
initial points.
10
11
Note that points that are not vertices of the convex hull need not be
12
used in the triangulation. A triangulation that does make use of all
13
points of the configuration is called fine, and you can restrict
14
yourself to such triangulations if you want. See
15
:class:`PointConfiguration` and
16
:meth:`~PointConfiguration.restrict_to_fine_triangulations` for
17
more details.
18
19
Finding a single triangulation and listing all connected
20
triangulations is implemented natively in this package. However, for
21
more advanced options [TOPCOM]_ needs to be installed. It is available
22
as an optional package for Sage, and you can install it with the
23
command::
24
25
sage: install_package('TOPCOM') # not tested
26
27
.. note::
28
29
TOPCOM and the internal algorithms tend to enumerate
30
triangulations in a different order. This is why we always
31
explicitly specify the engine as ``engine='TOPCOM'`` or
32
``engine='internal'`` in the doctests. In your own applications,
33
you do not need to specify the engine. By default, TOPCOM is used
34
if it is available and the internal algorithms are used otherwise.
35
36
EXAMPLES:
37
38
First, we select the internal implementation for enumerating
39
triangulations::
40
41
sage: PointConfiguration.set_engine('internal') # to make doctests independent of TOPCOM
42
43
A 2-dimensional point configuration::
44
45
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
46
sage: p
47
A point configuration in QQ^2 consisting of 5 points. The
48
triangulations of this point configuration are assumed to
49
be connected, not necessarily fine, not necessarily regular.
50
sage: t = p.triangulate() # a single triangulation
51
sage: t
52
(<1,3,4>, <2,3,4>)
53
sage: len(t)
54
2
55
sage: t[0]
56
(1, 3, 4)
57
sage: t[1]
58
(2, 3, 4)
59
sage: list(t)
60
[(1, 3, 4), (2, 3, 4)]
61
sage: t.plot(axes=False)
62
sage: list( p.triangulations() )
63
[(<1,3,4>, <2,3,4>),
64
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),
65
(<1,2,3>, <1,2,4>),
66
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]
67
sage: p_fine = p.restrict_to_fine_triangulations()
68
sage: p_fine
69
A point configuration in QQ^2 consisting of 5 points. The
70
triangulations of this point configuration are assumed to
71
be connected, fine, not necessarily regular.
72
sage: list( p_fine.triangulations() )
73
[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),
74
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]
75
76
A 3-dimensional point configuration::
77
78
sage: p = [[0,-1,-1],[0,0,1],[0,1,0], [1,-1,-1],[1,0,1],[1,1,0]]
79
sage: points = PointConfiguration(p)
80
sage: triang = points.triangulate()
81
sage: triang.plot(axes=False)
82
83
The standard example of a non-regular triangulation::
84
85
sage: p = PointConfiguration([[-1,-5/9],[0,10/9],[1,-5/9],[-2,-10/9],[0,20/9],[2,-10/9]])
86
sage: regular = p.restrict_to_regular_triangulations(True).triangulations_list() # optional - TOPCOM
87
sage: nonregular = p.restrict_to_regular_triangulations(False).triangulations_list() # optional - TOPCOM
88
sage: len(regular) # optional - TOPCOM
89
16
90
sage: len(nonregular) # optional - TOPCOM
91
2
92
sage: nonregular[0].plot(aspect_ratio=1, axes=False) # optional - TOPCOM
93
94
Note that the points need not be in general position. That is, the
95
points may lie in a hyperplane and the linear dependencies will be
96
removed before passing the data to TOPCOM which cannot handle it::
97
98
sage: points = [[0,0,0,1],[0,3,0,1],[3,0,0,1],[0,0,1,1],[0,3,1,1],[3,0,1,1],[1,1,2,1]]
99
sage: points = [ p+[1,2,3] for p in points ]
100
sage: pc = PointConfiguration(points)
101
sage: pc.ambient_dim()
102
7
103
sage: pc.dim()
104
3
105
sage: pc.triangulate()
106
(<0,1,2,6>, <0,1,3,6>, <0,2,3,6>, <1,2,4,6>, <1,3,4,6>, <2,3,5,6>, <2,4,5,6>)
107
sage: _ in pc.triangulations()
108
True
109
sage: len( pc.triangulations_list() )
110
26
111
112
REFERENCES:
113
114
.. [TOPCOM]
115
J. Rambau,
116
TOPCOM <http://www.rambau.wm.uni-bayreuth.de/TOPCOM/>.
117
118
.. [GKZ]
119
Gel'fand, I. M.; Kapranov, M. M.; and Zelevinsky, A. V.
120
"Discriminants, Resultants and Multidimensional Determinants" Birkhauser 1994.
121
122
.. [PUNTOS]
123
Jesus A. De Loera
124
http://www.math.ucdavis.edu/~deloera/RECENT_WORK/puntos2000
125
126
AUTHORS:
127
128
- Volker Braun: initial version, 2010
129
130
- Josh Whitney: added functionality for computing
131
volumes and secondary polytopes of PointConfigurations
132
133
- Marshall Hampton: improved documentation and doctest coverage
134
135
- Volker Braun: rewrite using Parent/Element and catgories. Added
136
a Point class. More doctests. Less zombies.
137
138
- Volker Braun: Cythonized parts of it, added a C++ implementation
139
of the bistellar flip algorithm to enumerate all connected
140
triangulations.
141
142
- Volker Braun 2011: switched the triangulate() method to the
143
placing triangulation (faster).
144
"""
145
146
########################################################################
147
# Note: The doctests that make use of TOPCOM are
148
# marked # optional - TOPCOM
149
# If you have it installed, run doctests as
150
#
151
# sage -tp 4 --long --optional=sage,topcom sage/geometry/triangulation/
152
########################################################################
153
154
155
########################################################################
156
# Copyright (C) 2010 Volker Braun <[email protected]>
157
# Copyright (C) 2010 Josh Whitney <[email protected]>
158
# Copyright (C) 2010 Marshall Hampton <[email protected]>
159
#
160
# Distributed under the terms of the GNU General Public License (GPL)
161
#
162
# http://www.gnu.org/licenses/
163
########################################################################
164
165
from sage.structure.unique_representation import UniqueRepresentation
166
from sage.structure.element import Element
167
from sage.misc.cachefunc import cached_method
168
169
from sage.combinat.combination import Combinations
170
from sage.rings.all import QQ, ZZ
171
from sage.matrix.constructor import matrix
172
from sage.modules.all import vector
173
from sage.groups.perm_gps.permgroup import PermutationGroup
174
175
from copy import copy
176
import sys
177
import pexpect
178
179
180
from sage.geometry.triangulation.base import \
181
PointConfiguration_base, Point, ConnectedTriangulationsIterator
182
183
from sage.geometry.triangulation.element import Triangulation
184
185
186
########################################################################
187
class PointConfiguration(UniqueRepresentation, PointConfiguration_base):
188
"""
189
A collection of points in Euclidean (or projective) space.
190
191
This is the parent class for the triangulations of the point
192
configuration. There are a few options to specifically select what
193
kind of triangulations are admissible.
194
195
INPUT:
196
197
The constructor accepts the following arguments:
198
199
- ``points`` -- the points. Technically, any iterable of iterables
200
will do. In particular, a :class:`PointConfiguration` can be passed.
201
202
- ``projective`` -- boolean (default: ``False``). Whether the
203
point coordinates should be interpreted as projective (``True``)
204
or affine (``False``) coordinates. If necessary, points are
205
projectivized by setting the last homogeneous coordinate to one
206
and/or affine patches are chosen internally.
207
208
- ``connected`` -- boolean (default: ``True``). Whether the
209
triangulations should be connected to the regular triangulations
210
via bistellar flips. These are much easier to compute than all
211
triangulations.
212
213
- ``fine`` -- boolean (default: ``False``). Whether the
214
triangulations must be fine, that is, make use of all points of
215
the configuration.
216
217
- ``regular`` -- boolean or ``None`` (default: ``None``). Whether
218
the triangulations must be regular. A regular triangulation is
219
one that is induced by a piecewise-linear convex support
220
function. In other words, the shadows of the faces of a
221
polyhedron in one higher dimension.
222
223
* ``True``: Only regular triangulations.
224
225
* ``False``: Only non-regular triangulations.
226
227
* ``None`` (default): Both kinds of triangulation.
228
229
- ``star`` -- either ``None`` or a point. Whether the
230
triangulations must be star. A triangulation is star if all
231
maximal simplices contain a common point. The central point can
232
be specified by its index (an integer) in the given points or by
233
its coordinates (anything iterable.)
234
235
EXAMPLES::
236
237
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
238
sage: p
239
A point configuration in QQ^2 consisting of 5 points. The
240
triangulations of this point configuration are assumed to
241
be connected, not necessarily fine, not necessarily regular.
242
sage: p.triangulate() # a single triangulation
243
(<1,3,4>, <2,3,4>)
244
"""
245
246
247
# we cache the output of _have_TOPCOM() in this class variable
248
_have_TOPCOM_cached = None
249
250
# whether to use TOPCOM. Will be set to True or False during
251
# initialization. All implementations should check this boolean
252
# variable to decide whether to call TOPCOM or not
253
_use_TOPCOM = None
254
255
256
@classmethod
257
def _have_TOPCOM(cls):
258
r"""
259
Return whether TOPCOM is installed.
260
261
EXAMPLES::
262
263
sage: PointConfiguration._have_TOPCOM() # optional - TOPCOM
264
True
265
"""
266
if PointConfiguration._have_TOPCOM_cached != None:
267
return PointConfiguration._have_TOPCOM_cached
268
269
try:
270
out = PointConfiguration._TOPCOM_exec('points2placingtriang',
271
'[[0,1],[1,1]]', verbose=False).next()
272
PointConfiguration._have_TOPCOM_cached = True
273
assert out=='{{0,1}}',\
274
'TOPCOM ran but did not produce the correct output!'
275
except pexpect.ExceptionPexpect:
276
PointConfiguration._have_TOPCOM_cached = False
277
278
PointConfiguration.set_engine('auto')
279
return PointConfiguration._have_TOPCOM_cached
280
281
282
@staticmethod
283
def __classcall__(cls, points, projective=False, connected=True, fine=False, regular=None, star=None):
284
r"""
285
Normalize the constructor arguments to be unique keys.
286
287
EXAMPLES::
288
289
sage: pc1 = PointConfiguration([[1,2],[2,3],[3,4]], connected=True)
290
sage: pc2 = PointConfiguration(((1,2),(2,3),(3,4)), regular=None)
291
sage: pc1 is pc2 # indirect doctest
292
True
293
"""
294
if isinstance(points, PointConfiguration_base):
295
pc = points
296
points = tuple( p.projective() for p in points )
297
projective = True
298
defined_affine = pc.is_affine()
299
elif projective:
300
points = tuple( tuple(p) for p in points )
301
defined_affine = False
302
else:
303
points = tuple( tuple(p)+(1,) for p in points )
304
defined_affine = True
305
if star!=None and star not in ZZ:
306
star_point = tuple(star)
307
if len(star_point)<len(points[0]):
308
star_point = tuple(star)+(1,)
309
star = points.index(star_point)
310
return super(PointConfiguration, cls)\
311
.__classcall__(cls, points, connected, fine, regular, star, defined_affine)
312
313
314
def __init__(self, points, connected, fine, regular, star, defined_affine):
315
"""
316
Initialize a :class:`PointConfiguration` object.
317
318
EXAMPLES::
319
320
sage: p = PointConfiguration([[0,4],[2,3],[3,2],[4,0],[3,-2],[2,-3],[0,-4],[-2,-3],[-3,-2],[-4,0],[-3,2],[-2,3]])
321
sage: len(p.triangulations_list()) # long time (26s on sage.math, 2012)
322
16796
323
324
TESTS::
325
326
sage: TestSuite(p).run()
327
"""
328
# first, test if we have TOPCOM and set up class variables accordingly
329
PointConfiguration._have_TOPCOM()
330
331
assert connected in [True, False], 'Unknown value: connected='+str(connected)
332
self._connected = connected
333
if connected!=True and not PointConfiguration._have_TOPCOM():
334
raise ValueError, 'You must install TOPCOM to find non-connected triangulations.'
335
336
assert fine in [True, False], 'Unknown value: fine='+str(fine)
337
self._fine = fine
338
339
assert regular in [True, False, None], 'Unknown value: regular='+str(regular)
340
self._regular = regular
341
if regular!=None and not PointConfiguration._have_TOPCOM():
342
raise ValueError, 'You must install TOPCOM to test for regularity.'
343
344
assert star==None or star in ZZ, 'Unknown value: fine='+str(star)
345
self._star = star
346
347
PointConfiguration_base.__init__(self, points, defined_affine)
348
349
350
@classmethod
351
def set_engine(cls, engine='auto'):
352
r"""
353
Set the engine used to compute triangulations.
354
355
INPUT:
356
357
- ``engine`` -- either 'auto' (default), 'internal', or
358
'TOPCOM'. The latter two instruct this package to always use
359
its own triangulation algorithms or TOPCOM's algorithms,
360
respectively. By default ('auto'), TOPCOM is used if it is
361
available and internal routines otherwise.
362
363
EXAMPLES::
364
365
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
366
sage: p.set_engine('internal') # to make doctests independent of TOPCOM
367
sage: p.triangulate()
368
(<1,3,4>, <2,3,4>)
369
sage: p.set_engine('TOPCOM') # optional - TOPCOM
370
sage: p.triangulate() # optional - TOPCOM
371
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
372
sage: p.set_engine('internal') # optional - TOPCOM
373
"""
374
if engine not in ['auto', 'TOPCOM', 'internal']:
375
raise ValueError, 'Unknown value for "engine": '+str(engine)
376
377
have_TOPCOM = PointConfiguration._have_TOPCOM()
378
PointConfiguration._use_TOPCOM = \
379
(engine=='TOPCOM') or (engine=='auto' and have_TOPCOM)
380
381
382
def star_center(self):
383
r"""
384
Return the center used for star triangulations.
385
386
.. seealso:: :meth:`restrict_to_star_triangulations`.
387
388
OUTPUT:
389
390
A :class:`~sage.geometry.triangulation.base.Point` if a
391
distinguished star central point has been fixed.
392
``ValueError`` exception is raised otherwise.
393
394
EXAMPLES::
395
396
sage: pc = PointConfiguration([(1,0),(-1,0),(0,1),(0,2)], star=(0,1)); pc
397
A point configuration in QQ^2 consisting of 4 points. The
398
triangulations of this point configuration are assumed to be
399
connected, not necessarily fine, not necessarily regular, and
400
star with center P(0, 1).
401
sage: pc.star_center()
402
P(0, 1)
403
404
sage: pc_nostar = pc.restrict_to_star_triangulations(None)
405
sage: pc_nostar
406
A point configuration in QQ^2 consisting of 4 points. The
407
triangulations of this point configuration are assumed to be
408
connected, not necessarily fine, not necessarily regular.
409
sage: pc_nostar.star_center()
410
Traceback (most recent call last):
411
...
412
ValueError: The point configuration has no star center defined.
413
"""
414
if self._star is None:
415
raise ValueError, 'The point configuration has no star center defined.'
416
else:
417
return self[self._star]
418
419
420
def __reduce__(self):
421
r"""
422
Override __reduce__ to correctly pickle/unpickle.
423
424
TESTS::
425
426
sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])
427
sage: loads(p.dumps()) is p
428
True
429
430
sage: p = PointConfiguration([[0, 1, 1], [0, 0, 1], [1, 0, 1], [1,1, 1]], projective=True)
431
sage: loads(p.dumps()) is p
432
True
433
"""
434
if self.is_affine():
435
points = tuple( p.affine() for p in self )
436
return (PointConfiguration, (points, False,
437
self._connected, self._fine, self._regular, self._star))
438
else:
439
points = tuple( p.projective() for p in self )
440
return (PointConfiguration, (points, True,
441
self._connected, self._fine, self._regular, self._star))
442
443
444
def an_element(self):
445
"""
446
Synonymous for :meth:`triangulate`.
447
448
TESTS::
449
450
sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])
451
sage: p.an_element()
452
(<0,1,3>, <1,2,3>)
453
"""
454
return self.triangulate()
455
456
457
def _element_constructor_(self, e):
458
"""
459
Construct a triangulation.
460
461
TESTS::
462
463
sage: p = PointConfiguration([[0, 1], [0, 0], [1, 0], [1,1]])
464
sage: p._element_constructor_([ (0,1,2), (2,3,0) ])
465
(<0,1,2>, <0,2,3>)
466
"""
467
return self.element_class(e, parent=self)
468
469
470
Element = Triangulation
471
472
473
def __iter__(self):
474
"""
475
Iterate through the points of the point configuration.
476
477
OUTPUT:
478
479
Returns projective coordinates of the points. See also the
480
``PointConfiguration.points()`` method, which returns affine
481
coordinates.
482
483
EXAMPLES::
484
485
sage: p = PointConfiguration([[1,1], [2,2], [3,3]]);
486
sage: list(p) # indirect doctest
487
[P(1, 1), P(2, 2), P(3, 3)]
488
sage: [ p[i] for i in range(0,p.n_points()) ]
489
[P(1, 1), P(2, 2), P(3, 3)]
490
sage: list(p.points())
491
[P(1, 1), P(2, 2), P(3, 3)]
492
sage: [ p.point(i) for i in range(0,p.n_points()) ]
493
[P(1, 1), P(2, 2), P(3, 3)]
494
"""
495
for p in self.points():
496
yield p
497
498
499
def _repr_(self):
500
r"""
501
Return a string representation.
502
503
TESTS::
504
505
sage: p = PointConfiguration([[1,1,1],[-1,1,1],[1,-1,1],[-1,-1,1],[1,1,-1],
506
... [-1,1,-1],[1,-1,-1],[-1,-1,-1],[0,0,0]])
507
sage: p._repr_()
508
'A point configuration in QQ^3 consisting of 9 points. The triangulations
509
of this point configuration are assumed to be connected, not necessarily
510
fine, not necessarily regular.'
511
512
sage: PointConfiguration([[1, 1, 1], [-1, 1, 1], [1, -1, 1], [-1, -1, 1]], projective=True)
513
A point configuration in P(QQ^3) consisting of 4 points. The triangulations
514
of this point configuration are assumed to be connected, not necessarily
515
fine, not necessarily regular.
516
"""
517
s = 'A point configuration'
518
if self.is_affine():
519
s += ' in QQ^'+str(self.ambient_dim())
520
else:
521
s += ' in P(QQ^'+str(self.ambient_dim()+1)+')'
522
if len(self)==1:
523
s += ' consisting of '+str(len(self))+' point. '
524
else:
525
s += ' consisting of '+str(len(self))+' points. '
526
527
s += 'The triangulations of this point configuration are assumed to be'
528
529
if self._connected:
530
s += ' connected,'
531
else:
532
s += ' not necessarily connected,'
533
534
if self._fine:
535
s += ' fine,'
536
else:
537
s += ' not necessarily fine,'
538
539
if self._regular==True:
540
s += ' regular'
541
elif self._regular==False:
542
s += ' irregular'
543
else:
544
s += ' not necessarily regular'
545
546
if self._star==None:
547
s += '.'
548
else:
549
s += ', and star with center '+str(self.star_center())+'.'
550
return s
551
552
553
def _TOPCOM_points(self):
554
r"""
555
Convert the list of input points to a string that can be fed
556
to TOPCOM.
557
558
TESTS::
559
560
sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])
561
sage: p._TOPCOM_points()
562
'[[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]'
563
"""
564
s = '['
565
s += ','.join([
566
'[' + ','.join(map(str,p.reduced_projective())) + ']'
567
for p in self ])
568
s += ']'
569
return s
570
571
572
@classmethod
573
def _TOPCOM_exec(cls, executable, input_string, verbose=True):
574
r"""
575
Run TOPCOM.
576
577
INPUT:
578
579
- ``executable`` -- string. The name of the executable.
580
581
- ``input_string`` -- string. Will be piped into the running
582
executable's stdin.
583
584
- ``verbose`` -- boolean. Whether to print out the TOPCOM
585
interaction.
586
587
TESTS::
588
589
sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])
590
sage: out = p._TOPCOM_exec('points2placingtriang', '[[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]', verbose=True)
591
sage: list(out) # optional - TOPCOM
592
#### TOPCOM input ####
593
# points2placingtriang
594
# [[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]
595
#### TOPCOM output ####
596
# {{0,1,2,4},{1,2,3,4}}
597
#######################
598
['{{0,1,2,4},{1,2,3,4}}']
599
"""
600
timeout = 600
601
proc = pexpect.spawn(executable, timeout=timeout)
602
proc.expect('Evaluating Commandline Options \.\.\.')
603
proc.expect('\.\.\. done\.')
604
proc.setecho(0)
605
assert proc.readline().strip() == ''
606
607
if verbose:
608
print "#### TOPCOM input ####"
609
print "# " + executable
610
print "# " + input_string
611
sys.stdout.flush()
612
613
proc.send(input_string)
614
proc.send('X\nX\n')
615
616
if verbose:
617
print "#### TOPCOM output ####"
618
sys.stdout.flush()
619
620
while True:
621
try:
622
line = proc.readline().strip()
623
except pexpect.TIMEOUT:
624
if verbose:
625
print '# Still runnnig '+str(executable)
626
continue
627
if len(line)==0: # EOF
628
break;
629
if verbose:
630
print "# " + line
631
sys.stdout.flush()
632
633
try:
634
yield line.strip()
635
except GeneratorExit:
636
proc.close(force=True)
637
raise StopIteration
638
639
if verbose:
640
print "#######################"
641
sys.stdout.flush()
642
643
644
def _TOPCOM_communicate(self, executable, verbose=True):
645
r"""
646
Execute TOPCOM and parse the output into a
647
:class:`~sage.geometry.triangulation.element.Triangulation`.
648
649
TESTS::
650
651
sage: p = PointConfiguration([[1,1,1], [-1,1,1], [1,-1,1], [-1,-1,1], [1,1,-1]])
652
sage: out = p._TOPCOM_communicate('points2placingtriang', verbose=True)
653
sage: list(out) # optional - TOPCOM
654
#### TOPCOM input ####
655
# points2placingtriang
656
# [[0,0,0,1],[-2,0,0,1],[0,-2,0,1],[-2,-2,0,1],[0,0,-2,1]]
657
#### TOPCOM output ####
658
# {{0,1,2,4},{1,2,3,4}}
659
#######################
660
[(<0,1,2,4>, <1,2,3,4>)]
661
"""
662
for line in self._TOPCOM_exec(executable,
663
self._TOPCOM_points(), verbose):
664
triangulation = line[ line.find('{{')+2 : line.rfind('}}') ]
665
triangulation = triangulation.split('},{')
666
triangulation = [ [ QQ(t) for t in triangle.split(',') ]
667
for triangle in triangulation ]
668
669
if self._star!=None:
670
o = self._star
671
if not all( t.count(o)>0 for t in triangulation):
672
continue
673
674
yield self(triangulation)
675
676
677
def _TOPCOM_triangulations(self, verbose=True):
678
r"""
679
Returns all triangulations satisfying the restrictions imposed.
680
681
EXAMPLES::
682
683
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
684
sage: iter = p._TOPCOM_triangulations(verbose=True)
685
sage: iter.next() # optional - TOPCOM
686
#### TOPCOM input ####
687
# points2triangs
688
# [[0,0,1],[0,1,1],[1,0,1],[1,1,1],[-1,-1,1]]
689
#### TOPCOM output ####
690
# T[1]:=[5,3:{{0,1,2},{1,2,3},{0,2,4},{0,1,4}}];
691
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
692
"""
693
command = 'points2'
694
695
if not self._connected:
696
command += 'all'
697
698
if self._fine:
699
command += 'fine'
700
701
command += 'triangs'
702
703
if self._regular==True:
704
command += ' --regular'
705
if self._regular==False:
706
command += ' --nonregular'
707
708
for t in self._TOPCOM_communicate(command, verbose):
709
yield t
710
711
712
def _TOPCOM_triangulate(self, verbose=True):
713
r"""
714
Return one (in no particular order) triangulation subject
715
to all restrictions imposed previously.
716
717
INPUT:
718
719
- ``verbose`` -- boolean. Whether to print out the TOPCOM
720
interaction.
721
722
OUTPUT:
723
724
A :class:`~sage.geometry.triangulation.element.Triangulation`
725
satisfying all restrictions imposed. Raises a ``ValueError``
726
if no such triangulation exists.
727
728
EXAMPLES::
729
730
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
731
sage: p.set_engine('TOPCOM') # optional - TOPCOM
732
sage: p._TOPCOM_triangulate(verbose=False) # optional - TOPCOM
733
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
734
sage: list( p.triangulate() ) # optional - TOPCOM
735
[(0, 1, 2), (0, 1, 4), (0, 2, 4), (1, 2, 3)]
736
sage: p.set_engine('internal') # optional - TOPCOM
737
"""
738
assert self._regular!=False, \
739
'When asked for a single triangulation TOPCOM ' + \
740
'always returns a regular triangulation.'
741
742
command = "points2"
743
if self._fine:
744
command += "finetriang"
745
else:
746
command += "placingtriang"
747
748
return self._TOPCOM_communicate(command, verbose).next()
749
750
751
def restrict_to_regular_triangulations(self, regular=True):
752
"""
753
Restrict to regular triangulations.
754
755
NOTE:
756
757
Regularity testing requires the optional TOPCOM package.
758
759
INPUT:
760
761
- ``regular`` -- ``True``, ``False``, or ``None``. Whether to
762
restrict to regular triangulations, irregular
763
triangulations, or lift any restrictions on regularity.
764
765
OUTPUT:
766
767
A new :class:`PointConfiguration` with the same points, but
768
whose triangulations will all be regular as specified. See
769
:class:`PointConfiguration` for details.
770
771
EXAMPLES::
772
773
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
774
sage: p
775
A point configuration in QQ^2 consisting of 5 points. The
776
triangulations of this point configuration are assumed to
777
be connected, not necessarily fine, not necessarily regular.
778
sage: len(p.triangulations_list())
779
4
780
sage: p_regular = p.restrict_to_regular_triangulations() # optional - TOPCOM
781
sage: len(p_regular.triangulations_list()) # optional - TOPCOM
782
4
783
sage: p == p_regular.restrict_to_regular_triangulations(regular=None) # optional - TOPCOM
784
True
785
"""
786
return PointConfiguration(self,
787
connected=self._connected,
788
fine=self._fine,
789
regular=regular,
790
star=self._star)
791
792
793
def restrict_to_connected_triangulations(self, connected=True):
794
"""
795
Restrict to connected triangulations.
796
797
NOTE:
798
799
Finding non-connected triangulations requires the optional
800
TOPCOM package.
801
802
INPUT:
803
804
- ``connected`` -- boolean. Whether to restrict to
805
triangulations that are connected by bistellar flips to the
806
regular triangulations.
807
808
OUTPUT:
809
810
A new :class:`PointConfiguration` with the same points, but
811
whose triangulations will all be in the connected
812
component. See :class:`PointConfiguration` for details.
813
814
EXAMPLES::
815
816
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
817
sage: p
818
A point configuration in QQ^2 consisting of 5 points. The
819
triangulations of this point configuration are assumed to
820
be connected, not necessarily fine, not necessarily regular.
821
sage: len(p.triangulations_list())
822
4
823
sage: p_all = p.restrict_to_connected_triangulations(connected=False) # optional - TOPCOM
824
sage: len(p_all.triangulations_list()) # optional - TOPCOM
825
4
826
sage: p == p_all.restrict_to_connected_triangulations(connected=True) # optional - TOPCOM
827
True
828
"""
829
return PointConfiguration(self,
830
connected=connected,
831
fine=self._fine,
832
regular=self._regular,
833
star=self._star)
834
835
836
def restrict_to_fine_triangulations(self, fine=True):
837
"""
838
Restrict to fine triangulations.
839
840
INPUT:
841
842
- ``fine`` -- boolean. Whether to restrict to fine triangulations.
843
844
OUTPUT:
845
846
A new :class:`PointConfiguration` with the same points, but
847
whose triangulations will all be fine. See
848
:class:`PointConfiguration` for details.
849
850
EXAMPLES::
851
852
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
853
sage: p
854
A point configuration in QQ^2 consisting of 5 points. The
855
triangulations of this point configuration are assumed to
856
be connected, not necessarily fine, not necessarily regular.
857
sage: len(p.triangulations_list())
858
4
859
sage: p_fine = p.restrict_to_fine_triangulations()
860
sage: len(p.triangulations_list())
861
4
862
sage: p == p_fine.restrict_to_fine_triangulations(fine=False)
863
True
864
"""
865
return PointConfiguration(self,
866
connected=self._connected,
867
fine=fine,
868
regular=self._regular,
869
star=self._star)
870
871
872
def restrict_to_star_triangulations(self, star):
873
"""
874
Restrict to star triangulations with the given point as the
875
center.
876
877
INPUT:
878
879
- ``origin`` -- ``None`` or an integer or the coordinates of a
880
point. An integer denotes the index of the central point. If
881
``None`` is passed, any restriction on the starshape will be
882
removed.
883
884
OUTPUT:
885
886
A new :class:`PointConfiguration` with the same points, but
887
whose triangulations will all be star. See
888
:class:`PointConfiguration` for details.
889
890
EXAMPLES::
891
892
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
893
sage: len(list( p.triangulations() ))
894
4
895
sage: p_star = p.restrict_to_star_triangulations(0)
896
sage: p_star is p.restrict_to_star_triangulations((0,0))
897
True
898
sage: p_star.triangulations_list()
899
[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)]
900
sage: p_newstar = p_star.restrict_to_star_triangulations(1) # pick different origin
901
sage: p_newstar.triangulations_list()
902
[(<1,2,3>, <1,2,4>)]
903
sage: p == p_star.restrict_to_star_triangulations(star=None)
904
True
905
"""
906
return PointConfiguration(self,
907
connected=self._connected,
908
fine=self._fine,
909
regular=self._regular,
910
star=star)
911
912
913
def triangulations(self, verbose=False):
914
r"""
915
Returns all triangulations.
916
917
- ``verbose`` -- boolean (default: ``False``). Whether to
918
print out the TOPCOM interaction, if any.
919
920
OUTPUT:
921
922
A generator for the triangulations satisfying all the
923
restrictions imposed. Each triangulation is returned as a
924
:class:`~sage.geometry.triangulation.element.Triangulation` object.
925
926
EXAMPLES::
927
928
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
929
sage: iter = p.triangulations()
930
sage: iter.next()
931
(<1,3,4>, <2,3,4>)
932
sage: iter.next()
933
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)
934
sage: iter.next()
935
(<1,2,3>, <1,2,4>)
936
sage: iter.next()
937
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
938
sage: p.triangulations_list()
939
[(<1,3,4>, <2,3,4>),
940
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),
941
(<1,2,3>, <1,2,4>),
942
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]
943
sage: p_fine = p.restrict_to_fine_triangulations()
944
sage: p_fine.triangulations_list()
945
[(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),
946
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)]
947
948
Note that we explicitly asked the internal algorithm to
949
compute the triangulations. Using TOPCOM, we obtain the same
950
triangulations but in a different order::
951
952
sage: p.set_engine('TOPCOM') # optional - TOPCOM
953
sage: iter = p.triangulations() # optional - TOPCOM
954
sage: iter.next() # optional - TOPCOM
955
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
956
sage: iter.next() # optional - TOPCOM
957
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)
958
sage: iter.next() # optional - TOPCOM
959
(<1,2,3>, <1,2,4>)
960
sage: iter.next() # optional - TOPCOM
961
(<1,3,4>, <2,3,4>)
962
sage: p.triangulations_list() # optional - TOPCOM
963
[(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>),
964
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>),
965
(<1,2,3>, <1,2,4>),
966
(<1,3,4>, <2,3,4>)]
967
sage: p_fine = p.restrict_to_fine_triangulations() # optional - TOPCOM
968
sage: p_fine.set_engine('TOPCOM') # optional - TOPCOM
969
sage: p_fine.triangulations_list() # optional - TOPCOM
970
[(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>),
971
(<0,1,3>, <0,1,4>, <0,2,3>, <0,2,4>)]
972
sage: p.set_engine('internal') # optional - TOPCOM
973
"""
974
if self._use_TOPCOM:
975
for triangulation in self._TOPCOM_triangulations(verbose):
976
yield triangulation
977
else:
978
if (self._connected!=True):
979
raise ValueError, 'Need TOPCOM to find disconnected triangulations.'
980
if (self._regular!=None):
981
raise ValueError, 'Need TOPCOM to test for regularity.'
982
ci = ConnectedTriangulationsIterator(self, star=self._star, fine=self._fine)
983
for encoded_triangulation in ci:
984
yield self(encoded_triangulation)
985
986
987
def triangulations_list(self, verbose=False):
988
r"""
989
Return all triangulations.
990
991
INPUT:
992
993
- ``verbose`` -- boolean. Whether to print out the TOPCOM
994
interaction, if any.
995
996
OUTPUT:
997
998
A list of triangulations (see
999
:class:`~sage.geometry.triangulation.element.Triangulation`)
1000
satisfying all restrictions imposed previously.
1001
1002
EXAMPLES::
1003
1004
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1]])
1005
sage: p.triangulations_list()
1006
[(<0,1,2>, <1,2,3>), (<0,1,3>, <0,2,3>)]
1007
sage: map(list, p.triangulations_list() )
1008
[[(0, 1, 2), (1, 2, 3)], [(0, 1, 3), (0, 2, 3)]]
1009
sage: p.set_engine('TOPCOM') # optional - TOPCOM
1010
sage: p.triangulations_list() # optional - TOPCOM
1011
[(<0,1,2>, <1,2,3>), (<0,1,3>, <0,2,3>)]
1012
sage: p.set_engine('internal') # optional - TOPCOM
1013
"""
1014
return list(self.triangulations(verbose))
1015
1016
1017
def triangulate(self, verbose=False):
1018
r"""
1019
Return one (in no particular order) triangulation.
1020
1021
INPUT:
1022
1023
- ``verbose`` -- boolean. Whether to print out the TOPCOM
1024
interaction, if any.
1025
1026
OUTPUT:
1027
1028
A :class:`~sage.geometry.triangulation.element.Triangulation`
1029
satisfying all restrictions imposed. Raises a ``ValueError``
1030
if no such triangulation exists.
1031
1032
EXAMPLES::
1033
1034
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
1035
sage: p.triangulate()
1036
(<1,3,4>, <2,3,4>)
1037
sage: list( p.triangulate() )
1038
[(1, 3, 4), (2, 3, 4)]
1039
1040
Using TOPCOM yields a different, but equally good, triangulation::
1041
1042
sage: p.set_engine('TOPCOM') # optional - TOPCOM
1043
sage: p.triangulate() # optional - TOPCOM
1044
(<0,1,2>, <0,1,4>, <0,2,4>, <1,2,3>)
1045
sage: list( p.triangulate() ) # optional - TOPCOM
1046
[(0, 1, 2), (0, 1, 4), (0, 2, 4), (1, 2, 3)]
1047
sage: p.set_engine('internal') # optional - TOPCOM
1048
"""
1049
if self._use_TOPCOM and self._regular!=False:
1050
try:
1051
return self._TOPCOM_triangulate(verbose)
1052
except StopIteration:
1053
# either topcom did not return a triangulation or we filtered it out
1054
pass
1055
1056
if self._connected and not self._fine and self._regular!=False and self._star==None:
1057
return self.placing_triangulation()
1058
1059
try:
1060
return self.triangulations(verbose).next()
1061
except StopIteration:
1062
# there is no triangulation
1063
pass
1064
raise ValueError, 'No triangulation with the required properties.'
1065
1066
1067
def convex_hull(self):
1068
"""
1069
Return the convex hull of the point configuration.
1070
1071
EXAMPLES::
1072
1073
sage: p = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
1074
sage: p.convex_hull()
1075
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices
1076
"""
1077
try:
1078
return self._polyhedron
1079
except AttributeError:
1080
pass
1081
1082
from sage.geometry.polyhedron.constructor import Polyhedron
1083
pts = [ p.reduced_affine() for p in self.points() ];
1084
self._polyhedron = Polyhedron(vertices=pts);
1085
return self._polyhedron
1086
1087
1088
def restricted_automorphism_group(self):
1089
r"""
1090
Return the restricted automorphism group.
1091
1092
First, let the linear automorphism group be the subgroup of
1093
the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d`
1094
preserving the `d`-dimensional point configuration. The
1095
Euclidean group acts in the usual way `\vec{x}\mapsto
1096
A\vec{x}+b` on the ambient space.
1097
1098
The restricted automorphism group is the subgroup of the
1099
linear automorphism group generated by permutations of
1100
points. See [BSS]_ for more details and a description of the
1101
algorithm.
1102
1103
OUTPUT:
1104
1105
A
1106
:class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`
1107
that is isomorphic to the restricted automorphism group is
1108
returned.
1109
1110
Note that in Sage, permutation groups always act on positive
1111
integers while lists etc. are indexed by nonnegative
1112
integers. The indexing of the permutation group is chosen to
1113
be shifted by ``+1``. That is, the transposition ``(i,j)`` in
1114
the permutation group corresponds to exchange of ``self[i-1]``
1115
and ``self[j-1]``.
1116
1117
EXAMPLES::
1118
1119
sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])
1120
sage: pyramid.restricted_automorphism_group()
1121
Permutation Group with generators [(3,5), (2,3)(4,5), (2,4)]
1122
sage: DihedralGroup(4).is_isomorphic(_)
1123
True
1124
1125
The square with an off-center point in the middle. Note thath
1126
the middle point breaks the restricted automorphism group
1127
`D_4` of the convex hull::
1128
1129
sage: square = PointConfiguration([(3/4,3/4),(1,1),(1,-1),(-1,-1),(-1,1)])
1130
sage: square.restricted_automorphism_group()
1131
Permutation Group with generators [(3,5)]
1132
sage: DihedralGroup(1).is_isomorphic(_)
1133
True
1134
"""
1135
if '_restricted_automorphism_group' in self.__dict__:
1136
return self._restricted_automorphism_group
1137
1138
v_list = [ vector(p.projective()) for p in self ]
1139
Qinv = sum( v.column() * v.row() for v in v_list ).inverse()
1140
1141
# construct the graph
1142
from sage.graphs.graph import Graph
1143
1144
# Was set to sparse = False, but there is a problem with Graph
1145
# backends. It should probably be set back to sparse = False as soon as
1146
# the backends are fixed.
1147
G = Graph(sparse=True)
1148
for i in range(0,len(v_list)):
1149
for j in range(i+1,len(v_list)):
1150
v_i = v_list[i]
1151
v_j = v_list[j]
1152
G.add_edge(i+1,j+1, v_i * Qinv * v_j)
1153
1154
group = G.automorphism_group(edge_labels=True)
1155
self._restricted_automorphism_group = group
1156
return group
1157
1158
1159
def face_codimension(self, point):
1160
r"""
1161
Return the smallest `d\in\mathbb{Z}` such that ``point`` is
1162
contained in the interior of a codimension-`d` face.
1163
1164
EXAMPLES::
1165
1166
sage: triangle = PointConfiguration([[0,0], [1,-1], [1,0], [1,1]]);
1167
sage: triangle.point(2)
1168
P(1, 0)
1169
sage: triangle.face_codimension(2)
1170
1
1171
sage: triangle.face_codimension( [1,0] )
1172
1
1173
1174
This also works for degenerate cases like the tip of the
1175
pyramid over a square (which saturates four inequalities)::
1176
1177
sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])
1178
sage: pyramid.face_codimension(0)
1179
3
1180
"""
1181
try:
1182
p = vector(self.point(point).reduced_affine())
1183
except TypeError:
1184
p = vector(point);
1185
1186
inequalities = []
1187
for ieq in self.convex_hull().inequality_generator():
1188
if (ieq.A()*p + ieq.b() == 0):
1189
inequalities += [ ieq.vector() ];
1190
return matrix(inequalities).rank();
1191
1192
1193
def face_interior(self, dim=None, codim=None):
1194
"""
1195
Return points by the codimension of the containing face in the convex hull.
1196
1197
EXAMPLES::
1198
1199
sage: triangle = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);
1200
sage: triangle.face_interior()
1201
((1,), (3,), (0, 2, 4))
1202
sage: triangle.face_interior(dim=0) # the vertices of the convex hull
1203
(0, 2, 4)
1204
sage: triangle.face_interior(codim=1) # interior of facets
1205
(3,)
1206
"""
1207
assert not (dim!=None and codim!=None), "You cannot specify both dim and codim."
1208
1209
if (dim!=None):
1210
return self.face_interior()[self.convex_hull().dim()-dim]
1211
if (codim!=None):
1212
return self.face_interior()[codim]
1213
1214
try:
1215
return self._face_interior
1216
except AttributeError:
1217
pass
1218
1219
d = [ self.face_codimension(i) for i in range(0,self.n_points()) ]
1220
1221
return tuple( tuple(filter( lambda i: d[i]==codim, range(0,self.n_points())) )
1222
for codim in range(0,self.dim()+1) )
1223
1224
1225
def exclude_points(self, point_idx_list):
1226
"""
1227
Return a new point configuration with the given points
1228
removed.
1229
1230
INPUT:
1231
1232
- ``point_idx_list`` -- a list of integers. The indices of
1233
points to exclude.
1234
1235
OUTPUT:
1236
1237
A new :class:`PointConfiguration` with the given points
1238
removed.
1239
1240
EXAMPLES::
1241
1242
sage: p = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);
1243
sage: list(p)
1244
[P(-1, 0), P(0, 0), P(1, -1), P(1, 0), P(1, 1)]
1245
sage: q = p.exclude_points([3])
1246
sage: list(q)
1247
[P(-1, 0), P(0, 0), P(1, -1), P(1, 1)]
1248
sage: p.exclude_points( p.face_interior(codim=1) ).points()
1249
(P(-1, 0), P(0, 0), P(1, -1), P(1, 1))
1250
"""
1251
points = [ self.point(i) for i in range(0,self.n_points())
1252
if not i in point_idx_list ]
1253
return PointConfiguration(points,
1254
projective=False,
1255
connected=self._connected,
1256
fine=self._fine,
1257
regular=self._regular,
1258
star=self._star)
1259
1260
1261
def volume(self, simplex=None):
1262
"""
1263
Find n! times the n-volume of a simplex of dimension n.
1264
1265
INPUT:
1266
1267
- ``simplex`` (optional argument) -- a simplex from a
1268
triangulation T specified as a list of point indices.
1269
1270
OUTPUT:
1271
1272
* If a simplex was passed as an argument: n!*(volume of ``simplex``).
1273
1274
* Without argument: n!*(the total volume of the convex hull).
1275
1276
EXAMPLES:
1277
1278
The volume of the standard simplex should always be 1::
1279
1280
sage: p = PointConfiguration([[0,0],[1,0],[0,1],[1,1]])
1281
sage: p.volume( [0,1,2] )
1282
1
1283
sage: simplex = p.triangulate()[0] # first simplex of triangulation
1284
sage: p.volume(simplex)
1285
1
1286
1287
The square can be triangulated into two minimal simplices, so
1288
in the "integral" normalization its volume equals two::
1289
1290
sage: p.volume()
1291
2
1292
1293
.. note::
1294
1295
We return n!*(metric volume of the simplex) to ensure that
1296
the volume is an integer. Essentially, this normalizes
1297
things so that the volume of the standard n-simplex is 1.
1298
See [GKZ]_ page 182.
1299
"""
1300
if (simplex==None):
1301
return sum([ self.volume(s) for s in self.triangulate() ])
1302
1303
#Form a matrix whose columns are the points of simplex
1304
#with the first point of simplex shifted to the origin.
1305
v = [ self.point(i).reduced_affine_vector() for i in simplex ]
1306
m = matrix([ v_i - v[0] for v_i in v[1:] ])
1307
return abs(m.det())
1308
1309
1310
def secondary_polytope(self):
1311
r"""
1312
Calculate the secondary polytope of the point configuration.
1313
1314
For a definition of the secondary polytope, see [GKZ]_ page 220
1315
Definition 1.6.
1316
1317
Note that if you restricted the admissible triangulations of
1318
the point configuration then the output will be the
1319
corresponding face of the whole secondary polytope.
1320
1321
OUTPUT:
1322
1323
The secondary polytope of the point configuration as an
1324
instance of
1325
:class:`~sage.geometry.polyhedron.base.Polyhedron_base`.
1326
1327
EXAMPLES::
1328
1329
sage: p = PointConfiguration([[0,0],[1,0],[2,1],[1,2],[0,1]])
1330
sage: poly = p.secondary_polytope()
1331
sage: poly.vertices_matrix()
1332
[1 1 3 3 5]
1333
[3 5 1 4 1]
1334
[4 2 5 2 4]
1335
[2 4 2 5 4]
1336
[5 3 4 1 1]
1337
sage: poly.Vrepresentation()
1338
(A vertex at (1, 3, 4, 2, 5),
1339
A vertex at (1, 5, 2, 4, 3),
1340
A vertex at (3, 1, 5, 2, 4),
1341
A vertex at (3, 4, 2, 5, 1),
1342
A vertex at (5, 1, 4, 4, 1))
1343
sage: poly.Hrepresentation()
1344
(An equation (0, 0, 1, 2, 1) x - 13 == 0,
1345
An equation (1, 0, 0, 2, 2) x - 15 == 0,
1346
An equation (0, 1, 0, -3, -2) x + 13 == 0,
1347
An inequality (0, 0, 0, -1, -1) x + 7 >= 0,
1348
An inequality (0, 0, 0, 1, 0) x - 2 >= 0,
1349
An inequality (0, 0, 0, -2, -1) x + 11 >= 0,
1350
An inequality (0, 0, 0, 0, 1) x - 1 >= 0,
1351
An inequality (0, 0, 0, 3, 2) x - 14 >= 0)
1352
"""
1353
from sage.geometry.polyhedron.constructor import Polyhedron
1354
#TODO: once restriction to regular triangulations is fixed,
1355
#change the next line to only take the regular triangulations,
1356
#since they are the vertices of the secondary polytope anyway.
1357
l = self.triangulations_list()
1358
return Polyhedron(vertices = [x.gkz_phi() for x in l])
1359
1360
1361
def circuits_support(self):
1362
r"""
1363
A generator for the supports of the circuits of the point configuration.
1364
1365
See :meth:`circuits` for details.
1366
1367
OUTPUT:
1368
1369
A generator for the supports `C_-\cup C_+` (returned as a
1370
Python tuple) for all circuits of the point configuration.
1371
1372
EXAMPLES::
1373
1374
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1375
sage: list( p.circuits_support() )
1376
[(0, 3, 4), (0, 1, 2), (1, 2, 3, 4)]
1377
"""
1378
n = len(self)
1379
U = [ self[i].reduced_projective() for i in range(0,n) ]
1380
1381
# the index set of U
1382
I = set(range(0,n))
1383
# The (indices of) known independent elements of U
1384
independent_k = [ (i,) for i in range(0,n) ]
1385
supports_k = []
1386
1387
supports = () # supports of circuits
1388
for k in range(2, self.dim()+3):
1389
1390
# possibly linear dependent subsets
1391
supports_knext = set()
1392
possible_dependency = set()
1393
for indep in independent_k:
1394
indep_plus_one = [ tuple(sorted(indep+(i,))) for i in (I-set(indep)) ]
1395
possible_dependency.update(indep_plus_one)
1396
for supp in supports_k:
1397
supp_plus_one = [ tuple(sorted(supp+(i,))) for i in (I-set(supp)) ]
1398
possible_dependency.difference_update(supp_plus_one)
1399
supports_knext.update(supp_plus_one)
1400
1401
# remember supports and independents for the next k-iteration
1402
supports_k = list(supports_knext)
1403
independent_k = []
1404
for idx in possible_dependency:
1405
rk = matrix([ U[i] for i in idx ]).rank()
1406
if rk==k:
1407
independent_k.append(idx)
1408
else:
1409
supports_k.append(idx)
1410
yield idx
1411
assert independent_k==[] # there are no independent (self.dim()+3)-tuples
1412
1413
1414
def circuits(self):
1415
r"""
1416
Return the circuits of the point configuration.
1417
1418
Roughly, a circuit is a minimal linearly dependent subset of
1419
the points. That is, a circuit is a partition
1420
1421
.. MATH::
1422
1423
\{ 0, 1, \dots, n-1 \} = C_+ \cup C_0 \cup C_-
1424
1425
such that there is an (unique up to an overall normalization) affine
1426
relation
1427
1428
.. MATH::
1429
1430
\sum_{i\in C_+} \alpha_i \vec{p}_i =
1431
\sum_{j\in C_-} \alpha_j \vec{p}_j
1432
1433
with all positive (or all negative) coefficients, where
1434
`\vec{p}_i=(p_1,\dots,p_k,1)` are the projective coordinates
1435
of the `i`-th point.
1436
1437
OUTPUT:
1438
1439
The list of (unsigned) circuits as triples `(C_+, C_0,
1440
C_-)`. The swapped circuit `(C_-, C_0, C_+)` is not returned
1441
separately.
1442
1443
EXAMPLES::
1444
1445
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1446
sage: p.circuits()
1447
(((0,), (1, 2), (3, 4)), ((0,), (3, 4), (1, 2)), ((1, 2), (0,), (3, 4)))
1448
1449
1450
TESTS::
1451
1452
sage: U=matrix([
1453
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1454
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1455
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1456
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1457
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1458
... ])
1459
sage: p = PointConfiguration(U.columns())
1460
sage: len( p.circuits() ) # long time
1461
218
1462
"""
1463
try:
1464
return self._circuits
1465
except AttributeError:
1466
pass
1467
1468
n = len(self)
1469
U = [ self[i].reduced_projective() for i in range(0,n) ]
1470
1471
Circuits = ()
1472
for support in self.circuits_support():
1473
m = matrix([ U[i] for i in support ]).transpose()
1474
ker = m.right_kernel().basis()[0]
1475
assert len(ker)==len(support)
1476
Cplus = [ support[i] for i in range(0,len(support)) if ker[i]>0 ]
1477
Cminus = [ support[i] for i in range(0,len(support)) if ker[i]<0 ]
1478
Czero = set( range(0,n) ).difference(support)
1479
Circuits += ( (tuple(Cplus), tuple(Czero), tuple(Cminus)), )
1480
self._circuits = Circuits
1481
return Circuits
1482
1483
1484
def positive_circuits(self, *negative):
1485
r"""
1486
Returns the positive part of circuits with fixed negative part.
1487
1488
A circuit is a pair `(C_+, C_-)`, each consisting of a subset
1489
(actually, an ordered tuple) of point indices.
1490
1491
INPUT:
1492
1493
- ``*negative`` -- integer. The indices of points.
1494
1495
OUTPUT:
1496
1497
A tuple of all circuits with `C_-` = ``negative``.
1498
1499
EXAMPLE::
1500
1501
sage: p = PointConfiguration([(1,0,0),(0,1,0),(0,0,1),(-2,0,-1),(-2,-1,0),(-3,-1,-1),(1,1,1),(-1,0,0),(0,0,0)])
1502
sage: p.positive_circuits(8)
1503
((0, 7), (0, 1, 4), (0, 2, 3), (0, 5, 6), (0, 1, 2, 5), (0, 3, 4, 6))
1504
sage: p.positive_circuits(0,5,6)
1505
((8,),)
1506
"""
1507
pos = ()
1508
negative = tuple(sorted(negative))
1509
for circuit in self.circuits():
1510
Cpos = circuit[0]
1511
Cneg = circuit[2]
1512
if Cpos == negative:
1513
pos += ( Cneg, )
1514
elif Cneg == negative:
1515
pos += ( Cpos, )
1516
return pos
1517
1518
1519
def bistellar_flips(self):
1520
r"""
1521
Return the bistellar flips.
1522
1523
OUTPUT:
1524
1525
The bistellar flips as a tuple. Each flip is a pair
1526
`(T_+,T_-)` where `T_+` and `T_-` are partial triangulations
1527
of the point configuration.
1528
1529
EXAMPLES::
1530
1531
sage: pc = PointConfiguration([(0,0),(1,0),(0,1),(1,1)])
1532
sage: pc.bistellar_flips()
1533
(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)
1534
sage: Tpos, Tneg = pc.bistellar_flips()[0]
1535
sage: Tpos.plot(axes=False)
1536
sage: Tneg.plot(axes=False)
1537
1538
The 3d analog::
1539
1540
sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(-1,0,0),(1,1,1)])
1541
sage: pc.bistellar_flips()
1542
(((<0,1,2,3>, <0,1,2,4>), (<0,1,3,4>, <0,2,3,4>, <1,2,3,4>)),)
1543
1544
A 2d flip on the base of the pyramid over a square::
1545
1546
sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(0,2,2),(1,1,1)])
1547
sage: pc.bistellar_flips()
1548
(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)
1549
sage: Tpos, Tneg = pc.bistellar_flips()[0]
1550
sage: Tpos.plot(axes=False)
1551
"""
1552
flips = []
1553
for C in self.circuits():
1554
Cpos = list(C[0])
1555
Cneg = list(C[2])
1556
support = sorted(Cpos+Cneg)
1557
Tpos = [ Cpos+Cneg[0:i]+Cneg[i+1:len(Cneg)] for i in range(0,len(Cneg)) ]
1558
Tneg = [ Cneg+Cpos[0:i]+Cpos[i+1:len(Cpos)] for i in range(0,len(Cpos)) ]
1559
flips.append( (self.element_class(Tpos, parent=self, check=False),
1560
self.element_class(Tneg, parent=self, check=False)) )
1561
return tuple(flips)
1562
1563
1564
def lexicographic_triangulation(self):
1565
r"""
1566
Return the lexicographic triangulation.
1567
1568
The algorithm was taken from [PUNTOS]_.
1569
1570
EXAMPLES::
1571
1572
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1573
sage: p.lexicographic_triangulation()
1574
(<1,3,4>, <2,3,4>)
1575
1576
TESTS::
1577
1578
sage: U=matrix([
1579
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1580
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1581
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1582
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1583
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1584
... ])
1585
sage: pc = PointConfiguration(U.columns())
1586
sage: pc.lexicographic_triangulation()
1587
(<1,3,4,7,10,13>, <1,3,4,8,10,13>, <1,3,6,7,10,13>, <1,3,6,8,10,13>,
1588
<1,4,6,7,10,13>, <1,4,6,8,10,13>, <2,3,4,6,7,12>, <2,3,4,7,12,13>,
1589
<2,3,6,7,12,13>, <2,4,6,7,12,13>, <3,4,5,6,9,12>, <3,4,5,8,9,12>,
1590
<3,4,6,7,11,12>, <3,4,6,9,11,12>, <3,4,7,10,11,13>, <3,4,7,11,12,13>,
1591
<3,4,8,9,10,12>, <3,4,8,10,12,13>, <3,4,9,10,11,12>, <3,4,10,11,12,13>,
1592
<3,5,6,8,9,12>, <3,6,7,10,11,13>, <3,6,7,11,12,13>, <3,6,8,9,10,12>,
1593
<3,6,8,10,12,13>, <3,6,9,10,11,12>, <3,6,10,11,12,13>, <4,5,6,8,9,12>,
1594
<4,6,7,10,11,13>, <4,6,7,11,12,13>, <4,6,8,9,10,12>, <4,6,8,10,12,13>,
1595
<4,6,9,10,11,12>, <4,6,10,11,12,13>)
1596
sage: len(_)
1597
34
1598
"""
1599
lex_supp = set()
1600
for circuit in self.circuits():
1601
Cplus = circuit[0]
1602
Cminus = circuit[2]
1603
s0 = min(Cplus + Cminus)
1604
if s0 in Cplus:
1605
lex_supp.add(Cplus)
1606
else:
1607
lex_supp.add(Cminus)
1608
1609
lex_supp = sorted(lex_supp, key=lambda x:-len(x))
1610
basepts = copy(lex_supp)
1611
for i in range(0,len(lex_supp)-1):
1612
for j in range(i+1,len(lex_supp)):
1613
if set(lex_supp[j]).issubset(set(lex_supp[i])):
1614
try:
1615
basepts.remove(lex_supp[i])
1616
except ValueError:
1617
pass
1618
1619
basepts = [ (len(b),)+b for b in basepts ] # decorate
1620
basepts = sorted(basepts) # sort
1621
basepts = [ b[1:] for b in basepts ] # undecorate
1622
1623
def make_cotriang(basepts):
1624
if len(basepts)==0:
1625
return [frozenset()]
1626
triangulation = set()
1627
for tail in make_cotriang(basepts[1:]):
1628
for head in basepts[0]:
1629
triangulation.update([ frozenset([head]).union(tail) ])
1630
1631
nonminimal = set()
1632
for rel in Combinations(triangulation,2):
1633
if rel[0].issubset(rel[1]): nonminimal.update([rel[1]])
1634
if rel[1].issubset(rel[0]): nonminimal.update([rel[0]])
1635
triangulation.difference_update(nonminimal)
1636
1637
triangulation = [ [len(t)]+sorted(t) for t in triangulation ] # decorate
1638
triangulation = sorted(triangulation) # sort
1639
triangulation = [ frozenset(t[1:]) for t in triangulation ] # undecorate
1640
1641
return triangulation
1642
1643
triangulation = make_cotriang(basepts)
1644
I = frozenset(range(0,self.n_points()))
1645
triangulation = [ tuple(I.difference(t)) for t in triangulation ]
1646
1647
return self(triangulation)
1648
1649
1650
@cached_method
1651
def distance_affine(self, x, y):
1652
r"""
1653
Returns the distance between two points.
1654
1655
The distance function used in this method is `d_{aff}(x,y)^2`,
1656
the square of the usual affine distance function
1657
1658
.. math::
1659
1660
d_{aff}(x,y) = |x-y|
1661
1662
INPUT:
1663
1664
- ``x``, ``y`` -- two points of the point configuration.
1665
1666
OUTPUT:
1667
1668
The metric distance-square `d_{aff}(x,y)^2`. Note that this
1669
distance lies in the same field as the entries of ``x``,
1670
``y``. That is, the distance of rational points will be
1671
rational and so on.
1672
1673
EXAMPLES::
1674
1675
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1676
sage: [ pc.distance_affine(pc.point(0), p) for p in pc.points() ]
1677
[0, 1, 5, 5, 1]
1678
"""
1679
self._assert_is_affine()
1680
d = 0
1681
for xi, yi in zip(x.projective(), y.projective()):
1682
d += (xi-yi)**2
1683
return d
1684
1685
1686
@cached_method
1687
def distance_FS(self, x, y):
1688
r"""
1689
Returns the distance between two points.
1690
1691
The distance function used in this method is `1-\cos
1692
d_{FS}(x,y)^2`, where `d_{FS}` is the Fubini-Study distance of
1693
projective points. Recall the Fubini-Studi distance function
1694
1695
.. math::
1696
1697
d_{FS}(x,y) = \arccos \sqrt{ \frac{(x\cdot y)^2}{|x|^2 |y|^2} }
1698
1699
INPUT:
1700
1701
- ``x``, ``y`` -- two points of the point configuration.
1702
1703
OUTPUT:
1704
1705
The distance `1-\cos d_{FS}(x,y)^2`. Note that this distance
1706
lies in the same field as the entries of ``x``, ``y``. That
1707
is, the distance of rational points will be rational and so
1708
on.
1709
1710
EXAMPLES::
1711
1712
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1713
sage: [ pc.distance_FS(pc.point(0), p) for p in pc.points() ]
1714
[0, 1/2, 5/6, 5/6, 1/2]
1715
"""
1716
x2 = y2 = xy = 0
1717
for xi, yi in zip(x.projective(), y.projective()):
1718
x2 += xi*xi
1719
y2 += yi*yi
1720
xy += xi*yi
1721
return 1-xy*xy/(x2*y2)
1722
1723
1724
@cached_method
1725
def distance(self, x, y):
1726
"""
1727
Returns the distance between two points.
1728
1729
INPUT:
1730
1731
- ``x``, ``y`` -- two points of the point configuration.
1732
1733
OUTPUT:
1734
1735
The distance between ``x`` and ``y``, measured either with
1736
:meth:`distance_affine` or :meth:`distance_FS` depending on
1737
whether the point configuration is defined by affine or
1738
projective points. These are related, but not equal to the
1739
usual flat and Fubini-Study distance.
1740
1741
EXAMPLES::
1742
1743
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1744
sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]
1745
[0, 1, 5, 5, 1]
1746
1747
sage: pc = PointConfiguration([(0,0,1),(1,0,1),(2,1,1),(1,2,1),(0,1,1)], projective=True)
1748
sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]
1749
[0, 1/2, 5/6, 5/6, 1/2]
1750
"""
1751
if self.is_affine():
1752
return self.distance_affine(x,y)
1753
else:
1754
return self.distance_FS(x,y)
1755
1756
1757
def farthest_point(self, points, among=None):
1758
"""
1759
Return the point with the most distance from ``points``.
1760
1761
INPUT:
1762
1763
- ``points`` -- a list of points.
1764
1765
- ``among`` -- a list of points or ``None`` (default). The set
1766
of points from which to pick the farthest one. By default,
1767
all points of the configuration are considered.
1768
1769
OUTPUT:
1770
1771
A :class:`~sage.geometry.triangulation.base.Point` with
1772
largest minimal distance from all given ``points``.
1773
1774
EXAMPLES::
1775
1776
sage: pc = PointConfiguration([(0,0),(1,0),(1,1),(0,1)])
1777
sage: pc.farthest_point([ pc.point(0) ])
1778
P(1, 1)
1779
"""
1780
if len(points)==0:
1781
return self.point(0)
1782
if among is None:
1783
among = self.points()
1784
p_max = None
1785
for p in among:
1786
if p in points:
1787
continue
1788
if p_max is None:
1789
p_max = p
1790
d_max = min(self.distance(p,q) for q in points)
1791
continue
1792
d = min(self.distance(p,q) for q in points)
1793
if d>d_max:
1794
p_max = p
1795
return p_max
1796
1797
1798
def contained_simplex(self, large=True, initial_point=None):
1799
"""
1800
Return a simplex contained in the point configuration.
1801
1802
INPUT:
1803
1804
- ``large`` -- boolean. Whether to attempt to return a large
1805
simplex.
1806
1807
- ``initial_point`` -- a
1808
:class:`~sage.geometry.triangulation.base.Point` or ``None``
1809
(default). A specific point to start with when picking the
1810
simplex vertices.
1811
1812
OUTPUT:
1813
1814
A tuple of points that span a simplex of dimension
1815
:meth:`dim`. If ``large==True``, the simplex is constructed by
1816
sucessively picking the farthest point. This will ensure that
1817
the simplex is not unneccessarily small, but will in general
1818
not return a maximal simplex.
1819
1820
EXAMPLES::
1821
1822
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])
1823
sage: pc.contained_simplex()
1824
(P(0, 1), P(2, 1), P(1, 0))
1825
sage: pc.contained_simplex(large=False)
1826
(P(0, 1), P(1, 1), P(1, 0))
1827
sage: pc.contained_simplex(initial_point=pc.point(0))
1828
(P(0, 0), P(1, 1), P(1, 0))
1829
1830
sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
1831
sage: pc.contained_simplex()
1832
(P(-1, -1), P(1, 1), P(0, 1))
1833
1834
TESTS::
1835
1836
sage: pc = PointConfiguration([[0,0],[0,1],[1,0]])
1837
sage: pc.contained_simplex()
1838
(P(1, 0), P(0, 1), P(0, 0))
1839
sage: pc = PointConfiguration([[0,0],[0,1]])
1840
sage: pc.contained_simplex()
1841
(P(0, 1), P(0, 0))
1842
sage: pc = PointConfiguration([[0,0]])
1843
sage: pc.contained_simplex()
1844
(P(0, 0),)
1845
sage: pc = PointConfiguration([])
1846
sage: pc.contained_simplex()
1847
()
1848
"""
1849
self._assert_is_affine()
1850
if self.n_points()==0:
1851
return tuple()
1852
points = list(self.points())
1853
if initial_point is None:
1854
origin = points.pop()
1855
else:
1856
origin = initial_point
1857
points.remove(origin)
1858
vertices = [origin]
1859
edges = []
1860
while len(vertices) <= self.dim():
1861
if large:
1862
p = self.farthest_point(vertices, points)
1863
points.remove(p)
1864
else:
1865
p = points.pop()
1866
edge = p.reduced_affine_vector()-origin.reduced_affine_vector()
1867
if len(edges)>0 and (ker * edge).is_zero():
1868
continue
1869
vertices.append(p)
1870
edges.append(edge)
1871
ker = matrix(edges).right_kernel().matrix()
1872
return tuple(vertices)
1873
1874
1875
def placing_triangulation(self, point_order=None):
1876
r"""
1877
Construct the placing (pushing) triangulation.
1878
1879
INPUT:
1880
1881
- ``point_order`` -- list of points or integers. The order in
1882
which the points are to be placed.
1883
1884
OUTPUT:
1885
1886
A :class:`~sage.geometry.triangulation.triangulation.Triangulation`.
1887
1888
EXAMPLES::
1889
1890
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1891
sage: pc.placing_triangulation()
1892
(<0,1,2>, <0,2,4>, <2,3,4>)
1893
1894
sage: U=matrix([
1895
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1896
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1897
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1898
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1899
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1900
... ])
1901
sage: p = PointConfiguration(U.columns())
1902
sage: triangulation = p.placing_triangulation(); triangulation
1903
(<0,2,3,4,6,7>, <0,2,3,4,6,12>, <0,2,3,4,7,13>, <0,2,3,4,12,13>,
1904
<0,2,3,6,7,13>, <0,2,3,6,12,13>, <0,2,4,6,7,13>, <0,2,4,6,12,13>,
1905
<0,3,4,6,7,12>, <0,3,4,7,12,13>, <0,3,6,7,12,13>, <0,4,6,7,12,13>,
1906
<1,3,4,5,6,12>, <1,3,4,6,11,12>, <1,3,4,7,11,13>, <1,3,4,11,12,13>,
1907
<1,3,6,7,11,13>, <1,3,6,11,12,13>, <1,4,6,7,11,13>, <1,4,6,11,12,13>,
1908
<3,4,6,7,11,12>, <3,4,7,11,12,13>, <3,6,7,11,12,13>, <4,6,7,11,12,13>)
1909
sage: sum(p.volume(t) for t in triangulation)
1910
42
1911
"""
1912
facet_normals = dict()
1913
def facets_of_simplex(simplex):
1914
"""
1915
Return the facets of the simplex and store the normals in facet_normals
1916
"""
1917
simplex = list(simplex)
1918
origin = simplex[0]
1919
rest = simplex[1:]
1920
span = matrix([ origin.reduced_affine_vector()-p.reduced_affine_vector()
1921
for p in rest ])
1922
# span.inverse() linearly transforms the simplex into the unit simplex
1923
normals = span.inverse().columns()
1924
facets = []
1925
# The facets incident to the chosen vertex "origin"
1926
for opposing_vertex, normal in zip(rest, normals):
1927
facet = frozenset([origin] + [ p for p in rest if p is not opposing_vertex ])
1928
facets.append(facet)
1929
normal.set_immutable()
1930
facet_normals[facet] = normal
1931
# The remaining facet that is not incident to "origin"
1932
facet = frozenset(rest)
1933
normal = -sum(normals)
1934
normal.set_immutable()
1935
facet_normals[facet] = normal
1936
facets.append(facet)
1937
return set(facets)
1938
1939
# input verification
1940
self._assert_is_affine()
1941
if point_order is None:
1942
point_order = list(self.points())
1943
elif isinstance(point_order[0], Point):
1944
point_order = list(point_order)
1945
assert all(p.point_configuration()==self for p in point_order)
1946
else:
1947
point_order = [ self.point(i) for i in point_order ]
1948
assert all(p in self.points() for p in point_order)
1949
1950
# construct the initial simplex
1951
simplices = [ frozenset(self.contained_simplex()) ]
1952
for s in simplices[0]:
1953
try:
1954
point_order.remove(s)
1955
except ValueError:
1956
pass
1957
facets = facets_of_simplex(simplices[0])
1958
1959
# successively place the remaining points
1960
for point in point_order:
1961
# identify visible facets
1962
visible_facets = []
1963
for facet in facets:
1964
origin = iter(facet).next()
1965
normal = facet_normals[facet]
1966
v = point.reduced_affine_vector() - origin.reduced_affine_vector()
1967
if v*normal>0:
1968
visible_facets.append(facet)
1969
1970
# construct simplices over each visible facet
1971
new_facets = set()
1972
for facet in visible_facets:
1973
simplex = frozenset(list(facet) + [point])
1974
# print 'simplex', simplex
1975
simplices.append(simplex)
1976
for facet in facets_of_simplex(simplex):
1977
if facet in visible_facets: continue
1978
if facet in new_facets:
1979
new_facets.remove(facet)
1980
continue
1981
new_facets.add(facet)
1982
facets.difference_update(visible_facets)
1983
facets.update(new_facets)
1984
1985
# construct the triangulation
1986
triangulation = [ [p.index() for p in simplex] for simplex in simplices ]
1987
return self(triangulation)
1988
1989
pushing_triangulation = placing_triangulation
1990
1991
@cached_method
1992
def Gale_transform(self, points=None):
1993
r"""
1994
Return the Gale transform of ``self``.
1995
1996
INPUT:
1997
1998
- ``points`` -- a tuple of points or point indices or ``None``
1999
(default). A subset of points for which to compute the Gale
2000
transform. By default, all points are used.
2001
2002
OUTPUT:
2003
2004
A matrix over :meth:`base_ring`.
2005
2006
EXAMPLES::
2007
2008
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])
2009
sage: pc.Gale_transform()
2010
[ 1 -1 0 1 -1]
2011
[ 0 0 1 -2 1]
2012
2013
sage: pc.Gale_transform((0,1,3,4))
2014
[ 1 -1 1 -1]
2015
2016
sage: points = (pc.point(0), pc.point(1), pc.point(3), pc.point(4))
2017
sage: pc.Gale_transform(points)
2018
[ 1 -1 1 -1]
2019
"""
2020
self._assert_is_affine()
2021
if points is None:
2022
points = self.points()
2023
else:
2024
try:
2025
points = [ self.point(ZZ(i)) for i in points ]
2026
except TypeError:
2027
pass
2028
m = matrix([ (1,) + p.affine() for p in points])
2029
return m.left_kernel().matrix()
2030
2031