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