Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/geometry/triangulation/point_configuration.py
4058 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 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 (30s 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 QQ^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,j, v_i * Qinv * v_j)
1153
1154
group, node_dict = G.automorphism_group(edge_labels=True, translation=True)
1155
1156
# Relabel the permutation group
1157
perm_to_vertex = dict( (i,v+1) for v,i in node_dict.items() )
1158
group = PermutationGroup([ [ tuple([ perm_to_vertex[i] for i in cycle ])
1159
for cycle in generator.cycle_tuples() ]
1160
for generator in group.gens() ])
1161
1162
self._restricted_automorphism_group = group
1163
return group
1164
1165
1166
def face_codimension(self, point):
1167
r"""
1168
Return the smallest `d\in\mathbb{Z}` such that ``point`` is
1169
contained in the interior of a codimension-`d` face.
1170
1171
EXAMPLES::
1172
1173
sage: triangle = PointConfiguration([[0,0], [1,-1], [1,0], [1,1]]);
1174
sage: triangle.point(2)
1175
P(1, 0)
1176
sage: triangle.face_codimension(2)
1177
1
1178
sage: triangle.face_codimension( [1,0] )
1179
1
1180
1181
This also works for degenerate cases like the tip of the
1182
pyramid over a square (which saturates four inequalities)::
1183
1184
sage: pyramid = PointConfiguration([[1,0,0],[0,1,1],[0,1,-1],[0,-1,-1],[0,-1,1]])
1185
sage: pyramid.face_codimension(0)
1186
3
1187
"""
1188
try:
1189
p = vector(self.point(point).reduced_affine())
1190
except TypeError:
1191
p = vector(point);
1192
1193
inequalities = []
1194
for ieq in self.convex_hull().inequality_generator():
1195
if (ieq.A()*p + ieq.b() == 0):
1196
inequalities += [ ieq.vector() ];
1197
return matrix(inequalities).rank();
1198
1199
1200
def face_interior(self, dim=None, codim=None):
1201
"""
1202
Return points by the codimension of the containing face in the convex hull.
1203
1204
EXAMPLES::
1205
1206
sage: triangle = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);
1207
sage: triangle.face_interior()
1208
((1,), (3,), (0, 2, 4))
1209
sage: triangle.face_interior(dim=0) # the vertices of the convex hull
1210
(0, 2, 4)
1211
sage: triangle.face_interior(codim=1) # interior of facets
1212
(3,)
1213
"""
1214
assert not (dim!=None and codim!=None), "You cannot specify both dim and codim."
1215
1216
if (dim!=None):
1217
return self.face_interior()[self.convex_hull().dim()-dim]
1218
if (codim!=None):
1219
return self.face_interior()[codim]
1220
1221
try:
1222
return self._face_interior
1223
except AttributeError:
1224
pass
1225
1226
d = [ self.face_codimension(i) for i in range(0,self.n_points()) ]
1227
1228
return tuple( tuple(filter( lambda i: d[i]==codim, range(0,self.n_points())) )
1229
for codim in range(0,self.dim()+1) )
1230
1231
1232
def exclude_points(self, point_idx_list):
1233
"""
1234
Return a new point configuration with the given points
1235
removed.
1236
1237
INPUT:
1238
1239
- ``point_idx_list`` -- a list of integers. The indices of
1240
points to exclude.
1241
1242
OUTPUT:
1243
1244
A new :class:`PointConfiguration` with the given points
1245
removed.
1246
1247
EXAMPLES::
1248
1249
sage: p = PointConfiguration([[-1,0], [0,0], [1,-1], [1,0], [1,1]]);
1250
sage: list(p)
1251
[P(-1, 0), P(0, 0), P(1, -1), P(1, 0), P(1, 1)]
1252
sage: q = p.exclude_points([3])
1253
sage: list(q)
1254
[P(-1, 0), P(0, 0), P(1, -1), P(1, 1)]
1255
sage: p.exclude_points( p.face_interior(codim=1) ).points()
1256
(P(-1, 0), P(0, 0), P(1, -1), P(1, 1))
1257
"""
1258
points = [ self.point(i) for i in range(0,self.n_points())
1259
if not i in point_idx_list ]
1260
return PointConfiguration(points,
1261
projective=False,
1262
connected=self._connected,
1263
fine=self._fine,
1264
regular=self._regular,
1265
star=self._star)
1266
1267
1268
def volume(self, simplex=None):
1269
"""
1270
Find n! times the n-volume of a simplex of dimension n.
1271
1272
INPUT:
1273
1274
- ``simplex`` (optional argument) -- a simplex from a
1275
triangulation T specified as a list of point indices.
1276
1277
OUTPUT:
1278
1279
* If a simplex was passed as an argument: n!*(volume of ``simplex``).
1280
1281
* Without argument: n!*(the total volume of the convex hull).
1282
1283
EXAMPLES:
1284
1285
The volume of the standard simplex should always be 1::
1286
1287
sage: p = PointConfiguration([[0,0],[1,0],[0,1],[1,1]])
1288
sage: p.volume( [0,1,2] )
1289
1
1290
sage: simplex = p.triangulate()[0] # first simplex of triangulation
1291
sage: p.volume(simplex)
1292
1
1293
1294
The square can be triangulated into two minimal simplices, so
1295
in the "integral" normalization its volume equals two::
1296
1297
sage: p.volume()
1298
2
1299
1300
.. note::
1301
1302
We return n!*(metric volume of the simplex) to ensure that
1303
the volume is an integer. Essentially, this normalizes
1304
things so that the volume of the standard n-simplex is 1.
1305
See [GKZ]_ page 182.
1306
"""
1307
if (simplex==None):
1308
return sum([ self.volume(s) for s in self.triangulate() ])
1309
1310
#Form a matrix whose columns are the points of simplex
1311
#with the first point of simplex shifted to the origin.
1312
v = [ self.point(i).reduced_affine_vector() for i in simplex ]
1313
m = matrix([ v_i - v[0] for v_i in v[1:] ])
1314
return abs(m.det())
1315
1316
1317
def secondary_polytope(self):
1318
r"""
1319
Calculate the secondary polytope of the point configuration.
1320
1321
For a definition of the secondary polytope, see [GKZ]_ page 220
1322
Definition 1.6.
1323
1324
Note that if you restricted the admissible triangulations of
1325
the point configuration then the output will be the
1326
corresponding face of the whole secondary polytope.
1327
1328
OUTPUT:
1329
1330
The secondary polytope of the point configuration as an
1331
instance of
1332
:class:`~sage.geometry.polyhedron.base.Polyhedron_base`.
1333
1334
EXAMPLES::
1335
1336
sage: p = PointConfiguration([[0,0],[1,0],[2,1],[1,2],[0,1]])
1337
sage: poly = p.secondary_polytope()
1338
sage: matrix(poly.vertices()).transpose()
1339
[1 1 3 3 5]
1340
[3 5 1 4 1]
1341
[4 2 5 2 4]
1342
[2 4 2 5 4]
1343
[5 3 4 1 1]
1344
sage: poly.Vrepresentation()
1345
(A vertex at (1, 3, 4, 2, 5),
1346
A vertex at (1, 5, 2, 4, 3),
1347
A vertex at (3, 1, 5, 2, 4),
1348
A vertex at (3, 4, 2, 5, 1),
1349
A vertex at (5, 1, 4, 4, 1))
1350
sage: poly.Hrepresentation()
1351
(An equation (0, 0, 1, 2, 1) x - 13 == 0,
1352
An equation (1, 0, 0, 2, 2) x - 15 == 0,
1353
An equation (0, 1, 0, -3, -2) x + 13 == 0,
1354
An inequality (0, 0, 0, -1, -1) x + 7 >= 0,
1355
An inequality (0, 0, 0, 1, 0) x - 2 >= 0,
1356
An inequality (0, 0, 0, -2, -1) x + 11 >= 0,
1357
An inequality (0, 0, 0, 0, 1) x - 1 >= 0,
1358
An inequality (0, 0, 0, 3, 2) x - 14 >= 0)
1359
"""
1360
from sage.geometry.polyhedron.constructor import Polyhedron
1361
#TODO: once restriction to regular triangulations is fixed,
1362
#change the next line to only take the regular triangulations,
1363
#since they are the vertices of the secondary polytope anyway.
1364
l = self.triangulations_list()
1365
return Polyhedron(vertices = [x.gkz_phi() for x in l])
1366
1367
1368
def circuits_support(self):
1369
r"""
1370
A generator for the supports of the circuits of the point configuration.
1371
1372
See :meth:`circuits` for details.
1373
1374
OUTPUT:
1375
1376
A generator for the supports `C_-\cup C_+` (returned as a
1377
Python tuple) for all circuits of the point configuration.
1378
1379
EXAMPLES::
1380
1381
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1382
sage: list( p.circuits_support() )
1383
[(0, 3, 4), (0, 1, 2), (1, 2, 3, 4)]
1384
"""
1385
n = len(self)
1386
U = [ self[i].reduced_projective() for i in range(0,n) ]
1387
1388
# the index set of U
1389
I = set(range(0,n))
1390
# The (indices of) known independent elements of U
1391
independent_k = [ (i,) for i in range(0,n) ]
1392
supports_k = []
1393
1394
supports = () # supports of circuits
1395
for k in range(2, self.dim()+3):
1396
1397
# possibly linear dependent subsets
1398
supports_knext = set()
1399
possible_dependency = set()
1400
for indep in independent_k:
1401
indep_plus_one = [ tuple(sorted(indep+(i,))) for i in (I-set(indep)) ]
1402
possible_dependency.update(indep_plus_one)
1403
for supp in supports_k:
1404
supp_plus_one = [ tuple(sorted(supp+(i,))) for i in (I-set(supp)) ]
1405
possible_dependency.difference_update(supp_plus_one)
1406
supports_knext.update(supp_plus_one)
1407
1408
# remember supports and independents for the next k-iteration
1409
supports_k = list(supports_knext)
1410
independent_k = []
1411
for idx in possible_dependency:
1412
rk = matrix([ U[i] for i in idx ]).rank()
1413
if rk==k:
1414
independent_k.append(idx)
1415
else:
1416
supports_k.append(idx)
1417
yield idx
1418
assert independent_k==[] # there are no independent (self.dim()+3)-tuples
1419
1420
1421
def circuits(self):
1422
r"""
1423
Return the circuits of the point configuration.
1424
1425
Roughly, a circuit is a minimal linearly dependent subset of
1426
the points. That is, a circuit is a partition
1427
1428
.. MATH::
1429
1430
\{ 0, 1, \dots, n-1 \} = C_+ \cup C_0 \cup C_-
1431
1432
such that there is an (unique up to an overall normalization) affine
1433
relation
1434
1435
.. MATH::
1436
1437
\sum_{i\in C_+} \alpha_i \vec{p}_i =
1438
\sum_{j\in C_-} \alpha_j \vec{p}_j
1439
1440
with all positive (or all negative) coefficients, where
1441
`\vec{p}_i=(p_1,\dots,p_k,1)` are the projective coordinates
1442
of the `i`-th point.
1443
1444
OUTPUT:
1445
1446
The list of (unsigned) circuits as triples `(C_+, C_0,
1447
C_-)`. The swapped circuit `(C_-, C_0, C_+)` is not returned
1448
separately.
1449
1450
EXAMPLES::
1451
1452
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1453
sage: p.circuits()
1454
(((0,), (1, 2), (3, 4)), ((0,), (3, 4), (1, 2)), ((1, 2), (0,), (3, 4)))
1455
1456
1457
TESTS::
1458
1459
sage: U=matrix([
1460
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1461
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1462
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1463
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1464
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1465
... ])
1466
sage: p = PointConfiguration(U.columns())
1467
sage: len( p.circuits() ) # long time
1468
218
1469
"""
1470
try:
1471
return self._circuits
1472
except AttributeError:
1473
pass
1474
1475
n = len(self)
1476
U = [ self[i].reduced_projective() for i in range(0,n) ]
1477
1478
Circuits = ()
1479
for support in self.circuits_support():
1480
m = matrix([ U[i] for i in support ]).transpose()
1481
ker = m.right_kernel().basis()[0]
1482
assert len(ker)==len(support)
1483
Cplus = [ support[i] for i in range(0,len(support)) if ker[i]>0 ]
1484
Cminus = [ support[i] for i in range(0,len(support)) if ker[i]<0 ]
1485
Czero = set( range(0,n) ).difference(support)
1486
Circuits += ( (tuple(Cplus), tuple(Czero), tuple(Cminus)), )
1487
self._circuits = Circuits
1488
return Circuits
1489
1490
1491
def positive_circuits(self, *negative):
1492
r"""
1493
Returns the positive part of circuits with fixed negative part.
1494
1495
A circuit is a pair `(C_+, C_-)`, each consisting of a subset
1496
(actually, an ordered tuple) of point indices.
1497
1498
INPUT:
1499
1500
- ``*negative`` -- integer. The indices of points.
1501
1502
OUTPUT:
1503
1504
A tuple of all circuits with `C_-` = ``negative``.
1505
1506
EXAMPLE::
1507
1508
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)])
1509
sage: p.positive_circuits(8)
1510
((0, 7), (0, 1, 4), (0, 2, 3), (0, 5, 6), (0, 1, 2, 5), (0, 3, 4, 6))
1511
sage: p.positive_circuits(0,5,6)
1512
((8,),)
1513
"""
1514
pos = ()
1515
negative = tuple(sorted(negative))
1516
for circuit in self.circuits():
1517
Cpos = circuit[0]
1518
Cneg = circuit[2]
1519
if Cpos == negative:
1520
pos += ( Cneg, )
1521
elif Cneg == negative:
1522
pos += ( Cpos, )
1523
return pos
1524
1525
1526
def bistellar_flips(self):
1527
r"""
1528
Return the bistellar flips.
1529
1530
OUTPUT:
1531
1532
The bistellar flips as a tuple. Each flip is a pair
1533
`(T_+,T_-)` where `T_+` and `T_-` are partial triangulations
1534
of the point configuration.
1535
1536
EXAMPLES::
1537
1538
sage: pc = PointConfiguration([(0,0),(1,0),(0,1),(1,1)])
1539
sage: pc.bistellar_flips()
1540
(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)
1541
sage: Tpos, Tneg = pc.bistellar_flips()[0]
1542
sage: Tpos.plot(axes=False)
1543
sage: Tneg.plot(axes=False)
1544
1545
The 3d analog::
1546
1547
sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(-1,0,0),(1,1,1)])
1548
sage: pc.bistellar_flips()
1549
(((<0,1,2,3>, <0,1,2,4>), (<0,1,3,4>, <0,2,3,4>, <1,2,3,4>)),)
1550
1551
A 2d flip on the base of the pyramid over a square::
1552
1553
sage: pc = PointConfiguration([(0,0,0),(0,2,0),(0,0,2),(0,2,2),(1,1,1)])
1554
sage: pc.bistellar_flips()
1555
(((<0,1,3>, <0,2,3>), (<0,1,2>, <1,2,3>)),)
1556
sage: Tpos, Tneg = pc.bistellar_flips()[0]
1557
sage: Tpos.plot(axes=False)
1558
"""
1559
flips = []
1560
for C in self.circuits():
1561
Cpos = list(C[0])
1562
Cneg = list(C[2])
1563
support = sorted(Cpos+Cneg)
1564
Tpos = [ Cpos+Cneg[0:i]+Cneg[i+1:len(Cneg)] for i in range(0,len(Cneg)) ]
1565
Tneg = [ Cneg+Cpos[0:i]+Cpos[i+1:len(Cpos)] for i in range(0,len(Cpos)) ]
1566
flips.append( (self.element_class(Tpos, parent=self, check=False),
1567
self.element_class(Tneg, parent=self, check=False)) )
1568
return tuple(flips)
1569
1570
1571
def lexicographic_triangulation(self):
1572
r"""
1573
Return the lexicographic triangulation.
1574
1575
The algorithm was taken from [PUNTOS]_.
1576
1577
EXAMPLES::
1578
1579
sage: p = PointConfiguration([(0,0),(+1,0),(-1,0),(0,+1),(0,-1)])
1580
sage: p.lexicographic_triangulation()
1581
(<1,3,4>, <2,3,4>)
1582
1583
TESTS::
1584
1585
sage: U=matrix([
1586
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1587
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1588
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1589
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1590
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1591
... ])
1592
sage: pc = PointConfiguration(U.columns())
1593
sage: pc.lexicographic_triangulation()
1594
(<1,3,4,7,10,13>, <1,3,4,8,10,13>, <1,3,6,7,10,13>, <1,3,6,8,10,13>,
1595
<1,4,6,7,10,13>, <1,4,6,8,10,13>, <2,3,4,6,7,12>, <2,3,4,7,12,13>,
1596
<2,3,6,7,12,13>, <2,4,6,7,12,13>, <3,4,5,6,9,12>, <3,4,5,8,9,12>,
1597
<3,4,6,7,11,12>, <3,4,6,9,11,12>, <3,4,7,10,11,13>, <3,4,7,11,12,13>,
1598
<3,4,8,9,10,12>, <3,4,8,10,12,13>, <3,4,9,10,11,12>, <3,4,10,11,12,13>,
1599
<3,5,6,8,9,12>, <3,6,7,10,11,13>, <3,6,7,11,12,13>, <3,6,8,9,10,12>,
1600
<3,6,8,10,12,13>, <3,6,9,10,11,12>, <3,6,10,11,12,13>, <4,5,6,8,9,12>,
1601
<4,6,7,10,11,13>, <4,6,7,11,12,13>, <4,6,8,9,10,12>, <4,6,8,10,12,13>,
1602
<4,6,9,10,11,12>, <4,6,10,11,12,13>)
1603
sage: len(_)
1604
34
1605
"""
1606
lex_supp = set()
1607
for circuit in self.circuits():
1608
Cplus = circuit[0]
1609
Cminus = circuit[2]
1610
s0 = min(Cplus + Cminus)
1611
if s0 in Cplus:
1612
lex_supp.add(Cplus)
1613
else:
1614
lex_supp.add(Cminus)
1615
1616
lex_supp = sorted(lex_supp, key=lambda x:-len(x))
1617
basepts = copy(lex_supp)
1618
for i in range(0,len(lex_supp)-1):
1619
for j in range(i+1,len(lex_supp)):
1620
if set(lex_supp[j]).issubset(set(lex_supp[i])):
1621
try:
1622
basepts.remove(lex_supp[i])
1623
except ValueError:
1624
pass
1625
1626
basepts = [ (len(b),)+b for b in basepts ] # decorate
1627
basepts = sorted(basepts) # sort
1628
basepts = [ b[1:] for b in basepts ] # undecorate
1629
1630
def make_cotriang(basepts):
1631
if len(basepts)==0:
1632
return [frozenset()]
1633
triangulation = set()
1634
for tail in make_cotriang(basepts[1:]):
1635
for head in basepts[0]:
1636
triangulation.update([ frozenset([head]).union(tail) ])
1637
1638
nonminimal = set()
1639
for rel in Combinations(triangulation,2):
1640
if rel[0].issubset(rel[1]): nonminimal.update([rel[1]])
1641
if rel[1].issubset(rel[0]): nonminimal.update([rel[0]])
1642
triangulation.difference_update(nonminimal)
1643
1644
triangulation = [ [len(t)]+sorted(t) for t in triangulation ] # decorate
1645
triangulation = sorted(triangulation) # sort
1646
triangulation = [ frozenset(t[1:]) for t in triangulation ] # undecorate
1647
1648
return triangulation
1649
1650
triangulation = make_cotriang(basepts)
1651
I = frozenset(range(0,self.n_points()))
1652
triangulation = [ tuple(I.difference(t)) for t in triangulation ]
1653
1654
return self(triangulation)
1655
1656
1657
@cached_method
1658
def distance_affine(self, x, y):
1659
r"""
1660
Returns the distance between two points.
1661
1662
The distance function used in this method is `d_{aff}(x,y)^2`,
1663
the square of the usual affine distance function
1664
1665
.. math::
1666
1667
d_{aff}(x,y) = |x-y|
1668
1669
INPUT:
1670
1671
- ``x``, ``y`` -- two points of the point configuration.
1672
1673
OUTPUT:
1674
1675
The metric distance-square `d_{aff}(x,y)^2`. Note that this
1676
distance lies in the same field as the entries of ``x``,
1677
``y``. That is, the distance of rational points will be
1678
rational and so on.
1679
1680
EXAMPLES::
1681
1682
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1683
sage: [ pc.distance_affine(pc.point(0), p) for p in pc.points() ]
1684
[0, 1, 5, 5, 1]
1685
"""
1686
self._assert_is_affine()
1687
d = 0
1688
for xi, yi in zip(x.projective(), y.projective()):
1689
d += (xi-yi)**2
1690
return d
1691
1692
1693
@cached_method
1694
def distance_FS(self, x, y):
1695
r"""
1696
Returns the distance between two points.
1697
1698
The distance function used in this method is `1-\cos
1699
d_{FS}(x,y)^2`, where `d_{FS}` is the Fubini-Study distance of
1700
projective points. Recall the Fubini-Studi distance function
1701
1702
.. math::
1703
1704
d_{FS}(x,y) = \arccos \sqrt{ \frac{(x\cdot y)^2}{|x|^2 |y|^2} }
1705
1706
INPUT:
1707
1708
- ``x``, ``y`` -- two points of the point configuration.
1709
1710
OUTPUT:
1711
1712
The distance `1-\cos d_{FS}(x,y)^2`. Note that this distance
1713
lies in the same field as the entries of ``x``, ``y``. That
1714
is, the distance of rational points will be rational and so
1715
on.
1716
1717
EXAMPLES::
1718
1719
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1720
sage: [ pc.distance_FS(pc.point(0), p) for p in pc.points() ]
1721
[0, 1/2, 5/6, 5/6, 1/2]
1722
"""
1723
x2 = y2 = xy = 0
1724
for xi, yi in zip(x.projective(), y.projective()):
1725
x2 += xi*xi
1726
y2 += yi*yi
1727
xy += xi*yi
1728
return 1-xy*xy/(x2*y2)
1729
1730
1731
@cached_method
1732
def distance(self, x, y):
1733
"""
1734
Returns the distance between two points.
1735
1736
INPUT:
1737
1738
- ``x``, ``y`` -- two points of the point configuration.
1739
1740
OUTPUT:
1741
1742
The distance between ``x`` and ``y``, measured either with
1743
:meth:`distance_affine` or :meth:`distance_FS` depending on
1744
whether the point configuration is defined by affine or
1745
projective points. These are related, but not equal to the
1746
usual flat and Fubini-Study distance.
1747
1748
EXAMPLES::
1749
1750
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1751
sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]
1752
[0, 1, 5, 5, 1]
1753
1754
sage: pc = PointConfiguration([(0,0,1),(1,0,1),(2,1,1),(1,2,1),(0,1,1)], projective=True)
1755
sage: [ pc.distance(pc.point(0), p) for p in pc.points() ]
1756
[0, 1/2, 5/6, 5/6, 1/2]
1757
"""
1758
if self.is_affine():
1759
return self.distance_affine(x,y)
1760
else:
1761
return self.distance_FS(x,y)
1762
1763
1764
def farthest_point(self, points, among=None):
1765
"""
1766
Return the point with the most distance from ``points``.
1767
1768
INPUT:
1769
1770
- ``points`` -- a list of points.
1771
1772
- ``among`` -- a list of points or ``None`` (default). The set
1773
of points from which to pick the farthest one. By default,
1774
all points of the configuration are considered.
1775
1776
OUTPUT:
1777
1778
A :class:`~sage.geometry.triangulation.base.Point` with
1779
largest minimal distance from all given ``points``.
1780
1781
EXAMPLES::
1782
1783
sage: pc = PointConfiguration([(0,0),(1,0),(1,1),(0,1)])
1784
sage: pc.farthest_point([ pc.point(0) ])
1785
P(1, 1)
1786
"""
1787
if len(points)==0:
1788
return self.point(0)
1789
if among is None:
1790
among = self.points()
1791
p_max = None
1792
for p in among:
1793
if p in points:
1794
continue
1795
if p_max is None:
1796
p_max = p
1797
d_max = min(self.distance(p,q) for q in points)
1798
continue
1799
d = min(self.distance(p,q) for q in points)
1800
if d>d_max:
1801
p_max = p
1802
return p_max
1803
1804
1805
def contained_simplex(self, large=True, initial_point=None):
1806
"""
1807
Return a simplex contained in the point configuration.
1808
1809
INPUT:
1810
1811
- ``large`` -- boolean. Whether to attempt to return a large
1812
simplex.
1813
1814
- ``initial_point`` -- a
1815
:class:`~sage.geometry.triangulation.base.Point` or ``None``
1816
(default). A specific point to start with when picking the
1817
simplex vertices.
1818
1819
OUTPUT:
1820
1821
A tuple of points that span a simplex of dimension
1822
:meth:`dim`. If ``large==True``, the simplex is constructed by
1823
sucessively picking the farthest point. This will ensure that
1824
the simplex is not unneccessarily small, but will in general
1825
not return a maximal simplex.
1826
1827
EXAMPLES::
1828
1829
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])
1830
sage: pc.contained_simplex()
1831
(P(0, 1), P(2, 1), P(1, 0))
1832
sage: pc.contained_simplex(large=False)
1833
(P(0, 1), P(1, 1), P(1, 0))
1834
sage: pc.contained_simplex(initial_point=pc.point(0))
1835
(P(0, 0), P(1, 1), P(1, 0))
1836
1837
sage: pc = PointConfiguration([[0,0],[0,1],[1,0],[1,1],[-1,-1]])
1838
sage: pc.contained_simplex()
1839
(P(-1, -1), P(1, 1), P(0, 1))
1840
1841
TESTS::
1842
1843
sage: pc = PointConfiguration([[0,0],[0,1],[1,0]])
1844
sage: pc.contained_simplex()
1845
(P(1, 0), P(0, 1), P(0, 0))
1846
sage: pc = PointConfiguration([[0,0],[0,1]])
1847
sage: pc.contained_simplex()
1848
(P(0, 1), P(0, 0))
1849
sage: pc = PointConfiguration([[0,0]])
1850
sage: pc.contained_simplex()
1851
(P(0, 0),)
1852
sage: pc = PointConfiguration([])
1853
sage: pc.contained_simplex()
1854
()
1855
"""
1856
self._assert_is_affine()
1857
if self.n_points()==0:
1858
return tuple()
1859
points = list(self.points())
1860
if initial_point is None:
1861
origin = points.pop()
1862
else:
1863
origin = initial_point
1864
points.remove(origin)
1865
vertices = [origin]
1866
edges = []
1867
while len(vertices) <= self.dim():
1868
if large:
1869
p = self.farthest_point(vertices, points)
1870
points.remove(p)
1871
else:
1872
p = points.pop()
1873
edge = p.reduced_affine_vector()-origin.reduced_affine_vector()
1874
if len(edges)>0 and (ker * edge).is_zero():
1875
continue
1876
vertices.append(p)
1877
edges.append(edge)
1878
ker = matrix(edges).right_kernel().matrix()
1879
return tuple(vertices)
1880
1881
1882
def placing_triangulation(self, point_order=None):
1883
r"""
1884
Construct the placing (pushing) triangulation.
1885
1886
INPUT:
1887
1888
- ``point_order`` -- list of points or integers. The order in
1889
which the points are to be placed.
1890
1891
OUTPUT:
1892
1893
A :class:`~sage.geometry.triangulation.triangulation.Triangulation`.
1894
1895
EXAMPLES::
1896
1897
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,2),(0,1)])
1898
sage: pc.placing_triangulation()
1899
(<0,1,2>, <0,2,4>, <2,3,4>)
1900
1901
sage: U=matrix([
1902
... [ 0, 0, 0, 0, 0, 2, 4,-1, 1, 1, 0, 0, 1, 0],
1903
... [ 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 0, 0],
1904
... [ 0, 2, 0, 0, 0, 0,-1, 0, 1, 0, 1, 0, 0, 1],
1905
... [ 0, 1, 1, 0, 0, 1, 0,-2, 1, 0, 0,-1, 1, 1],
1906
... [ 0, 0, 0, 0, 1, 0,-1, 0, 0, 0, 0, 0, 0, 0]
1907
... ])
1908
sage: p = PointConfiguration(U.columns())
1909
sage: triangulation = p.placing_triangulation(); triangulation
1910
(<0,2,3,4,6,7>, <0,2,3,4,6,12>, <0,2,3,4,7,13>, <0,2,3,4,12,13>,
1911
<0,2,3,6,7,13>, <0,2,3,6,12,13>, <0,2,4,6,7,13>, <0,2,4,6,12,13>,
1912
<0,3,4,6,7,12>, <0,3,4,7,12,13>, <0,3,6,7,12,13>, <0,4,6,7,12,13>,
1913
<1,3,4,5,6,12>, <1,3,4,6,11,12>, <1,3,4,7,11,13>, <1,3,4,11,12,13>,
1914
<1,3,6,7,11,13>, <1,3,6,11,12,13>, <1,4,6,7,11,13>, <1,4,6,11,12,13>,
1915
<3,4,6,7,11,12>, <3,4,7,11,12,13>, <3,6,7,11,12,13>, <4,6,7,11,12,13>)
1916
sage: sum(p.volume(t) for t in triangulation)
1917
42
1918
"""
1919
facet_normals = dict()
1920
def facets_of_simplex(simplex):
1921
"""
1922
Return the facets of the simplex and store the normals in facet_normals
1923
"""
1924
simplex = list(simplex)
1925
origin = simplex[0]
1926
rest = simplex[1:]
1927
span = matrix([ origin.reduced_affine_vector()-p.reduced_affine_vector()
1928
for p in rest ])
1929
# span.inverse() linearly transforms the simplex into the unit simplex
1930
normals = span.inverse().columns()
1931
facets = []
1932
# The facets incident to the chosen vertex "origin"
1933
for opposing_vertex, normal in zip(rest, normals):
1934
facet = frozenset([origin] + [ p for p in rest if p is not opposing_vertex ])
1935
facets.append(facet)
1936
normal.set_immutable()
1937
facet_normals[facet] = normal
1938
# The remaining facet that is not incident to "origin"
1939
facet = frozenset(rest)
1940
normal = -sum(normals)
1941
normal.set_immutable()
1942
facet_normals[facet] = normal
1943
facets.append(facet)
1944
return set(facets)
1945
1946
# input verification
1947
self._assert_is_affine()
1948
if point_order is None:
1949
point_order = list(self.points())
1950
elif isinstance(point_order[0], Point):
1951
point_order = list(point_order)
1952
assert all(p.point_configuration()==self for p in point_order)
1953
else:
1954
point_order = [ self.point(i) for i in point_order ]
1955
assert all(p in self.points() for p in point_order)
1956
1957
# construct the initial simplex
1958
simplices = [ frozenset(self.contained_simplex()) ]
1959
for s in simplices[0]:
1960
try:
1961
point_order.remove(s)
1962
except ValueError:
1963
pass
1964
facets = facets_of_simplex(simplices[0])
1965
1966
# successively place the remaining points
1967
for point in point_order:
1968
# identify visible facets
1969
visible_facets = []
1970
for facet in facets:
1971
origin = iter(facet).next()
1972
normal = facet_normals[facet]
1973
v = point.reduced_affine_vector() - origin.reduced_affine_vector()
1974
if v*normal>0:
1975
visible_facets.append(facet)
1976
1977
# construct simplices over each visible facet
1978
new_facets = set()
1979
for facet in visible_facets:
1980
simplex = frozenset(list(facet) + [point])
1981
# print 'simplex', simplex
1982
simplices.append(simplex)
1983
for facet in facets_of_simplex(simplex):
1984
if facet in visible_facets: continue
1985
if facet in new_facets:
1986
new_facets.remove(facet)
1987
continue
1988
new_facets.add(facet)
1989
facets.difference_update(visible_facets)
1990
facets.update(new_facets)
1991
1992
# construct the triangulation
1993
triangulation = [ [p.index() for p in simplex] for simplex in simplices ]
1994
return self(triangulation)
1995
1996
pushing_triangulation = placing_triangulation
1997
1998
@cached_method
1999
def Gale_transform(self, points=None):
2000
r"""
2001
Return the Gale transform of ``self``.
2002
2003
INPUT:
2004
2005
- ``points`` -- a tuple of points or point indices or ``None``
2006
(default). A subset of points for which to compute the Gale
2007
transform. By default, all points are used.
2008
2009
OUTPUT:
2010
2011
A matrix over :meth:`base_ring`.
2012
2013
EXAMPLES::
2014
2015
sage: pc = PointConfiguration([(0,0),(1,0),(2,1),(1,1),(0,1)])
2016
sage: pc.Gale_transform()
2017
[ 1 -1 0 1 -1]
2018
[ 0 0 1 -2 1]
2019
2020
sage: pc.Gale_transform((0,1,3,4))
2021
[ 1 -1 1 -1]
2022
2023
sage: points = (pc.point(0), pc.point(1), pc.point(3), pc.point(4))
2024
sage: pc.Gale_transform(points)
2025
[ 1 -1 1 -1]
2026
"""
2027
self._assert_is_affine()
2028
if points is None:
2029
points = self.points()
2030
else:
2031
try:
2032
points = [ self.point(ZZ(i)) for i in points ]
2033
except TypeError:
2034
pass
2035
m = matrix([ (1,) + p.affine() for p in points])
2036
return m.left_kernel().matrix()
2037
2038