Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/geometry/lattice_polytope.py
4094 views
1
r"""
2
Lattice and reflexive polytopes
3
4
This module provides tools for work with lattice and reflexive
5
polytopes. A *convex polytope* is the convex hull of finitely many
6
points in `\RR^n`. The dimension `n` of a
7
polytope is the smallest `n` such that the polytope can be
8
embedded in `\RR^n`.
9
10
A *lattice polytope* is a polytope whose vertices all have integer
11
coordinates.
12
13
If `L` is a lattice polytope, the dual polytope of
14
`L` is
15
16
.. math::
17
18
\{y \in \ZZ^n : x\cdot y \geq -1 \text{ all } x \in L\}
19
20
21
A *reflexive polytope* is a lattice polytope, such that its polar
22
is also a lattice polytope, i.e. it is bounded and has vertices with
23
integer coordinates.
24
25
This Sage module uses Package for Analyzing Lattice Polytopes
26
(PALP), which is a program written in C by Maximilian Kreuzer and
27
Harald Skarke, which is freely available under the GNU license
28
terms at http://tph16.tuwien.ac.at/~kreuzer/CY/. Moreover, PALP is
29
included standard with Sage.
30
31
PALP is described in the paper math.SC/0204356. Its distribution
32
also contains the application nef.x, which was created by Erwin
33
Riegler and computes nef-partitions and Hodge data for toric
34
complete intersections.
35
36
ACKNOWLEDGMENT: polytope.py module written by William Stein was
37
used as an example of organizing an interface between an external
38
program and Sage. William Stein also helped Andrey Novoseltsev with
39
debugging and tuning of this module.
40
41
Robert Bradshaw helped Andrey Novoseltsev to realize plot3d
42
function.
43
44
.. note::
45
46
IMPORTANT: PALP requires some parameters to be determined during
47
compilation time, i.e., the maximum dimension of polytopes, the
48
maximum number of points, etc. These limitations may lead to errors
49
during calls to different functions of these module. Currently, a
50
ValueError exception will be raised if the output of poly.x or
51
nef.x is empty or contains the exclamation mark. The error message
52
will contain the exact command that caused an error, the
53
description and vertices of the polytope, and the obtained output.
54
55
Data obtained from PALP and some other data is cached and most
56
returned values are immutable. In particular, you cannot change the
57
vertices of the polytope or their order after creation of the
58
polytope.
59
60
If you are going to work with large sets of data, take a look at
61
``all_*`` functions in this module. They precompute different data
62
for sequences of polynomials with a few runs of external programs.
63
This can significantly affect the time of future computations. You
64
can also use dump/load, but not all data will be stored (currently
65
only faces and the number of their internal and boundary points are
66
stored, in addition to polytope vertices and its polar).
67
68
AUTHORS:
69
70
- Andrey Novoseltsev (2007-01-11): initial version
71
72
- Andrey Novoseltsev (2007-01-15): ``all_*`` functions
73
74
- Andrey Novoseltsev (2008-04-01): second version, including:
75
76
- dual nef-partitions and necessary convex_hull and minkowski_sum
77
78
- built-in sequences of 2- and 3-dimensional reflexive polytopes
79
80
- plot3d, skeleton_show
81
82
- Andrey Novoseltsev (2009-08-26): dropped maximal dimension requirement
83
84
- Andrey Novoseltsev (2010-12-15): new version of nef-partitions
85
86
- Maximilian Kreuzer and Harald Skarke: authors of PALP (which was
87
also used to obtain the list of 3-dimensional reflexive polytopes)
88
89
- Erwin Riegler: the author of nef.x
90
91
"""
92
93
#*****************************************************************************
94
# Copyright (C) 2007-2010 Andrey Novoseltsev <[email protected]>
95
# Copyright (C) 2007-2010 William Stein <[email protected]>
96
#
97
# Distributed under the terms of the GNU General Public License (GPL)
98
# as published by the Free Software Foundation; either version 2 of
99
# the License, or (at your option) any later version.
100
# http://www.gnu.org/licenses/
101
#*****************************************************************************
102
103
from sage.graphs.all import Graph
104
from sage.interfaces.all import maxima
105
from sage.matrix.all import matrix, is_Matrix
106
from sage.misc.all import tmp_filename
107
from sage.misc.misc import SAGE_DATA
108
from sage.modules.all import vector
109
from sage.plot.plot3d.index_face_set import IndexFaceSet
110
from sage.plot.plot3d.all import line3d, point3d
111
from sage.plot.plot3d.shapes2 import text3d
112
from sage.rings.all import Integer, ZZ, QQ, gcd, lcm
113
from sage.sets.set import Set_generic
114
from sage.structure.all import Sequence
115
from sage.structure.sequence import Sequence_generic
116
from sage.structure.sage_object import SageObject
117
118
import collections
119
import copy_reg
120
import os
121
import subprocess
122
import StringIO
123
124
125
data_location = SAGE_DATA + '/reflexive_polytopes/'
126
127
128
class SetOfAllLatticePolytopesClass(Set_generic):
129
def _repr_(self):
130
r"""
131
Return a string representation.
132
133
TESTS::
134
135
sage: lattice_polytope.SetOfAllLatticePolytopesClass()._repr_()
136
'Set of all Lattice Polytopes'
137
"""
138
return "Set of all Lattice Polytopes"
139
140
def __call__(self, x):
141
r"""
142
TESTS::
143
144
sage: o = lattice_polytope.octahedron(3)
145
sage: lattice_polytope.SetOfAllLatticePolytopesClass().__call__(o)
146
An octahedron: 3-dimensional, 6 vertices.
147
"""
148
if isinstance(x, LatticePolytopeClass):
149
return x
150
raise TypeError
151
152
153
SetOfAllLatticePolytopes = SetOfAllLatticePolytopesClass()
154
155
156
def LatticePolytope(data, desc=None, compute_vertices=True,
157
copy_vertices=True, n=0):
158
r"""
159
Construct a lattice polytope.
160
161
LatticePolytope(data, [desc], [compute_vertices],
162
[copy_vertices], [n])
163
164
INPUT:
165
166
- ``data`` -- The points (which must be vertices if
167
``compute_vertices`` is False) spanning the lattice polytope,
168
specified as one of:
169
170
* a matrix whose columns are vertices of the polytope.
171
172
* an iterable of iterables (for example, a list of vectors)
173
defining the point coordinates.
174
175
* a file with matrix data, open for reading, or
176
177
* a filename of such a file. See ``read_palp_matrix`` for the
178
file format.
179
180
- ``desc`` -- (default: "A lattice polytope")
181
description of the polytope.
182
183
- ``compute_vertices`` -- boolean (default: True) if True, the
184
convex hull of the given points will be computed for
185
determining vertices. Otherwise, the given points must be
186
vertices.
187
188
- ``copy_vertices`` -- boolean (default: True) if False, and
189
compute_vertices is False, and ``data`` is a matrix of
190
vertices, it will be made immutable.
191
192
- ``n`` -- integer (default: 0) if ``data`` is a name of a file,
193
that contains data blocks for several polytopes, the n-th block
194
will be used. *ENUMERATION STARTS WITH ZERO*.
195
196
OUTPUT: a lattice polytope
197
198
EXAMPLES: Here we construct a polytope from a matrix whose columns
199
are vertices in 3-dimensional space. In the first case a copy of
200
the given matrix is made during construction, in the second one the
201
matrix is made immutable and used as a matrix of vertices.
202
203
::
204
205
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
206
... [0, 1, 0, 0, -1, 0],
207
... [0, 0, 1, 0, 0, -1]])
208
...
209
sage: p = LatticePolytope(m)
210
sage: p
211
A lattice polytope: 3-dimensional, 6 vertices.
212
sage: m.is_mutable()
213
True
214
sage: m is p.vertices()
215
False
216
sage: p = LatticePolytope(m, compute_vertices=False, copy_vertices=False)
217
sage: m.is_mutable()
218
False
219
sage: m is p.vertices()
220
True
221
222
We draw a pretty picture of the polytype in 3-dimensional space::
223
224
sage: p.plot3d().show()
225
226
Now we add an extra point, which is in the interior of the
227
polytope...
228
229
::
230
231
sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope constructed from 7 points")
232
sage: p
233
A lattice polytope constructed from 7 points: 3-dimensional, 6 vertices.
234
235
You can suppress vertex computation for speed but this can lead to
236
mistakes::
237
238
sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope with WRONG vertices",
239
... compute_vertices=False)
240
...
241
sage: p
242
A lattice polytope with WRONG vertices: 3-dimensional, 7 vertices.
243
244
Given points must be in the lattice::
245
246
sage: LatticePolytope(matrix([1/2, 3/2]))
247
Traceback (most recent call last):
248
...
249
ValueError: Points must be integral!
250
Given:
251
[1/2 3/2]
252
253
But it is OK to create polytopes of non-maximal dimension::
254
255
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0, 0],
256
... [0, 1, 0, 0, -1, 0, 0],
257
... [0, 0, 0, 0, 0, 0, 0]])
258
...
259
sage: p = LatticePolytope(m)
260
sage: p
261
A lattice polytope: 2-dimensional, 4 vertices.
262
sage: p.vertices()
263
[ 1 0 -1 0]
264
[ 0 1 0 -1]
265
[ 0 0 0 0]
266
267
An empty lattice polytope can be specified by a matrix with zero columns:
268
269
sage: p = LatticePolytope(matrix(ZZ,3,0)); p
270
A lattice polytope: -1-dimensional, 0 vertices.
271
sage: p.ambient_dim()
272
3
273
sage: p.npoints()
274
0
275
sage: p.nfacets()
276
0
277
sage: p.points()
278
[]
279
sage: p.faces()
280
[]
281
"""
282
if isinstance(data, LatticePolytopeClass):
283
return data
284
285
if not is_Matrix(data) and not isinstance(data,(file,basestring)):
286
try:
287
data = matrix(ZZ,data).transpose()
288
except ValueError:
289
pass
290
291
return LatticePolytopeClass(data, desc, compute_vertices, copy_vertices, n)
292
293
copy_reg.constructor(LatticePolytope) # "safe for unpickling"
294
295
def ReflexivePolytope(dim, n):
296
r"""
297
Return n-th reflexive polytope from the database of 2- or
298
3-dimensional reflexive polytopes.
299
300
.. note::
301
302
#. Numeration starts with zero: `0 \leq n \leq 15` for `{\rm dim} = 2`
303
and `0 \leq n \leq 4318` for `{\rm dim} = 3`.
304
305
#. During the first call, all reflexive polytopes of requested
306
dimension are loaded and cached for future use, so the first
307
call for 3-dimensional polytopes can take several seconds,
308
but all consecutive calls are fast.
309
310
#. Equivalent to ``ReflexivePolytopes(dim)[n]`` but checks bounds
311
first.
312
313
EXAMPLES: The 3rd 2-dimensional polytope is "the diamond:"
314
315
::
316
317
sage: ReflexivePolytope(2,3)
318
Reflexive polytope 3: 2-dimensional, 4 vertices.
319
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
320
[ 1 0 0 -1]
321
[ 0 1 -1 0]
322
323
There are 16 reflexive polygons and numeration starts with 0::
324
325
sage: ReflexivePolytope(2,16)
326
Traceback (most recent call last):
327
...
328
ValueError: there are only 16 reflexive polygons!
329
330
It is not possible to load a 4-dimensional polytope in this way::
331
332
sage: ReflexivePolytope(4,16)
333
Traceback (most recent call last):
334
...
335
NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!
336
"""
337
if dim == 2:
338
if n > 15:
339
raise ValueError, "there are only 16 reflexive polygons!"
340
return ReflexivePolytopes(2)[n]
341
elif dim == 3:
342
if n > 4318:
343
raise ValueError, "there are only 4319 reflexive 3-polytopes!"
344
return ReflexivePolytopes(3)[n]
345
else:
346
raise NotImplementedError, "only 2- and 3-dimensional reflexive polytopes are available!"
347
348
# Sequences of reflexive polytopes
349
_rp = [None]*4
350
351
def ReflexivePolytopes(dim):
352
r"""
353
Return the sequence of all 2- or 3-dimensional reflexive polytopes.
354
355
.. note::
356
357
During the first call the database is loaded and cached for
358
future use, so repetitive calls will return the same object in
359
memory.
360
361
:param dim: dimension of required reflexive polytopes
362
:type dim: 2 or 3
363
:rtype: list of lattice polytopes
364
365
EXAMPLES: There are 16 reflexive polygons::
366
367
sage: len(ReflexivePolytopes(2))
368
16
369
370
It is not possible to load 4-dimensional polytopes in this way::
371
372
373
sage: ReflexivePolytopes(4)
374
Traceback (most recent call last):
375
...
376
NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!
377
"""
378
global _rp
379
if dim not in [2, 3]:
380
raise NotImplementedError, "only 2- and 3-dimensional reflexive polytopes are available!"
381
if _rp[dim] == None:
382
rp = read_all_polytopes(
383
data_location+"reflexive_polytopes_%dd" % dim,
384
desc="Reflexive polytope %4d")
385
for n, p in enumerate(rp):
386
# Data files have normal form of reflexive polytopes
387
p._normal_form = p._vertices
388
# Prevents dimension computation later
389
p._dim = dim
390
# Compute "fast" data in one call to PALP
391
all_polars(rp)
392
all_points(rp + [p._polar for p in rp])
393
# TODO: improve faces representation so that we can uncomment
394
# all_faces(rp)
395
# It adds ~10s for dim=3, which is a bit annoying to wait for.
396
_rp[dim] = rp
397
return _rp[dim]
398
399
400
def is_LatticePolytope(x):
401
r"""
402
Check if ``x`` is a lattice polytope.
403
404
INPUT:
405
406
- ``x`` -- anything.
407
408
OUTPUT:
409
410
- ``True`` if ``x`` is a :class:`lattice polytope <LatticePolytopeClass>`,
411
``False`` otherwise.
412
413
EXAMPLES::
414
415
sage: from sage.geometry.lattice_polytope import is_LatticePolytope
416
sage: is_LatticePolytope(1)
417
False
418
sage: p = LatticePolytope([(1,0), (0,1), (-1,-1)])
419
sage: p
420
A lattice polytope: 2-dimensional, 3 vertices.
421
sage: is_LatticePolytope(p)
422
True
423
"""
424
return isinstance(x, LatticePolytopeClass)
425
426
427
class LatticePolytopeClass(SageObject, collections.Hashable):
428
r"""
429
Class of lattice/reflexive polytopes.
430
431
Use ``LatticePolytope`` for constructing a polytope.
432
"""
433
434
def __init__(self, data, desc, compute_vertices, copy_vertices=True, n=0):
435
r"""
436
Construct a lattice polytope. See ``LatticePolytope``.
437
438
Most tests/examples are also done in LatticePolytope.
439
440
TESTS::
441
442
sage: LatticePolytope(matrix(ZZ,[[1,2,3],[4,5,6]]))
443
A lattice polytope: 1-dimensional, 2 vertices.
444
"""
445
if is_Matrix(data):
446
if data.base_ring() != ZZ:
447
try:
448
data = matrix(ZZ, data)
449
except TypeError:
450
raise ValueError("Points must be integral!\nGiven:\n%s" %data)
451
if desc == None:
452
self._desc = "A lattice polytope"
453
else:
454
self._desc = desc
455
if compute_vertices:
456
self._vertices = data # Temporary assignment
457
self._compute_dim(compute_vertices=True)
458
else:
459
if copy_vertices:
460
self._vertices = data.__copy__()
461
else:
462
self._vertices = data
463
self._vertices.set_immutable()
464
465
elif isinstance(data, file) or isinstance(data, StringIO.StringIO):
466
m = read_palp_matrix(data)
467
self.__init__(m, desc, compute_vertices, False)
468
elif isinstance(data,str):
469
f = open(data)
470
skip_palp_matrix(f, n)
471
self.__init__(f, desc, compute_vertices)
472
f.close()
473
else:
474
raise TypeError, \
475
"Cannot make a polytope from given data!\nData:\n%s" % data
476
477
def __eq__(self, other):
478
r"""
479
Compare ``self`` with ``other``.
480
481
INPUT:
482
483
- ``other`` -- anything.
484
485
OUTPUT:
486
487
- ``True`` if ``other`` is a :class:`lattice polytope
488
<LatticePolytopeClass>` equal to ``self``, ``False`` otherwise.
489
490
.. NOTE::
491
492
Two lattice polytopes are if they have the same vertices listed in
493
the same order.
494
495
TESTS::
496
497
sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)])
498
sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)])
499
sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)])
500
sage: p1 == p1
501
True
502
sage: p1 == p2
503
True
504
sage: p1 is p2
505
False
506
sage: p1 == p3
507
False
508
sage: p1 == 0
509
False
510
"""
511
return (is_LatticePolytope(other)
512
and self._vertices == other._vertices)
513
514
def __hash__(self):
515
r"""
516
Return the hash of ``self``.
517
518
OUTPUT:
519
520
- an integer.
521
522
TESTS::
523
524
sage: o = lattice_polytope.octahedron(3)
525
sage: hash(o) == hash(o)
526
True
527
"""
528
try:
529
return self._hash
530
except AttributeError:
531
self._hash = hash(self._vertices)
532
return self._hash
533
534
def __ne__(self, other):
535
r"""
536
Compare ``self`` with ``other``.
537
538
INPUT:
539
540
- ``other`` -- anything.
541
542
OUTPUT:
543
544
- ``False`` if ``other`` is a :class:`lattice polytope
545
<LatticePolytopeClass>` equal to ``self``, ``True`` otherwise.
546
547
.. NOTE::
548
549
Two lattice polytopes are if they have the same vertices listed in
550
the same order.
551
552
TESTS::
553
554
sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)])
555
sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)])
556
sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)])
557
sage: p1 != p1
558
False
559
sage: p1 != p2
560
False
561
sage: p1 is p2
562
False
563
sage: p1 != p3
564
True
565
sage: p1 != 0
566
True
567
"""
568
return not (self == other)
569
570
def __reduce__(self):
571
r"""
572
Reduction function. Does not store data that can be relatively fast
573
recomputed.
574
575
TESTS::
576
577
sage: o = lattice_polytope.octahedron(3)
578
sage: o.vertices() == loads(o.dumps()).vertices()
579
True
580
"""
581
state = self.__dict__.copy()
582
state.pop('_vertices')
583
state.pop('_desc')
584
state.pop('_distances', None)
585
state.pop('_skeleton', None)
586
if state.has_key('_points'):
587
state['_npoints'] = state.pop('_points').ncols()
588
return (LatticePolytope, (self._vertices, self._desc, False, False), state)
589
590
def __setstate__(self, state):
591
r"""
592
Restores the state of pickled polytope.
593
594
TESTS::
595
596
sage: o = lattice_polytope.octahedron(3)
597
sage: o.vertices() == loads(o.dumps()).vertices()
598
True
599
"""
600
self.__dict__.update(state)
601
if state.has_key('_faces'): # Faces do not remember polytopes
602
for d_faces in self._faces:
603
for face in d_faces:
604
face._polytope = self
605
606
def _compute_dim(self, compute_vertices):
607
r"""
608
Compute the dimension of this polytope and its vertices, if necessary.
609
610
If ``compute_vertices`` is ``True``, then ``self._vertices`` should
611
contain points whose convex hull will be computed and placed back into
612
``self._vertices``.
613
614
If the dimension of this polytope is not equal to its ambient dimension,
615
auxiliary polytope will be created and stored for using PALP commands.
616
617
TESTS::
618
619
sage: p = LatticePolytope(matrix([1, 2, 3]), compute_vertices=False)
620
sage: p.vertices() # wrong, since these were not vertices
621
[1 2 3]
622
sage: hasattr(p, "_dim")
623
False
624
sage: p._compute_dim(compute_vertices=True)
625
sage: p.vertices()
626
[1 3]
627
sage: p._dim
628
1
629
"""
630
if hasattr(self, "_dim"):
631
return
632
if self._vertices.ncols()==0: # the empty lattice polytope
633
self._dim = -1
634
return
635
if compute_vertices:
636
points = []
637
for point in self._vertices.columns(copy=False):
638
if not point in points:
639
points.append(point)
640
else:
641
points = self._vertices.columns(copy=False)
642
p0 = points[0]
643
if p0 != 0:
644
points = [point - p0 for point in points]
645
N = ZZ**self.ambient_dim()
646
H = N.submodule(points)
647
self._dim = H.rank()
648
if self._dim == 0:
649
if compute_vertices:
650
self._vertices = matrix(p0).transpose()
651
elif self._dim == self.ambient_dim():
652
if compute_vertices:
653
if len(points) < self._vertices.ncols(): # Repeated vertices
654
if p0 != 0:
655
points = [point + p0 for point in points]
656
self._vertices = matrix(ZZ, points).transpose()
657
self._vertices = read_palp_matrix(self.poly_x("v"))
658
else:
659
# Setup auxiliary polytope and maps
660
H = H.saturation()
661
H_points = [H.coordinates(point) for point in points]
662
H_polytope = LatticePolytope(matrix(ZZ, H_points).transpose(),
663
compute_vertices=True)
664
self._sublattice = H
665
self._sublattice_polytope = H_polytope
666
self._embedding_matrix = H.basis_matrix().transpose()
667
self._shift_vector = p0
668
if compute_vertices:
669
self._vertices = self._embed(H_polytope.vertices())
670
# In order to use facet normals obtained from subpolytopes, we
671
# need the following (see Trac #9188).
672
M = self._embedding_matrix
673
# Basis for the ambient space with spanned subspace in front
674
basis = M.columns() + M.integer_kernel().basis()
675
# Let's represent it as columns of a matrix
676
basis = matrix(basis).transpose()
677
# Absolute value helps to keep normals "inner"
678
self._dual_embedding_scale = abs(basis.det())
679
dualbasis = matrix(ZZ, self._dual_embedding_scale * basis.inverse())
680
self._dual_embedding_matrix = dualbasis.submatrix(0,0,M.ncols())
681
682
def _compute_faces(self):
683
r"""
684
Compute and cache faces of this polytope.
685
686
If this polytope is reflexive and the polar polytope was already
687
computed, computes faces of both in order to save time and preserve
688
the one-to-one correspondence between the faces of this polytope of
689
dimension d and the faces of the polar polytope of codimension
690
d+1.
691
692
TESTS::
693
694
sage: o = lattice_polytope.octahedron(3)
695
sage: v = o.__dict__.pop("_faces", None) # faces may be cached already
696
sage: o.__dict__.has_key("_faces")
697
False
698
sage: o._compute_faces()
699
sage: o.__dict__.has_key("_faces")
700
True
701
702
Check that Trac 8934 is fixed::
703
704
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0, 0],
705
... [0, 1, 0, 0, -1, 0, 0],
706
... [0, 0, 0, 0, 0, 0, 0]])
707
...
708
sage: p = LatticePolytope(m)
709
sage: p._compute_faces()
710
sage: p.facets()
711
[[0, 3], [2, 3], [0, 1], [1, 2]]
712
"""
713
if hasattr(self, "_constructed_as_polar"):
714
# "Polar of polar polytope" computed by poly.x may have the
715
# order of vertices different from the original polytope. Thus,
716
# in order to have consistent enumeration of vertices and faces
717
# we must run poly.x on the original polytope.
718
self._copy_faces(self._polar, reverse=True)
719
elif hasattr(self, "_constructed_as_affine_transform"):
720
self._copy_faces(self._original)
721
elif self.dim() <= 0:
722
self._faces = []
723
else:
724
self._read_faces(self.poly_x("i", reduce_dimension=True))
725
726
def _compute_hodge_numbers(self):
727
r"""
728
Compute Hodge numbers for the current nef_partitions.
729
730
This function (currently) always raises an exception directing to
731
use another way for computing Hodge numbers.
732
733
TESTS::
734
735
sage: o = lattice_polytope.octahedron(3)
736
sage: o._compute_hodge_numbers()
737
Traceback (most recent call last):
738
...
739
NotImplementedError: use nef_partitions(hodge_numbers=True)!
740
"""
741
raise NotImplementedError, "use nef_partitions(hodge_numbers=True)!"
742
743
def _copy_faces(self, other, reverse=False):
744
r"""
745
Copy facial structure of another polytope.
746
747
This may be necessary for preserving natural correspondence of faces,
748
e.g. between this polytope and its multiple or translation. In case of
749
reflexive polytopes, faces of this polytope and its polar are in
750
inclusion reversing bijection.
751
752
.. note::
753
754
This function does not perform any checks that this operation makes
755
sense.
756
757
INPUT:
758
759
- ``other`` - another LatticePolytope, whose facial structure will be
760
copied
761
762
- ``reverse`` - (default: False) if True, the facial structure of the
763
other polytope will be reversed, i.e. vertices will correspond to
764
facets etc.
765
766
TESTS::
767
768
sage: o = lattice_polytope.octahedron(2)
769
sage: p = LatticePolytope(o.vertices())
770
sage: p._copy_faces(o)
771
sage: str(o.faces()) == str(p.faces())
772
True
773
sage: c = o.polar()
774
sage: p._copy_faces(c, reverse=True)
775
sage: str(o.faces()) == str(p.faces())
776
True
777
"""
778
self._faces = Sequence([], cr=True)
779
if reverse:
780
for d_faces in reversed(other.faces()):
781
self._faces.append([_PolytopeFace(self, f._facets, f._vertices)
782
for f in d_faces])
783
else:
784
for d_faces in other.faces():
785
self._faces.append([_PolytopeFace(self, f._vertices, f._facets)
786
for f in d_faces])
787
self._faces.set_immutable()
788
789
def _embed(self, data):
790
r"""
791
Embed given point(s) into the ambient space of this polytope.
792
793
INPUT:
794
795
- ``data`` - point or matrix of points (as columns) in the affine
796
subspace spanned by this polytope
797
798
OUTPUT: The same point(s) in the coordinates of the ambient space of
799
this polytope.
800
801
TESTS::
802
803
sage: o = lattice_polytope.octahedron(3)
804
sage: o._embed(o.vertices()) == o.vertices()
805
True
806
sage: m = matrix(ZZ, 3)
807
sage: m[0, 0] = 1
808
sage: m[1, 1] = 1
809
sage: p = o.affine_transform(m)
810
sage: p._embed((0,0))
811
(1, 0, 0)
812
"""
813
if self.ambient_dim() == self.dim():
814
return data
815
elif is_Matrix(data):
816
r = self._embedding_matrix * data
817
for i, col in enumerate(r.columns(copy=False)):
818
r.set_column(i, col + self._shift_vector)
819
return r
820
else:
821
return self._embedding_matrix * vector(QQ, data) + self._shift_vector
822
823
def _face_compute_points(self, face):
824
r"""
825
Compute and cache lattice points of the given ``face``
826
of this polytope.
827
828
TESTS::
829
830
sage: o = lattice_polytope.octahedron(3)
831
sage: e = o.faces(dim=1)[0]
832
sage: v = e.__dict__.pop("_points", None) # points may be cached already
833
sage: e.__dict__.has_key("_points")
834
False
835
sage: o._face_compute_points(e)
836
sage: e.__dict__.has_key("_points")
837
True
838
"""
839
m = self.distances().matrix_from_rows(face._facets)
840
cols = m.columns(copy=False)
841
points = [i for i, col in enumerate(cols) if sum(col) == 0]
842
face._points = Sequence(points, int, check=False)
843
face._points.set_immutable()
844
845
def _face_split_points(self, face):
846
r"""
847
Compute and cache boundary and interior lattice points of
848
``face``.
849
850
TESTS::
851
852
sage: c = lattice_polytope.octahedron(3).polar()
853
sage: f = c.facets()[0]
854
sage: v = f.__dict__.pop("_interior_points", None)
855
sage: f.__dict__.has_key("_interior_points")
856
False
857
sage: v = f.__dict__.pop("_boundary_points", None)
858
sage: f.__dict__.has_key("_boundary_points")
859
False
860
sage: c._face_split_points(f)
861
sage: f._interior_points
862
[10]
863
sage: f._boundary_points
864
[0, 2, 4, 6, 8, 9, 11, 12]
865
sage: f.points()
866
[0, 2, 4, 6, 8, 9, 10, 11, 12]
867
868
Vertices don't have boundary::
869
870
sage: f = c.faces(dim=0)[0]
871
sage: c._face_split_points(f)
872
sage: len(f._interior_points)
873
1
874
sage: len(f._boundary_points)
875
0
876
"""
877
if face.npoints() == 1: # Vertex
878
face._interior_points = face.points()
879
face._boundary_points = Sequence([], int, check=False)
880
else:
881
face._interior_points = Sequence([], int, check=False)
882
face._boundary_points = Sequence(face.points()[:face.nvertices()], int,
883
check=False)
884
non_vertices = face.points()[face.nvertices():]
885
distances = self.distances()
886
other_facets = [i for i in range(self.nfacets())
887
if not i in face._facets]
888
for p in non_vertices:
889
face._interior_points.append(p)
890
for f in other_facets:
891
if distances[f, p] == 0:
892
face._interior_points.pop()
893
face._boundary_points.append(p)
894
break
895
face._interior_points.set_immutable()
896
face._boundary_points.set_immutable()
897
898
def _latex_(self):
899
r"""
900
Return the latex representation of self.
901
902
OUTPUT:
903
904
- string
905
906
EXAMPLES:
907
908
Arbitrary lattice polytopes are printed as `\Delta^d`, where `d` is
909
the (actual) dimension of the polytope::
910
911
sage: LatticePolytope(matrix(2, [1, 0, 1, 0]))._latex_()
912
'\\Delta^{1}'
913
914
For reflexive polytopes the output is the same... ::
915
916
sage: o = lattice_polytope.octahedron(2)
917
sage: o._latex_()
918
'\\Delta^{2}'
919
920
... unless they are written in the normal from in which case the index
921
in the internal database is printed as a subscript::
922
923
sage: o.vertices()
924
[ 1 0 -1 0]
925
[ 0 1 0 -1]
926
sage: o = LatticePolytope(o.normal_form())
927
sage: o.vertices()
928
[ 1 0 0 -1]
929
[ 0 1 -1 0]
930
sage: o._latex_()
931
'\\Delta^{2}_{3}'
932
"""
933
if (self.dim() in (2, 3)
934
and self.is_reflexive()
935
and self.normal_form() == self.vertices()):
936
return r"\Delta^{%d}_{%d}" % (self.dim(), self.index())
937
else:
938
return r"\Delta^{%d}" % self.dim()
939
940
def _palp(self, command, reduce_dimension=False):
941
r"""
942
Run ``command`` on vertices of this polytope.
943
944
Returns the output of ``command`` as a string.
945
946
.. note::
947
948
PALP cannot be called for polytopes that do not span the ambient space.
949
If you specify ``reduce_dimension=True`` argument, PALP will be
950
called for vertices of this polytope in some basis of the affine space
951
it spans.
952
953
TESTS::
954
955
sage: o = lattice_polytope.octahedron(3)
956
sage: o._palp("poly.x -f")
957
'M:7 6 N:27 8 Pic:17 Cor:0\n'
958
sage: print o._palp("nef.x -f -N -p") # random time information
959
M:27 8 N:7 6 codim=2 #part=5
960
H:[0] P:0 V:2 4 5 0sec 0cpu
961
H:[0] P:2 V:3 4 5 0sec 0cpu
962
H:[0] P:3 V:4 5 0sec 0cpu
963
np=3 d:1 p:1 0sec 0cpu
964
965
sage: p = LatticePolytope(matrix([1]))
966
sage: p._palp("poly.x -f")
967
Traceback (most recent call last):
968
...
969
ValueError: Cannot run "poly.x -f" for the zero-dimensional polytope!
970
Polytope: A lattice polytope: 0-dimensional, 1 vertices.
971
972
sage: m = matrix(ZZ, [[1, 0, -1, 0],
973
... [0, 1, 0, -1],
974
... [0, 0, 0, 0]])
975
...
976
sage: p = LatticePolytope(m)
977
sage: p._palp("poly.x -f")
978
Traceback (most recent call last):
979
...
980
ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!
981
sage: p._palp("poly.x -f", reduce_dimension=True)
982
'M:5 4 F:4\n'
983
"""
984
if self.dim() <= 0:
985
raise ValueError, ("Cannot run \"%s\" for the zero-dimensional "
986
+ "polytope!\nPolytope: %s") % (command, self)
987
if self.dim() < self.ambient_dim() and not reduce_dimension:
988
raise ValueError(("Cannot run PALP for a %d-dimensional polytope " +
989
"in a %d-dimensional space!") % (self.dim(), self.ambient_dim()))
990
if _always_use_files:
991
fn = _palp(command, [self], reduce_dimension)
992
f = open(fn)
993
result = f.read()
994
f.close()
995
os.remove(fn)
996
else:
997
if _palp_dimension is not None:
998
dot = command.find(".")
999
command = (command[:dot] + "-%dd" % _palp_dimension +
1000
command[dot:])
1001
p = subprocess.Popen(command, shell=True, bufsize=2048,
1002
stdin=subprocess.PIPE,
1003
stdout=subprocess.PIPE,
1004
stderr=subprocess.PIPE,
1005
close_fds=True)
1006
stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr)
1007
write_palp_matrix(self._pullback(self._vertices), stdin)
1008
stdin.close()
1009
err = stderr.read()
1010
if len(err) > 0:
1011
raise RuntimeError, ("Error executing \"%s\" for the given polytope!"
1012
+ "\nPolytope: %s\nVertices:\n%s\nOutput:\n%s") % (command,
1013
self, self.vertices(), err)
1014
result = stdout.read()
1015
# We program around an issue with subprocess (this so far seems to
1016
# only be an issue on Cygwin).
1017
try:
1018
p.terminate()
1019
except OSError:
1020
pass
1021
if (not result or
1022
"!" in result or
1023
"failed." in result or
1024
"increase" in result or
1025
"Unable" in result):
1026
raise ValueError, ("Error executing \"%s\" for the given polytope!"
1027
+ "\nPolytope: %s\nVertices:\n%s\nOutput:\n%s") % (command,
1028
self, self.vertices(), result)
1029
return result
1030
1031
def _pullback(self, data):
1032
r"""
1033
Pull back given point(s) to the affine subspace spanned by this polytope.
1034
1035
INPUT:
1036
1037
- ``data`` - rational point or matrix of points (as columns) in the
1038
ambient space
1039
1040
OUTPUT: The same point(s) in the coordinates of the affine subspace
1041
space spanned by this polytope.
1042
1043
TESTS::
1044
1045
sage: o = lattice_polytope.octahedron(3)
1046
sage: o._pullback(o.vertices()) == o.vertices()
1047
True
1048
sage: m = matrix(ZZ, 3)
1049
sage: m[0, 0] = 1
1050
sage: m[1, 1] = 1
1051
sage: p = o.affine_transform(m)
1052
sage: p._pullback((0, 0, 0))
1053
[-1, 0]
1054
"""
1055
if self.ambient_dim() == self.dim():
1056
return data
1057
elif data is self._vertices:
1058
return self._sublattice_polytope._vertices
1059
elif is_Matrix(data):
1060
r = matrix([self._pullback(col)
1061
for col in data.columns(copy=False)]).transpose()
1062
return r
1063
else:
1064
data = vector(QQ, data)
1065
return self._sublattice.coordinates(data - self._shift_vector)
1066
1067
def _read_equations(self, data):
1068
r"""
1069
Read equations of facets/vertices of polar polytope from string or
1070
file.
1071
1072
TESTS: For a reflexive polytope construct the polar polytope::
1073
1074
sage: p = LatticePolytope(matrix(ZZ,2,3,[1,0,-1,0,1,-1]))
1075
sage: p.vertices()
1076
[ 1 0 -1]
1077
[ 0 1 -1]
1078
sage: s = p.poly_x("e")
1079
sage: print s
1080
3 2 Vertices of P-dual <-> Equations of P
1081
2 -1
1082
-1 2
1083
-1 -1
1084
sage: p.__dict__.has_key("_polar")
1085
False
1086
sage: p._read_equations(s)
1087
sage: p._polar._vertices
1088
[ 2 -1 -1]
1089
[-1 2 -1]
1090
1091
For a non-reflexive polytope cache facet equations::
1092
1093
sage: p = LatticePolytope(matrix(ZZ,2,3,[1,0,-1,0,2,-3]))
1094
sage: p.vertices()
1095
[ 1 0 -1]
1096
[ 0 2 -3]
1097
sage: p.__dict__.has_key("_facet_normals")
1098
False
1099
sage: p.__dict__.has_key("_facet_constants")
1100
False
1101
sage: s = p.poly_x("e")
1102
sage: print s
1103
3 2 Equations of P
1104
5 -1 2
1105
-2 -1 2
1106
-3 2 3
1107
sage: p._read_equations(s)
1108
sage: p._facet_normals
1109
[ 5 -1]
1110
[-2 -1]
1111
[-3 2]
1112
sage: p._facet_constants
1113
(2, 2, 3)
1114
"""
1115
if isinstance(data, str):
1116
f = StringIO.StringIO(data)
1117
self._read_equations(f)
1118
f.close()
1119
return
1120
if hasattr(self, "_is_reflexive"):
1121
# If it is already known that this polytope is reflexive, its
1122
# polar (whose vertices are equations of facets of this one)
1123
# is already computed and there is no need to read equations
1124
# of facets of this polytope. Moreover, doing so can corrupt
1125
# data if this polytope was constructed as polar. Skip input.
1126
skip_palp_matrix(data)
1127
return
1128
pos = data.tell()
1129
line = data.readline()
1130
self._is_reflexive = line.find("Vertices of P-dual") != -1
1131
if self._is_reflexive:
1132
data.seek(pos)
1133
self._polar = LatticePolytope(read_palp_matrix(data),
1134
"A polytope polar to " + str(self._desc),
1135
copy_vertices=False, compute_vertices=False)
1136
self._polar._dim = self._dim
1137
self._polar._is_reflexive = True
1138
self._polar._constructed_as_polar = True
1139
self._polar._polar = self
1140
else:
1141
normals = []
1142
constants = []
1143
d = self.dim()
1144
for i in range(int(line.split()[0])):
1145
line = data.readline()
1146
numbers = [int(number) for number in line.split()]
1147
constants.append(numbers.pop())
1148
normals.append(numbers)
1149
self._facet_normals = matrix(ZZ, normals)
1150
self._facet_constants = vector(ZZ, constants)
1151
self._facet_normals.set_immutable()
1152
1153
def _read_faces(self, data):
1154
r"""
1155
Read faces information from string or file.
1156
1157
TESTS::
1158
1159
sage: o = lattice_polytope.octahedron(3)
1160
sage: s = o.poly_x("i")
1161
sage: print s
1162
Incidences as binary numbers [F-vector=(6 12 8)]:
1163
v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j
1164
v[0]: 100000 000010 000001 001000 010000 000100
1165
v[1]: 100010 100001 000011 101000 001010 110000 010001 011000 000110 000101 001100 010100
1166
v[2]: 100011 101010 110001 111000 000111 001110 010101 011100
1167
f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j
1168
f[0]: 00001111 00110011 01010101 10101010 11001100 11110000
1169
f[1]: 00000011 00000101 00010001 00001010 00100010 00001100 01000100 10001000 00110000 01010000 10100000 11000000
1170
f[2]: 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000
1171
sage: v = o.__dict__.pop("_faces", None)
1172
sage: o.__dict__.has_key("_faces")
1173
False
1174
sage: o._read_faces(s)
1175
sage: o._faces
1176
[
1177
[[0], [1], [2], [3], [4], [5]],
1178
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
1179
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
1180
]
1181
1182
Cannot be used for "polar polytopes," their faces are constructed
1183
from faces of the original one to preserve facial duality.
1184
1185
::
1186
1187
sage: c = o.polar()
1188
sage: s = c.poly_x("i")
1189
sage: print s
1190
Incidences as binary numbers [F-vector=(8 12 6)]:
1191
v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j
1192
v[0]: 00010000 00000001 01000000 00000100 00100000 00000010 10000000 00001000
1193
v[1]: 00010001 01010000 00000101 01000100 00110000 00000011 00100010 11000000 10100000 00001100 00001010 10001000
1194
v[2]: 01010101 00110011 11110000 00001111 11001100 10101010
1195
f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j
1196
f[0]: 000111 001011 010101 011001 100110 101010 110100 111000
1197
f[1]: 000011 000101 001001 010001 000110 001010 100010 010100 100100 011000 101000 110000
1198
f[2]: 000001 000010 000100 001000 010000 100000
1199
sage: c._read_faces(s)
1200
Traceback (most recent call last):
1201
...
1202
ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!
1203
"""
1204
if isinstance(data, str):
1205
f = StringIO.StringIO(data)
1206
self._read_faces(f)
1207
f.close()
1208
return
1209
try:
1210
if self._constructed_as_polar:
1211
raise ValueError, ("Cannot read face structure for a polytope "
1212
+ "constructed as polar, use _compute_faces!")
1213
except AttributeError:
1214
pass
1215
data.readline()
1216
v = _read_poly_x_incidences(data, self.dim())
1217
f = _read_poly_x_incidences(data, self.dim())
1218
self._faces = Sequence([], cr=True)
1219
for i in range(len(v)):
1220
self._faces.append([_PolytopeFace(self, vertices, facets)
1221
for vertices, facets in zip(v[i], f[i])])
1222
# Zero-dimensional faces (i.e. vertices) from poly.x can be in "random"
1223
# order, so that the lists of corresponding facets are in increasing
1224
# order.
1225
# While this may be convenient for something, it is quite confusing to
1226
# have p.faces(dim=0)[0].vertices() == [5], which means "the 5th vertex
1227
# spans the 0th 0-dimensional face" and, on the polar side, "the 0th
1228
# facet is described by the 5th equation."
1229
# The next line sorts 0-dimensional faces to make these enumerations
1230
# more transparent.
1231
self._faces[0].sort(cmp = lambda x,y: cmp(x._vertices[0], y._vertices[0]))
1232
self._faces.set_immutable()
1233
1234
def _read_nef_partitions(self, data):
1235
r"""
1236
Read nef-partitions of ``self`` from ``data``.
1237
1238
INPUT:
1239
1240
- ``data`` -- a string or a file.
1241
1242
OUTPUT:
1243
1244
- none.
1245
1246
TESTS::
1247
1248
sage: o = lattice_polytope.octahedron(3)
1249
sage: s = o.nef_x("-p -N -Lv")
1250
sage: print s # random time values
1251
M:27 8 N:7 6 codim=2 #part=5
1252
3 6 Vertices in N-lattice:
1253
1 0 0 -1 0 0
1254
0 1 0 0 -1 0
1255
0 0 1 0 0 -1
1256
------------------------------
1257
1 0 0 1 0 0 d=2 codim=2
1258
0 1 0 0 1 0 d=2 codim=2
1259
0 0 1 0 0 1 d=2 codim=2
1260
P:0 V:2 4 5 (0 2) (1 1) (2 0) 0sec 0cpu
1261
P:2 V:3 4 5 (1 1) (1 1) (1 1) 0sec 0cpu
1262
P:3 V:4 5 (0 2) (1 1) (1 1) 0sec 0cpu
1263
np=3 d:1 p:1 0sec 0cpu
1264
1265
We make a copy of the octahedron since direct use of this function may
1266
destroy cache integrity and lead so strange effects in other doctests::
1267
1268
sage: o_copy = LatticePolytope(o.vertices())
1269
sage: o_copy.__dict__.has_key("_nef_partitions")
1270
False
1271
sage: o_copy._read_nef_partitions(s)
1272
sage: o_copy._nef_partitions
1273
[
1274
Nef-partition {0, 1, 3} U {2, 4, 5},
1275
Nef-partition {0, 1, 2} U {3, 4, 5},
1276
Nef-partition {0, 1, 2, 3} U {4, 5}
1277
]
1278
"""
1279
if isinstance(data, str):
1280
f = StringIO.StringIO(data)
1281
self._read_nef_partitions(f)
1282
f.close()
1283
return
1284
nvertices = self.nvertices()
1285
data.readline() # Skip M/N information
1286
nef_vertices = read_palp_matrix(data)
1287
if self.vertices() != nef_vertices:
1288
# It seems that we SHOULD worry about this...
1289
# raise RunTimeError, "nef.x changed the order of vertices!"
1290
trans = [self.vertices().columns().index(v)
1291
for v in nef_vertices.columns()]
1292
for i, p in enumerate(partitions):
1293
partitions[i] = [trans[v] for v in p]
1294
line = data.readline()
1295
if line == "":
1296
raise ValueError, "more data expected!"
1297
partitions = Sequence([], cr=True)
1298
while len(line) > 0 and line.find("np=") == -1:
1299
if line.find("V:") == -1:
1300
line = data.readline()
1301
continue
1302
start = line.find("V:") + 2
1303
end = line.find(" ", start) # Find DOUBLE space
1304
pvertices = Sequence(line[start:end].split(),int)
1305
partition = [0] * nvertices
1306
for v in pvertices:
1307
partition[v] = 1
1308
partition = NefPartition(partition, self)
1309
partition._is_product = line.find(" D ") != -1
1310
partition._is_projection = line.find(" DP ") != -1
1311
# Set the stuff
1312
start = line.find("H:")
1313
if start != -1:
1314
start += 2
1315
end = line.find("[", start)
1316
partition._hodge_numbers = tuple(int(h)
1317
for h in line[start:end].split())
1318
partitions.append(partition)
1319
line = data.readline()
1320
start = line.find("np=")
1321
if start == -1:
1322
raise ValueError, """Wrong data format, cannot find "np="!"""
1323
# The following block seems to be unnecessary (and requires taking into
1324
# account projections/products)
1325
# # Compare the number of found partitions with statistic.
1326
# start += 3
1327
# end = line.find(" ", start)
1328
# np = int(line[start:end])
1329
# if False and np != len(partitions):
1330
# raise ValueError, ("Found %d partitions, expected %d!" %
1331
# (len(partitions), np))
1332
partitions.set_immutable()
1333
self._nef_partitions = partitions
1334
1335
def _repr_(self):
1336
r"""
1337
Return a string representation of this polytope.
1338
1339
TESTS::
1340
1341
sage: o = lattice_polytope.octahedron(3)
1342
sage: o._repr_()
1343
'An octahedron: 3-dimensional, 6 vertices.'
1344
"""
1345
s = str(self._desc)
1346
s += ": %d-dimensional, %d vertices." % (self.dim(), self.nvertices())
1347
return s
1348
1349
def affine_transform(self, a=1, b=0):
1350
r"""
1351
Return a*P+b, where P is this lattice polytope.
1352
1353
.. note::
1354
1355
#. While ``a`` and ``b`` may be rational, the final result must be a
1356
lattice polytope, i.e. all vertices must be integral.
1357
1358
#. If the transform (restricted to this polytope) is bijective, facial
1359
structure will be preserved, e.g. the first facet of the image will be
1360
spanned by the images of vertices which span the first facet of the
1361
original polytope.
1362
1363
INPUT:
1364
1365
- ``a`` - (default: 1) rational scalar or matrix
1366
1367
- ``b`` - (default: 0) rational scalar or vector, scalars are
1368
interpreted as vectors with the same components
1369
1370
EXAMPLES::
1371
1372
sage: o = lattice_polytope.octahedron(2)
1373
sage: o.vertices()
1374
[ 1 0 -1 0]
1375
[ 0 1 0 -1]
1376
sage: o.affine_transform(2).vertices()
1377
[ 2 0 -2 0]
1378
[ 0 2 0 -2]
1379
sage: o.affine_transform(1,1).vertices()
1380
[2 1 0 1]
1381
[1 2 1 0]
1382
sage: o.affine_transform(b=1).vertices()
1383
[2 1 0 1]
1384
[1 2 1 0]
1385
sage: o.affine_transform(b=(1, 0)).vertices()
1386
[ 2 1 0 1]
1387
[ 0 1 0 -1]
1388
sage: a = matrix(QQ, 2, [1/2, 0, 0, 3/2])
1389
sage: o.polar().vertices()
1390
[-1 1 -1 1]
1391
[ 1 1 -1 -1]
1392
sage: o.polar().affine_transform(a, (1/2, -1/2)).vertices()
1393
[ 0 1 0 1]
1394
[ 1 1 -2 -2]
1395
1396
While you can use rational transformation, the result must be integer::
1397
1398
sage: o.affine_transform(a)
1399
Traceback (most recent call last):
1400
...
1401
ValueError: Points must be integral!
1402
Given:
1403
[ 1/2 0 -1/2 0]
1404
[ 0 3/2 0 -3/2]
1405
"""
1406
new_vertices = a * self.vertices()
1407
if b in QQ:
1408
b = vector(QQ, [b]*new_vertices.nrows())
1409
else:
1410
b = vector(QQ, b)
1411
for i, col in enumerate(new_vertices.columns(copy=False)):
1412
new_vertices.set_column(i, col + b)
1413
r = LatticePolytope(new_vertices, compute_vertices=True)
1414
if (a in QQ and a != 0) or r.dim() == self.dim():
1415
r._constructed_as_affine_transform = True
1416
if hasattr(self, "_constructed_as_affine_transform"):
1417
# Prevent long chains of "original-transform"
1418
r._original = self._original
1419
else:
1420
r._original = self
1421
return r
1422
1423
def ambient_dim(self):
1424
r"""
1425
Return the dimension of the ambient space of this polytope.
1426
1427
EXAMPLES: We create a 3-dimensional octahedron and check its
1428
ambient dimension::
1429
1430
sage: o = lattice_polytope.octahedron(3)
1431
sage: o.ambient_dim()
1432
3
1433
"""
1434
return self._vertices.nrows()
1435
1436
def dim(self):
1437
r"""
1438
Return the dimension of this polytope.
1439
1440
EXAMPLES: We create a 3-dimensional octahedron and check its
1441
dimension::
1442
1443
sage: o = lattice_polytope.octahedron(3)
1444
sage: o.dim()
1445
3
1446
1447
Now we create a 2-dimensional diamond in a 3-dimensional space::
1448
1449
sage: m = matrix(ZZ, [[1, 0, -1, 0],
1450
... [0, 1, 0, -1],
1451
... [0, 0, 0, 0]])
1452
...
1453
sage: p = LatticePolytope(m)
1454
sage: p.dim()
1455
2
1456
sage: p.ambient_dim()
1457
3
1458
"""
1459
if not hasattr(self, "_dim"):
1460
self._compute_dim(compute_vertices=False)
1461
return self._dim
1462
1463
def distances(self, point=None):
1464
r"""
1465
Return the matrix of distances for this polytope or distances for
1466
the given point.
1467
1468
The matrix of distances m gives distances m[i,j] between the i-th
1469
facet (which is also the i-th vertex of the polar polytope in the
1470
reflexive case) and j-th point of this polytope.
1471
1472
If point is specified, integral distances from the point to all
1473
facets of this polytope will be computed.
1474
1475
This function CAN be used for polytopes whose dimension is smaller
1476
than the dimension of the ambient space. In this case distances are
1477
computed in the affine subspace spanned by the polytope and if the
1478
point is given, it must be in this subspace.
1479
1480
EXAMPLES: The matrix of distances for a 3-dimensional octahedron::
1481
1482
sage: o = lattice_polytope.octahedron(3)
1483
sage: o.distances()
1484
[0 0 2 2 2 0 1]
1485
[2 0 2 0 2 0 1]
1486
[0 2 2 2 0 0 1]
1487
[2 2 2 0 0 0 1]
1488
[0 0 0 2 2 2 1]
1489
[2 0 0 0 2 2 1]
1490
[0 2 0 2 0 2 1]
1491
[2 2 0 0 0 2 1]
1492
1493
Distances from facets to the point (1,2,3)::
1494
1495
sage: o.distances([1,2,3])
1496
(1, 3, 5, 7, -5, -3, -1, 1)
1497
1498
It is OK to use RATIONAL coordinates::
1499
1500
sage: o.distances([1,2,3/2])
1501
(-1/2, 3/2, 7/2, 11/2, -7/2, -3/2, 1/2, 5/2)
1502
sage: o.distances([1,2,sqrt(2)])
1503
Traceback (most recent call last):
1504
...
1505
TypeError: unable to convert sqrt(2) to a rational
1506
1507
Now we create a non-spanning polytope::
1508
1509
sage: m = matrix(ZZ, [[1, 0, -1, 0],
1510
... [0, 1, 0, -1],
1511
... [0, 0, 0, 0]])
1512
...
1513
sage: p = LatticePolytope(m)
1514
sage: p.distances()
1515
[0 2 2 0 1]
1516
[2 2 0 0 1]
1517
[0 0 2 2 1]
1518
[2 0 0 2 1]
1519
sage: p.distances((1/2, 3, 0))
1520
(7/2, 9/2, -5/2, -3/2)
1521
sage: p.distances((1, 1, 1))
1522
Traceback (most recent call last):
1523
...
1524
ArithmeticError: vector is not in free module
1525
"""
1526
if point != None:
1527
if self.dim() < self.ambient_dim():
1528
return self._sublattice_polytope.distances(self._pullback(point))
1529
elif self.is_reflexive():
1530
return (self._polar._vertices.transpose() * vector(QQ, point)
1531
+ vector(QQ, [1]*self.nfacets()))
1532
else:
1533
return self._facet_normals*vector(QQ, point)+self._facet_constants
1534
if not hasattr(self, "_distances"):
1535
if self.dim() < self.ambient_dim():
1536
return self._sublattice_polytope.distances()
1537
elif self.is_reflexive():
1538
V = self._polar._vertices.transpose()
1539
P = self.points()
1540
self._distances = V * P
1541
for i in range(self._distances.nrows()):
1542
for j in range(self._distances.ncols()):
1543
self._distances[i,j] += 1
1544
else:
1545
self._distances = self._facet_normals * self.points()
1546
for i in range(self._distances.nrows()):
1547
for j in range(self._distances.ncols()):
1548
self._distances[i,j] += self._facet_constants[i]
1549
self._distances.set_immutable()
1550
return self._distances
1551
1552
def edges(self):
1553
r"""
1554
Return the sequence of edges of this polytope (i.e. faces of
1555
dimension 1).
1556
1557
EXAMPLES: The octahedron has 12 edges::
1558
1559
sage: o = lattice_polytope.octahedron(3)
1560
sage: len(o.edges())
1561
12
1562
sage: o.edges()
1563
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
1564
"""
1565
return self.faces(dim=1)
1566
1567
def faces(self, dim=None, codim=None):
1568
r"""
1569
Return the sequence of proper faces of this polytope.
1570
1571
If ``dim`` or ``codim`` are specified,
1572
returns a sequence of faces of the corresponding dimension or
1573
codimension. Otherwise returns the sequence of such sequences for
1574
all dimensions.
1575
1576
EXAMPLES: All faces of the 3-dimensional octahedron::
1577
1578
sage: o = lattice_polytope.octahedron(3)
1579
sage: o.faces()
1580
[
1581
[[0], [1], [2], [3], [4], [5]],
1582
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
1583
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
1584
]
1585
1586
Its faces of dimension one (i.e., edges)::
1587
1588
sage: o.faces(dim=1)
1589
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
1590
1591
Its faces of codimension two (also edges)::
1592
1593
sage: o.faces(codim=2)
1594
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
1595
1596
It is an error to specify both dimension and codimension at the
1597
same time, even if they do agree::
1598
1599
sage: o.faces(dim=1, codim=2)
1600
Traceback (most recent call last):
1601
...
1602
ValueError: Both dim and codim are given!
1603
1604
The only faces of a zero-dimensional polytope are the empty set and
1605
the polytope itself, i.e. it has no proper faces at all::
1606
1607
sage: p = LatticePolytope(matrix([[1]]))
1608
sage: p.vertices()
1609
[1]
1610
sage: p.faces()
1611
[]
1612
1613
In particular, you an exception will be raised if you try to access
1614
faces of the given dimension or codimension, including edges and
1615
facets::
1616
1617
sage: p.facets()
1618
Traceback (most recent call last):
1619
...
1620
IndexError: list index out of range
1621
"""
1622
try:
1623
if dim == None and codim == None:
1624
return self._faces
1625
elif dim != None and codim == None:
1626
return self._faces[dim]
1627
elif dim == None and codim != None:
1628
return self._faces[self.dim()-codim]
1629
else:
1630
raise ValueError, "Both dim and codim are given!"
1631
except AttributeError:
1632
self._compute_faces()
1633
return self.faces(dim, codim)
1634
1635
def facet_constant(self, i):
1636
r"""
1637
Return the constant in the ``i``-th facet inequality of this polytope.
1638
1639
The i-th facet inequality is given by
1640
self.facet_normal(i) * X + self.facet_constant(i) >= 0.
1641
1642
INPUT:
1643
1644
- ``i`` - integer, the index of the facet
1645
1646
OUTPUT:
1647
1648
- integer -- the constant in the ``i``-th facet inequality.
1649
1650
EXAMPLES:
1651
1652
Let's take a look at facets of the octahedron and some polytopes
1653
inside it::
1654
1655
sage: o = lattice_polytope.octahedron(3)
1656
sage: o.vertices()
1657
[ 1 0 0 -1 0 0]
1658
[ 0 1 0 0 -1 0]
1659
[ 0 0 1 0 0 -1]
1660
sage: o.facet_normal(0)
1661
(-1, -1, 1)
1662
sage: o.facet_constant(0)
1663
1
1664
sage: m = copy(o.vertices())
1665
sage: m[0,0] = 0
1666
sage: m
1667
[ 0 0 0 -1 0 0]
1668
[ 0 1 0 0 -1 0]
1669
[ 0 0 1 0 0 -1]
1670
sage: p = LatticePolytope(m)
1671
sage: p.facet_normal(0)
1672
(-1, 0, 0)
1673
sage: p.facet_constant(0)
1674
0
1675
sage: m[0,3] = 0
1676
sage: m
1677
[ 0 0 0 0 0 0]
1678
[ 0 1 0 0 -1 0]
1679
[ 0 0 1 0 0 -1]
1680
sage: p = LatticePolytope(m)
1681
sage: p.facet_normal(0)
1682
(0, -1, 1)
1683
sage: p.facet_constant(0)
1684
1
1685
1686
This is a 2-dimensional lattice polytope in a 4-dimensional space::
1687
1688
sage: m = matrix([(1,-1,1,3), (-1,-1,1,3), (0,0,0,0)])
1689
sage: p = LatticePolytope(m.transpose())
1690
sage: p
1691
A lattice polytope: 2-dimensional, 3 vertices.
1692
sage: p.vertices()
1693
[ 1 -1 0]
1694
[-1 -1 0]
1695
[ 1 1 0]
1696
[ 3 3 0]
1697
sage: fns = [p.facet_normal(i) for i in range(p.nfacets())]
1698
sage: fns
1699
[(11, -1, 1, 3), (-11, -1, 1, 3), (0, 1, -1, -3)]
1700
sage: fcs = [p.facet_constant(i) for i in range(p.nfacets())]
1701
sage: fcs
1702
[0, 0, 11]
1703
1704
Now we manually compute the distance matrix of this polytope. Since it
1705
is a triangle, each line (corresponding to a facet) should have two
1706
zeros (vertices of the corresponding facet) and one positive number
1707
(since our normals are inner)::
1708
1709
sage: matrix([[fns[i] * p.vertex(j) + fcs[i]
1710
... for j in range(p.nvertices())]
1711
... for i in range(p.nfacets())])
1712
[22 0 0]
1713
[ 0 22 0]
1714
[ 0 0 11]
1715
"""
1716
if self.is_reflexive():
1717
return 1
1718
elif self.ambient_dim() == self.dim():
1719
return self._facet_constants[i]
1720
else:
1721
return (self._sublattice_polytope.facet_constant(i)
1722
* self._dual_embedding_scale
1723
- self.facet_normal(i) * self._shift_vector)
1724
1725
def facet_normal(self, i):
1726
r"""
1727
Return the inner normal to the ``i``-th facet of this polytope.
1728
1729
If this polytope is not full-dimensional, facet normals will be
1730
orthogonal to the integer kernel of the affine subspace spanned by
1731
this polytope.
1732
1733
INPUT:
1734
1735
- ``i`` -- integer, the index of the facet
1736
1737
OUTPUT:
1738
1739
- vectors -- the inner normal of the ``i``-th facet
1740
1741
EXAMPLES:
1742
1743
Let's take a look at facets of the octahedron and some polytopes
1744
inside it::
1745
1746
sage: o = lattice_polytope.octahedron(3)
1747
sage: o.vertices()
1748
[ 1 0 0 -1 0 0]
1749
[ 0 1 0 0 -1 0]
1750
[ 0 0 1 0 0 -1]
1751
sage: o.facet_normal(0)
1752
(-1, -1, 1)
1753
sage: o.facet_constant(0)
1754
1
1755
sage: m = copy(o.vertices())
1756
sage: m[0,0] = 0
1757
sage: m
1758
[ 0 0 0 -1 0 0]
1759
[ 0 1 0 0 -1 0]
1760
[ 0 0 1 0 0 -1]
1761
sage: p = LatticePolytope(m)
1762
sage: p.facet_normal(0)
1763
(-1, 0, 0)
1764
sage: p.facet_constant(0)
1765
0
1766
sage: m[0,3] = 0
1767
sage: m
1768
[ 0 0 0 0 0 0]
1769
[ 0 1 0 0 -1 0]
1770
[ 0 0 1 0 0 -1]
1771
sage: p = LatticePolytope(m)
1772
sage: p.facet_normal(0)
1773
(0, -1, 1)
1774
sage: p.facet_constant(0)
1775
1
1776
1777
Here is an example of a 3-dimensional polytope in a 4-dimensional
1778
space::
1779
1780
sage: m = matrix([(0,0,0,0), (1,1,1,3), (1,-1,1,3), (-1,-1,1,3)])
1781
sage: p = LatticePolytope(m.transpose())
1782
sage: p.vertices()
1783
[ 0 1 1 -1]
1784
[ 0 1 -1 -1]
1785
[ 0 1 1 1]
1786
[ 0 3 3 3]
1787
sage: ker = p.vertices().integer_kernel().matrix()
1788
sage: ker
1789
[ 0 0 3 -1]
1790
sage: [ker * p.facet_normal(i) for i in range(p.nfacets())]
1791
[(0), (0), (0), (0)]
1792
1793
Now we manually compute the distance matrix of this polytope. Since it
1794
is a simplex, each line (corresponding to a facet) should consist of
1795
zeros (indicating generating vertices of the corresponding facet) and
1796
a single positive number (since our normals are inner)::
1797
1798
sage: matrix([[p.facet_normal(i) * p.vertex(j)
1799
... + p.facet_constant(i)
1800
... for j in range(p.nvertices())]
1801
... for i in range(p.nfacets())])
1802
[ 0 0 0 20]
1803
[ 0 0 20 0]
1804
[ 0 20 0 0]
1805
[10 0 0 0]
1806
"""
1807
if self.is_reflexive():
1808
return self.polar().vertex(i)
1809
elif self.ambient_dim() == self.dim():
1810
return self._facet_normals[i]
1811
else:
1812
return ( self._sublattice_polytope.facet_normal(i) *
1813
self._dual_embedding_matrix )
1814
1815
def facets(self):
1816
r"""
1817
Return the sequence of facets of this polytope (i.e. faces of
1818
codimension 1).
1819
1820
EXAMPLES: All facets of the 3-dimensional octahedron::
1821
1822
sage: o = lattice_polytope.octahedron(3)
1823
sage: o.facets()
1824
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
1825
1826
Facets are the same as faces of codimension one::
1827
1828
sage: o.facets() is o.faces(codim=1)
1829
True
1830
"""
1831
return self.faces(codim=1)
1832
1833
# Dictionaries of normal forms
1834
_rp_dict = [None]*4
1835
1836
def index(self):
1837
r"""
1838
Return the index of this polytope in the internal database of 2- or
1839
3-dimensional reflexive polytopes. Databases are stored in the
1840
directory of the package.
1841
1842
.. note::
1843
1844
The first call to this function for each dimension can take
1845
a few seconds while the dictionary of all polytopes is
1846
constructed, but after that it is cached and fast.
1847
1848
:rtype: integer
1849
1850
EXAMPLES: We check what is the index of the "diamond" in the
1851
database::
1852
1853
sage: o = lattice_polytope.octahedron(2)
1854
sage: o.index()
1855
3
1856
1857
Note that polytopes with the same index are not necessarily the
1858
same::
1859
1860
sage: o.vertices()
1861
[ 1 0 -1 0]
1862
[ 0 1 0 -1]
1863
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
1864
[ 1 0 0 -1]
1865
[ 0 1 -1 0]
1866
1867
But they are in the same `GL(Z^n)` orbit and have the same
1868
normal form::
1869
1870
sage: o.normal_form()
1871
[ 1 0 0 -1]
1872
[ 0 1 -1 0]
1873
sage: lattice_polytope.ReflexivePolytope(2,3).normal_form()
1874
[ 1 0 0 -1]
1875
[ 0 1 -1 0]
1876
"""
1877
if not hasattr(self, "_index"):
1878
if not self.is_reflexive():
1879
raise NotImplementedError, "only reflexive polytopes can be indexed!"
1880
dim = self.dim()
1881
if dim not in [2, 3]:
1882
raise NotImplementedError, "only 2- and 3-dimensional polytopes can be indexed!"
1883
if LatticePolytopeClass._rp_dict[dim] == None:
1884
rp_dict = dict()
1885
for n, p in enumerate(ReflexivePolytopes(dim)):
1886
rp_dict[str(p.normal_form())] = n
1887
LatticePolytopeClass._rp_dict[dim] = rp_dict
1888
self._index = LatticePolytopeClass._rp_dict[dim][str(self.normal_form())]
1889
return self._index
1890
1891
def is_reflexive(self):
1892
r"""
1893
Return True if this polytope is reflexive.
1894
1895
EXAMPLES: The 3-dimensional octahedron is reflexive (and 4318 other
1896
3-polytopes)::
1897
1898
sage: o = lattice_polytope.octahedron(3)
1899
sage: o.is_reflexive()
1900
True
1901
1902
But not all polytopes are reflexive::
1903
1904
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
1905
... [0, 1, 0, 0, -1, 0],
1906
... [0, 0, 0, 0, 0, -1]])
1907
...
1908
sage: p = LatticePolytope(m)
1909
sage: p.is_reflexive()
1910
False
1911
1912
Only full-dimensional polytopes can be reflexive (otherwise the polar
1913
set is not a polytope at all, since it is unbounded)::
1914
1915
sage: m = matrix(ZZ, [[0, 1, 0, -1, 0],
1916
... [0, 0, 1, 0, -1],
1917
... [0, 0, 0, 0, 0]])
1918
...
1919
sage: p = LatticePolytope(m)
1920
sage: p.is_reflexive()
1921
False
1922
"""
1923
# In the last doctest above the origin is added intentionall - it
1924
# causes the sublattice polytope to be reflexive, yet the original
1925
# still should not be one.
1926
if not hasattr(self, "_is_reflexive"):
1927
if self.dim() != self.ambient_dim():
1928
self._is_reflexive = False
1929
else:
1930
# Determine if the polytope is reflexive by computing vertices
1931
# of the dual polytope and save all obtained information.
1932
self._read_equations(self.poly_x("e"))
1933
return self._is_reflexive
1934
1935
def nef_partitions(self, keep_symmetric=False, keep_products=True,
1936
keep_projections=True, hodge_numbers=False):
1937
r"""
1938
Return 2-part nef-partitions of ``self``.
1939
1940
INPUT:
1941
1942
- ``keep_symmetric`` -- (default: ``False``) if ``True``, "-s" option
1943
will be passed to ``nef.x`` in order to keep symmetric partitions,
1944
i.e. partitions related by lattice automorphisms preserving ``self``;
1945
1946
- ``keep_products`` -- (default: ``True``) if ``True``, "-D" option
1947
will be passed to ``nef.x`` in order to keep product partitions,
1948
with corresponding complete intersections being direct products;
1949
1950
- ``keep_projections`` -- (default: ``True``) if ``True``, "-P" option
1951
will be passed to ``nef.x`` in order to keep projection partitions,
1952
i.e. partitions with one of the parts consisting of a single vertex;
1953
1954
- ``hodge_numbers`` -- (default: ``False``) if ``False``, "-p" option
1955
will be passed to ``nef.x`` in order to skip Hodge numbers
1956
computation, which takes a lot of time.
1957
1958
OUTPUT:
1959
1960
- a sequence of :class:`nef-partitions <NefPartition>`.
1961
1962
Type ``NefPartition?`` for definitions and notation.
1963
1964
EXAMPLES:
1965
1966
Nef-partitions of the 4-dimensional octahedron::
1967
1968
sage: o = lattice_polytope.octahedron(4)
1969
sage: o.nef_partitions()
1970
[
1971
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
1972
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
1973
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
1974
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
1975
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
1976
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
1977
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},
1978
Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)
1979
]
1980
1981
Now we omit projections::
1982
1983
sage: o.nef_partitions(keep_projections=False)
1984
[
1985
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
1986
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
1987
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
1988
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
1989
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
1990
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
1991
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7}
1992
]
1993
1994
Currently Hodge numbers cannot be computed for a given nef-partition::
1995
1996
sage: o.nef_partitions()[1].hodge_numbers()
1997
Traceback (most recent call last):
1998
...
1999
NotImplementedError: use nef_partitions(hodge_numbers=True)!
2000
2001
But they can be obtained from ``nef.x`` for all nef-partitions at once.
2002
Partitions will be exactly the same::
2003
2004
sage: o.nef_partitions(hodge_numbers=True) # long time (2s on sage.math, 2011)
2005
[
2006
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
2007
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
2008
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
2009
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
2010
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
2011
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
2012
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},
2013
Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)
2014
]
2015
2016
Now it is possible to get Hodge numbers::
2017
2018
sage: o.nef_partitions(hodge_numbers=True)[1].hodge_numbers()
2019
(20,)
2020
2021
Since nef-partitions are cached, their Hodge numbers are accessible
2022
after the first request, even if you do not specify
2023
``hodge_numbers=True`` anymore::
2024
2025
sage: o.nef_partitions()[1].hodge_numbers()
2026
(20,)
2027
2028
We illustrate removal of symmetric partitions on a diamond::
2029
2030
sage: o = lattice_polytope.octahedron(2)
2031
sage: o.nef_partitions()
2032
[
2033
Nef-partition {0, 2} U {1, 3} (direct product),
2034
Nef-partition {0, 1} U {2, 3},
2035
Nef-partition {0, 1, 2} U {3} (projection)
2036
]
2037
sage: o.nef_partitions(keep_symmetric=True)
2038
[
2039
Nef-partition {0, 1, 3} U {2} (projection),
2040
Nef-partition {0, 2, 3} U {1} (projection),
2041
Nef-partition {0, 3} U {1, 2},
2042
Nef-partition {1, 2, 3} U {0} (projection),
2043
Nef-partition {1, 3} U {0, 2} (direct product),
2044
Nef-partition {2, 3} U {0, 1},
2045
Nef-partition {0, 1, 2} U {3} (projection)
2046
]
2047
2048
Nef-partitions can be computed only for reflexive polytopes::
2049
2050
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
2051
... [0, 1, 0, 0, -1, 0],
2052
... [0, 0, 2, 0, 0, -1]])
2053
...
2054
sage: p = LatticePolytope(m)
2055
sage: p.nef_partitions()
2056
Traceback (most recent call last):
2057
...
2058
ValueError: The given polytope is not reflexive!
2059
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
2060
"""
2061
if not self.is_reflexive():
2062
raise ValueError, ("The given polytope is not reflexive!\n"
2063
+ "Polytope: %s") % self
2064
keys = "-N -V"
2065
if keep_symmetric:
2066
keys += " -s"
2067
if keep_products:
2068
keys += " -D"
2069
if keep_projections:
2070
keys += " -P"
2071
if not hodge_numbers:
2072
keys += " -p"
2073
if hasattr(self, "_npkeys"):
2074
oldkeys = self._npkeys
2075
if oldkeys == keys:
2076
return self._nef_partitions
2077
if not (hodge_numbers and oldkeys.find("-p") != -1
2078
or keep_symmetric and oldkeys.find("-s") == -1
2079
or not keep_symmetric and oldkeys.find("-s") != -1
2080
or keep_projections and oldkeys.find("-P") == -1
2081
or keep_products and oldkeys.find("-D") == -1):
2082
# Select only necessary partitions
2083
return Sequence([p for p in self._nef_partitions
2084
if (keep_projections or not p._is_projection)
2085
and (keep_products or not p._is_product)],
2086
cr=True, check=False)
2087
self._read_nef_partitions(self.nef_x(keys))
2088
self._npkeys = keys
2089
return self._nef_partitions
2090
2091
def nef_x(self, keys):
2092
r"""
2093
Run nef.x with given ``keys`` on vertices of this
2094
polytope.
2095
2096
INPUT:
2097
2098
2099
- ``keys`` - a string of options passed to nef.x. The
2100
key "-f" is added automatically.
2101
2102
2103
OUTPUT: the output of nef.x as a string.
2104
2105
EXAMPLES: This call is used internally for computing
2106
nef-partitions::
2107
2108
sage: o = lattice_polytope.octahedron(3)
2109
sage: s = o.nef_x("-N -V -p")
2110
sage: s # output contains random time
2111
M:27 8 N:7 6 codim=2 #part=5
2112
3 6 Vertices of P:
2113
1 0 0 -1 0 0
2114
0 1 0 0 -1 0
2115
0 0 1 0 0 -1
2116
P:0 V:2 4 5 0sec 0cpu
2117
P:2 V:3 4 5 0sec 0cpu
2118
P:3 V:4 5 0sec 0cpu
2119
np=3 d:1 p:1 0sec 0cpu
2120
"""
2121
return self._palp("nef.x -f " + keys)
2122
2123
def nfacets(self):
2124
r"""
2125
Return the number of facets of this polytope.
2126
2127
EXAMPLES: The number of facets of the 3-dimensional octahedron::
2128
2129
sage: o = lattice_polytope.octahedron(3)
2130
sage: o.nfacets()
2131
8
2132
2133
The number of facets of an interval is 2::
2134
2135
sage: LatticePolytope(matrix([1,2])).nfacets()
2136
2
2137
2138
Now consider a 2-dimensional diamond in a 3-dimensional space::
2139
2140
sage: m = matrix(ZZ, [[1, 0, -1, 0],
2141
... [0, 1, 0, -1],
2142
... [0, 0, 0, 0]])
2143
...
2144
sage: p = LatticePolytope(m)
2145
sage: p.nfacets()
2146
4
2147
"""
2148
if not hasattr(self, "_nfacets"):
2149
if self.is_reflexive():
2150
self._nfacets = self.polar().nvertices()
2151
elif self.dim() == self.ambient_dim():
2152
self._nfacets = self._facet_normals.nrows()
2153
elif self.dim() <= 0:
2154
self._nfacets = 0
2155
else:
2156
self._nfacets = self._sublattice_polytope.nfacets()
2157
return self._nfacets
2158
2159
def normal_form(self):
2160
r"""
2161
Return the normal form of vertices of the polytope.
2162
2163
Two full-dimensional lattice polytopes are in the same GL(Z)-orbit if
2164
and only if their normal forms are the same. Normal form is not defined
2165
and thus cannot be used for polytopes whose dimension is smaller than
2166
the dimension of the ambient space.
2167
2168
EXAMPLES: We compute the normal form of the "diamond"::
2169
2170
sage: o = lattice_polytope.octahedron(2)
2171
sage: o.vertices()
2172
[ 1 0 -1 0]
2173
[ 0 1 0 -1]
2174
sage: o.normal_form()
2175
[ 1 0 0 -1]
2176
[ 0 1 -1 0]
2177
2178
The diamond is the 3rd polytope in the internal database...
2179
2180
::
2181
2182
sage: o.index()
2183
3
2184
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
2185
[ 1 0 0 -1]
2186
[ 0 1 -1 0]
2187
2188
It is not possible to compute normal forms for polytopes which do not
2189
span the space::
2190
2191
sage: m = matrix(ZZ, [[1, 0, -1, 0],
2192
... [0, 1, 0, -1],
2193
... [0, 0, 0, 0]])
2194
...
2195
sage: p = LatticePolytope(m)
2196
sage: p.normal_form()
2197
Traceback (most recent call last):
2198
...
2199
ValueError: Normal form is not defined for a 2-dimensional polytope in a 3-dimensional space!
2200
"""
2201
if not hasattr(self, "_normal_form"):
2202
if self.dim() < self.ambient_dim():
2203
raise ValueError(
2204
("Normal form is not defined for a %d-dimensional polytope " +
2205
"in a %d-dimensional space!") % (self.dim(), self.ambient_dim()))
2206
self._normal_form = read_palp_matrix(self.poly_x("N"))
2207
self._normal_form.set_immutable()
2208
return self._normal_form
2209
2210
def npoints(self):
2211
r"""
2212
Return the number of lattice points of this polytope.
2213
2214
EXAMPLES: The number of lattice points of the 3-dimensional
2215
octahedron and its polar cube::
2216
2217
sage: o = lattice_polytope.octahedron(3)
2218
sage: o.npoints()
2219
7
2220
sage: cube = o.polar()
2221
sage: cube.npoints()
2222
27
2223
"""
2224
try:
2225
return self._npoints
2226
except AttributeError:
2227
return self.points().ncols()
2228
2229
def nvertices(self):
2230
r"""
2231
Return the number of vertices of this polytope.
2232
2233
EXAMPLES: The number of vertices of the 3-dimensional octahedron
2234
and its polar cube::
2235
2236
sage: o = lattice_polytope.octahedron(3)
2237
sage: o.nvertices()
2238
6
2239
sage: cube = o.polar()
2240
sage: cube.nvertices()
2241
8
2242
"""
2243
return self._vertices.ncols()
2244
2245
def origin(self):
2246
r"""
2247
Return the index of the origin in the list of points of self.
2248
2249
OUTPUT:
2250
2251
- integer if the origin belongs to this polytope, ``None`` otherwise.
2252
2253
EXAMPLES::
2254
2255
sage: o = lattice_polytope.octahedron(2)
2256
sage: o.origin()
2257
4
2258
sage: o.point(o.origin())
2259
(0, 0)
2260
2261
sage: p = LatticePolytope(matrix([[1,2]]))
2262
sage: p.points()
2263
[1 2]
2264
sage: print p.origin()
2265
None
2266
2267
Now we make sure that the origin of non-full-dimensional polytopes can
2268
be identified correctly (Trac #10661)::
2269
2270
sage: LatticePolytope([(1,0,0), (-1,0,0)]).origin()
2271
2
2272
"""
2273
if "_origin" not in self.__dict__:
2274
origin = vector(ZZ, self.ambient_dim())
2275
points = self.points().columns(copy=False)
2276
self._origin = points.index(origin) if origin in points else None
2277
return self._origin
2278
2279
def parent(self):
2280
"""
2281
Return the set of all lattice polytopes.
2282
2283
EXAMPLES::
2284
2285
sage: o = lattice_polytope.octahedron(3)
2286
sage: o.parent()
2287
Set of all Lattice Polytopes
2288
"""
2289
return SetOfAllLatticePolytopes
2290
2291
def plot3d(self,
2292
show_facets=True, facet_opacity=0.5, facet_color=(0,1,0),
2293
facet_colors=None,
2294
show_edges=True, edge_thickness=3, edge_color=(0.5,0.5,0.5),
2295
show_vertices=True, vertex_size=10, vertex_color=(1,0,0),
2296
show_points=True, point_size=10, point_color=(0,0,1),
2297
show_vindices=None, vindex_color=(0,0,0),
2298
vlabels=None,
2299
show_pindices=None, pindex_color=(0,0,0),
2300
index_shift=1.1):
2301
r"""
2302
Return a 3d-plot of this polytope.
2303
2304
Polytopes with ambient dimension 1 and 2 will be plotted along x-axis
2305
or in xy-plane respectively. Polytopes of dimension 3 and less with
2306
ambient dimension 4 and greater will be plotted in some basis of the
2307
spanned space.
2308
2309
By default, everything is shown with more or less pretty
2310
combination of size and color parameters.
2311
2312
INPUT: Most of the parameters are self-explanatory:
2313
2314
2315
- ``show_facets`` - (default:True)
2316
2317
- ``facet_opacity`` - (default:0.5)
2318
2319
- ``facet_color`` - (default:(0,1,0))
2320
2321
- ``facet_colors`` - (default:None) if specified, must be a list of
2322
colors for each facet separately, used instead of ``facet_color``
2323
2324
- ``show_edges`` - (default:True) whether to draw
2325
edges as lines
2326
2327
- ``edge_thickness`` - (default:3)
2328
2329
- ``edge_color`` - (default:(0.5,0.5,0.5))
2330
2331
- ``show_vertices`` - (default:True) whether to draw
2332
vertices as balls
2333
2334
- ``vertex_size`` - (default:10)
2335
2336
- ``vertex_color`` - (default:(1,0,0))
2337
2338
- ``show_points`` - (default:True) whether to draw
2339
other poits as balls
2340
2341
- ``point_size`` - (default:10)
2342
2343
- ``point_color`` - (default:(0,0,1))
2344
2345
- ``show_vindices`` - (default:same as
2346
show_vertices) whether to show indices of vertices
2347
2348
- ``vindex_color`` - (default:(0,0,0)) color for
2349
vertex labels
2350
2351
- ``vlabels`` - (default:None) if specified, must be a list of labels
2352
for each vertex, default labels are vertex indicies
2353
2354
- ``show_pindices`` - (default:same as show_points)
2355
whether to show indices of other points
2356
2357
- ``pindex_color`` - (default:(0,0,0)) color for
2358
point labels
2359
2360
- ``index_shift`` - (default:1.1)) if 1, labels are
2361
placed exactly at the corresponding points. Otherwise the label
2362
position is computed as a multiple of the point position vector.
2363
2364
2365
EXAMPLES: The default plot of a cube::
2366
2367
sage: c = lattice_polytope.octahedron(3).polar()
2368
sage: c.plot3d()
2369
2370
Plot without facets and points, shown without the frame::
2371
2372
sage: c.plot3d(show_facets=false,show_points=false).show(frame=False)
2373
2374
Plot with facets of different colors::
2375
2376
sage: c.plot3d(facet_colors=rainbow(c.nfacets(), 'rgbtuple'))
2377
2378
It is also possible to plot lower dimensional polytops in 3D (let's
2379
also change labels of vertices)::
2380
2381
sage: lattice_polytope.octahedron(2).plot3d(vlabels=["A", "B", "C", "D"])
2382
2383
TESTS::
2384
2385
sage: m = matrix([[0,0,0],[0,1,1],[1,0,1],[1,1,0]]).transpose()
2386
sage: p = LatticePolytope(m, compute_vertices=True)
2387
sage: p.plot3d()
2388
"""
2389
dim = self.dim()
2390
amb_dim = self.ambient_dim()
2391
if dim > 3:
2392
raise ValueError, "%d-dimensional polytopes can not be plotted in 3D!" % self.dim()
2393
elif amb_dim > 3:
2394
return self._sublattice_polytope.plot3d(
2395
show_facets, facet_opacity, facet_color,
2396
facet_colors,
2397
show_edges, edge_thickness, edge_color,
2398
show_vertices, vertex_size, vertex_color,
2399
show_points, point_size, point_color,
2400
show_vindices, vindex_color,
2401
vlabels,
2402
show_pindices, pindex_color,
2403
index_shift)
2404
elif dim == 3:
2405
vertices = self.vertices().columns(copy=False)
2406
if show_points or show_pindices:
2407
points = self.points().columns(copy=False)[self.nvertices():]
2408
else:
2409
vertices = [vector(ZZ, list(self.vertex(i))+[0]*(3-amb_dim))
2410
for i in range(self.nvertices())]
2411
if show_points or show_pindices:
2412
points = [vector(ZZ, list(self.point(i))+[0]*(3-amb_dim))
2413
for i in range(self.nvertices(), self.npoints())]
2414
pplot = 0
2415
if show_facets:
2416
if dim == 2:
2417
pplot += IndexFaceSet([self.traverse_boundary()],
2418
vertices, opacity=facet_opacity, rgbcolor=facet_color)
2419
elif dim == 3:
2420
if facet_colors != None:
2421
for i, f in enumerate(self.facets()):
2422
pplot += IndexFaceSet([f.traverse_boundary()],
2423
vertices, opacity=facet_opacity, rgbcolor=facet_colors[i])
2424
else:
2425
pplot += IndexFaceSet([f.traverse_boundary() for f in self.facets()],
2426
vertices, opacity=facet_opacity, rgbcolor=facet_color)
2427
if show_edges:
2428
if dim == 1:
2429
pplot += line3d(vertices, thickness=edge_thickness, rgbcolor=edge_color)
2430
else:
2431
for e in self.edges():
2432
pplot += line3d([vertices[e.vertices()[0]], vertices[e.vertices()[1]]],
2433
thickness=edge_thickness, rgbcolor=edge_color)
2434
if show_vertices:
2435
pplot += point3d(vertices, size=vertex_size, rgbcolor=vertex_color)
2436
if show_vindices == None:
2437
show_vindices = show_vertices
2438
if show_pindices == None:
2439
show_pindices = show_points
2440
if show_vindices or show_pindices:
2441
# Compute the barycenter and shift text of labels away from it
2442
bc = 1/Integer(len(vertices)) * sum(vertices)
2443
if show_vindices:
2444
if vlabels == None:
2445
vlabels = range(len(vertices))
2446
for i,v in enumerate(vertices):
2447
pplot += text3d(vlabels[i], bc+index_shift*(v-bc), rgbcolor=vindex_color)
2448
if show_points and len(points) > 0:
2449
pplot += point3d(points, size=point_size, rgbcolor=point_color)
2450
if show_pindices:
2451
for i, p in enumerate(points):
2452
pplot += text3d(i+self.nvertices(), bc+index_shift*(p-bc), rgbcolor=pindex_color)
2453
return pplot
2454
2455
def show3d(self):
2456
"""
2457
Show a 3d picture of the polytope with default settings and without axes or frame.
2458
2459
See self.plot3d? for more details.
2460
2461
EXAMPLES::
2462
2463
sage: o = lattice_polytope.octahedron(3)
2464
sage: o.show3d()
2465
"""
2466
self.plot3d().show(axis=False, frame=False)
2467
2468
def point(self, i):
2469
r"""
2470
Return the i-th point of this polytope, i.e. the i-th column of the
2471
matrix returned by points().
2472
2473
EXAMPLES: First few points are actually vertices::
2474
2475
sage: o = lattice_polytope.octahedron(3)
2476
sage: o.vertices()
2477
[ 1 0 0 -1 0 0]
2478
[ 0 1 0 0 -1 0]
2479
[ 0 0 1 0 0 -1]
2480
sage: o.point(1)
2481
(0, 1, 0)
2482
2483
The only other point in the octahedron is the origin::
2484
2485
sage: o.point(6)
2486
(0, 0, 0)
2487
sage: o.points()
2488
[ 1 0 0 -1 0 0 0]
2489
[ 0 1 0 0 -1 0 0]
2490
[ 0 0 1 0 0 -1 0]
2491
"""
2492
# Extra checks are made to compensate for a bug in Sage - column accepts any number.
2493
if i < 0:
2494
raise ValueError, "polytopes don't have negative points!"
2495
elif i < self.nvertices():
2496
return self.vertex(i)
2497
elif i < self.npoints():
2498
return self.points().column(i, from_list=True)
2499
else:
2500
raise ValueError, "the polytope does not have %d points!" % (i+1)
2501
2502
def points(self):
2503
r"""
2504
Return all lattice points of this polytope as columns of a matrix.
2505
2506
EXAMPLES: The lattice points of the 3-dimensional octahedron and
2507
its polar cube::
2508
2509
sage: o = lattice_polytope.octahedron(3)
2510
sage: o.points()
2511
[ 1 0 0 -1 0 0 0]
2512
[ 0 1 0 0 -1 0 0]
2513
[ 0 0 1 0 0 -1 0]
2514
sage: cube = o.polar()
2515
sage: cube.points()
2516
[-1 1 -1 1 -1 1 -1 1 -1 -1 -1 -1 -1 0 0 0 0 0 0 0 0 0 1 1 1 1 1]
2517
[-1 -1 1 1 -1 -1 1 1 -1 0 0 0 1 -1 -1 -1 0 0 0 1 1 1 -1 0 0 0 1]
2518
[ 1 1 1 1 -1 -1 -1 -1 0 -1 0 1 0 -1 0 1 -1 0 1 -1 0 1 0 -1 0 1 0]
2519
2520
Lattice points of a 2-dimensional diamond in a 3-dimensional space::
2521
2522
sage: m = matrix(ZZ, [[1, 0, -1, 0],
2523
... [0, 1, 0, -1],
2524
... [0, 0, 0, 0]])
2525
...
2526
sage: p = LatticePolytope(m)
2527
sage: p.points()
2528
[ 1 0 -1 0 0]
2529
[ 0 1 0 -1 0]
2530
[ 0 0 0 0 0]
2531
2532
We check that points of a zero-dimensional polytope can be computed::
2533
2534
sage: p = LatticePolytope(matrix([[1]]))
2535
sage: p.vertices()
2536
[1]
2537
sage: p.points()
2538
[1]
2539
"""
2540
if not hasattr(self, "_points"):
2541
if self.dim() <= 0:
2542
self._points = self._vertices
2543
else:
2544
self._points = self._embed(read_palp_matrix(
2545
self.poly_x("p", reduce_dimension=True)))
2546
self._points.set_immutable()
2547
return self._points
2548
2549
def polar(self):
2550
r"""
2551
Return the polar polytope, if this polytope is reflexive.
2552
2553
EXAMPLES: The polar polytope to the 3-dimensional octahedron::
2554
2555
sage: o = lattice_polytope.octahedron(3)
2556
sage: cube = o.polar()
2557
sage: cube
2558
A polytope polar to An octahedron: 3-dimensional, 8 vertices.
2559
2560
The polar polytope "remembers" the original one::
2561
2562
sage: cube.polar()
2563
An octahedron: 3-dimensional, 6 vertices.
2564
sage: cube.polar().polar() is cube
2565
True
2566
2567
Only reflexive polytopes have polars::
2568
2569
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
2570
... [0, 1, 0, 0, -1, 0],
2571
... [0, 0, 2, 0, 0, -1]])
2572
...
2573
sage: p = LatticePolytope(m)
2574
sage: p.polar()
2575
Traceback (most recent call last):
2576
...
2577
ValueError: The given polytope is not reflexive!
2578
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
2579
"""
2580
if self.is_reflexive():
2581
return self._polar
2582
else:
2583
raise ValueError, ("The given polytope is not reflexive!\n"
2584
+ "Polytope: %s") % self
2585
2586
def poly_x(self, keys, reduce_dimension=False):
2587
r"""
2588
Run poly.x with given ``keys`` on vertices of this
2589
polytope.
2590
2591
INPUT:
2592
2593
2594
- ``keys`` - a string of options passed to poly.x. The
2595
key "f" is added automatically.
2596
2597
- ``reduce_dimension`` - (default: False) if ``True`` and this
2598
polytope is not full-dimensional, poly.x will be called for the
2599
vertices of this polytope in some basis of the spanned affine space.
2600
2601
2602
OUTPUT: the output of poly.x as a string.
2603
2604
EXAMPLES: This call is used for determining if a polytope is
2605
reflexive or not::
2606
2607
sage: o = lattice_polytope.octahedron(3)
2608
sage: print o.poly_x("e")
2609
8 3 Vertices of P-dual <-> Equations of P
2610
-1 -1 1
2611
1 -1 1
2612
-1 1 1
2613
1 1 1
2614
-1 -1 -1
2615
1 -1 -1
2616
-1 1 -1
2617
1 1 -1
2618
2619
Since PALP has limits on different parameters determined during
2620
compilation, the following code is likely to fail, unless you
2621
change default settings of PALP::
2622
2623
sage: BIGO = lattice_polytope.octahedron(7)
2624
sage: BIGO
2625
An octahedron: 7-dimensional, 14 vertices.
2626
sage: BIGO.poly_x("e") # possibly different output depending on your system
2627
Traceback (most recent call last):
2628
...
2629
ValueError: Error executing "poly.x -fe" for the given polytope!
2630
Polytope: An octahedron: 7-dimensional, 14 vertices.
2631
Vertices:
2632
[ 1 0 0 0 0 0 0 -1 0 0 0 0 0 0]
2633
[ 0 1 0 0 0 0 0 0 -1 0 0 0 0 0]
2634
[ 0 0 1 0 0 0 0 0 0 -1 0 0 0 0]
2635
[ 0 0 0 1 0 0 0 0 0 0 -1 0 0 0]
2636
[ 0 0 0 0 1 0 0 0 0 0 0 -1 0 0]
2637
[ 0 0 0 0 0 1 0 0 0 0 0 0 -1 0]
2638
[ 0 0 0 0 0 0 1 0 0 0 0 0 0 -1]
2639
Output:
2640
Please increase POLY_Dmax to at least 7
2641
2642
You cannot call poly.x for polytopes that don't span the space (if you
2643
could, it would crush anyway)::
2644
2645
sage: m = matrix(ZZ, [[1, 0, -1, 0],
2646
... [0, 1, 0, -1],
2647
... [0, 0, 0, 0]])
2648
...
2649
sage: p = LatticePolytope(m)
2650
sage: p.poly_x("e")
2651
Traceback (most recent call last):
2652
...
2653
ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!
2654
2655
But if you know what you are doing, you can call it for the polytope in
2656
some basis of the spanned space::
2657
2658
sage: print p.poly_x("e", reduce_dimension=True)
2659
4 2 Equations of P
2660
-1 1 0
2661
1 1 2
2662
-1 -1 0
2663
1 -1 2
2664
"""
2665
return self._palp("poly.x -f" + keys, reduce_dimension)
2666
2667
def skeleton(self):
2668
r"""
2669
Return the graph of the one-skeleton of this polytope.
2670
2671
EXAMPLES: We construct the one-skeleton graph for the "diamond"::
2672
2673
sage: o = lattice_polytope.octahedron(2)
2674
sage: g = o.skeleton()
2675
sage: g
2676
Graph on 4 vertices
2677
sage: g.edges()
2678
[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]
2679
"""
2680
try:
2681
return self._skeleton
2682
except AttributeError:
2683
skeleton = Graph()
2684
skeleton.add_vertices(self.skeleton_points(1))
2685
for e in self.faces(dim=1):
2686
points = e.ordered_points()
2687
for i in range(len(points)-1):
2688
skeleton.add_edge(points[i], points[i+1])
2689
self._skeleton = skeleton
2690
return skeleton
2691
2692
def skeleton_points(self, k=1):
2693
r"""
2694
Return the increasing list of indices of lattice points in
2695
k-skeleton of the polytope (k is 1 by default).
2696
2697
EXAMPLES: We compute all skeleton points for the cube::
2698
2699
sage: o = lattice_polytope.octahedron(3)
2700
sage: c = o.polar()
2701
sage: c.skeleton_points()
2702
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]
2703
2704
The default was 1-skeleton::
2705
2706
sage: c.skeleton_points(k=1)
2707
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]
2708
2709
0-skeleton just lists all vertices::
2710
2711
sage: c.skeleton_points(k=0)
2712
[0, 1, 2, 3, 4, 5, 6, 7]
2713
2714
2-skeleton lists all points except for the origin (point #17)::
2715
2716
sage: c.skeleton_points(k=2)
2717
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26]
2718
2719
3-skeleton includes all points::
2720
2721
sage: c.skeleton_points(k=3)
2722
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
2723
2724
It is OK to compute higher dimensional skeletons - you will get the
2725
list of all points::
2726
2727
sage: c.skeleton_points(k=100)
2728
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
2729
"""
2730
if k >= self.dim():
2731
return range(self.npoints())
2732
skeleton = set([])
2733
for face in self.faces(dim=k):
2734
skeleton.update(face.points())
2735
skeleton = list(skeleton)
2736
skeleton.sort()
2737
return skeleton
2738
2739
def skeleton_show(self, normal=None):
2740
r"""Show the graph of one-skeleton of this polytope.
2741
Works only for polytopes in a 3-dimensional space.
2742
2743
INPUT:
2744
2745
2746
- ``normal`` - a 3-dimensional vector (can be given as
2747
a list), which should be perpendicular to the screen. If not given,
2748
will be selected randomly (new each time and it may be far from
2749
"nice").
2750
2751
2752
EXAMPLES: Show a pretty picture of the octahedron::
2753
2754
sage: o = lattice_polytope.octahedron(3)
2755
sage: o.skeleton_show([1,2,4])
2756
2757
Does not work for a diamond at the moment::
2758
2759
sage: o = lattice_polytope.octahedron(2)
2760
sage: o.skeleton_show()
2761
Traceback (most recent call last):
2762
...
2763
ValueError: need a polytope in a 3-dimensional space! Got:
2764
An octahedron: 2-dimensional, 4 vertices.
2765
"""
2766
if self.ambient_dim() != 3:
2767
raise ValueError("need a polytope in a 3-dimensional space! Got:\n%s" % self)
2768
if normal == None:
2769
normal = [ZZ.random_element(20),ZZ.random_element(20),ZZ.random_element(20)]
2770
normal = matrix(QQ,3,1,list(normal))
2771
projectionm = normal.kernel().basis_matrix()
2772
positions = dict(enumerate([list(c) for c in (projectionm*self.points()).columns(copy=False)]))
2773
self.skeleton().show(pos=positions)
2774
2775
def traverse_boundary(self):
2776
r"""
2777
Return a list of indices of vertices of a 2-dimensional polytope in their boundary order.
2778
2779
Needed for plot3d function of polytopes.
2780
2781
EXAMPLES:
2782
2783
sage: c = lattice_polytope.octahedron(2).polar()
2784
sage: c.traverse_boundary()
2785
[0, 1, 3, 2]
2786
"""
2787
if self.dim() != 2:
2788
raise ValueError, "Boundary can be traversed only for 2-polytopes!"
2789
edges = self.edges()
2790
l = [0]
2791
for e in edges:
2792
if 0 in e.vertices():
2793
next = e.vertices()[0] if e.vertices()[0] != 0 else e.vertices()[1]
2794
l.append(next)
2795
prev = 0
2796
while len(l) < self.nvertices():
2797
for e in edges:
2798
if next in e.vertices() and prev not in e.vertices():
2799
prev = next
2800
next = e.vertices()[0] if e.vertices()[0] != next else e.vertices()[1]
2801
l.append(next)
2802
break
2803
return l
2804
2805
def vertex(self, i):
2806
r"""
2807
Return the i-th vertex of this polytope, i.e. the i-th column of
2808
the matrix returned by vertices().
2809
2810
EXAMPLES: Note that numeration starts with zero::
2811
2812
sage: o = lattice_polytope.octahedron(3)
2813
sage: o.vertices()
2814
[ 1 0 0 -1 0 0]
2815
[ 0 1 0 0 -1 0]
2816
[ 0 0 1 0 0 -1]
2817
sage: o.vertex(3)
2818
(-1, 0, 0)
2819
"""
2820
# The check is added to compensate for a bug in Sage - column works for any numbers
2821
if i < 0:
2822
raise ValueError, "polytopes don't have negative vertices!"
2823
elif i > self.nvertices():
2824
raise ValueError, "the polytope does not have %d vertices!" % (i+1)
2825
else:
2826
return self._vertices.column(i, from_list=True)
2827
2828
def vertices(self):
2829
r"""
2830
Return vertices of this polytope as columns of a matrix.
2831
2832
EXAMPLES: The lattice points of the 3-dimensional octahedron and
2833
its polar cube::
2834
2835
sage: o = lattice_polytope.octahedron(3)
2836
sage: o.vertices()
2837
[ 1 0 0 -1 0 0]
2838
[ 0 1 0 0 -1 0]
2839
[ 0 0 1 0 0 -1]
2840
sage: cube = o.polar()
2841
sage: cube.vertices()
2842
[-1 1 -1 1 -1 1 -1 1]
2843
[-1 -1 1 1 -1 -1 1 1]
2844
[ 1 1 1 1 -1 -1 -1 -1]
2845
"""
2846
return self._vertices
2847
2848
2849
def is_NefPartition(x):
2850
r"""
2851
Check if ``x`` is a nef-partition.
2852
2853
INPUT:
2854
2855
- ``x`` -- anything.
2856
2857
OUTPUT:
2858
2859
- ``True`` if ``x`` is a :class:`nef-partition <NefPartition>` and
2860
``False`` otherwise.
2861
2862
EXAMPLES::
2863
2864
sage: from sage.geometry.lattice_polytope import is_NefPartition
2865
sage: is_NefPartition(1)
2866
False
2867
sage: o = lattice_polytope.octahedron(3)
2868
sage: np = o.nef_partitions()[0]
2869
sage: np
2870
Nef-partition {0, 1, 3} U {2, 4, 5}
2871
sage: is_NefPartition(np)
2872
True
2873
"""
2874
return isinstance(x, NefPartition)
2875
2876
2877
class NefPartition(SageObject,
2878
collections.Hashable):
2879
r"""
2880
Create a nef-partition.
2881
2882
INPUT:
2883
2884
- ``data`` -- a list of integers, the $i$-th element of this list must be
2885
the part of the $i$-th vertex of ``Delta_polar`` in this nef-partition;
2886
2887
- ``Delta_polar`` -- a :class:`lattice polytope
2888
<sage.geometry.lattice_polytope.LatticePolytopeClass>`;
2889
2890
- ``check`` -- by default the input will be checked for correctness, i.e.
2891
that ``data`` indeed specify a nef-partition. If you are sure that the
2892
input is correct, you can speed up construction via ``check=False``
2893
option.
2894
2895
OUTPUT:
2896
2897
- a nef-partition of ``Delta_polar``.
2898
2899
Let $M$ and $N$ be dual lattices. Let $\Delta \subset M_\RR$ be a reflexive
2900
polytope with polar $\Delta^\circ \subset N_\RR$. Let $X_\Delta$ be the
2901
toric variety associated to the normal fan of $\Delta$. A **nef-partition**
2902
is a decomposition of the vertex set $V$ of $\Delta^\circ$ into a disjoint
2903
union $V = V_0 \sqcup V_1 \sqcup \dots \sqcup V_{k-1}$ such that divisors
2904
$E_i = \sum_{v\in V_i} D_v$ are Cartier (here $D_v$ are prime
2905
torus-invariant Weil divisors corresponding to vertices of $\Delta^\circ$).
2906
Equivalently, let $\nabla_i \subset N_\RR$ be the convex hull of vertices
2907
from $V_i$ and the origin. These polytopes form a nef-partition if their
2908
Minkowski sum $\nabla \subset N_\RR$ is a reflexive polytope.
2909
2910
The **dual nef-partition** is formed by polytopes $\Delta_i \subset M_\RR$
2911
of $E_i$, which give a decomposition of the vertex set of $\nabla^\circ
2912
\subset M_\RR$ and their Minkowski sum is $\Delta$, i.e. the polar duality
2913
of reflexive polytopes switches convex hull and Minkowski sum for dual
2914
nef-partitions:
2915
2916
.. MATH::
2917
2918
\Delta^\circ
2919
&=
2920
\mathrm{Conv} \left(\nabla_0, \nabla_1, \dots, \nabla_{k-1}\right), \\
2921
\nabla^{\phantom{\circ}}
2922
&=
2923
\nabla_0 + \nabla_1 + \dots + \nabla_{k-1}, \\
2924
&
2925
\\
2926
\Delta^{\phantom{\circ}}
2927
&=
2928
\Delta_0 + \Delta_1 + \dots + \Delta_{k-1}, \\
2929
\nabla^\circ
2930
&=
2931
\mathrm{Conv} \left(\Delta_0, \Delta_1, \dots, \Delta_{k-1}\right).
2932
2933
See Section 4.3.1 in [CK99]_ and references therein for further details, or
2934
[BN08]_ for a purely combinatorial approach.
2935
2936
REFERENCES:
2937
2938
.. [BN08]
2939
Victor V. Batyrev and Benjamin Nill.
2940
Combinatorial aspects of mirror symmetry.
2941
In *Integer points in polyhedra --- geometry, number theory,
2942
representation theory, algebra, optimization, statistics*,
2943
volume 452 of *Contemp. Math.*, pages 35--66.
2944
Amer. Math. Soc., Providence, RI, 2008.
2945
arXiv:math/0703456v2 [math.CO].
2946
2947
.. [CK99]
2948
David A. Cox and Sheldon Katz.
2949
*Mirror symmetry and algebraic geometry*,
2950
volume 68 of *Mathematical Surveys and Monographs*.
2951
American Mathematical Society, Providence, RI, 1999.
2952
2953
EXAMPLES:
2954
2955
It is very easy to create a nef-partition for the octahedron, since for
2956
this polytope any decomposition of vertices is a nef-partition. We create a
2957
3-part nef-partition with the 0-th and 1-st vertices belonging to the 0-th
2958
part (recall that numeration in Sage starts with 0), the 2-nd and 5-th
2959
vertices belonging to the 1-st part, and 3-rd and 4-th vertices belonging
2960
to the 2-nd part::
2961
2962
sage: o = lattice_polytope.octahedron(3)
2963
sage: np = NefPartition([0,0,1,2,2,1], o)
2964
sage: np
2965
Nef-partition {0, 1} U {2, 5} U {3, 4}
2966
2967
The octahedron plays the role of `\Delta^\circ` in the above description::
2968
2969
sage: np.Delta_polar() is o
2970
True
2971
2972
The dual nef-partition (corresponding to the "mirror complete
2973
intersection") gives decomposition of the vertex set of `\nabla^\circ`::
2974
2975
sage: np.dual()
2976
Nef-partition {4, 5, 6} U {1, 3} U {0, 2, 7}
2977
sage: np.nabla_polar().vertices()
2978
[ 1 0 0 0 -1 0 -1 1]
2979
[ 1 0 1 0 -1 -1 0 0]
2980
[ 0 1 0 -1 0 0 0 0]
2981
2982
Of course, `\nabla^\circ` is `\Delta^\circ` from the point of view of the
2983
dual nef-partition::
2984
2985
sage: np.dual().Delta_polar() is np.nabla_polar()
2986
True
2987
sage: np.Delta(1).vertices()
2988
[ 0 0]
2989
[ 0 0]
2990
[ 1 -1]
2991
sage: np.dual().nabla(1).vertices()
2992
[ 0 0]
2993
[ 0 0]
2994
[ 1 -1]
2995
2996
Instead of constructing nef-partitions directly, you can request all 2-part
2997
nef-partitions of a given reflexive polytope (they will be computed using
2998
``nef.x`` program from PALP)::
2999
3000
sage: o.nef_partitions()
3001
[
3002
Nef-partition {0, 1, 3} U {2, 4, 5},
3003
Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),
3004
Nef-partition {0, 1, 2} U {3, 4, 5},
3005
Nef-partition {0, 1, 2, 3} U {4, 5},
3006
Nef-partition {0, 1, 2, 3, 4} U {5} (projection)
3007
]
3008
"""
3009
3010
def __init__(self, data, Delta_polar, check=True):
3011
r"""
3012
See :class:`NefPartition` for documentation.
3013
3014
TESTS::
3015
3016
sage: o = lattice_polytope.octahedron(3)
3017
sage: np = o.nef_partitions()[0]
3018
sage: TestSuite(np).run()
3019
"""
3020
if check and not Delta_polar.is_reflexive():
3021
raise ValueError("nef-partitions can be constructed for reflexive "
3022
"polytopes ony!")
3023
self._vertex_to_part = tuple(int(el) for el in data)
3024
self._nparts = max(self._vertex_to_part) + 1
3025
self._Delta_polar = Delta_polar
3026
if check and not self.nabla().is_reflexive():
3027
raise ValueError("%s do not form a nef-partition!" % str(data))
3028
3029
def __eq__(self, other):
3030
r"""
3031
Compare ``self`` with ``other``.
3032
3033
INPUT:
3034
3035
- ``other`` -- anything.
3036
3037
OUTPUT:
3038
3039
- ``True`` if ``other`` is a :class:`nef-partition <NefPartition>`
3040
equal to ``self``, ``False`` otherwise.
3041
3042
.. NOTE::
3043
3044
Two nef-partitions are equal if they correspond to equal polytopes
3045
and their parts are the same, including their order.
3046
3047
TESTS::
3048
3049
sage: o = lattice_polytope.octahedron(3)
3050
sage: np = o.nef_partitions()[0]
3051
sage: np == np
3052
True
3053
sage: np == o.nef_partitions()[1]
3054
False
3055
sage: np2 = NefPartition(np._vertex_to_part, o)
3056
sage: np2 is np
3057
False
3058
sage: np2 == np
3059
True
3060
sage: np == 0
3061
False
3062
"""
3063
return (is_NefPartition(other)
3064
and self._Delta_polar == other._Delta_polar
3065
and self._vertex_to_part == other._vertex_to_part)
3066
3067
def __hash__(self):
3068
r"""
3069
Return the hash of ``self``.
3070
3071
OUTPUT:
3072
3073
- an integer.
3074
3075
TESTS::
3076
3077
sage: o = lattice_polytope.octahedron(3)
3078
sage: np = o.nef_partitions()[0]
3079
sage: hash(np) == hash(np)
3080
True
3081
"""
3082
try:
3083
return self._hash
3084
except AttributeError:
3085
self._hash = hash(self._vertex_to_part) + hash(self._Delta_polar)
3086
return self._hash
3087
3088
def __ne__(self, other):
3089
r"""
3090
Compare ``self`` with ``other``.
3091
3092
INPUT:
3093
3094
- ``other`` -- anything.
3095
3096
OUTPUT:
3097
3098
- ``False`` if ``other`` is a :class:`nef-partition <NefPartition>`
3099
equal to ``self``, ``True`` otherwise.
3100
3101
.. NOTE::
3102
3103
Two nef-partitions are equal if they correspond to equal polytopes
3104
and their parts are the same, including their order.
3105
3106
TESTS::
3107
3108
sage: o = lattice_polytope.octahedron(3)
3109
sage: np = o.nef_partitions()[0]
3110
sage: np != np
3111
False
3112
sage: np != o.nef_partitions()[1]
3113
True
3114
sage: np2 = NefPartition(np._vertex_to_part, o)
3115
sage: np2 is np
3116
False
3117
sage: np2 != np
3118
False
3119
sage: np != 0
3120
True
3121
"""
3122
return not (self == other)
3123
3124
def _latex_(self):
3125
r"""
3126
Return a LaTeX representation of ``self``.
3127
3128
OUTPUT:
3129
3130
- a string.
3131
3132
TESTS::
3133
3134
sage: o = lattice_polytope.octahedron(3)
3135
sage: np = o.nef_partitions()[0]
3136
sage: latex(np) # indirect doctest
3137
\text{Nef-partition } \{0, 1, 3\} \sqcup \{2, 4, 5\}
3138
"""
3139
result = r"\text{Nef-partition } "
3140
for i, part in enumerate(self.parts()):
3141
if i != 0:
3142
result += " \sqcup "
3143
result += r"\{" + ", ".join("%d" % v for v in part) + r"\}"
3144
try:
3145
# We may or may not know the type of the partition
3146
if self._is_product:
3147
result += r" \text{ (direct product)}"
3148
if self._is_projection:
3149
result += r" \text{ (projection)}"
3150
except AttributeError:
3151
pass
3152
return result
3153
3154
def _repr_(self):
3155
r"""
3156
Return a string representation of ``self``.
3157
3158
OUTPUT:
3159
3160
- a string.
3161
3162
TESTS::
3163
3164
sage: o = lattice_polytope.octahedron(3)
3165
sage: np = o.nef_partitions()[0]
3166
sage: repr(np) # indirect doctest
3167
'Nef-partition {0, 1, 3} U {2, 4, 5}'
3168
"""
3169
result = "Nef-partition "
3170
for i, part in enumerate(self.parts()):
3171
if i != 0:
3172
result += " U "
3173
result += "{" + ", ".join("%d" % v for v in part) + "}"
3174
try:
3175
# We may or may not know the type of the partition
3176
if self._is_product:
3177
result += " (direct product)"
3178
if self._is_projection:
3179
result += " (projection)"
3180
except AttributeError:
3181
pass
3182
return result
3183
3184
def Delta(self, i=None):
3185
r"""
3186
Return the polytope $\Delta$ or $\Delta_i$ corresponding to ``self``.
3187
3188
INPUT:
3189
3190
- ``i`` -- an integer. If not given, $\Delta$ will be returned.
3191
3192
OUTPUT:
3193
3194
- a :class:`lattice polytope <LatticePolytopeClass>`.
3195
3196
See :class:`nef-partition <NefPartition>` class documentation for
3197
definitions and notation.
3198
3199
EXAMPLES::
3200
3201
sage: o = lattice_polytope.octahedron(3)
3202
sage: np = o.nef_partitions()[0]
3203
sage: np
3204
Nef-partition {0, 1, 3} U {2, 4, 5}
3205
sage: np.Delta().polar() is o
3206
True
3207
sage: np.Delta().vertices()
3208
[-1 1 -1 1 -1 1 -1 1]
3209
[-1 -1 1 1 -1 -1 1 1]
3210
[ 1 1 1 1 -1 -1 -1 -1]
3211
sage: np.Delta(0).vertices()
3212
[ 1 1 -1 -1]
3213
[-1 0 -1 0]
3214
[ 0 0 0 0]
3215
"""
3216
if i is None:
3217
return self._Delta_polar.polar()
3218
else:
3219
return self.dual().nabla(i)
3220
3221
def Delta_polar(self):
3222
r"""
3223
Return the polytope $\Delta^\circ$ corresponding to ``self``.
3224
3225
OUTPUT:
3226
3227
- a :class:`lattice polytope <LatticePolytopeClass>`.
3228
3229
See :class:`nef-partition <NefPartition>` class documentation for
3230
definitions and notation.
3231
3232
EXAMPLE::
3233
3234
sage: o = lattice_polytope.octahedron(3)
3235
sage: np = o.nef_partitions()[0]
3236
sage: np
3237
Nef-partition {0, 1, 3} U {2, 4, 5}
3238
sage: np.Delta_polar() is o
3239
True
3240
"""
3241
return self._Delta_polar
3242
3243
def Deltas(self):
3244
r"""
3245
Return the polytopes $\Delta_i$ corresponding to ``self``.
3246
3247
OUTPUT:
3248
3249
- a tuple of :class:`lattice polytopes <LatticePolytopeClass>`.
3250
3251
See :class:`nef-partition <NefPartition>` class documentation for
3252
definitions and notation.
3253
3254
EXAMPLES::
3255
3256
sage: o = lattice_polytope.octahedron(3)
3257
sage: np = o.nef_partitions()[0]
3258
sage: np
3259
Nef-partition {0, 1, 3} U {2, 4, 5}
3260
sage: np.Delta().vertices()
3261
[-1 1 -1 1 -1 1 -1 1]
3262
[-1 -1 1 1 -1 -1 1 1]
3263
[ 1 1 1 1 -1 -1 -1 -1]
3264
sage: [Delta_i.vertices() for Delta_i in np.Deltas()]
3265
[
3266
[ 1 1 -1 -1] [ 0 0 0 0]
3267
[-1 0 -1 0] [ 1 0 0 1]
3268
[ 0 0 0 0], [ 1 1 -1 -1]
3269
]
3270
sage: np.nabla_polar().vertices()
3271
[ 1 0 1 0 0 -1 0 -1]
3272
[-1 1 0 0 0 -1 1 0]
3273
[ 0 1 0 1 -1 0 -1 0]
3274
"""
3275
return self.dual().nablas()
3276
3277
def dual(self):
3278
r"""
3279
Return the dual nef-partition.
3280
3281
OUTPUT:
3282
3283
- a :class:`nef-partition <NefPartition>`.
3284
3285
See the class documentation for the definition.
3286
3287
ALGORITHM:
3288
3289
See Proposition 3.19 in [BN08]_.
3290
3291
EXAMPLES::
3292
3293
sage: o = lattice_polytope.octahedron(3)
3294
sage: np = o.nef_partitions()[0]
3295
sage: np
3296
Nef-partition {0, 1, 3} U {2, 4, 5}
3297
sage: np.dual()
3298
Nef-partition {0, 2, 5, 7} U {1, 3, 4, 6}
3299
sage: np.dual().Delta() is np.nabla()
3300
True
3301
sage: np.dual().nabla(0) is np.Delta(0)
3302
True
3303
"""
3304
try:
3305
return self._dual
3306
except AttributeError:
3307
# Delta and nabla are interchanged compared to [BN08]_.
3308
nabla_polar = self.nabla_polar()
3309
n = nabla_polar.nvertices()
3310
vertex_to_part = [-1] * n
3311
for i in range(self._nparts):
3312
A = nabla_polar.vertices().transpose()*self.nabla(i).vertices()
3313
for j in range(n):
3314
if min(A[j]) == -1:
3315
vertex_to_part[j] = i
3316
self._dual = NefPartition(vertex_to_part, nabla_polar)
3317
self._dual._dual = self
3318
self._dual._nabla = self.Delta() # For vertex order consistency
3319
return self._dual
3320
3321
def hodge_numbers(self):
3322
r"""
3323
Return Hodge numbers corresponding to ``self``.
3324
3325
OUTPUT:
3326
3327
- a tuple of integers (produced by ``nef.x`` program from PALP).
3328
3329
EXAMPLES:
3330
3331
Currently, you need to request Hodge numbers when you compute
3332
nef-partitions::
3333
3334
sage: o = lattice_polytope.octahedron(5)
3335
sage: np = o.nef_partitions()[0] # long time (4s on sage.math, 2011)
3336
sage: np.hodge_numbers() # long time
3337
Traceback (most recent call last):
3338
...
3339
NotImplementedError: use nef_partitions(hodge_numbers=True)!
3340
sage: np = o.nef_partitions(hodge_numbers=True)[0] # long time (13s on sage.math, 2011)
3341
sage: np.hodge_numbers() # long time
3342
(19, 19)
3343
"""
3344
try:
3345
return self._hodge_numbers
3346
except AttributeError:
3347
self._Delta_polar._compute_hodge_numbers()
3348
return self._hodge_numbers
3349
3350
def nabla(self, i=None):
3351
r"""
3352
Return the polytope $\nabla$ or $\nabla_i$ corresponding to ``self``.
3353
3354
INPUT:
3355
3356
- ``i`` -- an integer. If not given, $\nabla$ will be returned.
3357
3358
OUTPUT:
3359
3360
- a :class:`lattice polytope <LatticePolytopeClass>`.
3361
3362
See :class:`nef-partition <NefPartition>` class documentation for
3363
definitions and notation.
3364
3365
EXAMPLES::
3366
3367
sage: o = lattice_polytope.octahedron(3)
3368
sage: np = o.nef_partitions()[0]
3369
sage: np
3370
Nef-partition {0, 1, 3} U {2, 4, 5}
3371
sage: np.Delta_polar().vertices()
3372
[ 1 0 0 -1 0 0]
3373
[ 0 1 0 0 -1 0]
3374
[ 0 0 1 0 0 -1]
3375
sage: np.nabla(0).vertices()
3376
[ 1 0 -1]
3377
[ 0 1 0]
3378
[ 0 0 0]
3379
sage: np.nabla().vertices()
3380
[ 1 1 1 0 0 -1 -1 -1]
3381
[ 0 -1 0 1 1 0 -1 0]
3382
[ 1 0 -1 1 -1 1 0 -1]
3383
"""
3384
if i is None:
3385
try:
3386
return self._nabla
3387
except AttributeError:
3388
self._nabla = LatticePolytope(reduce(minkowski_sum,
3389
(nabla.vertices().columns() for nabla in self.nablas())))
3390
return self._nabla
3391
else:
3392
return self.nablas()[i]
3393
3394
def nabla_polar(self):
3395
r"""
3396
Return the polytope $\nabla^\circ$ corresponding to ``self``.
3397
3398
OUTPUT:
3399
3400
- a :class:`lattice polytope <LatticePolytopeClass>`.
3401
3402
See :class:`nef-partition <NefPartition>` class documentation for
3403
definitions and notation.
3404
3405
EXAMPLES::
3406
3407
sage: o = lattice_polytope.octahedron(3)
3408
sage: np = o.nef_partitions()[0]
3409
sage: np
3410
Nef-partition {0, 1, 3} U {2, 4, 5}
3411
sage: np.nabla_polar().vertices()
3412
[ 1 0 1 0 0 -1 0 -1]
3413
[-1 1 0 0 0 -1 1 0]
3414
[ 0 1 0 1 -1 0 -1 0]
3415
sage: np.nabla_polar() is np.dual().Delta_polar()
3416
True
3417
"""
3418
return self.nabla().polar()
3419
3420
def nablas(self):
3421
r"""
3422
Return the polytopes $\nabla_i$ corresponding to ``self``.
3423
3424
OUTPUT:
3425
3426
- a tuple of :class:`lattice polytopes <LatticePolytopeClass>`.
3427
3428
See :class:`nef-partition <NefPartition>` class documentation for
3429
definitions and notation.
3430
3431
EXAMPLES::
3432
3433
sage: o = lattice_polytope.octahedron(3)
3434
sage: np = o.nef_partitions()[0]
3435
sage: np
3436
Nef-partition {0, 1, 3} U {2, 4, 5}
3437
sage: np.Delta_polar().vertices()
3438
[ 1 0 0 -1 0 0]
3439
[ 0 1 0 0 -1 0]
3440
[ 0 0 1 0 0 -1]
3441
sage: [nabla_i.vertices() for nabla_i in np.nablas()]
3442
[
3443
[ 1 0 -1] [ 0 0 0]
3444
[ 0 1 0] [ 0 -1 0]
3445
[ 0 0 0], [ 1 0 -1]
3446
]
3447
"""
3448
try:
3449
return self._nablas
3450
except AttributeError:
3451
Delta_polar = self._Delta_polar
3452
origin = [[0] * Delta_polar.dim()]
3453
self._nablas = tuple(LatticePolytope(
3454
[Delta_polar.vertex(j) for j in part] + origin)
3455
for part in self.parts())
3456
return self._nablas
3457
3458
def nparts(self):
3459
r"""
3460
Return the number of parts in ``self``.
3461
3462
OUTPUT:
3463
3464
- an integer.
3465
3466
EXAMPLES::
3467
3468
sage: o = lattice_polytope.octahedron(3)
3469
sage: np = o.nef_partitions()[0]
3470
sage: np
3471
Nef-partition {0, 1, 3} U {2, 4, 5}
3472
sage: np.nparts()
3473
2
3474
"""
3475
return self._nparts
3476
3477
def part(self, i):
3478
r"""
3479
Return the ``i``-th part of ``self``.
3480
3481
INPUT:
3482
3483
- ``i`` -- an integer.
3484
3485
OUTPUT:
3486
3487
- a tuple of integers, indices of vertices of $\Delta^\circ$ belonging
3488
to $V_i$.
3489
3490
See :class:`nef-partition <NefPartition>` class documentation for
3491
definitions and notation.
3492
3493
EXAMPLES::
3494
3495
sage: o = lattice_polytope.octahedron(3)
3496
sage: np = o.nef_partitions()[0]
3497
sage: np
3498
Nef-partition {0, 1, 3} U {2, 4, 5}
3499
sage: np.part(0)
3500
(0, 1, 3)
3501
"""
3502
return self.parts()[i]
3503
3504
def parts(self):
3505
r"""
3506
Return all parts of ``self``.
3507
3508
OUTPUT:
3509
3510
- a tuple of tuples of integers. The $i$-th tuple contains indices of
3511
vertices of $\Delta^\circ$ belonging to $V_i$.
3512
3513
See :class:`nef-partition <NefPartition>` class documentation for
3514
definitions and notation.
3515
3516
EXAMPLES::
3517
3518
sage: o = lattice_polytope.octahedron(3)
3519
sage: np = o.nef_partitions()[0]
3520
sage: np
3521
Nef-partition {0, 1, 3} U {2, 4, 5}
3522
sage: np.parts()
3523
((0, 1, 3), (2, 4, 5))
3524
"""
3525
try:
3526
return self._parts
3527
except AttributeError:
3528
parts = []
3529
for part in range(self._nparts):
3530
parts.append([])
3531
for vertex, part in enumerate(self._vertex_to_part):
3532
parts[part].append(vertex)
3533
self._parts = tuple(tuple(part) for part in parts)
3534
return self._parts
3535
3536
def part_of(self, i):
3537
r"""
3538
Return the index of the part containing the ``i``-th vertex.
3539
3540
INPUT:
3541
3542
- ``i`` -- an integer.
3543
3544
OUTPUT:
3545
3546
- an integer $j$ such that the ``i``-th vertex of $\Delta^\circ$
3547
belongs to $V_j$.
3548
3549
See :class:`nef-partition <NefPartition>` class documentation for
3550
definitions and notation.
3551
3552
EXAMPLES::
3553
3554
sage: o = lattice_polytope.octahedron(3)
3555
sage: np = o.nef_partitions()[0]
3556
sage: np
3557
Nef-partition {0, 1, 3} U {2, 4, 5}
3558
sage: np.part_of(3)
3559
0
3560
sage: np.part_of(2)
3561
1
3562
"""
3563
return self._vertex_to_part[i]
3564
3565
def part_of_point(self, i):
3566
r"""
3567
Return the index of the part containing the ``i``-th point.
3568
3569
INPUT:
3570
3571
- ``i`` -- an integer.
3572
3573
OUTPUT:
3574
3575
- an integer `j` such that the ``i``-th point of `\Delta^\circ`
3576
belongs to `\nabla_j`.
3577
3578
.. NOTE::
3579
3580
Since a nef-partition induces a partition on the set of boundary
3581
lattice points of `\Delta^\circ`, the value of `j` is well-defined
3582
for all `i` but the one that corresponds to the origin, in which
3583
case this method will raise a ``ValueError`` exception. (The origin
3584
always belongs to all `\nabla_j`.)
3585
3586
See :class:`nef-partition <NefPartition>` class documentation for
3587
definitions and notation.
3588
3589
EXAMPLES:
3590
3591
We consider a relatively complicated reflexive polytope #2252 (easily
3592
accessible in Sage as ``ReflexivePolytope(3, 2252)``, we create it here
3593
explicitly to avoid loading the whole database)::
3594
3595
sage: p = LatticePolytope([(1,0,0), (0,1,0), (0,0,1), (0,1,-1),
3596
... (0,-1,1), (-1,1,0), (0,-1,-1), (-1,-1,0), (-1,-1,2)])
3597
sage: np = p.nef_partitions()[0]
3598
sage: np
3599
Nef-partition {1, 2, 5, 7, 8} U {0, 3, 4, 6}
3600
sage: p.nvertices()
3601
9
3602
sage: p.npoints()
3603
15
3604
3605
We see that the polytope has 6 more points in addition to vertices. One
3606
of them is the origin::
3607
3608
sage: p.origin()
3609
14
3610
sage: np.part_of_point(14)
3611
Traceback (most recent call last):
3612
...
3613
ValueError: the origin belongs to all parts!
3614
3615
But the remaining 5 are partitioned by ``np``::
3616
3617
sage: [n for n in range(p.npoints())
3618
... if p.origin() != n and np.part_of_point(n) == 0]
3619
[1, 2, 5, 7, 8, 9, 11, 13]
3620
sage: [n for n in range(p.npoints())
3621
... if p.origin() != n and np.part_of_point(n) == 1]
3622
[0, 3, 4, 6, 10, 12]
3623
"""
3624
try:
3625
ptp = self._point_to_part
3626
except AttributeError:
3627
ptp = [-1] * self._Delta_polar.npoints()
3628
for v, part in enumerate(self._vertex_to_part):
3629
ptp[v] = part
3630
self._point_to_part = ptp
3631
if ptp[i] > 0:
3632
return ptp[i]
3633
if i == self._Delta_polar.origin():
3634
raise ValueError("the origin belongs to all parts!")
3635
point = self._Delta_polar.point(i)
3636
for part, nabla in enumerate(self.nablas()):
3637
if min(nabla.distances(point)) >= 0:
3638
ptp[i] = part
3639
break
3640
return ptp[i]
3641
3642
3643
class _PolytopeFace(SageObject):
3644
r"""
3645
_PolytopeFace(polytope, vertices, facets)
3646
3647
Construct a polytope face.
3648
3649
POLYTOPE FACES SHOULD NOT BE CONSTRUCTED OUTSIDE OF LATTICE
3650
POLYTOPES!
3651
3652
INPUT:
3653
3654
3655
- ``polytope`` - a polytope whose face is being
3656
constructed.
3657
3658
- ``vertices`` - a sequence of indices of generating
3659
vertices.
3660
3661
- ``facets`` - a sequence of indices of facets
3662
containing this face.
3663
"""
3664
def __init__(self, polytope, vertices, facets):
3665
r"""
3666
Construct a face.
3667
3668
TESTS::
3669
3670
sage: o = lattice_polytope.octahedron(2)
3671
sage: o.faces()
3672
[
3673
[[0], [1], [2], [3]],
3674
[[0, 3], [2, 3], [0, 1], [1, 2]]
3675
]
3676
"""
3677
self._polytope = polytope
3678
self._vertices = vertices
3679
self._facets = facets
3680
3681
def __reduce__(self):
3682
r"""
3683
Reduction function. Does not store data that can be relatively fast
3684
recomputed.
3685
3686
TESTS::
3687
3688
sage: o = lattice_polytope.octahedron(2)
3689
sage: f = o.facets()[0]
3690
sage: fl = loads(f.dumps())
3691
sage: f.vertices() == fl.vertices()
3692
True
3693
sage: f.facets() == fl.facets()
3694
True
3695
"""
3696
state = self.__dict__.copy()
3697
state.pop('_polytope')
3698
state.pop('_vertices')
3699
state.pop('_facets')
3700
if state.has_key('_points'):
3701
state['_npoints'] = len(state.pop('_points'))
3702
if state.has_key('_interior_points'):
3703
state['_ninterior_points'] = len(state.pop('_interior_points'))
3704
state.pop('_boundary_points')
3705
# Reference to the polytope is not pickled - the polytope will restore it
3706
return (_PolytopeFace, (None, self._vertices, self._facets), state)
3707
3708
def _repr_(self):
3709
r"""
3710
Return a string representation of this face.
3711
3712
TESTS::
3713
3714
sage: o = lattice_polytope.octahedron(3)
3715
sage: f = o.facets()[0]
3716
sage: f._repr_()
3717
'[0, 1, 5]'
3718
"""
3719
return repr(self._vertices)
3720
3721
def boundary_points(self):
3722
r"""
3723
Return a sequence of indices of boundary lattice points of this
3724
face.
3725
3726
EXAMPLES: Boundary lattice points of one of the facets of the
3727
3-dimensional cube::
3728
3729
sage: o = lattice_polytope.octahedron(3)
3730
sage: cube = o.polar()
3731
sage: face = cube.facets()[0]
3732
sage: face.boundary_points()
3733
[0, 2, 4, 6, 8, 9, 11, 12]
3734
"""
3735
try:
3736
return self._boundary_points
3737
except AttributeError:
3738
self._polytope._face_split_points(self)
3739
return self._boundary_points
3740
3741
def facets(self):
3742
r"""
3743
Return a sequence of indices of facets containing this face.
3744
3745
EXAMPLES: Facets containing one of the edges of the 3-dimensional
3746
octahedron::
3747
3748
sage: o = lattice_polytope.octahedron(3)
3749
sage: edge = o.faces(dim=1)[0]
3750
sage: edge.facets()
3751
[0, 1]
3752
3753
Thus ``edge`` is the intersection of facets 0 and 1::
3754
3755
sage: edge
3756
[1, 5]
3757
sage: o.facets()[0]
3758
[0, 1, 5]
3759
sage: o.facets()[1]
3760
[1, 3, 5]
3761
"""
3762
return self._facets
3763
3764
def interior_points(self):
3765
r"""
3766
Return a sequence of indices of interior lattice points of this
3767
face.
3768
3769
EXAMPLES: Interior lattice points of one of the facets of the
3770
3-dimensional cube::
3771
3772
sage: o = lattice_polytope.octahedron(3)
3773
sage: cube = o.polar()
3774
sage: face = cube.facets()[0]
3775
sage: face.interior_points()
3776
[10]
3777
"""
3778
try:
3779
return self._interior_points
3780
except AttributeError:
3781
self._polytope._face_split_points(self)
3782
return self._interior_points
3783
3784
def nboundary_points(self):
3785
r"""
3786
Return the number of boundary lattice points of this face.
3787
3788
EXAMPLES: The number of boundary lattice points of one of the
3789
facets of the 3-dimensional cube::
3790
3791
sage: o = lattice_polytope.octahedron(3)
3792
sage: cube = o.polar()
3793
sage: face = cube.facets()[0]
3794
sage: face.nboundary_points()
3795
8
3796
"""
3797
return self.npoints() - self.ninterior_points()
3798
3799
def nfacets(self):
3800
r"""
3801
Return the number of facets containing this face.
3802
3803
EXAMPLES: The number of facets containing one of the edges of the
3804
3-dimensional octahedron::
3805
3806
sage: o = lattice_polytope.octahedron(3)
3807
sage: edge = o.faces(dim=1)[0]
3808
sage: edge.nfacets()
3809
2
3810
"""
3811
return len(self._facets)
3812
3813
def ninterior_points(self):
3814
r"""
3815
Return the number of interior lattice points of this face.
3816
3817
EXAMPLES: The number of interior lattice points of one of the
3818
facets of the 3-dimensional cube::
3819
3820
sage: o = lattice_polytope.octahedron(3)
3821
sage: cube = o.polar()
3822
sage: face = cube.facets()[0]
3823
sage: face.ninterior_points()
3824
1
3825
"""
3826
try:
3827
return self._ninterior_points
3828
except AttributeError:
3829
return len(self.interior_points())
3830
3831
def npoints(self):
3832
r"""
3833
Return the number of lattice points of this face.
3834
3835
EXAMPLES: The number of lattice points of one of the facets of the
3836
3-dimensional cube::
3837
3838
sage: o = lattice_polytope.octahedron(3)
3839
sage: cube = o.polar()
3840
sage: face = cube.facets()[0]
3841
sage: face.npoints()
3842
9
3843
"""
3844
try:
3845
return self._npoints
3846
except AttributeError:
3847
return len(self.points())
3848
3849
def nvertices(self):
3850
r"""
3851
Return the number of vertices generating this face.
3852
3853
EXAMPLES: The number of vertices generating one of the facets of
3854
the 3-dimensional cube::
3855
3856
sage: o = lattice_polytope.octahedron(3)
3857
sage: cube = o.polar()
3858
sage: face = cube.facets()[0]
3859
sage: face.nvertices()
3860
4
3861
"""
3862
return len(self._vertices)
3863
3864
def ordered_points(self):
3865
r"""
3866
Return the list of indices of lattice points on the edge in their
3867
geometric order, from one vertex to other.
3868
3869
Works only for edges, i.e. faces generated by exactly two
3870
vertices.
3871
3872
EXAMPLE: We find all points along an edge of the cube::
3873
3874
sage: o = lattice_polytope.octahedron(3)
3875
sage: c = o.polar()
3876
sage: e = c.edges()[0]
3877
sage: e.vertices()
3878
[0, 1]
3879
sage: e.ordered_points()
3880
[0, 15, 1]
3881
"""
3882
if len(self.vertices()) != 2:
3883
raise ValueError, "Order of points is defined for edges only!"
3884
pcol = self._polytope.points().columns(copy=False)
3885
start = pcol[self.vertices()[0]]
3886
end = pcol[self.vertices()[1]]
3887
primitive = end - start
3888
primitive = primitive * (1/integral_length(primitive.list()))
3889
result = [self.vertices()[0]]
3890
start = start + primitive
3891
while start != end:
3892
for i in self.points():
3893
if start == pcol[i]:
3894
result.append(i)
3895
break
3896
start = start + primitive
3897
result.append(self.vertices()[1])
3898
return result
3899
3900
def points(self):
3901
r"""
3902
Return a sequence of indices of lattice points of this face.
3903
3904
EXAMPLES: The lattice points of one of the facets of the
3905
3-dimensional cube::
3906
3907
sage: o = lattice_polytope.octahedron(3)
3908
sage: cube = o.polar()
3909
sage: face = cube.facets()[0]
3910
sage: face.points()
3911
[0, 2, 4, 6, 8, 9, 10, 11, 12]
3912
"""
3913
try:
3914
return self._points
3915
except AttributeError:
3916
self._polytope._face_compute_points(self)
3917
return self._points
3918
3919
def traverse_boundary(self):
3920
r"""
3921
Return a list of indices of vertices of a 2-face in their boundary
3922
order.
3923
3924
Needed for plot3d function of polytopes.
3925
3926
EXAMPLES::
3927
3928
sage: c = lattice_polytope.octahedron(3).polar()
3929
sage: f = c.facets()[0]
3930
sage: f.vertices()
3931
[0, 2, 4, 6]
3932
sage: f.traverse_boundary()
3933
[0, 4, 6, 2]
3934
"""
3935
if self not in self._polytope.faces(dim=2):
3936
raise ValueError, "Boundary can be traversed only for 2-faces!"
3937
edges = [e for e in self._polytope.edges() if e.vertices()[0] in self.vertices() and
3938
e.vertices()[1] in self.vertices()]
3939
start = self.vertices()[0]
3940
l = [start]
3941
for e in edges:
3942
if start in e.vertices():
3943
next = e.vertices()[0] if e.vertices()[0] != start else e.vertices()[1]
3944
l.append(next)
3945
prev = start
3946
while len(l) < self.nvertices():
3947
for e in edges:
3948
if next in e.vertices() and prev not in e.vertices():
3949
prev = next
3950
next = e.vertices()[0] if e.vertices()[0] != next else e.vertices()[1]
3951
l.append(next)
3952
break
3953
return l
3954
3955
def vertices(self):
3956
r"""
3957
Return a sequence of indices of vertices generating this face.
3958
3959
EXAMPLES: The vertices generating one of the facets of the
3960
3-dimensional cube::
3961
3962
sage: o = lattice_polytope.octahedron(3)
3963
sage: cube = o.polar()
3964
sage: face = cube.facets()[0]
3965
sage: face.vertices()
3966
[0, 2, 4, 6]
3967
"""
3968
return self._vertices
3969
3970
3971
def _create_octahedron(dim):
3972
r"""
3973
Create an octahedron of the given dimension.
3974
3975
Since we know that we are passing vertices, we suppress their
3976
computation.
3977
3978
TESTS::
3979
3980
sage: o = lattice_polytope._create_octahedron(3)
3981
sage: o.vertices()
3982
[ 1 0 0 -1 0 0]
3983
[ 0 1 0 0 -1 0]
3984
[ 0 0 1 0 0 -1]
3985
"""
3986
m = matrix(ZZ, dim, 2*dim)
3987
for i in range(dim):
3988
m[i,i] = 1
3989
m[i,dim+i] = -1
3990
return LatticePolytope(m, "An octahedron", compute_vertices=False, copy_vertices=False)
3991
3992
_octahedrons = dict() # Dictionary for storing created octahedrons
3993
3994
_palp_dimension = None
3995
3996
def _palp(command, polytopes, reduce_dimension=False):
3997
r"""
3998
Run ``command`` on vertices of given
3999
``polytopes``.
4000
4001
Returns the name of the file containing the output of
4002
``command``. You should delete it after using.
4003
4004
.. note::
4005
4006
PALP cannot be called for polytopes that do not span the ambient space.
4007
If you specify ``reduce_dimension=True`` argument, PALP will be
4008
called for vertices of this polytope in some basis of the affine space
4009
it spans.
4010
4011
TESTS::
4012
4013
sage: o = lattice_polytope.octahedron(3)
4014
sage: result_name = lattice_polytope._palp("poly.x -f", [o])
4015
sage: f = open(result_name)
4016
sage: f.readlines()
4017
['M:7 6 N:27 8 Pic:17 Cor:0\n']
4018
sage: f.close()
4019
sage: os.remove(result_name)
4020
4021
sage: m = matrix(ZZ, [[1, 0, -1, 0],
4022
... [0, 1, 0, -1],
4023
... [0, 0, 0, 0]])
4024
...
4025
sage: p = LatticePolytope(m)
4026
sage: lattice_polytope._palp("poly.x -f", [p])
4027
Traceback (most recent call last):
4028
ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!
4029
4030
sage: result_name = lattice_polytope._palp("poly.x -f", [p], reduce_dimension=True)
4031
sage: f = open(result_name)
4032
sage: f.readlines()
4033
['M:5 4 F:4\n']
4034
sage: f.close()
4035
sage: os.remove(result_name)
4036
"""
4037
if _palp_dimension is not None:
4038
dot = command.find(".")
4039
command = command[:dot] + "-%dd" % _palp_dimension + command[dot:]
4040
input_file_name = tmp_filename()
4041
input_file = open(input_file_name, "w")
4042
for p in polytopes:
4043
if p.dim() == 0:
4044
raise ValueError(("Cannot run \"%s\" for the zero-dimensional "
4045
+ "polytope!\nPolytope: %s") % (command, p))
4046
if p.dim() < p.ambient_dim() and not reduce_dimension:
4047
raise ValueError(("Cannot run PALP for a %d-dimensional polytope " +
4048
"in a %d-dimensional space!") % (p.dim(), p.ambient_dim()))
4049
write_palp_matrix(p._pullback(p._vertices), input_file)
4050
input_file.close()
4051
output_file_name = tmp_filename()
4052
c = "%s <%s >%s" % (command, input_file_name, output_file_name)
4053
p = subprocess.Popen(c, shell=True, bufsize=2048,
4054
stdin=subprocess.PIPE,
4055
stdout=subprocess.PIPE,
4056
stderr=subprocess.PIPE,
4057
close_fds=True)
4058
stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr)
4059
err = stderr.read()
4060
if len(err) > 0:
4061
raise RuntimeError, ("Error executing \"%s\" for a polytope sequence!"
4062
+ "\nOutput:\n%s") % (command, err)
4063
os.remove(input_file_name)
4064
try:
4065
p.terminate()
4066
except OSError:
4067
pass
4068
return output_file_name
4069
4070
def _read_nef_x_partitions(data):
4071
r"""
4072
Read all nef-partitions for one polytope from a string or an open
4073
file.
4074
4075
``data`` should be an output of nef.x.
4076
4077
Returns the sequence of nef-partitions. Each nef-partition is given
4078
as a sequence of integers.
4079
4080
If there are no nef-partitions, returns the empty sequence. If the
4081
string is empty or EOF is reached, raises ValueError.
4082
4083
TESTS::
4084
4085
sage: o = lattice_polytope.octahedron(3)
4086
sage: s = o.nef_x("-N -p")
4087
sage: print s
4088
M:27 8 N:7 6 codim=2 #part=5
4089
P:0 V:2 4 5 0sec 0cpu
4090
P:2 V:3 4 5 0sec 0cpu
4091
P:3 V:4 5 0sec 0cpu
4092
np=3 d:1 p:1 0sec 0cpu
4093
sage: lattice_polytope._read_nef_x_partitions(s)
4094
[[2, 4, 5], [3, 4, 5], [4, 5]]
4095
"""
4096
if isinstance(data, str):
4097
f = StringIO.StringIO(data)
4098
partitions = _read_nef_x_partitions(f)
4099
f.close()
4100
return partitions
4101
line = data.readline()
4102
if line == "":
4103
raise ValueError, "Empty file!"
4104
partitions = []
4105
while len(line) > 0 and line.find("np=") == -1:
4106
if line.find("V:") == -1:
4107
line = data.readline()
4108
continue
4109
start = line.find("V:") + 2
4110
end = line.find(" ", start) # Find DOUBLE space
4111
partitions.append(Sequence(line[start:end].split(),int))
4112
line = data.readline()
4113
# Compare the number of found partitions with np in data.
4114
start = line.find("np=")
4115
if start != -1:
4116
start += 3
4117
end = line.find(" ", start)
4118
np = int(line[start:end])
4119
if False and np != len(partitions):
4120
raise ValueError, ("Found %d partitions, expected %d!" %
4121
(len(partitions), np))
4122
else:
4123
raise ValueError, "Wrong data format, cannot find \"np=\"!"
4124
return partitions
4125
4126
def _read_poly_x_incidences(data, dim):
4127
r"""
4128
Convert incidence data from binary numbers to sequences.
4129
4130
INPUT:
4131
4132
4133
- ``data`` - an opened file with incidence
4134
information. The first line will be skipped, each consecutive line
4135
contains incidence information for all faces of one dimension, the
4136
first word of each line is a comment and is dropped.
4137
4138
- ``dim`` - dimension of the polytope.
4139
4140
4141
OUTPUT: a sequence F, such that F[d][i] is a sequence of vertices
4142
or facets corresponding to the i-th d-dimensional face.
4143
4144
TESTS::
4145
4146
sage: o = lattice_polytope.octahedron(2)
4147
sage: result_name = lattice_polytope._palp("poly.x -fi", [o])
4148
sage: f = open(result_name)
4149
sage: f.readlines()
4150
['Incidences as binary numbers [F-vector=(4 4)]:\n', "v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j\n", 'v[0]: 1000 0001 0100 0010 \n', 'v[1]: 1001 1100 0011 0110 \n', "f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j\n", 'f[0]: 0011 0101 1010 1100 \n', 'f[1]: 0001 0010 0100 1000 \n']
4151
sage: f.close()
4152
sage: f = open(result_name)
4153
sage: l = f.readline()
4154
sage: lattice_polytope._read_poly_x_incidences(f, 2)
4155
[[[3], [0], [2], [1]], [[0, 3], [2, 3], [0, 1], [1, 2]]]
4156
sage: f.close()
4157
sage: os.remove(result_name)
4158
"""
4159
data.readline()
4160
lines = [data.readline().split() for i in range(dim)]
4161
if len(lines) != dim:
4162
raise ValueError, "Not enough data!"
4163
n = len(lines[0][1]) # Number of vertices or facets
4164
result = []
4165
for line in lines:
4166
line.pop(0)
4167
subr = []
4168
for e in line:
4169
f = Sequence([j for j in range(n) if e[n-1-j] == '1'], int, check=False)
4170
f.set_immutable()
4171
subr.append(f)
4172
result.append(subr)
4173
return result
4174
4175
def all_cached_data(polytopes):
4176
r"""
4177
Compute all cached data for all given ``polytopes`` and
4178
their polars.
4179
4180
This functions does it MUCH faster than member functions of
4181
``LatticePolytope`` during the first run. So it is recommended to
4182
use this functions if you work with big sets of data. None of the
4183
polytopes in the given sequence should be constructed as the polar
4184
polytope to another one.
4185
4186
INPUT: a sequence of lattice polytopes.
4187
4188
EXAMPLES: This function has no output, it is just a fast way to
4189
work with long sequences of polytopes. Of course, you can use short
4190
sequences as well::
4191
4192
sage: o = lattice_polytope.octahedron(3)
4193
sage: lattice_polytope.all_cached_data([o])
4194
sage: o.faces()
4195
[
4196
[[0], [1], [2], [3], [4], [5]],
4197
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
4198
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
4199
]
4200
4201
However, you cannot use it for polytopes that are constructed as
4202
polar polytopes of others::
4203
4204
sage: lattice_polytope.all_cached_data([o.polar()])
4205
Traceback (most recent call last):
4206
...
4207
ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!
4208
"""
4209
all_polars(polytopes)
4210
all_points(polytopes)
4211
all_faces(polytopes)
4212
reflexive = [p for p in polytopes if p.is_reflexive()]
4213
all_nef_partitions(reflexive)
4214
polar = [p.polar() for p in reflexive]
4215
all_points(polar)
4216
all_nef_partitions(polar)
4217
for p in polytopes + polar:
4218
for d_faces in p.faces():
4219
for face in d_faces:
4220
face.boundary_points()
4221
4222
def all_faces(polytopes):
4223
r"""
4224
Compute faces for all given ``polytopes``.
4225
4226
This functions does it MUCH faster than member functions of
4227
``LatticePolytope`` during the first run. So it is recommended to
4228
use this functions if you work with big sets of data.
4229
4230
INPUT: a sequence of lattice polytopes.
4231
4232
EXAMPLES: This function has no output, it is just a fast way to
4233
work with long sequences of polytopes. Of course, you can use short
4234
sequences as well::
4235
4236
sage: o = lattice_polytope.octahedron(3)
4237
sage: lattice_polytope.all_faces([o])
4238
sage: o.faces()
4239
[
4240
[[0], [1], [2], [3], [4], [5]],
4241
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
4242
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
4243
]
4244
4245
However, you cannot use it for polytopes that are constructed as
4246
polar polytopes of others::
4247
4248
sage: lattice_polytope.all_faces([o.polar()])
4249
Traceback (most recent call last):
4250
...
4251
ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!
4252
"""
4253
result_name = _palp("poly.x -fi", polytopes, reduce_dimension=True)
4254
result = open(result_name)
4255
for p in polytopes:
4256
p._read_faces(result)
4257
result.close()
4258
os.remove(result_name)
4259
4260
def all_nef_partitions(polytopes, keep_symmetric=False):
4261
r"""
4262
Compute nef-partitions for all given ``polytopes``.
4263
4264
This functions does it MUCH faster than member functions of
4265
``LatticePolytope`` during the first run. So it is recommended to
4266
use this functions if you work with big sets of data.
4267
4268
Note: member function ``is_reflexive`` will be called
4269
separately for each polytope. It is strictly recommended to call
4270
``all_polars`` on the sequence of
4271
``polytopes`` before using this function.
4272
4273
INPUT: a sequence of lattice polytopes.
4274
4275
EXAMPLES: This function has no output, it is just a fast way to
4276
work with long sequences of polytopes. Of course, you can use short
4277
sequences as well::
4278
4279
sage: o = lattice_polytope.octahedron(3)
4280
sage: lattice_polytope.all_nef_partitions([o])
4281
sage: o.nef_partitions()
4282
[
4283
Nef-partition {0, 1, 3} U {2, 4, 5},
4284
Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),
4285
Nef-partition {0, 1, 2} U {3, 4, 5},
4286
Nef-partition {0, 1, 2, 3} U {4, 5},
4287
Nef-partition {0, 1, 2, 3, 4} U {5} (projection)
4288
]
4289
4290
You cannot use this function for non-reflexive polytopes::
4291
4292
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
4293
... [0, 1, 0, 0, -1, 0],
4294
... [0, 0, 2, 0, 0, -1]])
4295
...
4296
sage: p = LatticePolytope(m)
4297
sage: lattice_polytope.all_nef_partitions([o, p])
4298
Traceback (most recent call last):
4299
...
4300
ValueError: The given polytope is not reflexive!
4301
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
4302
"""
4303
keys = "-N -V -D -P -p"
4304
if keep_symmetric:
4305
keys += " -s"
4306
result_name = _palp("nef.x -f " + keys, polytopes)
4307
result = open(result_name)
4308
for p in polytopes:
4309
if not p.is_reflexive():
4310
raise ValueError, "The given polytope is not reflexive!\nPolytope: %s" % p
4311
p._read_nef_partitions(result)
4312
p._nef_partitions_s = keep_symmetric
4313
result.close()
4314
os.remove(result_name)
4315
4316
def all_points(polytopes):
4317
r"""
4318
Compute lattice points for all given ``polytopes``.
4319
4320
This functions does it MUCH faster than member functions of
4321
``LatticePolytope`` during the first run. So it is recommended to
4322
use this functions if you work with big sets of data.
4323
4324
INPUT: a sequence of lattice polytopes.
4325
4326
EXAMPLES: This function has no output, it is just a fast way to
4327
work with long sequences of polytopes. Of course, you can use short
4328
sequences as well::
4329
4330
sage: o = lattice_polytope.octahedron(3)
4331
sage: lattice_polytope.all_points([o])
4332
sage: o.points()
4333
[ 1 0 0 -1 0 0 0]
4334
[ 0 1 0 0 -1 0 0]
4335
[ 0 0 1 0 0 -1 0]
4336
"""
4337
result_name = _palp("poly.x -fp", polytopes, reduce_dimension=True)
4338
result = open(result_name)
4339
for p in polytopes:
4340
p._points = p._embed(read_palp_matrix(result))
4341
if p._points.nrows() == 0:
4342
raise RuntimeError, ("Cannot read points of a polytope!"
4343
+"\nPolytope: %s" % p)
4344
result.close()
4345
os.remove(result_name)
4346
4347
def all_polars(polytopes):
4348
r"""
4349
Compute polar polytopes for all reflexive and equations of facets
4350
for all non-reflexive ``polytopes``.
4351
4352
``all_facet_equations`` and ``all_polars`` are synonyms.
4353
4354
This functions does it MUCH faster than member functions of
4355
``LatticePolytope`` during the first run. So it is recommended to
4356
use this functions if you work with big sets of data.
4357
4358
INPUT: a sequence of lattice polytopes.
4359
4360
EXAMPLES: This function has no output, it is just a fast way to
4361
work with long sequences of polytopes. Of course, you can use short
4362
sequences as well::
4363
4364
sage: o = lattice_polytope.octahedron(3)
4365
sage: lattice_polytope.all_polars([o])
4366
sage: o.polar()
4367
A polytope polar to An octahedron: 3-dimensional, 8 vertices.
4368
"""
4369
result_name = _palp("poly.x -fe", polytopes)
4370
result = open(result_name)
4371
for p in polytopes:
4372
p._read_equations(result)
4373
result.close()
4374
os.remove(result_name)
4375
4376
# Synonym for the above function
4377
all_facet_equations = all_polars
4378
4379
4380
_always_use_files = True
4381
4382
4383
def always_use_files(new_state=None):
4384
r"""
4385
Set or get the way of using PALP for lattice polytopes.
4386
4387
INPUT:
4388
4389
- ``new_state`` - (default:None) if specified, must be ``True`` or ``False``.
4390
4391
OUTPUT: The current state of using PALP. If ``True``, files are used
4392
for all calls to PALP, otherwise pipes are used for single polytopes.
4393
While the latter may have some advantage in speed, the first method
4394
is more reliable when working with large outputs. The initial state
4395
is ``True``.
4396
4397
EXAMPLES::
4398
4399
sage: lattice_polytope.always_use_files()
4400
True
4401
sage: p = LatticePolytope(matrix([1, 20]))
4402
sage: p.npoints()
4403
20
4404
4405
Now let's use pipes instead of files::
4406
4407
sage: lattice_polytope.always_use_files(False)
4408
False
4409
sage: p = LatticePolytope(matrix([1, 20]))
4410
sage: p.npoints()
4411
20
4412
"""
4413
global _always_use_files
4414
if new_state != None:
4415
_always_use_files = new_state
4416
return _always_use_files
4417
4418
4419
def convex_hull(points):
4420
r"""
4421
Compute the convex hull of the given points.
4422
4423
.. note::
4424
4425
``points`` might not span the space. Also, it fails for large
4426
numbers of vertices in dimensions 4 or greater
4427
4428
INPUT:
4429
4430
4431
- ``points`` - a list that can be converted into
4432
vectors of the same dimension over ZZ.
4433
4434
4435
OUTPUT: list of vertices of the convex hull of the given points (as
4436
vectors).
4437
4438
EXAMPLES: Let's compute the convex hull of several points on a line
4439
in the plane::
4440
4441
sage: lattice_polytope.convex_hull([[1,2],[3,4],[5,6],[7,8]])
4442
[(1, 2), (7, 8)]
4443
"""
4444
if len(points) == 0:
4445
return []
4446
vpoints = []
4447
for p in points:
4448
v = vector(ZZ,p)
4449
if not v in vpoints:
4450
vpoints.append(v)
4451
p0 = vpoints[0]
4452
vpoints = [p-p0 for p in vpoints]
4453
N = ZZ**p0.degree()
4454
H = N.submodule(vpoints)
4455
if H.rank() == 0:
4456
return [p0]
4457
elif H.rank() == N.rank():
4458
vpoints = LatticePolytope(matrix(ZZ, vpoints).transpose(), compute_vertices=True).vertices().columns(copy=False)
4459
else:
4460
H_points = [H.coordinates(p) for p in vpoints]
4461
H_polytope = LatticePolytope(matrix(ZZ, H_points).transpose(), compute_vertices=True)
4462
vpoints = (H.basis_matrix().transpose() * H_polytope.vertices()).columns(copy=False)
4463
vpoints = [p+p0 for p in vpoints]
4464
return vpoints
4465
4466
def filter_polytopes(f, polytopes, subseq=None, print_numbers=False):
4467
r"""
4468
Use the function ``f`` to filter polytopes in a list.
4469
4470
INPUT:
4471
4472
4473
- ``f`` - filtering function, it must take one
4474
argument, a lattice polytope, and return True or False.
4475
4476
- ``polytopes`` - list of polytopes.
4477
4478
- ``subseq`` - (default: None) list of integers. If it
4479
is specified, only polytopes with these numbers will be
4480
considered.
4481
4482
- ``print_numbers`` - (default: False) if True, the
4483
number of the current polytope will be printed on the screen before
4484
calling ``f``.
4485
4486
4487
OUTPUT: a list of integers -- numbers of polytopes in the given
4488
list, that satisfy the given condition (i.e. function
4489
``f`` returns True) and are elements of subseq, if it
4490
is given.
4491
4492
EXAMPLES: Consider a sequence of octahedrons::
4493
4494
sage: polytopes = Sequence([lattice_polytope.octahedron(n) for n in range(2, 7)], cr=True)
4495
sage: polytopes
4496
[
4497
An octahedron: 2-dimensional, 4 vertices.,
4498
An octahedron: 3-dimensional, 6 vertices.,
4499
An octahedron: 4-dimensional, 8 vertices.,
4500
An octahedron: 5-dimensional, 10 vertices.,
4501
An octahedron: 6-dimensional, 12 vertices.
4502
]
4503
4504
This filters octahedrons of dimension at least 4::
4505
4506
sage: lattice_polytope.filter_polytopes(lambda p: p.dim() >= 4, polytopes)
4507
[2, 3, 4]
4508
4509
For long tests you can see the current progress::
4510
4511
sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, print_numbers=True)
4512
0
4513
1
4514
2
4515
3
4516
4
4517
[3, 4]
4518
4519
Here we consider only some of the polytopes::
4520
4521
sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, [2, 3, 4], print_numbers=True)
4522
2
4523
3
4524
4
4525
[3, 4]
4526
"""
4527
if subseq == []:
4528
return []
4529
elif subseq == None:
4530
subseq = range(len(polytopes))
4531
result = []
4532
for n in subseq:
4533
if print_numbers:
4534
print n
4535
os.sys.stdout.flush()
4536
if f(polytopes[n]):
4537
result.append(n)
4538
return result
4539
4540
4541
def integral_length(v):
4542
"""
4543
Compute the integral length of a given rational vector.
4544
4545
INPUT:
4546
4547
- ``v`` - any object which can be converted to a list of rationals
4548
4549
OUTPUT: Rational number ``r`` such that ``v = r u``, where ``u`` is the
4550
primitive integral vector in the direction of ``v``.
4551
4552
EXAMPLES::
4553
4554
sage: lattice_polytope.integral_length([1, 2, 4])
4555
1
4556
sage: lattice_polytope.integral_length([2, 2, 4])
4557
2
4558
sage: lattice_polytope.integral_length([2/3, 2, 4])
4559
2/3
4560
"""
4561
data = [QQ(e) for e in list(v)]
4562
ns = [e.numerator() for e in data]
4563
ds = [e.denominator() for e in data]
4564
return gcd(ns)/lcm(ds)
4565
4566
4567
def minkowski_sum(points1, points2):
4568
r"""
4569
Compute the Minkowski sum of two convex polytopes.
4570
4571
.. note::
4572
4573
Polytopes might not be of maximal dimension.
4574
4575
INPUT:
4576
4577
4578
- ``points1, points2`` - lists of objects that can be
4579
converted into vectors of the same dimension, treated as vertices
4580
of two polytopes.
4581
4582
4583
OUTPUT: list of vertices of the Minkowski sum, given as vectors.
4584
4585
EXAMPLES: Let's compute the Minkowski sum of two line segments::
4586
4587
sage: lattice_polytope.minkowski_sum([[1,0],[-1,0]],[[0,1],[0,-1]])
4588
[(1, 1), (1, -1), (-1, 1), (-1, -1)]
4589
"""
4590
points1 = [vector(p) for p in points1]
4591
points2 = [vector(p) for p in points2]
4592
points = []
4593
for p1 in points1:
4594
for p2 in points2:
4595
points.append(p1+p2)
4596
return convex_hull(points)
4597
4598
4599
def octahedron(dim):
4600
r"""
4601
Return an octahedron of the given dimension.
4602
4603
EXAMPLES: Here are 3- and 4-dimensional octahedrons::
4604
4605
sage: o = lattice_polytope.octahedron(3)
4606
sage: o
4607
An octahedron: 3-dimensional, 6 vertices.
4608
sage: o.vertices()
4609
[ 1 0 0 -1 0 0]
4610
[ 0 1 0 0 -1 0]
4611
[ 0 0 1 0 0 -1]
4612
sage: o = lattice_polytope.octahedron(4)
4613
sage: o
4614
An octahedron: 4-dimensional, 8 vertices.
4615
sage: o.vertices()
4616
[ 1 0 0 0 -1 0 0 0]
4617
[ 0 1 0 0 0 -1 0 0]
4618
[ 0 0 1 0 0 0 -1 0]
4619
[ 0 0 0 1 0 0 0 -1]
4620
4621
There exists only one octahedron of each dimension::
4622
4623
sage: o is lattice_polytope.octahedron(4)
4624
True
4625
"""
4626
if _octahedrons.has_key(dim):
4627
return _octahedrons[dim]
4628
else:
4629
_octahedrons[dim] = _create_octahedron(dim)
4630
return _octahedrons[dim]
4631
4632
4633
def positive_integer_relations(points):
4634
r"""
4635
Return relations between given points.
4636
4637
INPUT:
4638
4639
4640
- ``points`` - lattice points given as columns of a
4641
matrix
4642
4643
4644
OUTPUT: matrix of relations between given points with non-negative
4645
integer coefficients
4646
4647
EXAMPLES: This is a 3-dimensional reflexive polytope::
4648
4649
sage: m = matrix(ZZ,[[1, 0, -1, 0, -1],
4650
... [0, 1, -1, 0, 0],
4651
... [0, 0, 0, 1, -1]])
4652
...
4653
sage: p = LatticePolytope(m)
4654
sage: p.points()
4655
[ 1 0 -1 0 -1 0]
4656
[ 0 1 -1 0 0 0]
4657
[ 0 0 0 1 -1 0]
4658
4659
We can compute linear relations between its points in the following
4660
way::
4661
4662
sage: p.points().transpose().kernel().echelonized_basis_matrix()
4663
[ 1 0 0 1 1 0]
4664
[ 0 1 1 -1 -1 0]
4665
[ 0 0 0 0 0 1]
4666
4667
However, the above relations may contain negative and rational
4668
numbers. This function transforms them in such a way, that all
4669
coefficients are non-negative integers::
4670
4671
sage: lattice_polytope.positive_integer_relations(p.points())
4672
[1 0 0 1 1 0]
4673
[1 1 1 0 0 0]
4674
[0 0 0 0 0 1]
4675
sage: lattice_polytope.positive_integer_relations(ReflexivePolytope(2,1).vertices())
4676
[2 1 1]
4677
"""
4678
points = points.transpose().base_extend(QQ)
4679
relations = points.kernel().echelonized_basis_matrix()
4680
nonpivots = relations.nonpivots()
4681
nonpivot_relations = relations.matrix_from_columns(nonpivots)
4682
n_nonpivots = len(nonpivots)
4683
n = nonpivot_relations.nrows()
4684
a = matrix(QQ,n_nonpivots,n_nonpivots)
4685
for i in range(n_nonpivots):
4686
a[i, i] = -1
4687
a = nonpivot_relations.stack(a).transpose()
4688
a = sage_matrix_to_maxima(a)
4689
maxima.load("simplex")
4690
4691
new_relations = []
4692
for i in range(n_nonpivots):
4693
# Find a non-negative linear combination of relations,
4694
# such that all components are non-negative and the i-th one is 1
4695
b = [0]*i + [1] + [0]*(n_nonpivots - i - 1)
4696
c = [0]*(n+i) + [1] + [0]*(n_nonpivots - i - 1)
4697
x = maxima.linear_program(a, b, c)
4698
if x.str() == r'?Problem\not\feasible\!':
4699
raise ValueError, "cannot find required relations"
4700
x = x.sage()[0][:n]
4701
v = relations.linear_combination_of_rows(x)
4702
new_relations.append(v)
4703
4704
relations = relations.stack(matrix(QQ, new_relations))
4705
# Use the new relation to remove negative entries in non-pivot columns
4706
for i in range(n_nonpivots):
4707
for j in range(n):
4708
coef = relations[j,nonpivots[i]]
4709
if coef < 0:
4710
relations.add_multiple_of_row(j, n+i, -coef)
4711
# Get a new basis
4712
relations = relations.matrix_from_rows(relations.transpose().pivots())
4713
# Switch to integers
4714
for i in range(n):
4715
relations.rescale_row(i, 1/integral_length(relations[i]))
4716
return relations.change_ring(ZZ)
4717
4718
4719
def projective_space(dim):
4720
r"""
4721
Return a simplex of the given dimension, corresponding to
4722
`P_{dim}`.
4723
4724
EXAMPLES: We construct 3- and 4-dimensional simplexes::
4725
4726
sage: p = lattice_polytope.projective_space(3)
4727
sage: p
4728
A simplex: 3-dimensional, 4 vertices.
4729
sage: p.vertices()
4730
[ 1 0 0 -1]
4731
[ 0 1 0 -1]
4732
[ 0 0 1 -1]
4733
sage: p = lattice_polytope.projective_space(4)
4734
sage: p
4735
A simplex: 4-dimensional, 5 vertices.
4736
sage: p.vertices()
4737
[ 1 0 0 0 -1]
4738
[ 0 1 0 0 -1]
4739
[ 0 0 1 0 -1]
4740
[ 0 0 0 1 -1]
4741
"""
4742
m = matrix(ZZ, dim, dim+1)
4743
for i in range(dim):
4744
m[i,i] = 1
4745
m[i,dim] = -1
4746
return LatticePolytope(m, "A simplex", copy_vertices=False)
4747
4748
4749
def read_all_polytopes(file_name, desc=None):
4750
r"""
4751
Read all polytopes from the given file.
4752
4753
INPUT:
4754
4755
4756
- ``file_name`` - the name of a file with VERTICES of
4757
polytopes
4758
4759
- ``desc`` - a string, that will be used for creating
4760
polytope descriptions. By default it will be set to 'A lattice
4761
polytope #%d from "filename"' + and will be used as ``desc %
4762
n`` where ``n`` is the number of the polytope in
4763
the file (*STARTING WITH ZERO*).
4764
4765
4766
OUTPUT: a sequence of polytopes
4767
4768
EXAMPLES: We use poly.x to compute polar polytopes of 2- and
4769
3-octahedrons and read them::
4770
4771
sage: o2 = lattice_polytope.octahedron(2)
4772
sage: o3 = lattice_polytope.octahedron(3)
4773
sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])
4774
sage: f = open(result_name)
4775
sage: f.readlines()
4776
['4 2 Vertices of P-dual <-> Equations of P\n', ' -1 1\n', ' 1 1\n', ' -1 -1\n', ' 1 -1\n', '8 3 Vertices of P-dual <-> Equations of P\n', ' -1 -1 1\n', ' 1 -1 1\n', ' -1 1 1\n', ' 1 1 1\n', ' -1 -1 -1\n', ' 1 -1 -1\n', ' -1 1 -1\n', ' 1 1 -1\n']
4777
sage: f.close()
4778
sage: lattice_polytope.read_all_polytopes(result_name, desc="Polytope from file %d")
4779
[
4780
Polytope from file 0: 2-dimensional, 4 vertices.,
4781
Polytope from file 1: 3-dimensional, 8 vertices.
4782
]
4783
sage: os.remove(result_name)
4784
"""
4785
if desc == None:
4786
desc = r'A lattice polytope #%d from "'+file_name+'"'
4787
polytopes = Sequence([], LatticePolytope, cr=True)
4788
f = open(file_name)
4789
n = 0
4790
m = read_palp_matrix(f)
4791
while m.nrows() != 0:
4792
polytopes.append(LatticePolytope(m,
4793
desc=(desc % n), compute_vertices=False, copy_vertices=False))
4794
n += 1
4795
m = read_palp_matrix(f)
4796
f.close()
4797
return polytopes
4798
4799
4800
def read_palp_matrix(data):
4801
r"""
4802
Read and return an integer matrix from a string or an opened file.
4803
4804
First input line must start with two integers m and n, the number
4805
of rows and columns of the matrix. The rest of the first line is
4806
ignored. The next m lines must contain n numbers each.
4807
4808
If m>n, returns the transposed matrix. If the string is empty or EOF
4809
is reached, returns the empty matrix, constructed by
4810
``matrix()``.
4811
4812
EXAMPLES::
4813
4814
sage: lattice_polytope.read_palp_matrix("2 3 comment \n 1 2 3 \n 4 5 6")
4815
[1 2 3]
4816
[4 5 6]
4817
sage: lattice_polytope.read_palp_matrix("3 2 Will be transposed \n 1 2 \n 3 4 \n 5 6")
4818
[1 3 5]
4819
[2 4 6]
4820
"""
4821
if isinstance(data,str):
4822
f = StringIO.StringIO(data)
4823
mat = read_palp_matrix(f)
4824
f.close()
4825
return mat
4826
# If data is not a string, try to treat it as a file.
4827
line = data.readline()
4828
if line == "":
4829
return matrix()
4830
line = line.split()
4831
m = int(line[0])
4832
n = int(line[1])
4833
seq = []
4834
for i in range(m):
4835
seq.extend(int(el) for el in data.readline().split())
4836
mat = matrix(ZZ,m,n,seq)
4837
if m <= n:
4838
return mat
4839
else:
4840
return mat.transpose()
4841
4842
4843
def sage_matrix_to_maxima(m):
4844
r"""
4845
Convert a Sage matrix to the string representation of Maxima.
4846
4847
EXAMPLE::
4848
4849
sage: m = matrix(ZZ,2)
4850
sage: lattice_polytope.sage_matrix_to_maxima(m)
4851
matrix([0,0],[0,0])
4852
"""
4853
return maxima("matrix("+",".join(str(v.list()) for v in m.rows())+")")
4854
4855
4856
def set_palp_dimension(d):
4857
r"""
4858
Set the dimension for PALP calls to ``d``.
4859
4860
INPUT:
4861
4862
- ``d`` -- an integer from the list [4,5,6,11] or ``None``.
4863
4864
OUTPUT:
4865
4866
- none.
4867
4868
PALP has many hard-coded limits, which must be specified before
4869
compilation, one of them is dimension. Sage includes several versions with
4870
different dimension settings (which may also affect other limits and enable
4871
certain features of PALP). You can change the version which will be used by
4872
calling this function. Such a change is not done automatically for each
4873
polytope based on its dimension, since depending on what you are doing it
4874
may be necessary to use dimensions higher than that of the input polytope.
4875
4876
EXAMPLES:
4877
4878
By default, it is not possible to create the 7-dimensional simplex with
4879
vertices at the basis of the 8-dimensional space::
4880
4881
sage: LatticePolytope(identity_matrix(8))
4882
Traceback (most recent call last):
4883
...
4884
ValueError: Error executing "poly.x -fv" for the given polytope!
4885
Polytope: A lattice polytope: 7-dimensional, 8 vertices.
4886
Vertices:
4887
[ 0 -1 -1 -1 -1 -1 -1 -1]
4888
[ 0 1 0 0 0 0 0 0]
4889
[ 0 0 1 0 0 0 0 0]
4890
[ 0 0 0 1 0 0 0 0]
4891
[ 0 0 0 0 1 0 0 0]
4892
[ 0 0 0 0 0 1 0 0]
4893
[ 0 0 0 0 0 0 1 0]
4894
Output:
4895
Please increase POLY_Dmax to at least 7
4896
4897
However, we can work with this polytope by changing PALP dimension to 11::
4898
4899
sage: lattice_polytope.set_palp_dimension(11)
4900
sage: LatticePolytope(identity_matrix(8))
4901
A lattice polytope: 7-dimensional, 8 vertices.
4902
4903
Let's go back to default settings::
4904
4905
sage: lattice_polytope.set_palp_dimension(None)
4906
"""
4907
global _palp_dimension
4908
_palp_dimension = d
4909
4910
4911
def skip_palp_matrix(data, n=1):
4912
r"""
4913
Skip matrix data in a file.
4914
4915
INPUT:
4916
4917
4918
- ``data`` - opened file with blocks of matrix data in
4919
the following format: A block consisting of m+1 lines has the
4920
number m as the first element of its first line.
4921
4922
- ``n`` - (default: 1) integer, specifies how many
4923
blocks should be skipped
4924
4925
4926
If EOF is reached during the process, raises ValueError exception.
4927
4928
EXAMPLE: We create a file with vertices of the square and the cube,
4929
but read only the second set::
4930
4931
sage: o2 = lattice_polytope.octahedron(2)
4932
sage: o3 = lattice_polytope.octahedron(3)
4933
sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])
4934
sage: f = open(result_name)
4935
sage: f.readlines()
4936
['4 2 Vertices of P-dual <-> Equations of P\n',
4937
' -1 1\n',
4938
' 1 1\n',
4939
' -1 -1\n',
4940
' 1 -1\n',
4941
'8 3 Vertices of P-dual <-> Equations of P\n',
4942
' -1 -1 1\n',
4943
' 1 -1 1\n',
4944
' -1 1 1\n',
4945
' 1 1 1\n',
4946
' -1 -1 -1\n',
4947
' 1 -1 -1\n',
4948
' -1 1 -1\n',
4949
' 1 1 -1\n']
4950
sage: f.close()
4951
sage: f = open(result_name)
4952
sage: lattice_polytope.skip_palp_matrix(f)
4953
sage: lattice_polytope.read_palp_matrix(f)
4954
[-1 1 -1 1 -1 1 -1 1]
4955
[-1 -1 1 1 -1 -1 1 1]
4956
[ 1 1 1 1 -1 -1 -1 -1]
4957
sage: f.close()
4958
sage: os.remove(result_name)
4959
"""
4960
for i in range(n):
4961
line = data.readline()
4962
if line == "":
4963
raise ValueError, "There are not enough data to skip!"
4964
for j in range(int(line.split()[0])):
4965
if data.readline() == "":
4966
raise ValueError, "There are not enough data to skip!"
4967
4968
4969
def write_palp_matrix(m, ofile=None, comment="", format=None):
4970
r"""
4971
Write a matrix into a file.
4972
4973
INPUT:
4974
4975
4976
- ``m`` - a matrix over integers.
4977
4978
- ``ofile`` - a file opened for writing (default:
4979
stdout)
4980
4981
- ``comment`` - a string (default: empty) see output
4982
description
4983
4984
- ``format`` - a format string used to print matrix entries.
4985
4986
4987
OUTPUT: First line: number_of_rows number_of_columns comment
4988
Next number_of_rows lines: rows of the matrix.
4989
4990
EXAMPLES: This functions is used for writing polytope vertices in
4991
PALP format::
4992
4993
sage: o = lattice_polytope.octahedron(3)
4994
sage: lattice_polytope.write_palp_matrix(o.vertices(), comment="3D Octahedron")
4995
3 6 3D Octahedron
4996
1 0 0 -1 0 0
4997
0 1 0 0 -1 0
4998
0 0 1 0 0 -1
4999
sage: lattice_polytope.write_palp_matrix(o.vertices(), format="%4d")
5000
3 6
5001
1 0 0 -1 0 0
5002
0 1 0 0 -1 0
5003
0 0 1 0 0 -1
5004
"""
5005
if format == None:
5006
n = max(len(str(m[i,j]))
5007
for i in range(m.nrows()) for j in range(m.ncols()))
5008
format = "%" + str(n) + "d"
5009
s = "%d %d %s\n" % (m.nrows(),m.ncols(),comment)
5010
if ofile is None:
5011
print s,
5012
else:
5013
ofile.write(s)
5014
for i in range(m.nrows()):
5015
s = " ".join(format % m[i,j] for j in range(m.ncols()))+"\n"
5016
if ofile is None:
5017
print s,
5018
else:
5019
ofile.write(s)
5020
5021