This module provides tools for work with lattice and reflexive polytopes. A convex polytope is the convex hull of finitely many points in \(\RR^n\). The dimension \(n\) of a polytope is the smallest \(n\) such that the polytope can be embedded in \(\RR^n\).
A lattice polytope is a polytope whose vertices all have integer coordinates.
If \(L\) is a lattice polytope, the dual polytope of \(L\) is
A reflexive polytope is a lattice polytope, such that its polar is also a lattice polytope, i.e. it is bounded and has vertices with integer coordinates.
This Sage module uses Package for Analyzing Lattice Polytopes (PALP), which is a program written in C by Maximilian Kreuzer and Harald Skarke, which is freely available under the GNU license terms at http://hep.itp.tuwien.ac.at/~kreuzer/CY/. Moreover, PALP is included standard with Sage.
PALP is described in the paper Arxiv math.SC/0204356. Its distribution also contains the application nef.x, which was created by Erwin Riegler and computes nef-partitions and Hodge data for toric complete intersections.
ACKNOWLEDGMENT: polytope.py module written by William Stein was used as an example of organizing an interface between an external program and Sage. William Stein also helped Andrey Novoseltsev with debugging and tuning of this module.
Robert Bradshaw helped Andrey Novoseltsev to realize plot3d function.
Note
IMPORTANT: PALP requires some parameters to be determined during compilation time, i.e., the maximum dimension of polytopes, the maximum number of points, etc. These limitations may lead to errors during calls to different functions of these module. Currently, a ValueError exception will be raised if the output of poly.x or nef.x is empty or contains the exclamation mark. The error message will contain the exact command that caused an error, the description and vertices of the polytope, and the obtained output.
Data obtained from PALP and some other data is cached and most returned values are immutable. In particular, you cannot change the vertices of the polytope or their order after creation of the polytope.
If you are going to work with large sets of data, take a look at all_* functions in this module. They precompute different data for sequences of polynomials with a few runs of external programs. This can significantly affect the time of future computations. You can also use dump/load, but not all data will be stored (currently only faces and the number of their internal and boundary points are stored, in addition to polytope vertices and its polar).
AUTHORS:
Andrey Novoseltsev (2007-01-11): initial version
Andrey Novoseltsev (2007-01-15): all_* functions
Andrey Novoseltsev (2008-04-01): second version, including:
- dual nef-partitions and necessary convex_hull and minkowski_sum
- built-in sequences of 2- and 3-dimensional reflexive polytopes
- plot3d, skeleton_show
Andrey Novoseltsev (2009-08-26): dropped maximal dimension requirement
Andrey Novoseltsev (2010-12-15): new version of nef-partitions
Maximilian Kreuzer and Harald Skarke: authors of PALP (which was also used to obtain the list of 3-dimensional reflexive polytopes)
Erwin Riegler: the author of nef.x
Construct a lattice polytope.
LatticePolytope(data, [desc], [compute_vertices], [copy_vertices], [n])
INPUT:
data – The points (which must be vertices if compute_vertices is False) spanning the lattice polytope, specified as one of:
- a matrix whose columns are vertices of the polytope.
- an iterable of iterables (for example, a list of vectors) defining the point coordinates.
- a file with matrix data, open for reading, or
- a filename of such a file. See read_palp_matrix for the file format.
desc – (default: “A lattice polytope”) description of the polytope.
convex hull of the given points will be computed for determining vertices. Otherwise, the given points must be vertices.
compute_vertices is False, and data is a matrix of vertices, it will be made immutable.
that contains data blocks for several polytopes, the n-th block will be used. ENUMERATION STARTS WITH ZERO.
OUTPUT: a lattice polytope
EXAMPLES: Here we construct a polytope from a matrix whose columns are vertices in 3-dimensional space. In the first case a copy of the given matrix is made during construction, in the second one the matrix is made immutable and used as a matrix of vertices.
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
... [0, 1, 0, 0, -1, 0],
... [0, 0, 1, 0, 0, -1]])
...
sage: p = LatticePolytope(m)
sage: p
A lattice polytope: 3-dimensional, 6 vertices.
sage: m.is_mutable()
True
sage: m is p.vertices()
False
sage: p = LatticePolytope(m, compute_vertices=False, copy_vertices=False)
sage: m.is_mutable()
False
sage: m is p.vertices()
True
We draw a pretty picture of the polytype in 3-dimensional space:
sage: p.plot3d().show()
Now we add an extra point, which is in the interior of the polytope...
sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope constructed from 7 points")
sage: p
A lattice polytope constructed from 7 points: 3-dimensional, 6 vertices.
You can suppress vertex computation for speed but this can lead to mistakes:
sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope with WRONG vertices",
... compute_vertices=False)
...
sage: p
A lattice polytope with WRONG vertices: 3-dimensional, 7 vertices.
Given points must be in the lattice:
sage: LatticePolytope(matrix([1/2, 3/2]))
Traceback (most recent call last):
...
ValueError: Points must be integral!
Given:
[1/2 3/2]
But it is OK to create polytopes of non-maximal dimension:
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0, 0],
... [0, 1, 0, 0, -1, 0, 0],
... [0, 0, 0, 0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p
A lattice polytope: 2-dimensional, 4 vertices.
sage: p.vertices()
[ 1 0 -1 0]
[ 0 1 0 -1]
[ 0 0 0 0]
An empty lattice polytope can be specified by a matrix with zero columns:
sage: p = LatticePolytope(matrix(ZZ,3,0)); p A lattice polytope: -1-dimensional, 0 vertices. sage: p.ambient_dim() 3 sage: p.npoints() 0 sage: p.nfacets() 0 sage: p.points() [] sage: p.faces() []
Bases: sage.structure.sage_object.SageObject, _abcoll.Hashable
Class of lattice/reflexive polytopes.
Use LatticePolytope for constructing a polytope.
Return a*P+b, where P is this lattice polytope.
Note
#. While a and b may be rational, the final result must be a lattice polytope, i.e. all vertices must be integral.
#. If the transform (restricted to this polytope) is bijective, facial structure will be preserved, e.g. the first facet of the image will be spanned by the images of vertices which span the first facet of the original polytope.
INPUT:
EXAMPLES:
sage: o = lattice_polytope.octahedron(2)
sage: o.vertices()
[ 1 0 -1 0]
[ 0 1 0 -1]
sage: o.affine_transform(2).vertices()
[ 2 0 -2 0]
[ 0 2 0 -2]
sage: o.affine_transform(1,1).vertices()
[2 1 0 1]
[1 2 1 0]
sage: o.affine_transform(b=1).vertices()
[2 1 0 1]
[1 2 1 0]
sage: o.affine_transform(b=(1, 0)).vertices()
[ 2 1 0 1]
[ 0 1 0 -1]
sage: a = matrix(QQ, 2, [1/2, 0, 0, 3/2])
sage: o.polar().vertices()
[-1 1 -1 1]
[ 1 1 -1 -1]
sage: o.polar().affine_transform(a, (1/2, -1/2)).vertices()
[ 0 1 0 1]
[ 1 1 -2 -2]
While you can use rational transformation, the result must be integer:
sage: o.affine_transform(a)
Traceback (most recent call last):
...
ValueError: Points must be integral!
Given:
[ 1/2 0 -1/2 0]
[ 0 3/2 0 -3/2]
Return the dimension of the ambient space of this polytope.
EXAMPLES: We create a 3-dimensional octahedron and check its ambient dimension:
sage: o = lattice_polytope.octahedron(3)
sage: o.ambient_dim()
3
Return the dimension of this polytope.
EXAMPLES: We create a 3-dimensional octahedron and check its dimension:
sage: o = lattice_polytope.octahedron(3)
sage: o.dim()
3
Now we create a 2-dimensional diamond in a 3-dimensional space:
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.dim()
2
sage: p.ambient_dim()
3
Return the matrix of distances for this polytope or distances for the given point.
The matrix of distances m gives distances m[i,j] between the i-th facet (which is also the i-th vertex of the polar polytope in the reflexive case) and j-th point of this polytope.
If point is specified, integral distances from the point to all facets of this polytope will be computed.
This function CAN be used for polytopes whose dimension is smaller than the dimension of the ambient space. In this case distances are computed in the affine subspace spanned by the polytope and if the point is given, it must be in this subspace.
EXAMPLES: The matrix of distances for a 3-dimensional octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: o.distances()
[0 0 2 2 2 0 1]
[2 0 2 0 2 0 1]
[0 2 2 2 0 0 1]
[2 2 2 0 0 0 1]
[0 0 0 2 2 2 1]
[2 0 0 0 2 2 1]
[0 2 0 2 0 2 1]
[2 2 0 0 0 2 1]
Distances from facets to the point (1,2,3):
sage: o.distances([1,2,3])
(1, 3, 5, 7, -5, -3, -1, 1)
It is OK to use RATIONAL coordinates:
sage: o.distances([1,2,3/2])
(-1/2, 3/2, 7/2, 11/2, -7/2, -3/2, 1/2, 5/2)
sage: o.distances([1,2,sqrt(2)])
Traceback (most recent call last):
...
TypeError: unable to convert sqrt(2) to a rational
Now we create a non-spanning polytope:
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.distances()
[0 2 2 0 1]
[2 2 0 0 1]
[0 0 2 2 1]
[2 0 0 2 1]
sage: p.distances((1/2, 3, 0))
(7/2, 9/2, -5/2, -3/2)
sage: p.distances((1, 1, 1))
Traceback (most recent call last):
...
ArithmeticError: vector is not in free module
Return the sequence of edges of this polytope (i.e. faces of dimension 1).
EXAMPLES: The octahedron has 12 edges:
sage: o = lattice_polytope.octahedron(3)
sage: len(o.edges())
12
sage: o.edges()
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
Return the sequence of proper faces of this polytope.
If dim or codim are specified, returns a sequence of faces of the corresponding dimension or codimension. Otherwise returns the sequence of such sequences for all dimensions.
EXAMPLES: All faces of the 3-dimensional octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: o.faces()
[
[[0], [1], [2], [3], [4], [5]],
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
]
Its faces of dimension one (i.e., edges):
sage: o.faces(dim=1)
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
Its faces of codimension two (also edges):
sage: o.faces(codim=2)
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]
It is an error to specify both dimension and codimension at the same time, even if they do agree:
sage: o.faces(dim=1, codim=2)
Traceback (most recent call last):
...
ValueError: Both dim and codim are given!
The only faces of a zero-dimensional polytope are the empty set and the polytope itself, i.e. it has no proper faces at all:
sage: p = LatticePolytope(matrix([[1]]))
sage: p.vertices()
[1]
sage: p.faces()
[]
In particular, you an exception will be raised if you try to access faces of the given dimension or codimension, including edges and facets:
sage: p.facets()
Traceback (most recent call last):
...
IndexError: list index out of range
Return the constant in the i-th facet inequality of this polytope.
The i-th facet inequality is given by self.facet_normal(i) * X + self.facet_constant(i) >= 0.
INPUT:
OUTPUT:
EXAMPLES:
Let’s take a look at facets of the octahedron and some polytopes inside it:
sage: o = lattice_polytope.octahedron(3)
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: o.facet_normal(0)
(-1, -1, 1)
sage: o.facet_constant(0)
1
sage: m = copy(o.vertices())
sage: m[0,0] = 0
sage: m
[ 0 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: p = LatticePolytope(m)
sage: p.facet_normal(0)
(-1, 0, 0)
sage: p.facet_constant(0)
0
sage: m[0,3] = 0
sage: m
[ 0 0 0 0 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: p = LatticePolytope(m)
sage: p.facet_normal(0)
(0, -1, 1)
sage: p.facet_constant(0)
1
This is a 2-dimensional lattice polytope in a 4-dimensional space:
sage: m = matrix([(1,-1,1,3), (-1,-1,1,3), (0,0,0,0)])
sage: p = LatticePolytope(m.transpose())
sage: p
A lattice polytope: 2-dimensional, 3 vertices.
sage: p.vertices()
[ 1 -1 0]
[-1 -1 0]
[ 1 1 0]
[ 3 3 0]
sage: fns = [p.facet_normal(i) for i in range(p.nfacets())]
sage: fns
[(11, -1, 1, 3), (-11, -1, 1, 3), (0, 1, -1, -3)]
sage: fcs = [p.facet_constant(i) for i in range(p.nfacets())]
sage: fcs
[0, 0, 11]
Now we manually compute the distance matrix of this polytope. Since it is a triangle, each line (corresponding to a facet) should have two zeros (vertices of the corresponding facet) and one positive number (since our normals are inner):
sage: matrix([[fns[i] * p.vertex(j) + fcs[i]
... for j in range(p.nvertices())]
... for i in range(p.nfacets())])
[22 0 0]
[ 0 22 0]
[ 0 0 11]
Return the inner normal to the i-th facet of this polytope.
If this polytope is not full-dimensional, facet normals will be orthogonal to the integer kernel of the affine subspace spanned by this polytope.
INPUT:
OUTPUT:
EXAMPLES:
Let’s take a look at facets of the octahedron and some polytopes inside it:
sage: o = lattice_polytope.octahedron(3)
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: o.facet_normal(0)
(-1, -1, 1)
sage: o.facet_constant(0)
1
sage: m = copy(o.vertices())
sage: m[0,0] = 0
sage: m
[ 0 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: p = LatticePolytope(m)
sage: p.facet_normal(0)
(-1, 0, 0)
sage: p.facet_constant(0)
0
sage: m[0,3] = 0
sage: m
[ 0 0 0 0 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: p = LatticePolytope(m)
sage: p.facet_normal(0)
(0, -1, 1)
sage: p.facet_constant(0)
1
Here is an example of a 3-dimensional polytope in a 4-dimensional space:
sage: m = matrix([(0,0,0,0), (1,1,1,3), (1,-1,1,3), (-1,-1,1,3)])
sage: p = LatticePolytope(m.transpose())
sage: p.vertices()
[ 0 1 1 -1]
[ 0 1 -1 -1]
[ 0 1 1 1]
[ 0 3 3 3]
sage: ker = p.vertices().integer_kernel().matrix()
sage: ker
[ 0 0 3 -1]
sage: [ker * p.facet_normal(i) for i in range(p.nfacets())]
[(0), (0), (0), (0)]
Now we manually compute the distance matrix of this polytope. Since it is a simplex, each line (corresponding to a facet) should consist of zeros (indicating generating vertices of the corresponding facet) and a single positive number (since our normals are inner):
sage: matrix([[p.facet_normal(i) * p.vertex(j)
... + p.facet_constant(i)
... for j in range(p.nvertices())]
... for i in range(p.nfacets())])
[ 0 0 0 20]
[ 0 0 20 0]
[ 0 20 0 0]
[10 0 0 0]
Return the sequence of facets of this polytope (i.e. faces of codimension 1).
EXAMPLES: All facets of the 3-dimensional octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: o.facets()
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
Facets are the same as faces of codimension one:
sage: o.facets() is o.faces(codim=1)
True
Return the index of this polytope in the internal database of 2- or 3-dimensional reflexive polytopes. Databases are stored in the directory of the package.
Note
The first call to this function for each dimension can take a few seconds while the dictionary of all polytopes is constructed, but after that it is cached and fast.
Return type: | integer |
---|
EXAMPLES: We check what is the index of the “diamond” in the database:
sage: o = lattice_polytope.octahedron(2)
sage: o.index()
3
Note that polytopes with the same index are not necessarily the same:
sage: o.vertices()
[ 1 0 -1 0]
[ 0 1 0 -1]
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
[ 1 0 0 -1]
[ 0 1 -1 0]
But they are in the same \(GL(Z^n)\) orbit and have the same normal form:
sage: o.normal_form()
[ 1 0 0 -1]
[ 0 1 -1 0]
sage: lattice_polytope.ReflexivePolytope(2,3).normal_form()
[ 1 0 0 -1]
[ 0 1 -1 0]
Return True if this polytope is reflexive.
EXAMPLES: The 3-dimensional octahedron is reflexive (and 4318 other 3-polytopes):
sage: o = lattice_polytope.octahedron(3)
sage: o.is_reflexive()
True
But not all polytopes are reflexive:
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
... [0, 1, 0, 0, -1, 0],
... [0, 0, 0, 0, 0, -1]])
...
sage: p = LatticePolytope(m)
sage: p.is_reflexive()
False
Only full-dimensional polytopes can be reflexive (otherwise the polar set is not a polytope at all, since it is unbounded):
sage: m = matrix(ZZ, [[0, 1, 0, -1, 0],
... [0, 0, 1, 0, -1],
... [0, 0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.is_reflexive()
False
Return 2-part nef-partitions of self.
INPUT:
OUTPUT:
Type NefPartition? for definitions and notation.
EXAMPLES:
Nef-partitions of the 4-dimensional octahedron:
sage: o = lattice_polytope.octahedron(4)
sage: o.nef_partitions()
[
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},
Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)
]
Now we omit projections:
sage: o.nef_partitions(keep_projections=False)
[
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7}
]
Currently Hodge numbers cannot be computed for a given nef-partition:
sage: o.nef_partitions()[1].hodge_numbers()
Traceback (most recent call last):
...
NotImplementedError: use nef_partitions(hodge_numbers=True)!
But they can be obtained from nef.x for all nef-partitions at once. Partitions will be exactly the same:
sage: o.nef_partitions(hodge_numbers=True) # long time (2s on sage.math, 2011)
[
Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),
Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},
Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},
Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),
Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},
Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},
Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},
Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)
]
Now it is possible to get Hodge numbers:
sage: o.nef_partitions(hodge_numbers=True)[1].hodge_numbers()
(20,)
Since nef-partitions are cached, their Hodge numbers are accessible after the first request, even if you do not specify hodge_numbers=True anymore:
sage: o.nef_partitions()[1].hodge_numbers()
(20,)
We illustrate removal of symmetric partitions on a diamond:
sage: o = lattice_polytope.octahedron(2)
sage: o.nef_partitions()
[
Nef-partition {0, 2} U {1, 3} (direct product),
Nef-partition {0, 1} U {2, 3},
Nef-partition {0, 1, 2} U {3} (projection)
]
sage: o.nef_partitions(keep_symmetric=True)
[
Nef-partition {0, 1, 3} U {2} (projection),
Nef-partition {0, 2, 3} U {1} (projection),
Nef-partition {0, 3} U {1, 2},
Nef-partition {1, 2, 3} U {0} (projection),
Nef-partition {1, 3} U {0, 2} (direct product),
Nef-partition {2, 3} U {0, 1},
Nef-partition {0, 1, 2} U {3} (projection)
]
Nef-partitions can be computed only for reflexive polytopes:
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
... [0, 1, 0, 0, -1, 0],
... [0, 0, 2, 0, 0, -1]])
...
sage: p = LatticePolytope(m)
sage: p.nef_partitions()
Traceback (most recent call last):
...
ValueError: The given polytope is not reflexive!
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
Run nef.x with given keys on vertices of this polytope.
INPUT:
OUTPUT: the output of nef.x as a string.
EXAMPLES: This call is used internally for computing nef-partitions:
sage: o = lattice_polytope.octahedron(3)
sage: s = o.nef_x("-N -V -p")
sage: s # output contains random time
M:27 8 N:7 6 codim=2 #part=5
3 6 Vertices of P:
1 0 0 -1 0 0
0 1 0 0 -1 0
0 0 1 0 0 -1
P:0 V:2 4 5 0sec 0cpu
P:2 V:3 4 5 0sec 0cpu
P:3 V:4 5 0sec 0cpu
np=3 d:1 p:1 0sec 0cpu
Return the number of facets of this polytope.
EXAMPLES: The number of facets of the 3-dimensional octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: o.nfacets()
8
The number of facets of an interval is 2:
sage: LatticePolytope(matrix([1,2])).nfacets()
2
Now consider a 2-dimensional diamond in a 3-dimensional space:
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.nfacets()
4
Return the normal form of vertices of the polytope.
Two full-dimensional lattice polytopes are in the same GL(Z)-orbit if and only if their normal forms are the same. Normal form is not defined and thus cannot be used for polytopes whose dimension is smaller than the dimension of the ambient space.
EXAMPLES: We compute the normal form of the “diamond”:
sage: o = lattice_polytope.octahedron(2)
sage: o.vertices()
[ 1 0 -1 0]
[ 0 1 0 -1]
sage: o.normal_form()
[ 1 0 0 -1]
[ 0 1 -1 0]
The diamond is the 3rd polytope in the internal database...
sage: o.index()
3
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
[ 1 0 0 -1]
[ 0 1 -1 0]
It is not possible to compute normal forms for polytopes which do not span the space:
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.normal_form()
Traceback (most recent call last):
...
ValueError: Normal form is not defined for a 2-dimensional polytope in a 3-dimensional space!
Return the number of lattice points of this polytope.
EXAMPLES: The number of lattice points of the 3-dimensional octahedron and its polar cube:
sage: o = lattice_polytope.octahedron(3)
sage: o.npoints()
7
sage: cube = o.polar()
sage: cube.npoints()
27
Return the number of vertices of this polytope.
EXAMPLES: The number of vertices of the 3-dimensional octahedron and its polar cube:
sage: o = lattice_polytope.octahedron(3)
sage: o.nvertices()
6
sage: cube = o.polar()
sage: cube.nvertices()
8
Return the index of the origin in the list of points of self.
OUTPUT:
EXAMPLES:
sage: o = lattice_polytope.octahedron(2)
sage: o.origin()
4
sage: o.point(o.origin())
(0, 0)
sage: p = LatticePolytope(matrix([[1,2]]))
sage: p.points()
[1 2]
sage: print p.origin()
None
Now we make sure that the origin of non-full-dimensional polytopes can be identified correctly (Trac #10661):
sage: LatticePolytope([(1,0,0), (-1,0,0)]).origin()
2
Return the set of all lattice polytopes.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: o.parent()
Set of all Lattice Polytopes
Return a 3d-plot of this polytope.
Polytopes with ambient dimension 1 and 2 will be plotted along x-axis or in xy-plane respectively. Polytopes of dimension 3 and less with ambient dimension 4 and greater will be plotted in some basis of the spanned space.
By default, everything is shown with more or less pretty combination of size and color parameters.
INPUT: Most of the parameters are self-explanatory:
EXAMPLES: The default plot of a cube:
sage: c = lattice_polytope.octahedron(3).polar()
sage: c.plot3d()
Plot without facets and points, shown without the frame:
sage: c.plot3d(show_facets=false,show_points=false).show(frame=False)
Plot with facets of different colors:
sage: c.plot3d(facet_colors=rainbow(c.nfacets(), 'rgbtuple'))
It is also possible to plot lower dimensional polytops in 3D (let’s also change labels of vertices):
sage: lattice_polytope.octahedron(2).plot3d(vlabels=["A", "B", "C", "D"])
TESTS:
sage: m = matrix([[0,0,0],[0,1,1],[1,0,1],[1,1,0]]).transpose()
sage: p = LatticePolytope(m, compute_vertices=True)
sage: p.plot3d()
Return the i-th point of this polytope, i.e. the i-th column of the matrix returned by points().
EXAMPLES: First few points are actually vertices:
sage: o = lattice_polytope.octahedron(3)
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: o.point(1)
(0, 1, 0)
The only other point in the octahedron is the origin:
sage: o.point(6)
(0, 0, 0)
sage: o.points()
[ 1 0 0 -1 0 0 0]
[ 0 1 0 0 -1 0 0]
[ 0 0 1 0 0 -1 0]
Return all lattice points of this polytope as columns of a matrix.
EXAMPLES: The lattice points of the 3-dimensional octahedron and its polar cube:
sage: o = lattice_polytope.octahedron(3)
sage: o.points()
[ 1 0 0 -1 0 0 0]
[ 0 1 0 0 -1 0 0]
[ 0 0 1 0 0 -1 0]
sage: cube = o.polar()
sage: cube.points()
[-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]
[-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]
[ 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]
Lattice points of a 2-dimensional diamond in a 3-dimensional space:
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.points()
[ 1 0 -1 0 0]
[ 0 1 0 -1 0]
[ 0 0 0 0 0]
We check that points of a zero-dimensional polytope can be computed:
sage: p = LatticePolytope(matrix([[1]]))
sage: p.vertices()
[1]
sage: p.points()
[1]
Return the polar polytope, if this polytope is reflexive.
EXAMPLES: The polar polytope to the 3-dimensional octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: cube = o.polar()
sage: cube
A polytope polar to An octahedron: 3-dimensional, 8 vertices.
The polar polytope “remembers” the original one:
sage: cube.polar()
An octahedron: 3-dimensional, 6 vertices.
sage: cube.polar().polar() is cube
True
Only reflexive polytopes have polars:
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
... [0, 1, 0, 0, -1, 0],
... [0, 0, 2, 0, 0, -1]])
...
sage: p = LatticePolytope(m)
sage: p.polar()
Traceback (most recent call last):
...
ValueError: The given polytope is not reflexive!
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
Run poly.x with given keys on vertices of this polytope.
INPUT:
OUTPUT: the output of poly.x as a string.
EXAMPLES: This call is used for determining if a polytope is reflexive or not:
sage: o = lattice_polytope.octahedron(3)
sage: print o.poly_x("e")
8 3 Vertices of P-dual <-> Equations of P
-1 -1 1
1 -1 1
-1 1 1
1 1 1
-1 -1 -1
1 -1 -1
-1 1 -1
1 1 -1
Since PALP has limits on different parameters determined during compilation, the following code is likely to fail, unless you change default settings of PALP:
sage: BIGO = lattice_polytope.octahedron(7)
sage: BIGO
An octahedron: 7-dimensional, 14 vertices.
sage: BIGO.poly_x("e") # possibly different output depending on your system
Traceback (most recent call last):
...
ValueError: Error executing "poly.x -fe" for the given polytope!
Polytope: An octahedron: 7-dimensional, 14 vertices.
Vertices:
[ 1 0 0 0 0 0 0 -1 0 0 0 0 0 0]
[ 0 1 0 0 0 0 0 0 -1 0 0 0 0 0]
[ 0 0 1 0 0 0 0 0 0 -1 0 0 0 0]
[ 0 0 0 1 0 0 0 0 0 0 -1 0 0 0]
[ 0 0 0 0 1 0 0 0 0 0 0 -1 0 0]
[ 0 0 0 0 0 1 0 0 0 0 0 0 -1 0]
[ 0 0 0 0 0 0 1 0 0 0 0 0 0 -1]
Output:
Please increase POLY_Dmax to at least 7
You cannot call poly.x for polytopes that don’t span the space (if you could, it would crush anyway):
sage: m = matrix(ZZ, [[1, 0, -1, 0],
... [0, 1, 0, -1],
... [0, 0, 0, 0]])
...
sage: p = LatticePolytope(m)
sage: p.poly_x("e")
Traceback (most recent call last):
...
ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!
But if you know what you are doing, you can call it for the polytope in some basis of the spanned space:
sage: print p.poly_x("e", reduce_dimension=True)
4 2 Equations of P
-1 1 0
1 1 2
-1 -1 0
1 -1 2
Show a 3d picture of the polytope with default settings and without axes or frame.
See self.plot3d? for more details.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: o.show3d()
Return the graph of the one-skeleton of this polytope.
EXAMPLES: We construct the one-skeleton graph for the “diamond”:
sage: o = lattice_polytope.octahedron(2)
sage: g = o.skeleton()
sage: g
Graph on 4 vertices
sage: g.edges()
[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]
Return the increasing list of indices of lattice points in k-skeleton of the polytope (k is 1 by default).
EXAMPLES: We compute all skeleton points for the cube:
sage: o = lattice_polytope.octahedron(3)
sage: c = o.polar()
sage: c.skeleton_points()
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]
The default was 1-skeleton:
sage: c.skeleton_points(k=1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]
0-skeleton just lists all vertices:
sage: c.skeleton_points(k=0)
[0, 1, 2, 3, 4, 5, 6, 7]
2-skeleton lists all points except for the origin (point #17):
sage: c.skeleton_points(k=2)
[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]
3-skeleton includes all points:
sage: c.skeleton_points(k=3)
[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]
It is OK to compute higher dimensional skeletons - you will get the list of all points:
sage: c.skeleton_points(k=100)
[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]
Show the graph of one-skeleton of this polytope. Works only for polytopes in a 3-dimensional space.
INPUT:
EXAMPLES: Show a pretty picture of the octahedron:
sage: o = lattice_polytope.octahedron(3)
sage: o.skeleton_show([1,2,4])
Does not work for a diamond at the moment:
sage: o = lattice_polytope.octahedron(2)
sage: o.skeleton_show()
Traceback (most recent call last):
...
ValueError: need a polytope in a 3-dimensional space! Got:
An octahedron: 2-dimensional, 4 vertices.
Return a list of indices of vertices of a 2-dimensional polytope in their boundary order.
Needed for plot3d function of polytopes.
EXAMPLES:
sage: c = lattice_polytope.octahedron(2).polar() sage: c.traverse_boundary() [0, 1, 3, 2]
Return the i-th vertex of this polytope, i.e. the i-th column of the matrix returned by vertices().
EXAMPLES: Note that numeration starts with zero:
sage: o = lattice_polytope.octahedron(3)
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: o.vertex(3)
(-1, 0, 0)
Return vertices of this polytope as columns of a matrix.
EXAMPLES: The lattice points of the 3-dimensional octahedron and its polar cube:
sage: o = lattice_polytope.octahedron(3)
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: cube = o.polar()
sage: cube.vertices()
[-1 1 -1 1 -1 1 -1 1]
[-1 -1 1 1 -1 -1 1 1]
[ 1 1 1 1 -1 -1 -1 -1]
Bases: sage.structure.sage_object.SageObject, _abcoll.Hashable
Create a nef-partition.
INPUT:
OUTPUT:
Let \(M\) and \(N\) be dual lattices. Let \(\Delta \subset M_\RR\) be a reflexive polytope with polar \(\Delta^\circ \subset N_\RR\). Let \(X_\Delta\) be the toric variety associated to the normal fan of \(\Delta\). A nef-partition is a decomposition of the vertex set \(V\) of \(\Delta^\circ\) into a disjoint union \(V = V_0 \sqcup V_1 \sqcup \dots \sqcup V_{k-1}\) such that divisors \(E_i = \sum_{v\in V_i} D_v\) are Cartier (here \(D_v\) are prime torus-invariant Weil divisors corresponding to vertices of \(\Delta^\circ\)). Equivalently, let \(\nabla_i \subset N_\RR\) be the convex hull of vertices from \(V_i\) and the origin. These polytopes form a nef-partition if their Minkowski sum \(\nabla \subset N_\RR\) is a reflexive polytope.
The dual nef-partition is formed by polytopes \(\Delta_i \subset M_\RR\) of \(E_i\), which give a decomposition of the vertex set of \(\nabla^\circ \subset M_\RR\) and their Minkowski sum is \(\Delta\), i.e. the polar duality of reflexive polytopes switches convex hull and Minkowski sum for dual nef-partitions:
See Section 4.3.1 in [CK99] and references therein for further details, or [BN08] for a purely combinatorial approach.
REFERENCES:
[BN08] | (1, 2) Victor V. Batyrev and Benjamin Nill. Combinatorial aspects of mirror symmetry. In Integer points in polyhedra — geometry, number theory, representation theory, algebra, optimization, statistics, volume 452 of Contemp. Math., pages 35–66. Amer. Math. Soc., Providence, RI, 2008. arXiv:math/0703456v2 [math.CO]. |
[CK99] | David A. Cox and Sheldon Katz. Mirror symmetry and algebraic geometry, volume 68 of Mathematical Surveys and Monographs. American Mathematical Society, Providence, RI, 1999. |
EXAMPLES:
It is very easy to create a nef-partition for the octahedron, since for this polytope any decomposition of vertices is a nef-partition. We create a 3-part nef-partition with the 0-th and 1-st vertices belonging to the 0-th part (recall that numeration in Sage starts with 0), the 2-nd and 5-th vertices belonging to the 1-st part, and 3-rd and 4-th vertices belonging to the 2-nd part:
sage: o = lattice_polytope.octahedron(3)
sage: np = NefPartition([0,0,1,2,2,1], o)
sage: np
Nef-partition {0, 1} U {2, 5} U {3, 4}
The octahedron plays the role of \(\Delta^\circ\) in the above description:
sage: np.Delta_polar() is o
True
The dual nef-partition (corresponding to the “mirror complete intersection”) gives decomposition of the vertex set of \(\nabla^\circ\):
sage: np.dual()
Nef-partition {4, 5, 6} U {1, 3} U {0, 2, 7}
sage: np.nabla_polar().vertices()
[ 1 0 0 0 -1 0 -1 1]
[ 1 0 1 0 -1 -1 0 0]
[ 0 1 0 -1 0 0 0 0]
Of course, \(\nabla^\circ\) is \(\Delta^\circ\) from the point of view of the dual nef-partition:
sage: np.dual().Delta_polar() is np.nabla_polar()
True
sage: np.Delta(1).vertices()
[ 0 0]
[ 0 0]
[ 1 -1]
sage: np.dual().nabla(1).vertices()
[ 0 0]
[ 0 0]
[ 1 -1]
Instead of constructing nef-partitions directly, you can request all 2-part nef-partitions of a given reflexive polytope (they will be computed using nef.x program from PALP):
sage: o.nef_partitions()
[
Nef-partition {0, 1, 3} U {2, 4, 5},
Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),
Nef-partition {0, 1, 2} U {3, 4, 5},
Nef-partition {0, 1, 2, 3} U {4, 5},
Nef-partition {0, 1, 2, 3, 4} U {5} (projection)
]
Return the polytope \(\Delta\) or \(\Delta_i\) corresponding to self.
INPUT:
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.Delta().polar() is o
True
sage: np.Delta().vertices()
[-1 1 -1 1 -1 1 -1 1]
[-1 -1 1 1 -1 -1 1 1]
[ 1 1 1 1 -1 -1 -1 -1]
sage: np.Delta(0).vertices()
[ 1 1 -1 -1]
[-1 0 -1 0]
[ 0 0 0 0]
Return the polytope \(\Delta^\circ\) corresponding to self.
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLE:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.Delta_polar() is o
True
Return the polytopes \(\Delta_i\) corresponding to self.
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.Delta().vertices()
[-1 1 -1 1 -1 1 -1 1]
[-1 -1 1 1 -1 -1 1 1]
[ 1 1 1 1 -1 -1 -1 -1]
sage: [Delta_i.vertices() for Delta_i in np.Deltas()]
[
[ 1 1 -1 -1] [ 0 0 0 0]
[-1 0 -1 0] [ 1 0 0 1]
[ 0 0 0 0], [ 1 1 -1 -1]
]
sage: np.nabla_polar().vertices()
[ 1 0 1 0 0 -1 0 -1]
[-1 1 0 0 0 -1 1 0]
[ 0 1 0 1 -1 0 -1 0]
Return the dual nef-partition.
OUTPUT:
See the class documentation for the definition.
ALGORITHM:
See Proposition 3.19 in [BN08].
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.dual()
Nef-partition {0, 2, 5, 7} U {1, 3, 4, 6}
sage: np.dual().Delta() is np.nabla()
True
sage: np.dual().nabla(0) is np.Delta(0)
True
Return Hodge numbers corresponding to self.
OUTPUT:
EXAMPLES:
Currently, you need to request Hodge numbers when you compute nef-partitions:
sage: o = lattice_polytope.octahedron(5)
sage: np = o.nef_partitions()[0] # long time (4s on sage.math, 2011)
sage: np.hodge_numbers() # long time
Traceback (most recent call last):
...
NotImplementedError: use nef_partitions(hodge_numbers=True)!
sage: np = o.nef_partitions(hodge_numbers=True)[0] # long time (13s on sage.math, 2011)
sage: np.hodge_numbers() # long time
(19, 19)
Return the polytope \(\nabla\) or \(\nabla_i\) corresponding to self.
INPUT:
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.Delta_polar().vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: np.nabla(0).vertices()
[ 1 0 -1]
[ 0 1 0]
[ 0 0 0]
sage: np.nabla().vertices()
[ 1 1 1 0 0 -1 -1 -1]
[ 0 -1 0 1 1 0 -1 0]
[ 1 0 -1 1 -1 1 0 -1]
Return the polytope \(\nabla^\circ\) corresponding to self.
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.nabla_polar().vertices()
[ 1 0 1 0 0 -1 0 -1]
[-1 1 0 0 0 -1 1 0]
[ 0 1 0 1 -1 0 -1 0]
sage: np.nabla_polar() is np.dual().Delta_polar()
True
Return the polytopes \(\nabla_i\) corresponding to self.
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.Delta_polar().vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: [nabla_i.vertices() for nabla_i in np.nablas()]
[
[ 1 0 -1] [ 0 0 0]
[ 0 1 0] [ 0 -1 0]
[ 0 0 0], [ 1 0 -1]
]
Return the number of parts in self.
OUTPUT:
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.nparts()
2
Return the i-th part of self.
INPUT:
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.part(0)
(0, 1, 3)
Return the index of the part containing the i-th vertex.
INPUT:
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.part_of(3)
0
sage: np.part_of(2)
1
Return the index of the part containing the i-th point.
INPUT:
OUTPUT:
Note
Since a nef-partition induces a partition on the set of boundary lattice points of \(\Delta^\circ\), the value of \(j\) is well-defined for all \(i\) but the one that corresponds to the origin, in which case this method will raise a ValueError exception. (The origin always belongs to all \(\nabla_j\).)
See nef-partition class documentation for definitions and notation.
EXAMPLES:
We consider a relatively complicated reflexive polytope #2252 (easily accessible in Sage as ReflexivePolytope(3, 2252), we create it here explicitly to avoid loading the whole database):
sage: p = LatticePolytope([(1,0,0), (0,1,0), (0,0,1), (0,1,-1),
... (0,-1,1), (-1,1,0), (0,-1,-1), (-1,-1,0), (-1,-1,2)])
sage: np = p.nef_partitions()[0]
sage: np
Nef-partition {1, 2, 5, 7, 8} U {0, 3, 4, 6}
sage: p.nvertices()
9
sage: p.npoints()
15
We see that the polytope has 6 more points in addition to vertices. One of them is the origin:
sage: p.origin()
14
sage: np.part_of_point(14)
Traceback (most recent call last):
...
ValueError: the origin belongs to all parts!
But the remaining 5 are partitioned by np:
sage: [n for n in range(p.npoints())
... if p.origin() != n and np.part_of_point(n) == 0]
[1, 2, 5, 7, 8, 9, 11, 13]
sage: [n for n in range(p.npoints())
... if p.origin() != n and np.part_of_point(n) == 1]
[0, 3, 4, 6, 10, 12]
Return all parts of self.
OUTPUT:
See nef-partition class documentation for definitions and notation.
EXAMPLES:
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: np.parts()
((0, 1, 3), (2, 4, 5))
Return n-th reflexive polytope from the database of 2- or 3-dimensional reflexive polytopes.
Note
EXAMPLES: The 3rd 2-dimensional polytope is “the diamond:”
sage: ReflexivePolytope(2,3)
Reflexive polytope 3: 2-dimensional, 4 vertices.
sage: lattice_polytope.ReflexivePolytope(2,3).vertices()
[ 1 0 0 -1]
[ 0 1 -1 0]
There are 16 reflexive polygons and numeration starts with 0:
sage: ReflexivePolytope(2,16)
Traceback (most recent call last):
...
ValueError: there are only 16 reflexive polygons!
It is not possible to load a 4-dimensional polytope in this way:
sage: ReflexivePolytope(4,16)
Traceback (most recent call last):
...
NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!
Return the sequence of all 2- or 3-dimensional reflexive polytopes.
Note
During the first call the database is loaded and cached for future use, so repetitive calls will return the same object in memory.
Parameters: | dim (2 or 3) – dimension of required reflexive polytopes |
---|---|
Return type: | list of lattice polytopes |
EXAMPLES: There are 16 reflexive polygons:
sage: len(ReflexivePolytopes(2))
16
It is not possible to load 4-dimensional polytopes in this way:
sage: ReflexivePolytopes(4)
Traceback (most recent call last):
...
NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!
Bases: sage.structure.parent.Set_generic
Base class for all parents.
Parents are the Sage/mathematical analogues of container objects in computer science.
INPUT:
If facade is specified, then Sets().Facades() is added to the categories of the parent. Furthermore, if facade is not True, the internal attribute _facade_for is set accordingly for use by Sets.Facades.ParentMethods.facade_for().
Internal invariants:
Todo
Eventually, category should be Sets by default.
TESTS:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.SetOfAllLatticePolytopesClass().__call__(o)
An octahedron: 3-dimensional, 6 vertices.
This function allows one to specify coercions, actions, conversions and embeddings involving this parent.
IT SHOULD ONLY BE CALLED DURING THE __INIT__ method, often at the end.
INPUT:
coerce_list – a list of coercion Morphisms to self and parents with canonical coercions to self
action_list – a list of actions on and by self
parents with conversions to self
embedding – a single Morphism from self
convert_method_name – a name to look for that other elements can implement to create elements of self (e.g. _integer_)
element_constructor – A callable object used by the __call__ method to construct new elements. Typically the element class or a bound method (defaults to self._element_constructor_).
init_no_parent – if True omit passing self in as the first argument of element_constructor for conversion. This is useful if parents are unique, or element_constructor is a bound method (this latter case can be detected automatically).
This is a multiplication method that more or less directly calls another attribute _mul_ (single underscore). This is because __mul__ can not be implemented via inheritance from the parent methods of the category, but _mul_ can be inherited. This is, e.g., used when creating twosided ideals of matrix algebras. See trac ticket #7797.
EXAMPLE:
sage: MS = MatrixSpace(QQ,2,2)
This matrix space is in fact an algebra, and in particular it is a ring, from the point of view of categories:
sage: MS.category()
Category of algebras over Rational Field
sage: MS in Rings()
True
However, its class does not inherit from the base class Ring:
sage: isinstance(MS,Ring)
False
Its _mul_ method is inherited from the category, and can be used to create a left or right ideal:
sage: MS._mul_.__module__
'sage.categories.rings'
sage: MS*MS.1 # indirect doctest
Left Ideal
(
[0 1]
[0 0]
)
of Full MatrixSpace of 2 by 2 dense matrices over Rational Field
sage: MS*[MS.1,2]
Left Ideal
(
[0 1]
[0 0],
[2 0]
[0 2]
)
of Full MatrixSpace of 2 by 2 dense matrices over Rational Field
sage: MS.1*MS
Right Ideal
(
[0 1]
[0 0]
)
of Full MatrixSpace of 2 by 2 dense matrices over Rational Field
sage: [MS.1,2]*MS
Right Ideal
(
[0 1]
[0 0],
[2 0]
[0 2]
)
of Full MatrixSpace of 2 by 2 dense matrices over Rational Field
True if there is an element of self that is equal to x under ==, or if x is already an element of self. Also, True in other cases involving the Symbolic Ring, which is handled specially.
For many structures we test this by using __call__() and then testing equality between x and the result.
The Symbolic Ring is treated differently because it is ultra-permissive about letting other rings coerce in, but ultra-strict about doing comparisons.
EXAMPLES:
sage: 2 in Integers(7)
True
sage: 2 in ZZ
True
sage: Integers(7)(3) in ZZ
True
sage: 3/1 in ZZ
True
sage: 5 in QQ
True
sage: I in RR
False
sage: SR(2) in ZZ
True
sage: RIF(1, 2) in RIF
True
sage: pi in RIF # there is no element of RIF equal to pi
False
sage: sqrt(2) in CC
True
sage: pi in RR
True
sage: pi in CC
True
sage: pi in RDF
True
sage: pi in CDF
True
TESTS:
Check that trac ticket #13824 is fixed:
sage: 4/3 in GF(3)
False
sage: 15/50 in GF(25, 'a')
False
sage: 7/4 in Integers(4)
False
sage: 15/36 in Integers(6)
False
Override this method to specify coercions beyond those specified in coerce_list.
If no such coercion exists, return None or False. Otherwise, it may return either an actual Map to use for the coercion, a callable (in which case it will be wrapped in a Map), or True (in which case a generic map will be provided).
Override this method to provide additional conversions beyond those given in convert_list.
This function is called after coercions are attempted. If there is a coercion morphism in the opposite direction, one should consider adding a section method to that.
This MUST return a Map from S to self, or None. If None is returned then a generic map will be provided.
Override this method to provide an action of self on S or S on self beyond what was specified in action_list.
This must return an action which accepts an element of self and an element of S (in the order specified by self_on_left).
Returns an element of self. Want it in sufficient generality that poorly-written functions won’t work when they’re not supposed to. This is cached so doesn’t have to be super fast.
EXAMPLES:
sage: QQ._an_element_()
1/2
sage: ZZ['x,y,z']._an_element_()
x
TESTS:
Since Parent comes before the parent classes provided by categories in the hierarchy of classes, we make sure that this default implementation of _an_element_() does not override some provided by the categories. Eventually, this default implementation should be moved into the categories to avoid this workaround:
sage: S = FiniteEnumeratedSet([1,2,3])
sage: S.category()
Category of facade finite enumerated sets
sage: super(Parent, S)._an_element_
Cached version of <function _an_element_from_iterator at ...>
sage: S._an_element_()
1
sage: S = FiniteEnumeratedSet([])
sage: S._an_element_()
Traceback (most recent call last):
...
EmptySetError
Metadata about the _repr_() output.
INPUT:
Valid key arguments are:
OUTPUT:
Boolean.
EXAMPLES:
sage: ZZ._repr_option('ascii_art')
False
sage: MatrixSpace(ZZ, 2)._repr_option('element_ascii_art')
True
Initialize the category framework
Most parents initialize their category upon construction, and this is the recommended behavior. For example, this happens when the constructor calls Parent.__init__() directly or indirectly. However, some parents defer this for performance reasons. For example, sage.matrix.matrix_space.MatrixSpace does not.
EXAMPLES:
sage: P = Parent()
sage: P.category()
Category of sets
sage: class MyParent(Parent):
....: def __init__(self):
....: self._init_category_(Groups())
sage: MyParent().category()
Category of groups
Compute all cached data for all given polytopes and their polars.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data. None of the polytopes in the given sequence should be constructed as the polar polytope to another one.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_cached_data([o])
sage: o.faces()
[
[[0], [1], [2], [3], [4], [5]],
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
]
However, you cannot use it for polytopes that are constructed as polar polytopes of others:
sage: lattice_polytope.all_cached_data([o.polar()])
Traceback (most recent call last):
...
ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!
Compute faces for all given polytopes.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_faces([o])
sage: o.faces()
[
[[0], [1], [2], [3], [4], [5]],
[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],
[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]
]
However, you cannot use it for polytopes that are constructed as polar polytopes of others:
sage: lattice_polytope.all_faces([o.polar()])
Traceback (most recent call last):
...
ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!
Compute polar polytopes for all reflexive and equations of facets for all non-reflexive polytopes.
all_facet_equations and all_polars are synonyms.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_polars([o])
sage: o.polar()
A polytope polar to An octahedron: 3-dimensional, 8 vertices.
Compute nef-partitions for all given polytopes.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data.
Note: member function is_reflexive will be called separately for each polytope. It is strictly recommended to call all_polars on the sequence of polytopes before using this function.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_nef_partitions([o])
sage: o.nef_partitions()
[
Nef-partition {0, 1, 3} U {2, 4, 5},
Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),
Nef-partition {0, 1, 2} U {3, 4, 5},
Nef-partition {0, 1, 2, 3} U {4, 5},
Nef-partition {0, 1, 2, 3, 4} U {5} (projection)
]
You cannot use this function for non-reflexive polytopes:
sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],
... [0, 1, 0, 0, -1, 0],
... [0, 0, 2, 0, 0, -1]])
...
sage: p = LatticePolytope(m)
sage: lattice_polytope.all_nef_partitions([o, p])
Traceback (most recent call last):
...
ValueError: The given polytope is not reflexive!
Polytope: A lattice polytope: 3-dimensional, 6 vertices.
Compute lattice points for all given polytopes.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_points([o])
sage: o.points()
[ 1 0 0 -1 0 0 0]
[ 0 1 0 0 -1 0 0]
[ 0 0 1 0 0 -1 0]
Compute polar polytopes for all reflexive and equations of facets for all non-reflexive polytopes.
all_facet_equations and all_polars are synonyms.
This functions does it MUCH faster than member functions of LatticePolytope during the first run. So it is recommended to use this functions if you work with big sets of data.
INPUT: a sequence of lattice polytopes.
EXAMPLES: This function has no output, it is just a fast way to work with long sequences of polytopes. Of course, you can use short sequences as well:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.all_polars([o])
sage: o.polar()
A polytope polar to An octahedron: 3-dimensional, 8 vertices.
Set or get the way of using PALP for lattice polytopes.
INPUT:
OUTPUT: The current state of using PALP. If True, files are used for all calls to PALP, otherwise pipes are used for single polytopes. While the latter may have some advantage in speed, the first method is more reliable when working with large outputs. The initial state is True.
EXAMPLES:
sage: lattice_polytope.always_use_files()
True
sage: p = LatticePolytope(matrix([1, 20]))
sage: p.npoints()
20
Now let’s use pipes instead of files:
sage: lattice_polytope.always_use_files(False)
False
sage: p = LatticePolytope(matrix([1, 20]))
sage: p.npoints()
20
Compute the convex hull of the given points.
Note
points might not span the space. Also, it fails for large numbers of vertices in dimensions 4 or greater
INPUT:
OUTPUT: list of vertices of the convex hull of the given points (as vectors).
EXAMPLES: Let’s compute the convex hull of several points on a line in the plane:
sage: lattice_polytope.convex_hull([[1,2],[3,4],[5,6],[7,8]])
[(1, 2), (7, 8)]
Use the function f to filter polytopes in a list.
INPUT:
OUTPUT: a list of integers – numbers of polytopes in the given list, that satisfy the given condition (i.e. function f returns True) and are elements of subseq, if it is given.
EXAMPLES: Consider a sequence of octahedrons:
sage: polytopes = Sequence([lattice_polytope.octahedron(n) for n in range(2, 7)], cr=True)
sage: polytopes
[
An octahedron: 2-dimensional, 4 vertices.,
An octahedron: 3-dimensional, 6 vertices.,
An octahedron: 4-dimensional, 8 vertices.,
An octahedron: 5-dimensional, 10 vertices.,
An octahedron: 6-dimensional, 12 vertices.
]
This filters octahedrons of dimension at least 4:
sage: lattice_polytope.filter_polytopes(lambda p: p.dim() >= 4, polytopes)
[2, 3, 4]
For long tests you can see the current progress:
sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, print_numbers=True)
0
1
2
3
4
[3, 4]
Here we consider only some of the polytopes:
sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, [2, 3, 4], print_numbers=True)
2
3
4
[3, 4]
Compute the integral length of a given rational vector.
INPUT:
OUTPUT: Rational number r such that v = r u, where u is the primitive integral vector in the direction of v.
EXAMPLES:
sage: lattice_polytope.integral_length([1, 2, 4])
1
sage: lattice_polytope.integral_length([2, 2, 4])
2
sage: lattice_polytope.integral_length([2/3, 2, 4])
2/3
Check if x is a lattice polytope.
INPUT:
OUTPUT:
EXAMPLES:
sage: from sage.geometry.lattice_polytope import is_LatticePolytope
sage: is_LatticePolytope(1)
False
sage: p = LatticePolytope([(1,0), (0,1), (-1,-1)])
sage: p
A lattice polytope: 2-dimensional, 3 vertices.
sage: is_LatticePolytope(p)
True
Check if x is a nef-partition.
INPUT:
OUTPUT:
EXAMPLES:
sage: from sage.geometry.lattice_polytope import is_NefPartition
sage: is_NefPartition(1)
False
sage: o = lattice_polytope.octahedron(3)
sage: np = o.nef_partitions()[0]
sage: np
Nef-partition {0, 1, 3} U {2, 4, 5}
sage: is_NefPartition(np)
True
Compute the Minkowski sum of two convex polytopes.
Note
Polytopes might not be of maximal dimension.
INPUT:
OUTPUT: list of vertices of the Minkowski sum, given as vectors.
EXAMPLES: Let’s compute the Minkowski sum of two line segments:
sage: lattice_polytope.minkowski_sum([[1,0],[-1,0]],[[0,1],[0,-1]])
[(1, 1), (1, -1), (-1, 1), (-1, -1)]
Return an octahedron of the given dimension.
EXAMPLES: Here are 3- and 4-dimensional octahedrons:
sage: o = lattice_polytope.octahedron(3)
sage: o
An octahedron: 3-dimensional, 6 vertices.
sage: o.vertices()
[ 1 0 0 -1 0 0]
[ 0 1 0 0 -1 0]
[ 0 0 1 0 0 -1]
sage: o = lattice_polytope.octahedron(4)
sage: o
An octahedron: 4-dimensional, 8 vertices.
sage: o.vertices()
[ 1 0 0 0 -1 0 0 0]
[ 0 1 0 0 0 -1 0 0]
[ 0 0 1 0 0 0 -1 0]
[ 0 0 0 1 0 0 0 -1]
There exists only one octahedron of each dimension:
sage: o is lattice_polytope.octahedron(4)
True
Return relations between given points.
INPUT:
OUTPUT: matrix of relations between given points with non-negative integer coefficients
EXAMPLES: This is a 3-dimensional reflexive polytope:
sage: m = matrix(ZZ,[[1, 0, -1, 0, -1],
... [0, 1, -1, 0, 0],
... [0, 0, 0, 1, -1]])
...
sage: p = LatticePolytope(m)
sage: p.points()
[ 1 0 -1 0 -1 0]
[ 0 1 -1 0 0 0]
[ 0 0 0 1 -1 0]
We can compute linear relations between its points in the following way:
sage: p.points().transpose().kernel().echelonized_basis_matrix()
[ 1 0 0 1 1 0]
[ 0 1 1 -1 -1 0]
[ 0 0 0 0 0 1]
However, the above relations may contain negative and rational numbers. This function transforms them in such a way, that all coefficients are non-negative integers:
sage: lattice_polytope.positive_integer_relations(p.points())
[1 0 0 1 1 0]
[1 1 1 0 0 0]
[0 0 0 0 0 1]
sage: lattice_polytope.positive_integer_relations(ReflexivePolytope(2,1).vertices())
[2 1 1]
Return a simplex of the given dimension, corresponding to \(P_{dim}\).
EXAMPLES: We construct 3- and 4-dimensional simplexes:
sage: p = lattice_polytope.projective_space(3)
sage: p
A simplex: 3-dimensional, 4 vertices.
sage: p.vertices()
[ 1 0 0 -1]
[ 0 1 0 -1]
[ 0 0 1 -1]
sage: p = lattice_polytope.projective_space(4)
sage: p
A simplex: 4-dimensional, 5 vertices.
sage: p.vertices()
[ 1 0 0 0 -1]
[ 0 1 0 0 -1]
[ 0 0 1 0 -1]
[ 0 0 0 1 -1]
Read all polytopes from the given file.
INPUT:
OUTPUT: a sequence of polytopes
EXAMPLES: We use poly.x to compute polar polytopes of 2- and 3-octahedrons and read them:
sage: o2 = lattice_polytope.octahedron(2)
sage: o3 = lattice_polytope.octahedron(3)
sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])
sage: f = open(result_name)
sage: f.readlines()
['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']
sage: f.close()
sage: lattice_polytope.read_all_polytopes(result_name, desc="Polytope from file %d")
[
Polytope from file 0: 2-dimensional, 4 vertices.,
Polytope from file 1: 3-dimensional, 8 vertices.
]
sage: os.remove(result_name)
Read and return an integer matrix from a string or an opened file.
First input line must start with two integers m and n, the number of rows and columns of the matrix. The rest of the first line is ignored. The next m lines must contain n numbers each.
If m>n, returns the transposed matrix. If the string is empty or EOF is reached, returns the empty matrix, constructed by matrix().
EXAMPLES:
sage: lattice_polytope.read_palp_matrix("2 3 comment \n 1 2 3 \n 4 5 6")
[1 2 3]
[4 5 6]
sage: lattice_polytope.read_palp_matrix("3 2 Will be transposed \n 1 2 \n 3 4 \n 5 6")
[1 3 5]
[2 4 6]
Convert a Sage matrix to the string representation of Maxima.
EXAMPLE:
sage: m = matrix(ZZ,2)
sage: lattice_polytope.sage_matrix_to_maxima(m)
matrix([0,0],[0,0])
Set the dimension for PALP calls to d.
INPUT:
OUTPUT:
PALP has many hard-coded limits, which must be specified before compilation, one of them is dimension. Sage includes several versions with different dimension settings (which may also affect other limits and enable certain features of PALP). You can change the version which will be used by calling this function. Such a change is not done automatically for each polytope based on its dimension, since depending on what you are doing it may be necessary to use dimensions higher than that of the input polytope.
EXAMPLES:
By default, it is not possible to create the 7-dimensional simplex with vertices at the basis of the 8-dimensional space:
sage: LatticePolytope(identity_matrix(8))
Traceback (most recent call last):
...
ValueError: Error executing "poly.x -fv" for the given polytope!
Polytope: A lattice polytope: 7-dimensional, 8 vertices.
Vertices:
[ 0 -1 -1 -1 -1 -1 -1 -1]
[ 0 1 0 0 0 0 0 0]
[ 0 0 1 0 0 0 0 0]
[ 0 0 0 1 0 0 0 0]
[ 0 0 0 0 1 0 0 0]
[ 0 0 0 0 0 1 0 0]
[ 0 0 0 0 0 0 1 0]
Output:
Please increase POLY_Dmax to at least 7
However, we can work with this polytope by changing PALP dimension to 11:
sage: lattice_polytope.set_palp_dimension(11)
sage: LatticePolytope(identity_matrix(8))
A lattice polytope: 7-dimensional, 8 vertices.
Let’s go back to default settings:
sage: lattice_polytope.set_palp_dimension(None)
Skip matrix data in a file.
INPUT:
If EOF is reached during the process, raises ValueError exception.
EXAMPLE: We create a file with vertices of the square and the cube, but read only the second set:
sage: o2 = lattice_polytope.octahedron(2)
sage: o3 = lattice_polytope.octahedron(3)
sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])
sage: f = open(result_name)
sage: f.readlines()
['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']
sage: f.close()
sage: f = open(result_name)
sage: lattice_polytope.skip_palp_matrix(f)
sage: lattice_polytope.read_palp_matrix(f)
[-1 1 -1 1 -1 1 -1 1]
[-1 -1 1 1 -1 -1 1 1]
[ 1 1 1 1 -1 -1 -1 -1]
sage: f.close()
sage: os.remove(result_name)
Write a matrix into a file.
INPUT:
OUTPUT: First line: number_of_rows number_of_columns comment Next number_of_rows lines: rows of the matrix.
EXAMPLES: This functions is used for writing polytope vertices in PALP format:
sage: o = lattice_polytope.octahedron(3)
sage: lattice_polytope.write_palp_matrix(o.vertices(), comment="3D Octahedron")
3 6 3D Octahedron
1 0 0 -1 0 0
0 1 0 0 -1 0
0 0 1 0 0 -1
sage: lattice_polytope.write_palp_matrix(o.vertices(), format="%4d")
3 6
1 0 0 -1 0 0
0 1 0 0 -1 0
0 0 1 0 0 -1