Path: blob/master/src/sage/geometry/lattice_polytope.py
8815 views
r"""1Lattice and reflexive polytopes23This module provides tools for work with lattice and reflexive4polytopes. A *convex polytope* is the convex hull of finitely many5points in `\RR^n`. The dimension `n` of a6polytope is the smallest `n` such that the polytope can be7embedded in `\RR^n`.89A *lattice polytope* is a polytope whose vertices all have integer10coordinates.1112If `L` is a lattice polytope, the dual polytope of13`L` is1415.. math::1617\{y \in \ZZ^n : x\cdot y \geq -1 \text{ all } x \in L\}181920A *reflexive polytope* is a lattice polytope, such that its polar21is also a lattice polytope, i.e. it is bounded and has vertices with22integer coordinates.2324This Sage module uses Package for Analyzing Lattice Polytopes25(PALP), which is a program written in C by Maximilian Kreuzer and26Harald Skarke, which is freely available under the GNU license27terms at http://hep.itp.tuwien.ac.at/~kreuzer/CY/. Moreover, PALP is28included standard with Sage.2930PALP is described in the paper :arxiv:`math.SC/0204356`. Its distribution31also contains the application nef.x, which was created by Erwin32Riegler and computes nef-partitions and Hodge data for toric33complete intersections.3435ACKNOWLEDGMENT: polytope.py module written by William Stein was36used as an example of organizing an interface between an external37program and Sage. William Stein also helped Andrey Novoseltsev with38debugging and tuning of this module.3940Robert Bradshaw helped Andrey Novoseltsev to realize plot3d41function.4243.. note::4445IMPORTANT: PALP requires some parameters to be determined during46compilation time, i.e., the maximum dimension of polytopes, the47maximum number of points, etc. These limitations may lead to errors48during calls to different functions of these module. Currently, a49ValueError exception will be raised if the output of poly.x or50nef.x is empty or contains the exclamation mark. The error message51will contain the exact command that caused an error, the52description and vertices of the polytope, and the obtained output.5354Data obtained from PALP and some other data is cached and most55returned values are immutable. In particular, you cannot change the56vertices of the polytope or their order after creation of the57polytope.5859If you are going to work with large sets of data, take a look at60``all_*`` functions in this module. They precompute different data61for sequences of polynomials with a few runs of external programs.62This can significantly affect the time of future computations. You63can also use dump/load, but not all data will be stored (currently64only faces and the number of their internal and boundary points are65stored, in addition to polytope vertices and its polar).6667AUTHORS:6869- Andrey Novoseltsev (2007-01-11): initial version7071- Andrey Novoseltsev (2007-01-15): ``all_*`` functions7273- Andrey Novoseltsev (2008-04-01): second version, including:7475- dual nef-partitions and necessary convex_hull and minkowski_sum7677- built-in sequences of 2- and 3-dimensional reflexive polytopes7879- plot3d, skeleton_show8081- Andrey Novoseltsev (2009-08-26): dropped maximal dimension requirement8283- Andrey Novoseltsev (2010-12-15): new version of nef-partitions8485- Maximilian Kreuzer and Harald Skarke: authors of PALP (which was86also used to obtain the list of 3-dimensional reflexive polytopes)8788- Erwin Riegler: the author of nef.x8990"""9192#*****************************************************************************93# Copyright (C) 2007-2010 Andrey Novoseltsev <[email protected]>94# Copyright (C) 2007-2010 William Stein <[email protected]>95#96# Distributed under the terms of the GNU General Public License (GPL)97# as published by the Free Software Foundation; either version 2 of98# the License, or (at your option) any later version.99# http://www.gnu.org/licenses/100#*****************************************************************************101102from sage.graphs.graph import Graph103from sage.interfaces.all import maxima104from sage.matrix.all import matrix105from sage.matrix.matrix import is_Matrix106from sage.misc.all import tmp_filename107from sage.misc.misc import SAGE_SHARE108from sage.modules.all import vector109from sage.plot.plot3d.index_face_set import IndexFaceSet110from sage.plot.plot3d.all import line3d, point3d111from sage.plot.plot3d.shapes2 import text3d112from sage.rings.all import Integer, ZZ, QQ, gcd, lcm113from sage.sets.set import Set_generic114from sage.structure.all import Sequence115from sage.structure.sequence import Sequence_generic116from sage.structure.sage_object import SageObject117118import collections119import copy_reg120import os121import subprocess122import StringIO123124125data_location = os.path.join(SAGE_SHARE,'reflexive_polytopes')126127128class SetOfAllLatticePolytopesClass(Set_generic):129def _repr_(self):130r"""131Return a string representation.132133TESTS::134135sage: lattice_polytope.SetOfAllLatticePolytopesClass()._repr_()136'Set of all Lattice Polytopes'137"""138return "Set of all Lattice Polytopes"139140def __call__(self, x):141r"""142TESTS::143144sage: o = lattice_polytope.octahedron(3)145sage: lattice_polytope.SetOfAllLatticePolytopesClass().__call__(o)146An octahedron: 3-dimensional, 6 vertices.147"""148if isinstance(x, LatticePolytopeClass):149return x150raise TypeError151152153SetOfAllLatticePolytopes = SetOfAllLatticePolytopesClass()154155156def LatticePolytope(data, desc=None, compute_vertices=True,157copy_vertices=True, n=0):158r"""159Construct a lattice polytope.160161LatticePolytope(data, [desc], [compute_vertices],162[copy_vertices], [n])163164INPUT:165166- ``data`` -- The points (which must be vertices if167``compute_vertices`` is False) spanning the lattice polytope,168specified as one of:169170* a matrix whose columns are vertices of the polytope.171172* an iterable of iterables (for example, a list of vectors)173defining the point coordinates.174175* a file with matrix data, open for reading, or176177* a filename of such a file. See ``read_palp_matrix`` for the178file format.179180- ``desc`` -- (default: "A lattice polytope")181description of the polytope.182183- ``compute_vertices`` -- boolean (default: True) if True, the184convex hull of the given points will be computed for185determining vertices. Otherwise, the given points must be186vertices.187188- ``copy_vertices`` -- boolean (default: True) if False, and189compute_vertices is False, and ``data`` is a matrix of190vertices, it will be made immutable.191192- ``n`` -- integer (default: 0) if ``data`` is a name of a file,193that contains data blocks for several polytopes, the n-th block194will be used. *ENUMERATION STARTS WITH ZERO*.195196OUTPUT: a lattice polytope197198EXAMPLES: Here we construct a polytope from a matrix whose columns199are vertices in 3-dimensional space. In the first case a copy of200the given matrix is made during construction, in the second one the201matrix is made immutable and used as a matrix of vertices.202203::204205sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],206... [0, 1, 0, 0, -1, 0],207... [0, 0, 1, 0, 0, -1]])208...209sage: p = LatticePolytope(m)210sage: p211A lattice polytope: 3-dimensional, 6 vertices.212sage: m.is_mutable()213True214sage: m is p.vertices()215False216sage: p = LatticePolytope(m, compute_vertices=False, copy_vertices=False)217sage: m.is_mutable()218False219sage: m is p.vertices()220True221222We draw a pretty picture of the polytype in 3-dimensional space::223224sage: p.plot3d().show()225226Now we add an extra point, which is in the interior of the227polytope...228229::230231sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope constructed from 7 points")232sage: p233A lattice polytope constructed from 7 points: 3-dimensional, 6 vertices.234235You can suppress vertex computation for speed but this can lead to236mistakes::237238sage: p = LatticePolytope(m.columns() + [(0,0,0)], "A lattice polytope with WRONG vertices",239... compute_vertices=False)240...241sage: p242A lattice polytope with WRONG vertices: 3-dimensional, 7 vertices.243244Given points must be in the lattice::245246sage: LatticePolytope(matrix([1/2, 3/2]))247Traceback (most recent call last):248...249ValueError: Points must be integral!250Given:251[1/2 3/2]252253But it is OK to create polytopes of non-maximal dimension::254255sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0, 0],256... [0, 1, 0, 0, -1, 0, 0],257... [0, 0, 0, 0, 0, 0, 0]])258...259sage: p = LatticePolytope(m)260sage: p261A lattice polytope: 2-dimensional, 4 vertices.262sage: p.vertices()263[ 1 0 -1 0]264[ 0 1 0 -1]265[ 0 0 0 0]266267An empty lattice polytope can be specified by a matrix with zero columns:268269sage: p = LatticePolytope(matrix(ZZ,3,0)); p270A lattice polytope: -1-dimensional, 0 vertices.271sage: p.ambient_dim()2723273sage: p.npoints()2740275sage: p.nfacets()2760277sage: p.points()278[]279sage: p.faces()280[]281"""282if isinstance(data, LatticePolytopeClass):283return data284285if not is_Matrix(data) and not isinstance(data,(file,basestring)):286try:287data = matrix(ZZ,data).transpose()288except ValueError:289pass290291return LatticePolytopeClass(data, desc, compute_vertices, copy_vertices, n)292293copy_reg.constructor(LatticePolytope) # "safe for unpickling"294295def ReflexivePolytope(dim, n):296r"""297Return n-th reflexive polytope from the database of 2- or2983-dimensional reflexive polytopes.299300.. note::301302#. Numeration starts with zero: `0 \leq n \leq 15` for `{\rm dim} = 2`303and `0 \leq n \leq 4318` for `{\rm dim} = 3`.304305#. During the first call, all reflexive polytopes of requested306dimension are loaded and cached for future use, so the first307call for 3-dimensional polytopes can take several seconds,308but all consecutive calls are fast.309310#. Equivalent to ``ReflexivePolytopes(dim)[n]`` but checks bounds311first.312313EXAMPLES: The 3rd 2-dimensional polytope is "the diamond:"314315::316317sage: ReflexivePolytope(2,3)318Reflexive polytope 3: 2-dimensional, 4 vertices.319sage: lattice_polytope.ReflexivePolytope(2,3).vertices()320[ 1 0 0 -1]321[ 0 1 -1 0]322323There are 16 reflexive polygons and numeration starts with 0::324325sage: ReflexivePolytope(2,16)326Traceback (most recent call last):327...328ValueError: there are only 16 reflexive polygons!329330It is not possible to load a 4-dimensional polytope in this way::331332sage: ReflexivePolytope(4,16)333Traceback (most recent call last):334...335NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!336"""337if dim == 2:338if n > 15:339raise ValueError, "there are only 16 reflexive polygons!"340return ReflexivePolytopes(2)[n]341elif dim == 3:342if n > 4318:343raise ValueError, "there are only 4319 reflexive 3-polytopes!"344return ReflexivePolytopes(3)[n]345else:346raise NotImplementedError, "only 2- and 3-dimensional reflexive polytopes are available!"347348# Sequences of reflexive polytopes349_rp = [None]*4350351def ReflexivePolytopes(dim):352r"""353Return the sequence of all 2- or 3-dimensional reflexive polytopes.354355.. note::356357During the first call the database is loaded and cached for358future use, so repetitive calls will return the same object in359memory.360361:param dim: dimension of required reflexive polytopes362:type dim: 2 or 3363:rtype: list of lattice polytopes364365EXAMPLES: There are 16 reflexive polygons::366367sage: len(ReflexivePolytopes(2))36816369370It is not possible to load 4-dimensional polytopes in this way::371372373sage: ReflexivePolytopes(4)374Traceback (most recent call last):375...376NotImplementedError: only 2- and 3-dimensional reflexive polytopes are available!377"""378global _rp379if dim not in [2, 3]:380raise NotImplementedError, "only 2- and 3-dimensional reflexive polytopes are available!"381if _rp[dim] == None:382rp = read_all_polytopes(383os.path.join(data_location,"reflexive_polytopes_%dd"%dim),384desc="Reflexive polytope %4d")385for n, p in enumerate(rp):386# Data files have normal form of reflexive polytopes387p._normal_form = p._vertices388# Prevents dimension computation later389p._dim = dim390# Compute "fast" data in one call to PALP391all_polars(rp)392all_points(rp + [p._polar for p in rp])393# TODO: improve faces representation so that we can uncomment394# all_faces(rp)395# It adds ~10s for dim=3, which is a bit annoying to wait for.396_rp[dim] = rp397return _rp[dim]398399400def is_LatticePolytope(x):401r"""402Check if ``x`` is a lattice polytope.403404INPUT:405406- ``x`` -- anything.407408OUTPUT:409410- ``True`` if ``x`` is a :class:`lattice polytope <LatticePolytopeClass>`,411``False`` otherwise.412413EXAMPLES::414415sage: from sage.geometry.lattice_polytope import is_LatticePolytope416sage: is_LatticePolytope(1)417False418sage: p = LatticePolytope([(1,0), (0,1), (-1,-1)])419sage: p420A lattice polytope: 2-dimensional, 3 vertices.421sage: is_LatticePolytope(p)422True423"""424return isinstance(x, LatticePolytopeClass)425426427class LatticePolytopeClass(SageObject, collections.Hashable):428r"""429Class of lattice/reflexive polytopes.430431Use ``LatticePolytope`` for constructing a polytope.432"""433434def __init__(self, data, desc, compute_vertices, copy_vertices=True, n=0):435r"""436Construct a lattice polytope. See ``LatticePolytope``.437438Most tests/examples are also done in LatticePolytope.439440TESTS::441442sage: LatticePolytope(matrix(ZZ,[[1,2,3],[4,5,6]]))443A lattice polytope: 1-dimensional, 2 vertices.444"""445if is_Matrix(data):446if data.base_ring() != ZZ:447try:448data = matrix(ZZ, data)449except TypeError:450raise ValueError("Points must be integral!\nGiven:\n%s" %data)451if desc == None:452self._desc = "A lattice polytope"453else:454self._desc = desc455if compute_vertices:456self._vertices = data # Temporary assignment457self._compute_dim(compute_vertices=True)458else:459if copy_vertices:460self._vertices = data.__copy__()461else:462self._vertices = data463self._vertices.set_immutable()464465elif isinstance(data, file) or isinstance(data, StringIO.StringIO):466m = read_palp_matrix(data)467self.__init__(m, desc, compute_vertices, False)468elif isinstance(data,str):469f = open(data)470skip_palp_matrix(f, n)471self.__init__(f, desc, compute_vertices)472f.close()473else:474raise TypeError, \475"Cannot make a polytope from given data!\nData:\n%s" % data476477def __eq__(self, other):478r"""479Compare ``self`` with ``other``.480481INPUT:482483- ``other`` -- anything.484485OUTPUT:486487- ``True`` if ``other`` is a :class:`lattice polytope488<LatticePolytopeClass>` equal to ``self``, ``False`` otherwise.489490.. NOTE::491492Two lattice polytopes are if they have the same vertices listed in493the same order.494495TESTS::496497sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)])498sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)])499sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)])500sage: p1 == p1501True502sage: p1 == p2503True504sage: p1 is p2505False506sage: p1 == p3507False508sage: p1 == 0509False510"""511return (is_LatticePolytope(other)512and self._vertices == other._vertices)513514def __hash__(self):515r"""516Return the hash of ``self``.517518OUTPUT:519520- an integer.521522TESTS::523524sage: o = lattice_polytope.octahedron(3)525sage: hash(o) == hash(o)526True527"""528try:529return self._hash530except AttributeError:531self._hash = hash(self._vertices)532return self._hash533534def __ne__(self, other):535r"""536Compare ``self`` with ``other``.537538INPUT:539540- ``other`` -- anything.541542OUTPUT:543544- ``False`` if ``other`` is a :class:`lattice polytope545<LatticePolytopeClass>` equal to ``self``, ``True`` otherwise.546547.. NOTE::548549Two lattice polytopes are if they have the same vertices listed in550the same order.551552TESTS::553554sage: p1 = LatticePolytope([(1,0), (0,1), (-1,-1)])555sage: p2 = LatticePolytope([(1,0), (0,1), (-1,-1)])556sage: p3 = LatticePolytope([(0,1), (1,0), (-1,-1)])557sage: p1 != p1558False559sage: p1 != p2560False561sage: p1 is p2562False563sage: p1 != p3564True565sage: p1 != 0566True567"""568return not (self == other)569570def __reduce__(self):571r"""572Reduction function. Does not store data that can be relatively fast573recomputed.574575TESTS::576577sage: o = lattice_polytope.octahedron(3)578sage: o.vertices() == loads(o.dumps()).vertices()579True580"""581state = self.__dict__.copy()582state.pop('_vertices')583state.pop('_desc')584state.pop('_distances', None)585state.pop('_skeleton', None)586if state.has_key('_points'):587state['_npoints'] = state.pop('_points').ncols()588return (LatticePolytope, (self._vertices, self._desc, False, False), state)589590def __setstate__(self, state):591r"""592Restores the state of pickled polytope.593594TESTS::595596sage: o = lattice_polytope.octahedron(3)597sage: o.vertices() == loads(o.dumps()).vertices()598True599"""600self.__dict__.update(state)601if state.has_key('_faces'): # Faces do not remember polytopes602for d_faces in self._faces:603for face in d_faces:604face._polytope = self605606def _compute_dim(self, compute_vertices):607r"""608Compute the dimension of this polytope and its vertices, if necessary.609610If ``compute_vertices`` is ``True``, then ``self._vertices`` should611contain points whose convex hull will be computed and placed back into612``self._vertices``.613614If the dimension of this polytope is not equal to its ambient dimension,615auxiliary polytope will be created and stored for using PALP commands.616617TESTS::618619sage: p = LatticePolytope(matrix([1, 2, 3]), compute_vertices=False)620sage: p.vertices() # wrong, since these were not vertices621[1 2 3]622sage: hasattr(p, "_dim")623False624sage: p._compute_dim(compute_vertices=True)625sage: p.vertices()626[1 3]627sage: p._dim6281629"""630if hasattr(self, "_dim"):631return632if self._vertices.ncols()==0: # the empty lattice polytope633self._dim = -1634return635if compute_vertices:636points = []637for point in self._vertices.columns(copy=False):638if not point in points:639points.append(point)640else:641points = self._vertices.columns(copy=False)642p0 = points[0]643if p0 != 0:644points = [point - p0 for point in points]645N = ZZ**self.ambient_dim()646H = N.submodule(points)647self._dim = H.rank()648if self._dim == 0:649if compute_vertices:650self._vertices = matrix(p0).transpose()651elif self._dim == self.ambient_dim():652if compute_vertices:653if len(points) < self._vertices.ncols(): # Repeated vertices654if p0 != 0:655points = [point + p0 for point in points]656self._vertices = matrix(ZZ, points).transpose()657self._vertices = read_palp_matrix(self.poly_x("v"))658else:659# Setup auxiliary polytope and maps660H = H.saturation()661H_points = [H.coordinates(point) for point in points]662H_polytope = LatticePolytope(matrix(ZZ, H_points).transpose(),663compute_vertices=True)664self._sublattice = H665self._sublattice_polytope = H_polytope666self._embedding_matrix = H.basis_matrix().transpose()667self._shift_vector = p0668if compute_vertices:669self._vertices = self._embed(H_polytope.vertices())670# In order to use facet normals obtained from subpolytopes, we671# need the following (see Trac #9188).672M = self._embedding_matrix673# Basis for the ambient space with spanned subspace in front674basis = M.columns() + M.integer_kernel().basis()675# Let's represent it as columns of a matrix676basis = matrix(basis).transpose()677# Absolute value helps to keep normals "inner"678self._dual_embedding_scale = abs(basis.det())679dualbasis = matrix(ZZ, self._dual_embedding_scale * basis.inverse())680self._dual_embedding_matrix = dualbasis.submatrix(0,0,M.ncols())681682def _compute_faces(self):683r"""684Compute and cache faces of this polytope.685686If this polytope is reflexive and the polar polytope was already687computed, computes faces of both in order to save time and preserve688the one-to-one correspondence between the faces of this polytope of689dimension d and the faces of the polar polytope of codimension690d+1.691692TESTS::693694sage: o = lattice_polytope.octahedron(3)695sage: v = o.__dict__.pop("_faces", None) # faces may be cached already696sage: o.__dict__.has_key("_faces")697False698sage: o._compute_faces()699sage: o.__dict__.has_key("_faces")700True701702Check that Trac 8934 is fixed::703704sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0, 0],705... [0, 1, 0, 0, -1, 0, 0],706... [0, 0, 0, 0, 0, 0, 0]])707...708sage: p = LatticePolytope(m)709sage: p._compute_faces()710sage: p.facets()711[[0, 3], [2, 3], [0, 1], [1, 2]]712"""713if hasattr(self, "_constructed_as_polar"):714# "Polar of polar polytope" computed by poly.x may have the715# order of vertices different from the original polytope. Thus,716# in order to have consistent enumeration of vertices and faces717# we must run poly.x on the original polytope.718self._copy_faces(self._polar, reverse=True)719elif hasattr(self, "_constructed_as_affine_transform"):720self._copy_faces(self._original)721elif self.dim() <= 0:722self._faces = []723else:724self._read_faces(self.poly_x("i", reduce_dimension=True))725726def _compute_hodge_numbers(self):727r"""728Compute Hodge numbers for the current nef_partitions.729730This function (currently) always raises an exception directing to731use another way for computing Hodge numbers.732733TESTS::734735sage: o = lattice_polytope.octahedron(3)736sage: o._compute_hodge_numbers()737Traceback (most recent call last):738...739NotImplementedError: use nef_partitions(hodge_numbers=True)!740"""741raise NotImplementedError, "use nef_partitions(hodge_numbers=True)!"742743def _copy_faces(self, other, reverse=False):744r"""745Copy facial structure of another polytope.746747This may be necessary for preserving natural correspondence of faces,748e.g. between this polytope and its multiple or translation. In case of749reflexive polytopes, faces of this polytope and its polar are in750inclusion reversing bijection.751752.. note::753754This function does not perform any checks that this operation makes755sense.756757INPUT:758759- ``other`` - another LatticePolytope, whose facial structure will be760copied761762- ``reverse`` - (default: False) if True, the facial structure of the763other polytope will be reversed, i.e. vertices will correspond to764facets etc.765766TESTS::767768sage: o = lattice_polytope.octahedron(2)769sage: p = LatticePolytope(o.vertices())770sage: p._copy_faces(o)771sage: str(o.faces()) == str(p.faces())772True773sage: c = o.polar()774sage: p._copy_faces(c, reverse=True)775sage: str(o.faces()) == str(p.faces())776True777"""778self._faces = Sequence([], cr=True)779if reverse:780for d_faces in reversed(other.faces()):781self._faces.append([_PolytopeFace(self, f._facets, f._vertices)782for f in d_faces])783else:784for d_faces in other.faces():785self._faces.append([_PolytopeFace(self, f._vertices, f._facets)786for f in d_faces])787self._faces.set_immutable()788789def _embed(self, data):790r"""791Embed given point(s) into the ambient space of this polytope.792793INPUT:794795- ``data`` - point or matrix of points (as columns) in the affine796subspace spanned by this polytope797798OUTPUT: The same point(s) in the coordinates of the ambient space of799this polytope.800801TESTS::802803sage: o = lattice_polytope.octahedron(3)804sage: o._embed(o.vertices()) == o.vertices()805True806sage: m = matrix(ZZ, 3)807sage: m[0, 0] = 1808sage: m[1, 1] = 1809sage: p = o.affine_transform(m)810sage: p._embed((0,0))811(1, 0, 0)812"""813if self.ambient_dim() == self.dim():814return data815elif is_Matrix(data):816r = self._embedding_matrix * data817for i, col in enumerate(r.columns(copy=False)):818r.set_column(i, col + self._shift_vector)819return r820else:821return self._embedding_matrix * vector(QQ, data) + self._shift_vector822823def _face_compute_points(self, face):824r"""825Compute and cache lattice points of the given ``face``826of this polytope.827828TESTS::829830sage: o = lattice_polytope.octahedron(3)831sage: e = o.faces(dim=1)[0]832sage: v = e.__dict__.pop("_points", None) # points may be cached already833sage: e.__dict__.has_key("_points")834False835sage: o._face_compute_points(e)836sage: e.__dict__.has_key("_points")837True838"""839m = self.distances().matrix_from_rows(face._facets)840cols = m.columns(copy=False)841points = [i for i, col in enumerate(cols) if sum(col) == 0]842face._points = Sequence(points, int, check=False)843face._points.set_immutable()844845def _face_split_points(self, face):846r"""847Compute and cache boundary and interior lattice points of848``face``.849850TESTS::851852sage: c = lattice_polytope.octahedron(3).polar()853sage: f = c.facets()[0]854sage: v = f.__dict__.pop("_interior_points", None)855sage: f.__dict__.has_key("_interior_points")856False857sage: v = f.__dict__.pop("_boundary_points", None)858sage: f.__dict__.has_key("_boundary_points")859False860sage: c._face_split_points(f)861sage: f._interior_points862[10]863sage: f._boundary_points864[0, 2, 4, 6, 8, 9, 11, 12]865sage: f.points()866[0, 2, 4, 6, 8, 9, 10, 11, 12]867868Vertices don't have boundary::869870sage: f = c.faces(dim=0)[0]871sage: c._face_split_points(f)872sage: len(f._interior_points)8731874sage: len(f._boundary_points)8750876"""877if face.npoints() == 1: # Vertex878face._interior_points = face.points()879face._boundary_points = Sequence([], int, check=False)880else:881face._interior_points = Sequence([], int, check=False)882face._boundary_points = Sequence(face.points()[:face.nvertices()], int,883check=False)884non_vertices = face.points()[face.nvertices():]885distances = self.distances()886other_facets = [i for i in range(self.nfacets())887if not i in face._facets]888for p in non_vertices:889face._interior_points.append(p)890for f in other_facets:891if distances[f, p] == 0:892face._interior_points.pop()893face._boundary_points.append(p)894break895face._interior_points.set_immutable()896face._boundary_points.set_immutable()897898def _latex_(self):899r"""900Return the latex representation of self.901902OUTPUT:903904- string905906EXAMPLES:907908Arbitrary lattice polytopes are printed as `\Delta^d`, where `d` is909the (actual) dimension of the polytope::910911sage: LatticePolytope(matrix(2, [1, 0, 1, 0]))._latex_()912'\\Delta^{1}'913914For reflexive polytopes the output is the same... ::915916sage: o = lattice_polytope.octahedron(2)917sage: o._latex_()918'\\Delta^{2}'919920... unless they are written in the normal from in which case the index921in the internal database is printed as a subscript::922923sage: o.vertices()924[ 1 0 -1 0]925[ 0 1 0 -1]926sage: o = LatticePolytope(o.normal_form())927sage: o.vertices()928[ 1 0 0 -1]929[ 0 1 -1 0]930sage: o._latex_()931'\\Delta^{2}_{3}'932"""933if (self.dim() in (2, 3)934and self.is_reflexive()935and self.normal_form() == self.vertices()):936return r"\Delta^{%d}_{%d}" % (self.dim(), self.index())937else:938return r"\Delta^{%d}" % self.dim()939940def _palp(self, command, reduce_dimension=False):941r"""942Run ``command`` on vertices of this polytope.943944Returns the output of ``command`` as a string.945946.. note::947948PALP cannot be called for polytopes that do not span the ambient space.949If you specify ``reduce_dimension=True`` argument, PALP will be950called for vertices of this polytope in some basis of the affine space951it spans.952953TESTS::954955sage: o = lattice_polytope.octahedron(3)956sage: o._palp("poly.x -f")957'M:7 6 N:27 8 Pic:17 Cor:0\n'958sage: print o._palp("nef.x -f -N -p") # random time information959M:27 8 N:7 6 codim=2 #part=5960H:[0] P:0 V:2 4 5 0sec 0cpu961H:[0] P:2 V:3 4 5 0sec 0cpu962H:[0] P:3 V:4 5 0sec 0cpu963np=3 d:1 p:1 0sec 0cpu964965sage: p = LatticePolytope(matrix([1]))966sage: p._palp("poly.x -f")967Traceback (most recent call last):968...969ValueError: Cannot run "poly.x -f" for the zero-dimensional polytope!970Polytope: A lattice polytope: 0-dimensional, 1 vertices.971972sage: m = matrix(ZZ, [[1, 0, -1, 0],973... [0, 1, 0, -1],974... [0, 0, 0, 0]])975...976sage: p = LatticePolytope(m)977sage: p._palp("poly.x -f")978Traceback (most recent call last):979...980ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!981sage: p._palp("poly.x -f", reduce_dimension=True)982'M:5 4 F:4\n'983"""984if self.dim() <= 0:985raise ValueError, ("Cannot run \"%s\" for the zero-dimensional "986+ "polytope!\nPolytope: %s") % (command, self)987if self.dim() < self.ambient_dim() and not reduce_dimension:988raise ValueError(("Cannot run PALP for a %d-dimensional polytope " +989"in a %d-dimensional space!") % (self.dim(), self.ambient_dim()))990if _always_use_files:991fn = _palp(command, [self], reduce_dimension)992f = open(fn)993result = f.read()994f.close()995os.remove(fn)996else:997if _palp_dimension is not None:998dot = command.find(".")999command = (command[:dot] + "-%dd" % _palp_dimension +1000command[dot:])1001p = subprocess.Popen(command, shell=True, bufsize=2048,1002stdin=subprocess.PIPE,1003stdout=subprocess.PIPE,1004stderr=subprocess.PIPE,1005close_fds=True)1006stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr)1007write_palp_matrix(self._pullback(self._vertices), stdin)1008stdin.close()1009err = stderr.read()1010if len(err) > 0:1011raise RuntimeError, ("Error executing \"%s\" for the given polytope!"1012+ "\nPolytope: %s\nVertices:\n%s\nOutput:\n%s") % (command,1013self, self.vertices(), err)1014result = stdout.read()1015# We program around an issue with subprocess (this so far seems to1016# only be an issue on Cygwin).1017try:1018p.terminate()1019except OSError:1020pass1021if (not result or1022"!" in result or1023"failed." in result or1024"increase" in result or1025"Unable" in result):1026raise ValueError, ("Error executing \"%s\" for the given polytope!"1027+ "\nPolytope: %s\nVertices:\n%s\nOutput:\n%s") % (command,1028self, self.vertices(), result)1029return result10301031def _pullback(self, data):1032r"""1033Pull back given point(s) to the affine subspace spanned by this polytope.10341035INPUT:10361037- ``data`` - rational point or matrix of points (as columns) in the1038ambient space10391040OUTPUT: The same point(s) in the coordinates of the affine subspace1041space spanned by this polytope.10421043TESTS::10441045sage: o = lattice_polytope.octahedron(3)1046sage: o._pullback(o.vertices()) == o.vertices()1047True1048sage: m = matrix(ZZ, 3)1049sage: m[0, 0] = 11050sage: m[1, 1] = 11051sage: p = o.affine_transform(m)1052sage: p._pullback((0, 0, 0))1053[-1, 0]1054"""1055if self.ambient_dim() == self.dim():1056return data1057elif data is self._vertices:1058return self._sublattice_polytope._vertices1059elif is_Matrix(data):1060r = matrix([self._pullback(col)1061for col in data.columns(copy=False)]).transpose()1062return r1063else:1064data = vector(QQ, data)1065return self._sublattice.coordinates(data - self._shift_vector)10661067def _read_equations(self, data):1068r"""1069Read equations of facets/vertices of polar polytope from string or1070file.10711072TESTS: For a reflexive polytope construct the polar polytope::10731074sage: p = LatticePolytope(matrix(ZZ,2,3,[1,0,-1,0,1,-1]))1075sage: p.vertices()1076[ 1 0 -1]1077[ 0 1 -1]1078sage: s = p.poly_x("e")1079sage: print s10803 2 Vertices of P-dual <-> Equations of P10812 -11082-1 21083-1 -11084sage: p.__dict__.has_key("_polar")1085False1086sage: p._read_equations(s)1087sage: p._polar._vertices1088[ 2 -1 -1]1089[-1 2 -1]10901091For a non-reflexive polytope cache facet equations::10921093sage: p = LatticePolytope(matrix(ZZ,2,3,[1,0,-1,0,2,-3]))1094sage: p.vertices()1095[ 1 0 -1]1096[ 0 2 -3]1097sage: p.__dict__.has_key("_facet_normals")1098False1099sage: p.__dict__.has_key("_facet_constants")1100False1101sage: s = p.poly_x("e")1102sage: print s11033 2 Equations of P11045 -1 21105-2 -1 21106-3 2 31107sage: p._read_equations(s)1108sage: p._facet_normals1109[ 5 -1]1110[-2 -1]1111[-3 2]1112sage: p._facet_constants1113(2, 2, 3)1114"""1115if isinstance(data, str):1116f = StringIO.StringIO(data)1117self._read_equations(f)1118f.close()1119return1120if hasattr(self, "_is_reflexive"):1121# If it is already known that this polytope is reflexive, its1122# polar (whose vertices are equations of facets of this one)1123# is already computed and there is no need to read equations1124# of facets of this polytope. Moreover, doing so can corrupt1125# data if this polytope was constructed as polar. Skip input.1126skip_palp_matrix(data)1127return1128pos = data.tell()1129line = data.readline()1130self._is_reflexive = line.find("Vertices of P-dual") != -11131if self._is_reflexive:1132data.seek(pos)1133self._polar = LatticePolytope(read_palp_matrix(data),1134"A polytope polar to " + str(self._desc),1135copy_vertices=False, compute_vertices=False)1136self._polar._dim = self._dim1137self._polar._is_reflexive = True1138self._polar._constructed_as_polar = True1139self._polar._polar = self1140else:1141normals = []1142constants = []1143d = self.dim()1144for i in range(int(line.split()[0])):1145line = data.readline()1146numbers = [int(number) for number in line.split()]1147constants.append(numbers.pop())1148normals.append(numbers)1149self._facet_normals = matrix(ZZ, normals)1150self._facet_constants = vector(ZZ, constants)1151self._facet_normals.set_immutable()11521153def _read_faces(self, data):1154r"""1155Read faces information from string or file.11561157TESTS::11581159sage: o = lattice_polytope.octahedron(3)1160sage: s = o.poly_x("i")1161sage: print s1162Incidences as binary numbers [F-vector=(6 12 8)]:1163v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j1164v[0]: 100000 000010 000001 001000 010000 0001001165v[1]: 100010 100001 000011 101000 001010 110000 010001 011000 000110 000101 001100 0101001166v[2]: 100011 101010 110001 111000 000111 001110 010101 0111001167f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j1168f[0]: 00001111 00110011 01010101 10101010 11001100 111100001169f[1]: 00000011 00000101 00010001 00001010 00100010 00001100 01000100 10001000 00110000 01010000 10100000 110000001170f[2]: 00000001 00000010 00000100 00001000 00010000 00100000 01000000 100000001171sage: v = o.__dict__.pop("_faces", None)1172sage: o.__dict__.has_key("_faces")1173False1174sage: o._read_faces(s)1175sage: o._faces1176[1177[[0], [1], [2], [3], [4], [5]],1178[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],1179[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]1180]11811182Cannot be used for "polar polytopes," their faces are constructed1183from faces of the original one to preserve facial duality.11841185::11861187sage: c = o.polar()1188sage: s = c.poly_x("i")1189sage: print s1190Incidences as binary numbers [F-vector=(8 12 6)]:1191v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j1192v[0]: 00010000 00000001 01000000 00000100 00100000 00000010 10000000 000010001193v[1]: 00010001 01010000 00000101 01000100 00110000 00000011 00100010 11000000 10100000 00001100 00001010 100010001194v[2]: 01010101 00110011 11110000 00001111 11001100 101010101195f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j1196f[0]: 000111 001011 010101 011001 100110 101010 110100 1110001197f[1]: 000011 000101 001001 010001 000110 001010 100010 010100 100100 011000 101000 1100001198f[2]: 000001 000010 000100 001000 010000 1000001199sage: c._read_faces(s)1200Traceback (most recent call last):1201...1202ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!1203"""1204if isinstance(data, str):1205f = StringIO.StringIO(data)1206self._read_faces(f)1207f.close()1208return1209try:1210if self._constructed_as_polar:1211raise ValueError, ("Cannot read face structure for a polytope "1212+ "constructed as polar, use _compute_faces!")1213except AttributeError:1214pass1215data.readline()1216v = _read_poly_x_incidences(data, self.dim())1217f = _read_poly_x_incidences(data, self.dim())1218self._faces = Sequence([], cr=True)1219for i in range(len(v)):1220self._faces.append([_PolytopeFace(self, vertices, facets)1221for vertices, facets in zip(v[i], f[i])])1222# Zero-dimensional faces (i.e. vertices) from poly.x can be in "random"1223# order, so that the lists of corresponding facets are in increasing1224# order.1225# While this may be convenient for something, it is quite confusing to1226# have p.faces(dim=0)[0].vertices() == [5], which means "the 5th vertex1227# spans the 0th 0-dimensional face" and, on the polar side, "the 0th1228# facet is described by the 5th equation."1229# The next line sorts 0-dimensional faces to make these enumerations1230# more transparent.1231self._faces[0].sort(cmp = lambda x,y: cmp(x._vertices[0], y._vertices[0]))1232self._faces.set_immutable()12331234def _read_nef_partitions(self, data):1235r"""1236Read nef-partitions of ``self`` from ``data``.12371238INPUT:12391240- ``data`` -- a string or a file.12411242OUTPUT:12431244- none.12451246TESTS::12471248sage: o = lattice_polytope.octahedron(3)1249sage: s = o.nef_x("-p -N -Lv")1250sage: print s # random time values1251M:27 8 N:7 6 codim=2 #part=512523 6 Vertices in N-lattice:12531 0 0 -1 0 012540 1 0 0 -1 012550 0 1 0 0 -11256------------------------------12571 0 0 1 0 0 d=2 codim=212580 1 0 0 1 0 d=2 codim=212590 0 1 0 0 1 d=2 codim=21260P:0 V:2 4 5 (0 2) (1 1) (2 0) 0sec 0cpu1261P:2 V:3 4 5 (1 1) (1 1) (1 1) 0sec 0cpu1262P:3 V:4 5 (0 2) (1 1) (1 1) 0sec 0cpu1263np=3 d:1 p:1 0sec 0cpu12641265We make a copy of the octahedron since direct use of this function may1266destroy cache integrity and lead so strange effects in other doctests::12671268sage: o_copy = LatticePolytope(o.vertices())1269sage: o_copy.__dict__.has_key("_nef_partitions")1270False1271sage: o_copy._read_nef_partitions(s)1272sage: o_copy._nef_partitions1273[1274Nef-partition {0, 1, 3} U {2, 4, 5},1275Nef-partition {0, 1, 2} U {3, 4, 5},1276Nef-partition {0, 1, 2, 3} U {4, 5}1277]1278"""1279if isinstance(data, str):1280f = StringIO.StringIO(data)1281self._read_nef_partitions(f)1282f.close()1283return1284nvertices = self.nvertices()1285data.readline() # Skip M/N information1286nef_vertices = read_palp_matrix(data)1287if self.vertices() != nef_vertices:1288# It seems that we SHOULD worry about this...1289# raise RunTimeError, "nef.x changed the order of vertices!"1290trans = [self.vertices().columns().index(v)1291for v in nef_vertices.columns()]1292for i, p in enumerate(partitions):1293partitions[i] = [trans[v] for v in p]1294line = data.readline()1295if line == "":1296raise ValueError, "more data expected!"1297partitions = Sequence([], cr=True)1298while len(line) > 0 and line.find("np=") == -1:1299if line.find("V:") == -1:1300line = data.readline()1301continue1302start = line.find("V:") + 21303end = line.find(" ", start) # Find DOUBLE space1304pvertices = Sequence(line[start:end].split(),int)1305partition = [0] * nvertices1306for v in pvertices:1307partition[v] = 11308partition = NefPartition(partition, self)1309partition._is_product = line.find(" D ") != -11310partition._is_projection = line.find(" DP ") != -11311# Set the stuff1312start = line.find("H:")1313if start != -1:1314start += 21315end = line.find("[", start)1316partition._hodge_numbers = tuple(int(h)1317for h in line[start:end].split())1318partitions.append(partition)1319line = data.readline()1320start = line.find("np=")1321if start == -1:1322raise ValueError, """Wrong data format, cannot find "np="!"""1323# The following block seems to be unnecessary (and requires taking into1324# account projections/products)1325# # Compare the number of found partitions with statistic.1326# start += 31327# end = line.find(" ", start)1328# np = int(line[start:end])1329# if False and np != len(partitions):1330# raise ValueError, ("Found %d partitions, expected %d!" %1331# (len(partitions), np))1332partitions.set_immutable()1333self._nef_partitions = partitions13341335def _repr_(self):1336r"""1337Return a string representation of this polytope.13381339TESTS::13401341sage: o = lattice_polytope.octahedron(3)1342sage: o._repr_()1343'An octahedron: 3-dimensional, 6 vertices.'1344"""1345s = str(self._desc)1346s += ": %d-dimensional, %d vertices." % (self.dim(), self.nvertices())1347return s13481349def affine_transform(self, a=1, b=0):1350r"""1351Return a*P+b, where P is this lattice polytope.13521353.. note::13541355#. While ``a`` and ``b`` may be rational, the final result must be a1356lattice polytope, i.e. all vertices must be integral.13571358#. If the transform (restricted to this polytope) is bijective, facial1359structure will be preserved, e.g. the first facet of the image will be1360spanned by the images of vertices which span the first facet of the1361original polytope.13621363INPUT:13641365- ``a`` - (default: 1) rational scalar or matrix13661367- ``b`` - (default: 0) rational scalar or vector, scalars are1368interpreted as vectors with the same components13691370EXAMPLES::13711372sage: o = lattice_polytope.octahedron(2)1373sage: o.vertices()1374[ 1 0 -1 0]1375[ 0 1 0 -1]1376sage: o.affine_transform(2).vertices()1377[ 2 0 -2 0]1378[ 0 2 0 -2]1379sage: o.affine_transform(1,1).vertices()1380[2 1 0 1]1381[1 2 1 0]1382sage: o.affine_transform(b=1).vertices()1383[2 1 0 1]1384[1 2 1 0]1385sage: o.affine_transform(b=(1, 0)).vertices()1386[ 2 1 0 1]1387[ 0 1 0 -1]1388sage: a = matrix(QQ, 2, [1/2, 0, 0, 3/2])1389sage: o.polar().vertices()1390[-1 1 -1 1]1391[ 1 1 -1 -1]1392sage: o.polar().affine_transform(a, (1/2, -1/2)).vertices()1393[ 0 1 0 1]1394[ 1 1 -2 -2]13951396While you can use rational transformation, the result must be integer::13971398sage: o.affine_transform(a)1399Traceback (most recent call last):1400...1401ValueError: Points must be integral!1402Given:1403[ 1/2 0 -1/2 0]1404[ 0 3/2 0 -3/2]1405"""1406new_vertices = a * self.vertices()1407if b in QQ:1408b = vector(QQ, [b]*new_vertices.nrows())1409else:1410b = vector(QQ, b)1411for i, col in enumerate(new_vertices.columns(copy=False)):1412new_vertices.set_column(i, col + b)1413r = LatticePolytope(new_vertices, compute_vertices=True)1414if (a in QQ and a != 0) or r.dim() == self.dim():1415r._constructed_as_affine_transform = True1416if hasattr(self, "_constructed_as_affine_transform"):1417# Prevent long chains of "original-transform"1418r._original = self._original1419else:1420r._original = self1421return r14221423def ambient_dim(self):1424r"""1425Return the dimension of the ambient space of this polytope.14261427EXAMPLES: We create a 3-dimensional octahedron and check its1428ambient dimension::14291430sage: o = lattice_polytope.octahedron(3)1431sage: o.ambient_dim()143231433"""1434return self._vertices.nrows()14351436def dim(self):1437r"""1438Return the dimension of this polytope.14391440EXAMPLES: We create a 3-dimensional octahedron and check its1441dimension::14421443sage: o = lattice_polytope.octahedron(3)1444sage: o.dim()1445314461447Now we create a 2-dimensional diamond in a 3-dimensional space::14481449sage: m = matrix(ZZ, [[1, 0, -1, 0],1450... [0, 1, 0, -1],1451... [0, 0, 0, 0]])1452...1453sage: p = LatticePolytope(m)1454sage: p.dim()145521456sage: p.ambient_dim()145731458"""1459if not hasattr(self, "_dim"):1460self._compute_dim(compute_vertices=False)1461return self._dim14621463def distances(self, point=None):1464r"""1465Return the matrix of distances for this polytope or distances for1466the given point.14671468The matrix of distances m gives distances m[i,j] between the i-th1469facet (which is also the i-th vertex of the polar polytope in the1470reflexive case) and j-th point of this polytope.14711472If point is specified, integral distances from the point to all1473facets of this polytope will be computed.14741475This function CAN be used for polytopes whose dimension is smaller1476than the dimension of the ambient space. In this case distances are1477computed in the affine subspace spanned by the polytope and if the1478point is given, it must be in this subspace.14791480EXAMPLES: The matrix of distances for a 3-dimensional octahedron::14811482sage: o = lattice_polytope.octahedron(3)1483sage: o.distances()1484[0 0 2 2 2 0 1]1485[2 0 2 0 2 0 1]1486[0 2 2 2 0 0 1]1487[2 2 2 0 0 0 1]1488[0 0 0 2 2 2 1]1489[2 0 0 0 2 2 1]1490[0 2 0 2 0 2 1]1491[2 2 0 0 0 2 1]14921493Distances from facets to the point (1,2,3)::14941495sage: o.distances([1,2,3])1496(1, 3, 5, 7, -5, -3, -1, 1)14971498It is OK to use RATIONAL coordinates::14991500sage: o.distances([1,2,3/2])1501(-1/2, 3/2, 7/2, 11/2, -7/2, -3/2, 1/2, 5/2)1502sage: o.distances([1,2,sqrt(2)])1503Traceback (most recent call last):1504...1505TypeError: unable to convert sqrt(2) to a rational15061507Now we create a non-spanning polytope::15081509sage: m = matrix(ZZ, [[1, 0, -1, 0],1510... [0, 1, 0, -1],1511... [0, 0, 0, 0]])1512...1513sage: p = LatticePolytope(m)1514sage: p.distances()1515[0 2 2 0 1]1516[2 2 0 0 1]1517[0 0 2 2 1]1518[2 0 0 2 1]1519sage: p.distances((1/2, 3, 0))1520(7/2, 9/2, -5/2, -3/2)1521sage: p.distances((1, 1, 1))1522Traceback (most recent call last):1523...1524ArithmeticError: vector is not in free module1525"""1526if point != None:1527if self.dim() < self.ambient_dim():1528return self._sublattice_polytope.distances(self._pullback(point))1529elif self.is_reflexive():1530return (self._polar._vertices.transpose() * vector(QQ, point)1531+ vector(QQ, [1]*self.nfacets()))1532else:1533return self._facet_normals*vector(QQ, point)+self._facet_constants1534if not hasattr(self, "_distances"):1535if self.dim() < self.ambient_dim():1536return self._sublattice_polytope.distances()1537elif self.is_reflexive():1538V = self._polar._vertices.transpose()1539P = self.points()1540self._distances = V * P1541for i in range(self._distances.nrows()):1542for j in range(self._distances.ncols()):1543self._distances[i,j] += 11544else:1545self._distances = self._facet_normals * self.points()1546for i in range(self._distances.nrows()):1547for j in range(self._distances.ncols()):1548self._distances[i,j] += self._facet_constants[i]1549self._distances.set_immutable()1550return self._distances15511552def edges(self):1553r"""1554Return the sequence of edges of this polytope (i.e. faces of1555dimension 1).15561557EXAMPLES: The octahedron has 12 edges::15581559sage: o = lattice_polytope.octahedron(3)1560sage: len(o.edges())1561121562sage: o.edges()1563[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]1564"""1565return self.faces(dim=1)15661567def faces(self, dim=None, codim=None):1568r"""1569Return the sequence of proper faces of this polytope.15701571If ``dim`` or ``codim`` are specified,1572returns a sequence of faces of the corresponding dimension or1573codimension. Otherwise returns the sequence of such sequences for1574all dimensions.15751576EXAMPLES: All faces of the 3-dimensional octahedron::15771578sage: o = lattice_polytope.octahedron(3)1579sage: o.faces()1580[1581[[0], [1], [2], [3], [4], [5]],1582[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],1583[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]1584]15851586Its faces of dimension one (i.e., edges)::15871588sage: o.faces(dim=1)1589[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]15901591Its faces of codimension two (also edges)::15921593sage: o.faces(codim=2)1594[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]]15951596It is an error to specify both dimension and codimension at the1597same time, even if they do agree::15981599sage: o.faces(dim=1, codim=2)1600Traceback (most recent call last):1601...1602ValueError: Both dim and codim are given!16031604The only faces of a zero-dimensional polytope are the empty set and1605the polytope itself, i.e. it has no proper faces at all::16061607sage: p = LatticePolytope(matrix([[1]]))1608sage: p.vertices()1609[1]1610sage: p.faces()1611[]16121613In particular, you an exception will be raised if you try to access1614faces of the given dimension or codimension, including edges and1615facets::16161617sage: p.facets()1618Traceback (most recent call last):1619...1620IndexError: list index out of range1621"""1622try:1623if dim == None and codim == None:1624return self._faces1625elif dim != None and codim == None:1626return self._faces[dim]1627elif dim == None and codim != None:1628return self._faces[self.dim()-codim]1629else:1630raise ValueError, "Both dim and codim are given!"1631except AttributeError:1632self._compute_faces()1633return self.faces(dim, codim)16341635def facet_constant(self, i):1636r"""1637Return the constant in the ``i``-th facet inequality of this polytope.16381639The i-th facet inequality is given by1640self.facet_normal(i) * X + self.facet_constant(i) >= 0.16411642INPUT:16431644- ``i`` - integer, the index of the facet16451646OUTPUT:16471648- integer -- the constant in the ``i``-th facet inequality.16491650EXAMPLES:16511652Let's take a look at facets of the octahedron and some polytopes1653inside it::16541655sage: o = lattice_polytope.octahedron(3)1656sage: o.vertices()1657[ 1 0 0 -1 0 0]1658[ 0 1 0 0 -1 0]1659[ 0 0 1 0 0 -1]1660sage: o.facet_normal(0)1661(-1, -1, 1)1662sage: o.facet_constant(0)166311664sage: m = copy(o.vertices())1665sage: m[0,0] = 01666sage: m1667[ 0 0 0 -1 0 0]1668[ 0 1 0 0 -1 0]1669[ 0 0 1 0 0 -1]1670sage: p = LatticePolytope(m)1671sage: p.facet_normal(0)1672(-1, 0, 0)1673sage: p.facet_constant(0)167401675sage: m[0,3] = 01676sage: m1677[ 0 0 0 0 0 0]1678[ 0 1 0 0 -1 0]1679[ 0 0 1 0 0 -1]1680sage: p = LatticePolytope(m)1681sage: p.facet_normal(0)1682(0, -1, 1)1683sage: p.facet_constant(0)1684116851686This is a 2-dimensional lattice polytope in a 4-dimensional space::16871688sage: m = matrix([(1,-1,1,3), (-1,-1,1,3), (0,0,0,0)])1689sage: p = LatticePolytope(m.transpose())1690sage: p1691A lattice polytope: 2-dimensional, 3 vertices.1692sage: p.vertices()1693[ 1 -1 0]1694[-1 -1 0]1695[ 1 1 0]1696[ 3 3 0]1697sage: fns = [p.facet_normal(i) for i in range(p.nfacets())]1698sage: fns1699[(11, -1, 1, 3), (-11, -1, 1, 3), (0, 1, -1, -3)]1700sage: fcs = [p.facet_constant(i) for i in range(p.nfacets())]1701sage: fcs1702[0, 0, 11]17031704Now we manually compute the distance matrix of this polytope. Since it1705is a triangle, each line (corresponding to a facet) should have two1706zeros (vertices of the corresponding facet) and one positive number1707(since our normals are inner)::17081709sage: matrix([[fns[i] * p.vertex(j) + fcs[i]1710... for j in range(p.nvertices())]1711... for i in range(p.nfacets())])1712[22 0 0]1713[ 0 22 0]1714[ 0 0 11]1715"""1716if self.is_reflexive():1717return 11718elif self.ambient_dim() == self.dim():1719return self._facet_constants[i]1720else:1721return (self._sublattice_polytope.facet_constant(i)1722* self._dual_embedding_scale1723- self.facet_normal(i) * self._shift_vector)17241725def facet_normal(self, i):1726r"""1727Return the inner normal to the ``i``-th facet of this polytope.17281729If this polytope is not full-dimensional, facet normals will be1730orthogonal to the integer kernel of the affine subspace spanned by1731this polytope.17321733INPUT:17341735- ``i`` -- integer, the index of the facet17361737OUTPUT:17381739- vectors -- the inner normal of the ``i``-th facet17401741EXAMPLES:17421743Let's take a look at facets of the octahedron and some polytopes1744inside it::17451746sage: o = lattice_polytope.octahedron(3)1747sage: o.vertices()1748[ 1 0 0 -1 0 0]1749[ 0 1 0 0 -1 0]1750[ 0 0 1 0 0 -1]1751sage: o.facet_normal(0)1752(-1, -1, 1)1753sage: o.facet_constant(0)175411755sage: m = copy(o.vertices())1756sage: m[0,0] = 01757sage: m1758[ 0 0 0 -1 0 0]1759[ 0 1 0 0 -1 0]1760[ 0 0 1 0 0 -1]1761sage: p = LatticePolytope(m)1762sage: p.facet_normal(0)1763(-1, 0, 0)1764sage: p.facet_constant(0)176501766sage: m[0,3] = 01767sage: m1768[ 0 0 0 0 0 0]1769[ 0 1 0 0 -1 0]1770[ 0 0 1 0 0 -1]1771sage: p = LatticePolytope(m)1772sage: p.facet_normal(0)1773(0, -1, 1)1774sage: p.facet_constant(0)1775117761777Here is an example of a 3-dimensional polytope in a 4-dimensional1778space::17791780sage: m = matrix([(0,0,0,0), (1,1,1,3), (1,-1,1,3), (-1,-1,1,3)])1781sage: p = LatticePolytope(m.transpose())1782sage: p.vertices()1783[ 0 1 1 -1]1784[ 0 1 -1 -1]1785[ 0 1 1 1]1786[ 0 3 3 3]1787sage: ker = p.vertices().integer_kernel().matrix()1788sage: ker1789[ 0 0 3 -1]1790sage: [ker * p.facet_normal(i) for i in range(p.nfacets())]1791[(0), (0), (0), (0)]17921793Now we manually compute the distance matrix of this polytope. Since it1794is a simplex, each line (corresponding to a facet) should consist of1795zeros (indicating generating vertices of the corresponding facet) and1796a single positive number (since our normals are inner)::17971798sage: matrix([[p.facet_normal(i) * p.vertex(j)1799... + p.facet_constant(i)1800... for j in range(p.nvertices())]1801... for i in range(p.nfacets())])1802[ 0 0 0 20]1803[ 0 0 20 0]1804[ 0 20 0 0]1805[10 0 0 0]1806"""1807if self.is_reflexive():1808return self.polar().vertex(i)1809elif self.ambient_dim() == self.dim():1810return self._facet_normals[i]1811else:1812return ( self._sublattice_polytope.facet_normal(i) *1813self._dual_embedding_matrix )18141815def facets(self):1816r"""1817Return the sequence of facets of this polytope (i.e. faces of1818codimension 1).18191820EXAMPLES: All facets of the 3-dimensional octahedron::18211822sage: o = lattice_polytope.octahedron(3)1823sage: o.facets()1824[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]18251826Facets are the same as faces of codimension one::18271828sage: o.facets() is o.faces(codim=1)1829True1830"""1831return self.faces(codim=1)18321833# Dictionaries of normal forms1834_rp_dict = [None]*418351836def index(self):1837r"""1838Return the index of this polytope in the internal database of 2- or18393-dimensional reflexive polytopes. Databases are stored in the1840directory of the package.18411842.. note::18431844The first call to this function for each dimension can take1845a few seconds while the dictionary of all polytopes is1846constructed, but after that it is cached and fast.18471848:rtype: integer18491850EXAMPLES: We check what is the index of the "diamond" in the1851database::18521853sage: o = lattice_polytope.octahedron(2)1854sage: o.index()1855318561857Note that polytopes with the same index are not necessarily the1858same::18591860sage: o.vertices()1861[ 1 0 -1 0]1862[ 0 1 0 -1]1863sage: lattice_polytope.ReflexivePolytope(2,3).vertices()1864[ 1 0 0 -1]1865[ 0 1 -1 0]18661867But they are in the same `GL(Z^n)` orbit and have the same1868normal form::18691870sage: o.normal_form()1871[ 1 0 0 -1]1872[ 0 1 -1 0]1873sage: lattice_polytope.ReflexivePolytope(2,3).normal_form()1874[ 1 0 0 -1]1875[ 0 1 -1 0]1876"""1877if not hasattr(self, "_index"):1878if not self.is_reflexive():1879raise NotImplementedError, "only reflexive polytopes can be indexed!"1880dim = self.dim()1881if dim not in [2, 3]:1882raise NotImplementedError, "only 2- and 3-dimensional polytopes can be indexed!"1883if LatticePolytopeClass._rp_dict[dim] == None:1884rp_dict = dict()1885for n, p in enumerate(ReflexivePolytopes(dim)):1886rp_dict[str(p.normal_form())] = n1887LatticePolytopeClass._rp_dict[dim] = rp_dict1888self._index = LatticePolytopeClass._rp_dict[dim][str(self.normal_form())]1889return self._index18901891def is_reflexive(self):1892r"""1893Return True if this polytope is reflexive.18941895EXAMPLES: The 3-dimensional octahedron is reflexive (and 4318 other18963-polytopes)::18971898sage: o = lattice_polytope.octahedron(3)1899sage: o.is_reflexive()1900True19011902But not all polytopes are reflexive::19031904sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],1905... [0, 1, 0, 0, -1, 0],1906... [0, 0, 0, 0, 0, -1]])1907...1908sage: p = LatticePolytope(m)1909sage: p.is_reflexive()1910False19111912Only full-dimensional polytopes can be reflexive (otherwise the polar1913set is not a polytope at all, since it is unbounded)::19141915sage: m = matrix(ZZ, [[0, 1, 0, -1, 0],1916... [0, 0, 1, 0, -1],1917... [0, 0, 0, 0, 0]])1918...1919sage: p = LatticePolytope(m)1920sage: p.is_reflexive()1921False1922"""1923# In the last doctest above the origin is added intentionall - it1924# causes the sublattice polytope to be reflexive, yet the original1925# still should not be one.1926if not hasattr(self, "_is_reflexive"):1927if self.dim() != self.ambient_dim():1928self._is_reflexive = False1929else:1930# Determine if the polytope is reflexive by computing vertices1931# of the dual polytope and save all obtained information.1932self._read_equations(self.poly_x("e"))1933return self._is_reflexive19341935def nef_partitions(self, keep_symmetric=False, keep_products=True,1936keep_projections=True, hodge_numbers=False):1937r"""1938Return 2-part nef-partitions of ``self``.19391940INPUT:19411942- ``keep_symmetric`` -- (default: ``False``) if ``True``, "-s" option1943will be passed to ``nef.x`` in order to keep symmetric partitions,1944i.e. partitions related by lattice automorphisms preserving ``self``;19451946- ``keep_products`` -- (default: ``True``) if ``True``, "-D" option1947will be passed to ``nef.x`` in order to keep product partitions,1948with corresponding complete intersections being direct products;19491950- ``keep_projections`` -- (default: ``True``) if ``True``, "-P" option1951will be passed to ``nef.x`` in order to keep projection partitions,1952i.e. partitions with one of the parts consisting of a single vertex;19531954- ``hodge_numbers`` -- (default: ``False``) if ``False``, "-p" option1955will be passed to ``nef.x`` in order to skip Hodge numbers1956computation, which takes a lot of time.19571958OUTPUT:19591960- a sequence of :class:`nef-partitions <NefPartition>`.19611962Type ``NefPartition?`` for definitions and notation.19631964EXAMPLES:19651966Nef-partitions of the 4-dimensional octahedron::19671968sage: o = lattice_polytope.octahedron(4)1969sage: o.nef_partitions()1970[1971Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),1972Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},1973Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},1974Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),1975Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},1976Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},1977Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},1978Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)1979]19801981Now we omit projections::19821983sage: o.nef_partitions(keep_projections=False)1984[1985Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),1986Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},1987Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},1988Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),1989Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},1990Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},1991Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7}1992]19931994Currently Hodge numbers cannot be computed for a given nef-partition::19951996sage: o.nef_partitions()[1].hodge_numbers()1997Traceback (most recent call last):1998...1999NotImplementedError: use nef_partitions(hodge_numbers=True)!20002001But they can be obtained from ``nef.x`` for all nef-partitions at once.2002Partitions will be exactly the same::20032004sage: o.nef_partitions(hodge_numbers=True) # long time (2s on sage.math, 2011)2005[2006Nef-partition {0, 1, 4, 5} U {2, 3, 6, 7} (direct product),2007Nef-partition {0, 1, 2, 4} U {3, 5, 6, 7},2008Nef-partition {0, 1, 2, 4, 5} U {3, 6, 7},2009Nef-partition {0, 1, 2, 4, 5, 6} U {3, 7} (direct product),2010Nef-partition {0, 1, 2, 3} U {4, 5, 6, 7},2011Nef-partition {0, 1, 2, 3, 4} U {5, 6, 7},2012Nef-partition {0, 1, 2, 3, 4, 5} U {6, 7},2013Nef-partition {0, 1, 2, 3, 4, 5, 6} U {7} (projection)2014]20152016Now it is possible to get Hodge numbers::20172018sage: o.nef_partitions(hodge_numbers=True)[1].hodge_numbers()2019(20,)20202021Since nef-partitions are cached, their Hodge numbers are accessible2022after the first request, even if you do not specify2023``hodge_numbers=True`` anymore::20242025sage: o.nef_partitions()[1].hodge_numbers()2026(20,)20272028We illustrate removal of symmetric partitions on a diamond::20292030sage: o = lattice_polytope.octahedron(2)2031sage: o.nef_partitions()2032[2033Nef-partition {0, 2} U {1, 3} (direct product),2034Nef-partition {0, 1} U {2, 3},2035Nef-partition {0, 1, 2} U {3} (projection)2036]2037sage: o.nef_partitions(keep_symmetric=True)2038[2039Nef-partition {0, 1, 3} U {2} (projection),2040Nef-partition {0, 2, 3} U {1} (projection),2041Nef-partition {0, 3} U {1, 2},2042Nef-partition {1, 2, 3} U {0} (projection),2043Nef-partition {1, 3} U {0, 2} (direct product),2044Nef-partition {2, 3} U {0, 1},2045Nef-partition {0, 1, 2} U {3} (projection)2046]20472048Nef-partitions can be computed only for reflexive polytopes::20492050sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],2051... [0, 1, 0, 0, -1, 0],2052... [0, 0, 2, 0, 0, -1]])2053...2054sage: p = LatticePolytope(m)2055sage: p.nef_partitions()2056Traceback (most recent call last):2057...2058ValueError: The given polytope is not reflexive!2059Polytope: A lattice polytope: 3-dimensional, 6 vertices.2060"""2061if not self.is_reflexive():2062raise ValueError, ("The given polytope is not reflexive!\n"2063+ "Polytope: %s") % self2064keys = "-N -V"2065if keep_symmetric:2066keys += " -s"2067if keep_products:2068keys += " -D"2069if keep_projections:2070keys += " -P"2071if not hodge_numbers:2072keys += " -p"2073if hasattr(self, "_npkeys"):2074oldkeys = self._npkeys2075if oldkeys == keys:2076return self._nef_partitions2077if not (hodge_numbers and oldkeys.find("-p") != -12078or keep_symmetric and oldkeys.find("-s") == -12079or not keep_symmetric and oldkeys.find("-s") != -12080or keep_projections and oldkeys.find("-P") == -12081or keep_products and oldkeys.find("-D") == -1):2082# Select only necessary partitions2083return Sequence([p for p in self._nef_partitions2084if (keep_projections or not p._is_projection)2085and (keep_products or not p._is_product)],2086cr=True, check=False)2087self._read_nef_partitions(self.nef_x(keys))2088self._npkeys = keys2089return self._nef_partitions20902091def nef_x(self, keys):2092r"""2093Run nef.x with given ``keys`` on vertices of this2094polytope.20952096INPUT:209720982099- ``keys`` - a string of options passed to nef.x. The2100key "-f" is added automatically.210121022103OUTPUT: the output of nef.x as a string.21042105EXAMPLES: This call is used internally for computing2106nef-partitions::21072108sage: o = lattice_polytope.octahedron(3)2109sage: s = o.nef_x("-N -V -p")2110sage: s # output contains random time2111M:27 8 N:7 6 codim=2 #part=521123 6 Vertices of P:21131 0 0 -1 0 021140 1 0 0 -1 021150 0 1 0 0 -12116P:0 V:2 4 5 0sec 0cpu2117P:2 V:3 4 5 0sec 0cpu2118P:3 V:4 5 0sec 0cpu2119np=3 d:1 p:1 0sec 0cpu2120"""2121return self._palp("nef.x -f " + keys)21222123def nfacets(self):2124r"""2125Return the number of facets of this polytope.21262127EXAMPLES: The number of facets of the 3-dimensional octahedron::21282129sage: o = lattice_polytope.octahedron(3)2130sage: o.nfacets()2131821322133The number of facets of an interval is 2::21342135sage: LatticePolytope(matrix([1,2])).nfacets()2136221372138Now consider a 2-dimensional diamond in a 3-dimensional space::21392140sage: m = matrix(ZZ, [[1, 0, -1, 0],2141... [0, 1, 0, -1],2142... [0, 0, 0, 0]])2143...2144sage: p = LatticePolytope(m)2145sage: p.nfacets()214642147"""2148if not hasattr(self, "_nfacets"):2149if self.is_reflexive():2150self._nfacets = self.polar().nvertices()2151elif self.dim() == self.ambient_dim():2152self._nfacets = self._facet_normals.nrows()2153elif self.dim() <= 0:2154self._nfacets = 02155else:2156self._nfacets = self._sublattice_polytope.nfacets()2157return self._nfacets21582159def normal_form(self):2160r"""2161Return the normal form of vertices of the polytope.21622163Two full-dimensional lattice polytopes are in the same GL(Z)-orbit if2164and only if their normal forms are the same. Normal form is not defined2165and thus cannot be used for polytopes whose dimension is smaller than2166the dimension of the ambient space.21672168EXAMPLES: We compute the normal form of the "diamond"::21692170sage: o = lattice_polytope.octahedron(2)2171sage: o.vertices()2172[ 1 0 -1 0]2173[ 0 1 0 -1]2174sage: o.normal_form()2175[ 1 0 0 -1]2176[ 0 1 -1 0]21772178The diamond is the 3rd polytope in the internal database...21792180::21812182sage: o.index()218332184sage: lattice_polytope.ReflexivePolytope(2,3).vertices()2185[ 1 0 0 -1]2186[ 0 1 -1 0]21872188It is not possible to compute normal forms for polytopes which do not2189span the space::21902191sage: m = matrix(ZZ, [[1, 0, -1, 0],2192... [0, 1, 0, -1],2193... [0, 0, 0, 0]])2194...2195sage: p = LatticePolytope(m)2196sage: p.normal_form()2197Traceback (most recent call last):2198...2199ValueError: Normal form is not defined for a 2-dimensional polytope in a 3-dimensional space!2200"""2201if not hasattr(self, "_normal_form"):2202if self.dim() < self.ambient_dim():2203raise ValueError(2204("Normal form is not defined for a %d-dimensional polytope " +2205"in a %d-dimensional space!") % (self.dim(), self.ambient_dim()))2206self._normal_form = read_palp_matrix(self.poly_x("N"))2207self._normal_form.set_immutable()2208return self._normal_form22092210def npoints(self):2211r"""2212Return the number of lattice points of this polytope.22132214EXAMPLES: The number of lattice points of the 3-dimensional2215octahedron and its polar cube::22162217sage: o = lattice_polytope.octahedron(3)2218sage: o.npoints()221972220sage: cube = o.polar()2221sage: cube.npoints()2222272223"""2224try:2225return self._npoints2226except AttributeError:2227return self.points().ncols()22282229def nvertices(self):2230r"""2231Return the number of vertices of this polytope.22322233EXAMPLES: The number of vertices of the 3-dimensional octahedron2234and its polar cube::22352236sage: o = lattice_polytope.octahedron(3)2237sage: o.nvertices()223862239sage: cube = o.polar()2240sage: cube.nvertices()224182242"""2243return self._vertices.ncols()22442245def origin(self):2246r"""2247Return the index of the origin in the list of points of self.22482249OUTPUT:22502251- integer if the origin belongs to this polytope, ``None`` otherwise.22522253EXAMPLES::22542255sage: o = lattice_polytope.octahedron(2)2256sage: o.origin()225742258sage: o.point(o.origin())2259(0, 0)22602261sage: p = LatticePolytope(matrix([[1,2]]))2262sage: p.points()2263[1 2]2264sage: print p.origin()2265None22662267Now we make sure that the origin of non-full-dimensional polytopes can2268be identified correctly (Trac #10661)::22692270sage: LatticePolytope([(1,0,0), (-1,0,0)]).origin()227122272"""2273if "_origin" not in self.__dict__:2274origin = vector(ZZ, self.ambient_dim())2275points = self.points().columns(copy=False)2276self._origin = points.index(origin) if origin in points else None2277return self._origin22782279def parent(self):2280"""2281Return the set of all lattice polytopes.22822283EXAMPLES::22842285sage: o = lattice_polytope.octahedron(3)2286sage: o.parent()2287Set of all Lattice Polytopes2288"""2289return SetOfAllLatticePolytopes22902291def plot3d(self,2292show_facets=True, facet_opacity=0.5, facet_color=(0,1,0),2293facet_colors=None,2294show_edges=True, edge_thickness=3, edge_color=(0.5,0.5,0.5),2295show_vertices=True, vertex_size=10, vertex_color=(1,0,0),2296show_points=True, point_size=10, point_color=(0,0,1),2297show_vindices=None, vindex_color=(0,0,0),2298vlabels=None,2299show_pindices=None, pindex_color=(0,0,0),2300index_shift=1.1):2301r"""2302Return a 3d-plot of this polytope.23032304Polytopes with ambient dimension 1 and 2 will be plotted along x-axis2305or in xy-plane respectively. Polytopes of dimension 3 and less with2306ambient dimension 4 and greater will be plotted in some basis of the2307spanned space.23082309By default, everything is shown with more or less pretty2310combination of size and color parameters.23112312INPUT: Most of the parameters are self-explanatory:231323142315- ``show_facets`` - (default:True)23162317- ``facet_opacity`` - (default:0.5)23182319- ``facet_color`` - (default:(0,1,0))23202321- ``facet_colors`` - (default:None) if specified, must be a list of2322colors for each facet separately, used instead of ``facet_color``23232324- ``show_edges`` - (default:True) whether to draw2325edges as lines23262327- ``edge_thickness`` - (default:3)23282329- ``edge_color`` - (default:(0.5,0.5,0.5))23302331- ``show_vertices`` - (default:True) whether to draw2332vertices as balls23332334- ``vertex_size`` - (default:10)23352336- ``vertex_color`` - (default:(1,0,0))23372338- ``show_points`` - (default:True) whether to draw2339other poits as balls23402341- ``point_size`` - (default:10)23422343- ``point_color`` - (default:(0,0,1))23442345- ``show_vindices`` - (default:same as2346show_vertices) whether to show indices of vertices23472348- ``vindex_color`` - (default:(0,0,0)) color for2349vertex labels23502351- ``vlabels`` - (default:None) if specified, must be a list of labels2352for each vertex, default labels are vertex indicies23532354- ``show_pindices`` - (default:same as show_points)2355whether to show indices of other points23562357- ``pindex_color`` - (default:(0,0,0)) color for2358point labels23592360- ``index_shift`` - (default:1.1)) if 1, labels are2361placed exactly at the corresponding points. Otherwise the label2362position is computed as a multiple of the point position vector.236323642365EXAMPLES: The default plot of a cube::23662367sage: c = lattice_polytope.octahedron(3).polar()2368sage: c.plot3d()23692370Plot without facets and points, shown without the frame::23712372sage: c.plot3d(show_facets=false,show_points=false).show(frame=False)23732374Plot with facets of different colors::23752376sage: c.plot3d(facet_colors=rainbow(c.nfacets(), 'rgbtuple'))23772378It is also possible to plot lower dimensional polytops in 3D (let's2379also change labels of vertices)::23802381sage: lattice_polytope.octahedron(2).plot3d(vlabels=["A", "B", "C", "D"])23822383TESTS::23842385sage: m = matrix([[0,0,0],[0,1,1],[1,0,1],[1,1,0]]).transpose()2386sage: p = LatticePolytope(m, compute_vertices=True)2387sage: p.plot3d()2388"""2389dim = self.dim()2390amb_dim = self.ambient_dim()2391if dim > 3:2392raise ValueError, "%d-dimensional polytopes can not be plotted in 3D!" % self.dim()2393elif amb_dim > 3:2394return self._sublattice_polytope.plot3d(2395show_facets, facet_opacity, facet_color,2396facet_colors,2397show_edges, edge_thickness, edge_color,2398show_vertices, vertex_size, vertex_color,2399show_points, point_size, point_color,2400show_vindices, vindex_color,2401vlabels,2402show_pindices, pindex_color,2403index_shift)2404elif dim == 3:2405vertices = self.vertices().columns(copy=False)2406if show_points or show_pindices:2407points = self.points().columns(copy=False)[self.nvertices():]2408else:2409vertices = [vector(ZZ, list(self.vertex(i))+[0]*(3-amb_dim))2410for i in range(self.nvertices())]2411if show_points or show_pindices:2412points = [vector(ZZ, list(self.point(i))+[0]*(3-amb_dim))2413for i in range(self.nvertices(), self.npoints())]2414pplot = 02415if show_facets:2416if dim == 2:2417pplot += IndexFaceSet([self.traverse_boundary()],2418vertices, opacity=facet_opacity, rgbcolor=facet_color)2419elif dim == 3:2420if facet_colors != None:2421for i, f in enumerate(self.facets()):2422pplot += IndexFaceSet([f.traverse_boundary()],2423vertices, opacity=facet_opacity, rgbcolor=facet_colors[i])2424else:2425pplot += IndexFaceSet([f.traverse_boundary() for f in self.facets()],2426vertices, opacity=facet_opacity, rgbcolor=facet_color)2427if show_edges:2428if dim == 1:2429pplot += line3d(vertices, thickness=edge_thickness, rgbcolor=edge_color)2430else:2431for e in self.edges():2432pplot += line3d([vertices[e.vertices()[0]], vertices[e.vertices()[1]]],2433thickness=edge_thickness, rgbcolor=edge_color)2434if show_vertices:2435pplot += point3d(vertices, size=vertex_size, rgbcolor=vertex_color)2436if show_vindices == None:2437show_vindices = show_vertices2438if show_pindices == None:2439show_pindices = show_points2440if show_vindices or show_pindices:2441# Compute the barycenter and shift text of labels away from it2442bc = 1/Integer(len(vertices)) * sum(vertices)2443if show_vindices:2444if vlabels == None:2445vlabels = range(len(vertices))2446for i,v in enumerate(vertices):2447pplot += text3d(vlabels[i], bc+index_shift*(v-bc), rgbcolor=vindex_color)2448if show_points and len(points) > 0:2449pplot += point3d(points, size=point_size, rgbcolor=point_color)2450if show_pindices:2451for i, p in enumerate(points):2452pplot += text3d(i+self.nvertices(), bc+index_shift*(p-bc), rgbcolor=pindex_color)2453return pplot24542455def show3d(self):2456"""2457Show a 3d picture of the polytope with default settings and without axes or frame.24582459See self.plot3d? for more details.24602461EXAMPLES::24622463sage: o = lattice_polytope.octahedron(3)2464sage: o.show3d()2465"""2466self.plot3d().show(axis=False, frame=False)24672468def point(self, i):2469r"""2470Return the i-th point of this polytope, i.e. the i-th column of the2471matrix returned by points().24722473EXAMPLES: First few points are actually vertices::24742475sage: o = lattice_polytope.octahedron(3)2476sage: o.vertices()2477[ 1 0 0 -1 0 0]2478[ 0 1 0 0 -1 0]2479[ 0 0 1 0 0 -1]2480sage: o.point(1)2481(0, 1, 0)24822483The only other point in the octahedron is the origin::24842485sage: o.point(6)2486(0, 0, 0)2487sage: o.points()2488[ 1 0 0 -1 0 0 0]2489[ 0 1 0 0 -1 0 0]2490[ 0 0 1 0 0 -1 0]2491"""2492# Extra checks are made to compensate for a bug in Sage - column accepts any number.2493if i < 0:2494raise ValueError, "polytopes don't have negative points!"2495elif i < self.nvertices():2496return self.vertex(i)2497elif i < self.npoints():2498return self.points().column(i, from_list=True)2499else:2500raise ValueError, "the polytope does not have %d points!" % (i+1)25012502def points(self):2503r"""2504Return all lattice points of this polytope as columns of a matrix.25052506EXAMPLES: The lattice points of the 3-dimensional octahedron and2507its polar cube::25082509sage: o = lattice_polytope.octahedron(3)2510sage: o.points()2511[ 1 0 0 -1 0 0 0]2512[ 0 1 0 0 -1 0 0]2513[ 0 0 1 0 0 -1 0]2514sage: cube = o.polar()2515sage: cube.points()2516[-1 1 -1 1 -1 1 -1 1 -1 -1 -1 -1 -1 0 0 0 0 0 0 0 0 0 1 1 1 1 1]2517[-1 -1 1 1 -1 -1 1 1 -1 0 0 0 1 -1 -1 -1 0 0 0 1 1 1 -1 0 0 0 1]2518[ 1 1 1 1 -1 -1 -1 -1 0 -1 0 1 0 -1 0 1 -1 0 1 -1 0 1 0 -1 0 1 0]25192520Lattice points of a 2-dimensional diamond in a 3-dimensional space::25212522sage: m = matrix(ZZ, [[1, 0, -1, 0],2523... [0, 1, 0, -1],2524... [0, 0, 0, 0]])2525...2526sage: p = LatticePolytope(m)2527sage: p.points()2528[ 1 0 -1 0 0]2529[ 0 1 0 -1 0]2530[ 0 0 0 0 0]25312532We check that points of a zero-dimensional polytope can be computed::25332534sage: p = LatticePolytope(matrix([[1]]))2535sage: p.vertices()2536[1]2537sage: p.points()2538[1]2539"""2540if not hasattr(self, "_points"):2541if self.dim() <= 0:2542self._points = self._vertices2543else:2544self._points = self._embed(read_palp_matrix(2545self.poly_x("p", reduce_dimension=True)))2546self._points.set_immutable()2547return self._points25482549def polar(self):2550r"""2551Return the polar polytope, if this polytope is reflexive.25522553EXAMPLES: The polar polytope to the 3-dimensional octahedron::25542555sage: o = lattice_polytope.octahedron(3)2556sage: cube = o.polar()2557sage: cube2558A polytope polar to An octahedron: 3-dimensional, 8 vertices.25592560The polar polytope "remembers" the original one::25612562sage: cube.polar()2563An octahedron: 3-dimensional, 6 vertices.2564sage: cube.polar().polar() is cube2565True25662567Only reflexive polytopes have polars::25682569sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],2570... [0, 1, 0, 0, -1, 0],2571... [0, 0, 2, 0, 0, -1]])2572...2573sage: p = LatticePolytope(m)2574sage: p.polar()2575Traceback (most recent call last):2576...2577ValueError: The given polytope is not reflexive!2578Polytope: A lattice polytope: 3-dimensional, 6 vertices.2579"""2580if self.is_reflexive():2581return self._polar2582else:2583raise ValueError, ("The given polytope is not reflexive!\n"2584+ "Polytope: %s") % self25852586def poly_x(self, keys, reduce_dimension=False):2587r"""2588Run poly.x with given ``keys`` on vertices of this2589polytope.25902591INPUT:259225932594- ``keys`` - a string of options passed to poly.x. The2595key "f" is added automatically.25962597- ``reduce_dimension`` - (default: False) if ``True`` and this2598polytope is not full-dimensional, poly.x will be called for the2599vertices of this polytope in some basis of the spanned affine space.260026012602OUTPUT: the output of poly.x as a string.26032604EXAMPLES: This call is used for determining if a polytope is2605reflexive or not::26062607sage: o = lattice_polytope.octahedron(3)2608sage: print o.poly_x("e")26098 3 Vertices of P-dual <-> Equations of P2610-1 -1 126111 -1 12612-1 1 126131 1 12614-1 -1 -126151 -1 -12616-1 1 -126171 1 -126182619Since PALP has limits on different parameters determined during2620compilation, the following code is likely to fail, unless you2621change default settings of PALP::26222623sage: BIGO = lattice_polytope.octahedron(7)2624sage: BIGO2625An octahedron: 7-dimensional, 14 vertices.2626sage: BIGO.poly_x("e") # possibly different output depending on your system2627Traceback (most recent call last):2628...2629ValueError: Error executing "poly.x -fe" for the given polytope!2630Polytope: An octahedron: 7-dimensional, 14 vertices.2631Vertices:2632[ 1 0 0 0 0 0 0 -1 0 0 0 0 0 0]2633[ 0 1 0 0 0 0 0 0 -1 0 0 0 0 0]2634[ 0 0 1 0 0 0 0 0 0 -1 0 0 0 0]2635[ 0 0 0 1 0 0 0 0 0 0 -1 0 0 0]2636[ 0 0 0 0 1 0 0 0 0 0 0 -1 0 0]2637[ 0 0 0 0 0 1 0 0 0 0 0 0 -1 0]2638[ 0 0 0 0 0 0 1 0 0 0 0 0 0 -1]2639Output:2640Please increase POLY_Dmax to at least 726412642You cannot call poly.x for polytopes that don't span the space (if you2643could, it would crush anyway)::26442645sage: m = matrix(ZZ, [[1, 0, -1, 0],2646... [0, 1, 0, -1],2647... [0, 0, 0, 0]])2648...2649sage: p = LatticePolytope(m)2650sage: p.poly_x("e")2651Traceback (most recent call last):2652...2653ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!26542655But if you know what you are doing, you can call it for the polytope in2656some basis of the spanned space::26572658sage: print p.poly_x("e", reduce_dimension=True)26594 2 Equations of P2660-1 1 026611 1 22662-1 -1 026631 -1 22664"""2665return self._palp("poly.x -f" + keys, reduce_dimension)26662667def skeleton(self):2668r"""2669Return the graph of the one-skeleton of this polytope.26702671EXAMPLES: We construct the one-skeleton graph for the "diamond"::26722673sage: o = lattice_polytope.octahedron(2)2674sage: g = o.skeleton()2675sage: g2676Graph on 4 vertices2677sage: g.edges()2678[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]2679"""2680try:2681return self._skeleton2682except AttributeError:2683skeleton = Graph()2684skeleton.add_vertices(self.skeleton_points(1))2685for e in self.faces(dim=1):2686points = e.ordered_points()2687for i in range(len(points)-1):2688skeleton.add_edge(points[i], points[i+1])2689self._skeleton = skeleton2690return skeleton26912692def skeleton_points(self, k=1):2693r"""2694Return the increasing list of indices of lattice points in2695k-skeleton of the polytope (k is 1 by default).26962697EXAMPLES: We compute all skeleton points for the cube::26982699sage: o = lattice_polytope.octahedron(3)2700sage: c = o.polar()2701sage: c.skeleton_points()2702[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]27032704The default was 1-skeleton::27052706sage: c.skeleton_points(k=1)2707[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 19, 21, 22, 23, 25, 26]270827090-skeleton just lists all vertices::27102711sage: c.skeleton_points(k=0)2712[0, 1, 2, 3, 4, 5, 6, 7]271327142-skeleton lists all points except for the origin (point #17)::27152716sage: c.skeleton_points(k=2)2717[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26]271827193-skeleton includes all points::27202721sage: c.skeleton_points(k=3)2722[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]27232724It is OK to compute higher dimensional skeletons - you will get the2725list of all points::27262727sage: c.skeleton_points(k=100)2728[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]2729"""2730if k >= self.dim():2731return range(self.npoints())2732skeleton = set([])2733for face in self.faces(dim=k):2734skeleton.update(face.points())2735skeleton = list(skeleton)2736skeleton.sort()2737return skeleton27382739def skeleton_show(self, normal=None):2740r"""Show the graph of one-skeleton of this polytope.2741Works only for polytopes in a 3-dimensional space.27422743INPUT:274427452746- ``normal`` - a 3-dimensional vector (can be given as2747a list), which should be perpendicular to the screen. If not given,2748will be selected randomly (new each time and it may be far from2749"nice").275027512752EXAMPLES: Show a pretty picture of the octahedron::27532754sage: o = lattice_polytope.octahedron(3)2755sage: o.skeleton_show([1,2,4])27562757Does not work for a diamond at the moment::27582759sage: o = lattice_polytope.octahedron(2)2760sage: o.skeleton_show()2761Traceback (most recent call last):2762...2763ValueError: need a polytope in a 3-dimensional space! Got:2764An octahedron: 2-dimensional, 4 vertices.2765"""2766if self.ambient_dim() != 3:2767raise ValueError("need a polytope in a 3-dimensional space! Got:\n%s" % self)2768if normal == None:2769normal = [ZZ.random_element(20),ZZ.random_element(20),ZZ.random_element(20)]2770normal = matrix(QQ,3,1,list(normal))2771projectionm = normal.kernel().basis_matrix()2772positions = dict(enumerate([list(c) for c in (projectionm*self.points()).columns(copy=False)]))2773self.skeleton().show(pos=positions)27742775def traverse_boundary(self):2776r"""2777Return a list of indices of vertices of a 2-dimensional polytope in their boundary order.27782779Needed for plot3d function of polytopes.27802781EXAMPLES:27822783sage: c = lattice_polytope.octahedron(2).polar()2784sage: c.traverse_boundary()2785[0, 1, 3, 2]2786"""2787if self.dim() != 2:2788raise ValueError, "Boundary can be traversed only for 2-polytopes!"2789edges = self.edges()2790l = [0]2791for e in edges:2792if 0 in e.vertices():2793next = e.vertices()[0] if e.vertices()[0] != 0 else e.vertices()[1]2794l.append(next)2795prev = 02796while len(l) < self.nvertices():2797for e in edges:2798if next in e.vertices() and prev not in e.vertices():2799prev = next2800next = e.vertices()[0] if e.vertices()[0] != next else e.vertices()[1]2801l.append(next)2802break2803return l28042805def vertex(self, i):2806r"""2807Return the i-th vertex of this polytope, i.e. the i-th column of2808the matrix returned by vertices().28092810EXAMPLES: Note that numeration starts with zero::28112812sage: o = lattice_polytope.octahedron(3)2813sage: o.vertices()2814[ 1 0 0 -1 0 0]2815[ 0 1 0 0 -1 0]2816[ 0 0 1 0 0 -1]2817sage: o.vertex(3)2818(-1, 0, 0)2819"""2820# The check is added to compensate for a bug in Sage - column works for any numbers2821if i < 0:2822raise ValueError, "polytopes don't have negative vertices!"2823elif i > self.nvertices():2824raise ValueError, "the polytope does not have %d vertices!" % (i+1)2825else:2826return self._vertices.column(i, from_list=True)28272828def vertices(self):2829r"""2830Return vertices of this polytope as columns of a matrix.28312832EXAMPLES: The lattice points of the 3-dimensional octahedron and2833its polar cube::28342835sage: o = lattice_polytope.octahedron(3)2836sage: o.vertices()2837[ 1 0 0 -1 0 0]2838[ 0 1 0 0 -1 0]2839[ 0 0 1 0 0 -1]2840sage: cube = o.polar()2841sage: cube.vertices()2842[-1 1 -1 1 -1 1 -1 1]2843[-1 -1 1 1 -1 -1 1 1]2844[ 1 1 1 1 -1 -1 -1 -1]2845"""2846return self._vertices284728482849def is_NefPartition(x):2850r"""2851Check if ``x`` is a nef-partition.28522853INPUT:28542855- ``x`` -- anything.28562857OUTPUT:28582859- ``True`` if ``x`` is a :class:`nef-partition <NefPartition>` and2860``False`` otherwise.28612862EXAMPLES::28632864sage: from sage.geometry.lattice_polytope import is_NefPartition2865sage: is_NefPartition(1)2866False2867sage: o = lattice_polytope.octahedron(3)2868sage: np = o.nef_partitions()[0]2869sage: np2870Nef-partition {0, 1, 3} U {2, 4, 5}2871sage: is_NefPartition(np)2872True2873"""2874return isinstance(x, NefPartition)287528762877class NefPartition(SageObject,2878collections.Hashable):2879r"""2880Create a nef-partition.28812882INPUT:28832884- ``data`` -- a list of integers, the $i$-th element of this list must be2885the part of the $i$-th vertex of ``Delta_polar`` in this nef-partition;28862887- ``Delta_polar`` -- a :class:`lattice polytope2888<sage.geometry.lattice_polytope.LatticePolytopeClass>`;28892890- ``check`` -- by default the input will be checked for correctness, i.e.2891that ``data`` indeed specify a nef-partition. If you are sure that the2892input is correct, you can speed up construction via ``check=False``2893option.28942895OUTPUT:28962897- a nef-partition of ``Delta_polar``.28982899Let $M$ and $N$ be dual lattices. Let $\Delta \subset M_\RR$ be a reflexive2900polytope with polar $\Delta^\circ \subset N_\RR$. Let $X_\Delta$ be the2901toric variety associated to the normal fan of $\Delta$. A **nef-partition**2902is a decomposition of the vertex set $V$ of $\Delta^\circ$ into a disjoint2903union $V = V_0 \sqcup V_1 \sqcup \dots \sqcup V_{k-1}$ such that divisors2904$E_i = \sum_{v\in V_i} D_v$ are Cartier (here $D_v$ are prime2905torus-invariant Weil divisors corresponding to vertices of $\Delta^\circ$).2906Equivalently, let $\nabla_i \subset N_\RR$ be the convex hull of vertices2907from $V_i$ and the origin. These polytopes form a nef-partition if their2908Minkowski sum $\nabla \subset N_\RR$ is a reflexive polytope.29092910The **dual nef-partition** is formed by polytopes $\Delta_i \subset M_\RR$2911of $E_i$, which give a decomposition of the vertex set of $\nabla^\circ2912\subset M_\RR$ and their Minkowski sum is $\Delta$, i.e. the polar duality2913of reflexive polytopes switches convex hull and Minkowski sum for dual2914nef-partitions:29152916.. MATH::29172918\Delta^\circ2919&=2920\mathrm{Conv} \left(\nabla_0, \nabla_1, \dots, \nabla_{k-1}\right), \\2921\nabla^{\phantom{\circ}}2922&=2923\nabla_0 + \nabla_1 + \dots + \nabla_{k-1}, \\2924&2925\\2926\Delta^{\phantom{\circ}}2927&=2928\Delta_0 + \Delta_1 + \dots + \Delta_{k-1}, \\2929\nabla^\circ2930&=2931\mathrm{Conv} \left(\Delta_0, \Delta_1, \dots, \Delta_{k-1}\right).29322933See Section 4.3.1 in [CK99]_ and references therein for further details, or2934[BN08]_ for a purely combinatorial approach.29352936REFERENCES:29372938.. [BN08]2939Victor V. Batyrev and Benjamin Nill.2940Combinatorial aspects of mirror symmetry.2941In *Integer points in polyhedra --- geometry, number theory,2942representation theory, algebra, optimization, statistics*,2943volume 452 of *Contemp. Math.*, pages 35--66.2944Amer. Math. Soc., Providence, RI, 2008.2945arXiv:math/0703456v2 [math.CO].29462947.. [CK99]2948David A. Cox and Sheldon Katz.2949*Mirror symmetry and algebraic geometry*,2950volume 68 of *Mathematical Surveys and Monographs*.2951American Mathematical Society, Providence, RI, 1999.29522953EXAMPLES:29542955It is very easy to create a nef-partition for the octahedron, since for2956this polytope any decomposition of vertices is a nef-partition. We create a29573-part nef-partition with the 0-th and 1-st vertices belonging to the 0-th2958part (recall that numeration in Sage starts with 0), the 2-nd and 5-th2959vertices belonging to the 1-st part, and 3-rd and 4-th vertices belonging2960to the 2-nd part::29612962sage: o = lattice_polytope.octahedron(3)2963sage: np = NefPartition([0,0,1,2,2,1], o)2964sage: np2965Nef-partition {0, 1} U {2, 5} U {3, 4}29662967The octahedron plays the role of `\Delta^\circ` in the above description::29682969sage: np.Delta_polar() is o2970True29712972The dual nef-partition (corresponding to the "mirror complete2973intersection") gives decomposition of the vertex set of `\nabla^\circ`::29742975sage: np.dual()2976Nef-partition {4, 5, 6} U {1, 3} U {0, 2, 7}2977sage: np.nabla_polar().vertices()2978[ 1 0 0 0 -1 0 -1 1]2979[ 1 0 1 0 -1 -1 0 0]2980[ 0 1 0 -1 0 0 0 0]29812982Of course, `\nabla^\circ` is `\Delta^\circ` from the point of view of the2983dual nef-partition::29842985sage: np.dual().Delta_polar() is np.nabla_polar()2986True2987sage: np.Delta(1).vertices()2988[ 0 0]2989[ 0 0]2990[ 1 -1]2991sage: np.dual().nabla(1).vertices()2992[ 0 0]2993[ 0 0]2994[ 1 -1]29952996Instead of constructing nef-partitions directly, you can request all 2-part2997nef-partitions of a given reflexive polytope (they will be computed using2998``nef.x`` program from PALP)::29993000sage: o.nef_partitions()3001[3002Nef-partition {0, 1, 3} U {2, 4, 5},3003Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),3004Nef-partition {0, 1, 2} U {3, 4, 5},3005Nef-partition {0, 1, 2, 3} U {4, 5},3006Nef-partition {0, 1, 2, 3, 4} U {5} (projection)3007]3008"""30093010def __init__(self, data, Delta_polar, check=True):3011r"""3012See :class:`NefPartition` for documentation.30133014TESTS::30153016sage: o = lattice_polytope.octahedron(3)3017sage: np = o.nef_partitions()[0]3018sage: TestSuite(np).run()3019"""3020if check and not Delta_polar.is_reflexive():3021raise ValueError("nef-partitions can be constructed for reflexive "3022"polytopes ony!")3023self._vertex_to_part = tuple(int(el) for el in data)3024self._nparts = max(self._vertex_to_part) + 13025self._Delta_polar = Delta_polar3026if check and not self.nabla().is_reflexive():3027raise ValueError("%s do not form a nef-partition!" % str(data))30283029def __eq__(self, other):3030r"""3031Compare ``self`` with ``other``.30323033INPUT:30343035- ``other`` -- anything.30363037OUTPUT:30383039- ``True`` if ``other`` is a :class:`nef-partition <NefPartition>`3040equal to ``self``, ``False`` otherwise.30413042.. NOTE::30433044Two nef-partitions are equal if they correspond to equal polytopes3045and their parts are the same, including their order.30463047TESTS::30483049sage: o = lattice_polytope.octahedron(3)3050sage: np = o.nef_partitions()[0]3051sage: np == np3052True3053sage: np == o.nef_partitions()[1]3054False3055sage: np2 = NefPartition(np._vertex_to_part, o)3056sage: np2 is np3057False3058sage: np2 == np3059True3060sage: np == 03061False3062"""3063return (is_NefPartition(other)3064and self._Delta_polar == other._Delta_polar3065and self._vertex_to_part == other._vertex_to_part)30663067def __hash__(self):3068r"""3069Return the hash of ``self``.30703071OUTPUT:30723073- an integer.30743075TESTS::30763077sage: o = lattice_polytope.octahedron(3)3078sage: np = o.nef_partitions()[0]3079sage: hash(np) == hash(np)3080True3081"""3082try:3083return self._hash3084except AttributeError:3085self._hash = hash(self._vertex_to_part) + hash(self._Delta_polar)3086return self._hash30873088def __ne__(self, other):3089r"""3090Compare ``self`` with ``other``.30913092INPUT:30933094- ``other`` -- anything.30953096OUTPUT:30973098- ``False`` if ``other`` is a :class:`nef-partition <NefPartition>`3099equal to ``self``, ``True`` otherwise.31003101.. NOTE::31023103Two nef-partitions are equal if they correspond to equal polytopes3104and their parts are the same, including their order.31053106TESTS::31073108sage: o = lattice_polytope.octahedron(3)3109sage: np = o.nef_partitions()[0]3110sage: np != np3111False3112sage: np != o.nef_partitions()[1]3113True3114sage: np2 = NefPartition(np._vertex_to_part, o)3115sage: np2 is np3116False3117sage: np2 != np3118False3119sage: np != 03120True3121"""3122return not (self == other)31233124def _latex_(self):3125r"""3126Return a LaTeX representation of ``self``.31273128OUTPUT:31293130- a string.31313132TESTS::31333134sage: o = lattice_polytope.octahedron(3)3135sage: np = o.nef_partitions()[0]3136sage: latex(np) # indirect doctest3137\text{Nef-partition } \{0, 1, 3\} \sqcup \{2, 4, 5\}3138"""3139result = r"\text{Nef-partition } "3140for i, part in enumerate(self.parts()):3141if i != 0:3142result += " \sqcup "3143result += r"\{" + ", ".join("%d" % v for v in part) + r"\}"3144try:3145# We may or may not know the type of the partition3146if self._is_product:3147result += r" \text{ (direct product)}"3148if self._is_projection:3149result += r" \text{ (projection)}"3150except AttributeError:3151pass3152return result31533154def _repr_(self):3155r"""3156Return a string representation of ``self``.31573158OUTPUT:31593160- a string.31613162TESTS::31633164sage: o = lattice_polytope.octahedron(3)3165sage: np = o.nef_partitions()[0]3166sage: repr(np) # indirect doctest3167'Nef-partition {0, 1, 3} U {2, 4, 5}'3168"""3169result = "Nef-partition "3170for i, part in enumerate(self.parts()):3171if i != 0:3172result += " U "3173result += "{" + ", ".join("%d" % v for v in part) + "}"3174try:3175# We may or may not know the type of the partition3176if self._is_product:3177result += " (direct product)"3178if self._is_projection:3179result += " (projection)"3180except AttributeError:3181pass3182return result31833184def Delta(self, i=None):3185r"""3186Return the polytope $\Delta$ or $\Delta_i$ corresponding to ``self``.31873188INPUT:31893190- ``i`` -- an integer. If not given, $\Delta$ will be returned.31913192OUTPUT:31933194- a :class:`lattice polytope <LatticePolytopeClass>`.31953196See :class:`nef-partition <NefPartition>` class documentation for3197definitions and notation.31983199EXAMPLES::32003201sage: o = lattice_polytope.octahedron(3)3202sage: np = o.nef_partitions()[0]3203sage: np3204Nef-partition {0, 1, 3} U {2, 4, 5}3205sage: np.Delta().polar() is o3206True3207sage: np.Delta().vertices()3208[-1 1 -1 1 -1 1 -1 1]3209[-1 -1 1 1 -1 -1 1 1]3210[ 1 1 1 1 -1 -1 -1 -1]3211sage: np.Delta(0).vertices()3212[ 1 1 -1 -1]3213[-1 0 -1 0]3214[ 0 0 0 0]3215"""3216if i is None:3217return self._Delta_polar.polar()3218else:3219return self.dual().nabla(i)32203221def Delta_polar(self):3222r"""3223Return the polytope $\Delta^\circ$ corresponding to ``self``.32243225OUTPUT:32263227- a :class:`lattice polytope <LatticePolytopeClass>`.32283229See :class:`nef-partition <NefPartition>` class documentation for3230definitions and notation.32313232EXAMPLE::32333234sage: o = lattice_polytope.octahedron(3)3235sage: np = o.nef_partitions()[0]3236sage: np3237Nef-partition {0, 1, 3} U {2, 4, 5}3238sage: np.Delta_polar() is o3239True3240"""3241return self._Delta_polar32423243def Deltas(self):3244r"""3245Return the polytopes $\Delta_i$ corresponding to ``self``.32463247OUTPUT:32483249- a tuple of :class:`lattice polytopes <LatticePolytopeClass>`.32503251See :class:`nef-partition <NefPartition>` class documentation for3252definitions and notation.32533254EXAMPLES::32553256sage: o = lattice_polytope.octahedron(3)3257sage: np = o.nef_partitions()[0]3258sage: np3259Nef-partition {0, 1, 3} U {2, 4, 5}3260sage: np.Delta().vertices()3261[-1 1 -1 1 -1 1 -1 1]3262[-1 -1 1 1 -1 -1 1 1]3263[ 1 1 1 1 -1 -1 -1 -1]3264sage: [Delta_i.vertices() for Delta_i in np.Deltas()]3265[3266[ 1 1 -1 -1] [ 0 0 0 0]3267[-1 0 -1 0] [ 1 0 0 1]3268[ 0 0 0 0], [ 1 1 -1 -1]3269]3270sage: np.nabla_polar().vertices()3271[ 1 0 1 0 0 -1 0 -1]3272[-1 1 0 0 0 -1 1 0]3273[ 0 1 0 1 -1 0 -1 0]3274"""3275return self.dual().nablas()32763277def dual(self):3278r"""3279Return the dual nef-partition.32803281OUTPUT:32823283- a :class:`nef-partition <NefPartition>`.32843285See the class documentation for the definition.32863287ALGORITHM:32883289See Proposition 3.19 in [BN08]_.32903291EXAMPLES::32923293sage: o = lattice_polytope.octahedron(3)3294sage: np = o.nef_partitions()[0]3295sage: np3296Nef-partition {0, 1, 3} U {2, 4, 5}3297sage: np.dual()3298Nef-partition {0, 2, 5, 7} U {1, 3, 4, 6}3299sage: np.dual().Delta() is np.nabla()3300True3301sage: np.dual().nabla(0) is np.Delta(0)3302True3303"""3304try:3305return self._dual3306except AttributeError:3307# Delta and nabla are interchanged compared to [BN08]_.3308nabla_polar = self.nabla_polar()3309n = nabla_polar.nvertices()3310vertex_to_part = [-1] * n3311for i in range(self._nparts):3312A = nabla_polar.vertices().transpose()*self.nabla(i).vertices()3313for j in range(n):3314if min(A[j]) == -1:3315vertex_to_part[j] = i3316self._dual = NefPartition(vertex_to_part, nabla_polar)3317self._dual._dual = self3318self._dual._nabla = self.Delta() # For vertex order consistency3319return self._dual33203321def hodge_numbers(self):3322r"""3323Return Hodge numbers corresponding to ``self``.33243325OUTPUT:33263327- a tuple of integers (produced by ``nef.x`` program from PALP).33283329EXAMPLES:33303331Currently, you need to request Hodge numbers when you compute3332nef-partitions::33333334sage: o = lattice_polytope.octahedron(5)3335sage: np = o.nef_partitions()[0] # long time (4s on sage.math, 2011)3336sage: np.hodge_numbers() # long time3337Traceback (most recent call last):3338...3339NotImplementedError: use nef_partitions(hodge_numbers=True)!3340sage: np = o.nef_partitions(hodge_numbers=True)[0] # long time (13s on sage.math, 2011)3341sage: np.hodge_numbers() # long time3342(19, 19)3343"""3344try:3345return self._hodge_numbers3346except AttributeError:3347self._Delta_polar._compute_hodge_numbers()3348return self._hodge_numbers33493350def nabla(self, i=None):3351r"""3352Return the polytope $\nabla$ or $\nabla_i$ corresponding to ``self``.33533354INPUT:33553356- ``i`` -- an integer. If not given, $\nabla$ will be returned.33573358OUTPUT:33593360- a :class:`lattice polytope <LatticePolytopeClass>`.33613362See :class:`nef-partition <NefPartition>` class documentation for3363definitions and notation.33643365EXAMPLES::33663367sage: o = lattice_polytope.octahedron(3)3368sage: np = o.nef_partitions()[0]3369sage: np3370Nef-partition {0, 1, 3} U {2, 4, 5}3371sage: np.Delta_polar().vertices()3372[ 1 0 0 -1 0 0]3373[ 0 1 0 0 -1 0]3374[ 0 0 1 0 0 -1]3375sage: np.nabla(0).vertices()3376[ 1 0 -1]3377[ 0 1 0]3378[ 0 0 0]3379sage: np.nabla().vertices()3380[ 1 1 1 0 0 -1 -1 -1]3381[ 0 -1 0 1 1 0 -1 0]3382[ 1 0 -1 1 -1 1 0 -1]3383"""3384if i is None:3385try:3386return self._nabla3387except AttributeError:3388self._nabla = LatticePolytope(reduce(minkowski_sum,3389(nabla.vertices().columns() for nabla in self.nablas())))3390return self._nabla3391else:3392return self.nablas()[i]33933394def nabla_polar(self):3395r"""3396Return the polytope $\nabla^\circ$ corresponding to ``self``.33973398OUTPUT:33993400- a :class:`lattice polytope <LatticePolytopeClass>`.34013402See :class:`nef-partition <NefPartition>` class documentation for3403definitions and notation.34043405EXAMPLES::34063407sage: o = lattice_polytope.octahedron(3)3408sage: np = o.nef_partitions()[0]3409sage: np3410Nef-partition {0, 1, 3} U {2, 4, 5}3411sage: np.nabla_polar().vertices()3412[ 1 0 1 0 0 -1 0 -1]3413[-1 1 0 0 0 -1 1 0]3414[ 0 1 0 1 -1 0 -1 0]3415sage: np.nabla_polar() is np.dual().Delta_polar()3416True3417"""3418return self.nabla().polar()34193420def nablas(self):3421r"""3422Return the polytopes $\nabla_i$ corresponding to ``self``.34233424OUTPUT:34253426- a tuple of :class:`lattice polytopes <LatticePolytopeClass>`.34273428See :class:`nef-partition <NefPartition>` class documentation for3429definitions and notation.34303431EXAMPLES::34323433sage: o = lattice_polytope.octahedron(3)3434sage: np = o.nef_partitions()[0]3435sage: np3436Nef-partition {0, 1, 3} U {2, 4, 5}3437sage: np.Delta_polar().vertices()3438[ 1 0 0 -1 0 0]3439[ 0 1 0 0 -1 0]3440[ 0 0 1 0 0 -1]3441sage: [nabla_i.vertices() for nabla_i in np.nablas()]3442[3443[ 1 0 -1] [ 0 0 0]3444[ 0 1 0] [ 0 -1 0]3445[ 0 0 0], [ 1 0 -1]3446]3447"""3448try:3449return self._nablas3450except AttributeError:3451Delta_polar = self._Delta_polar3452origin = [[0] * Delta_polar.dim()]3453self._nablas = tuple(LatticePolytope(3454[Delta_polar.vertex(j) for j in part] + origin)3455for part in self.parts())3456return self._nablas34573458def nparts(self):3459r"""3460Return the number of parts in ``self``.34613462OUTPUT:34633464- an integer.34653466EXAMPLES::34673468sage: o = lattice_polytope.octahedron(3)3469sage: np = o.nef_partitions()[0]3470sage: np3471Nef-partition {0, 1, 3} U {2, 4, 5}3472sage: np.nparts()347323474"""3475return self._nparts34763477def part(self, i):3478r"""3479Return the ``i``-th part of ``self``.34803481INPUT:34823483- ``i`` -- an integer.34843485OUTPUT:34863487- a tuple of integers, indices of vertices of $\Delta^\circ$ belonging3488to $V_i$.34893490See :class:`nef-partition <NefPartition>` class documentation for3491definitions and notation.34923493EXAMPLES::34943495sage: o = lattice_polytope.octahedron(3)3496sage: np = o.nef_partitions()[0]3497sage: np3498Nef-partition {0, 1, 3} U {2, 4, 5}3499sage: np.part(0)3500(0, 1, 3)3501"""3502return self.parts()[i]35033504def parts(self):3505r"""3506Return all parts of ``self``.35073508OUTPUT:35093510- a tuple of tuples of integers. The $i$-th tuple contains indices of3511vertices of $\Delta^\circ$ belonging to $V_i$.35123513See :class:`nef-partition <NefPartition>` class documentation for3514definitions and notation.35153516EXAMPLES::35173518sage: o = lattice_polytope.octahedron(3)3519sage: np = o.nef_partitions()[0]3520sage: np3521Nef-partition {0, 1, 3} U {2, 4, 5}3522sage: np.parts()3523((0, 1, 3), (2, 4, 5))3524"""3525try:3526return self._parts3527except AttributeError:3528parts = []3529for part in range(self._nparts):3530parts.append([])3531for vertex, part in enumerate(self._vertex_to_part):3532parts[part].append(vertex)3533self._parts = tuple(tuple(part) for part in parts)3534return self._parts35353536def part_of(self, i):3537r"""3538Return the index of the part containing the ``i``-th vertex.35393540INPUT:35413542- ``i`` -- an integer.35433544OUTPUT:35453546- an integer $j$ such that the ``i``-th vertex of $\Delta^\circ$3547belongs to $V_j$.35483549See :class:`nef-partition <NefPartition>` class documentation for3550definitions and notation.35513552EXAMPLES::35533554sage: o = lattice_polytope.octahedron(3)3555sage: np = o.nef_partitions()[0]3556sage: np3557Nef-partition {0, 1, 3} U {2, 4, 5}3558sage: np.part_of(3)355903560sage: np.part_of(2)356113562"""3563return self._vertex_to_part[i]35643565def part_of_point(self, i):3566r"""3567Return the index of the part containing the ``i``-th point.35683569INPUT:35703571- ``i`` -- an integer.35723573OUTPUT:35743575- an integer `j` such that the ``i``-th point of `\Delta^\circ`3576belongs to `\nabla_j`.35773578.. NOTE::35793580Since a nef-partition induces a partition on the set of boundary3581lattice points of `\Delta^\circ`, the value of `j` is well-defined3582for all `i` but the one that corresponds to the origin, in which3583case this method will raise a ``ValueError`` exception. (The origin3584always belongs to all `\nabla_j`.)35853586See :class:`nef-partition <NefPartition>` class documentation for3587definitions and notation.35883589EXAMPLES:35903591We consider a relatively complicated reflexive polytope #2252 (easily3592accessible in Sage as ``ReflexivePolytope(3, 2252)``, we create it here3593explicitly to avoid loading the whole database)::35943595sage: p = LatticePolytope([(1,0,0), (0,1,0), (0,0,1), (0,1,-1),3596... (0,-1,1), (-1,1,0), (0,-1,-1), (-1,-1,0), (-1,-1,2)])3597sage: np = p.nef_partitions()[0]3598sage: np3599Nef-partition {1, 2, 5, 7, 8} U {0, 3, 4, 6}3600sage: p.nvertices()360193602sage: p.npoints()36031536043605We see that the polytope has 6 more points in addition to vertices. One3606of them is the origin::36073608sage: p.origin()3609143610sage: np.part_of_point(14)3611Traceback (most recent call last):3612...3613ValueError: the origin belongs to all parts!36143615But the remaining 5 are partitioned by ``np``::36163617sage: [n for n in range(p.npoints())3618... if p.origin() != n and np.part_of_point(n) == 0]3619[1, 2, 5, 7, 8, 9, 11, 13]3620sage: [n for n in range(p.npoints())3621... if p.origin() != n and np.part_of_point(n) == 1]3622[0, 3, 4, 6, 10, 12]3623"""3624try:3625ptp = self._point_to_part3626except AttributeError:3627ptp = [-1] * self._Delta_polar.npoints()3628for v, part in enumerate(self._vertex_to_part):3629ptp[v] = part3630self._point_to_part = ptp3631if ptp[i] > 0:3632return ptp[i]3633if i == self._Delta_polar.origin():3634raise ValueError("the origin belongs to all parts!")3635point = self._Delta_polar.point(i)3636for part, nabla in enumerate(self.nablas()):3637if min(nabla.distances(point)) >= 0:3638ptp[i] = part3639break3640return ptp[i]364136423643class _PolytopeFace(SageObject):3644r"""3645_PolytopeFace(polytope, vertices, facets)36463647Construct a polytope face.36483649POLYTOPE FACES SHOULD NOT BE CONSTRUCTED OUTSIDE OF LATTICE3650POLYTOPES!36513652INPUT:365336543655- ``polytope`` - a polytope whose face is being3656constructed.36573658- ``vertices`` - a sequence of indices of generating3659vertices.36603661- ``facets`` - a sequence of indices of facets3662containing this face.3663"""3664def __init__(self, polytope, vertices, facets):3665r"""3666Construct a face.36673668TESTS::36693670sage: o = lattice_polytope.octahedron(2)3671sage: o.faces()3672[3673[[0], [1], [2], [3]],3674[[0, 3], [2, 3], [0, 1], [1, 2]]3675]3676"""3677self._polytope = polytope3678self._vertices = vertices3679self._facets = facets36803681def __reduce__(self):3682r"""3683Reduction function. Does not store data that can be relatively fast3684recomputed.36853686TESTS::36873688sage: o = lattice_polytope.octahedron(2)3689sage: f = o.facets()[0]3690sage: fl = loads(f.dumps())3691sage: f.vertices() == fl.vertices()3692True3693sage: f.facets() == fl.facets()3694True3695"""3696state = self.__dict__.copy()3697state.pop('_polytope')3698state.pop('_vertices')3699state.pop('_facets')3700if state.has_key('_points'):3701state['_npoints'] = len(state.pop('_points'))3702if state.has_key('_interior_points'):3703state['_ninterior_points'] = len(state.pop('_interior_points'))3704state.pop('_boundary_points')3705# Reference to the polytope is not pickled - the polytope will restore it3706return (_PolytopeFace, (None, self._vertices, self._facets), state)37073708def _repr_(self):3709r"""3710Return a string representation of this face.37113712TESTS::37133714sage: o = lattice_polytope.octahedron(3)3715sage: f = o.facets()[0]3716sage: f._repr_()3717'[0, 1, 5]'3718"""3719return repr(self._vertices)37203721def boundary_points(self):3722r"""3723Return a sequence of indices of boundary lattice points of this3724face.37253726EXAMPLES: Boundary lattice points of one of the facets of the37273-dimensional cube::37283729sage: o = lattice_polytope.octahedron(3)3730sage: cube = o.polar()3731sage: face = cube.facets()[0]3732sage: face.boundary_points()3733[0, 2, 4, 6, 8, 9, 11, 12]3734"""3735try:3736return self._boundary_points3737except AttributeError:3738self._polytope._face_split_points(self)3739return self._boundary_points37403741def facets(self):3742r"""3743Return a sequence of indices of facets containing this face.37443745EXAMPLES: Facets containing one of the edges of the 3-dimensional3746octahedron::37473748sage: o = lattice_polytope.octahedron(3)3749sage: edge = o.faces(dim=1)[0]3750sage: edge.facets()3751[0, 1]37523753Thus ``edge`` is the intersection of facets 0 and 1::37543755sage: edge3756[1, 5]3757sage: o.facets()[0]3758[0, 1, 5]3759sage: o.facets()[1]3760[1, 3, 5]3761"""3762return self._facets37633764def interior_points(self):3765r"""3766Return a sequence of indices of interior lattice points of this3767face.37683769EXAMPLES: Interior lattice points of one of the facets of the37703-dimensional cube::37713772sage: o = lattice_polytope.octahedron(3)3773sage: cube = o.polar()3774sage: face = cube.facets()[0]3775sage: face.interior_points()3776[10]3777"""3778try:3779return self._interior_points3780except AttributeError:3781self._polytope._face_split_points(self)3782return self._interior_points37833784def nboundary_points(self):3785r"""3786Return the number of boundary lattice points of this face.37873788EXAMPLES: The number of boundary lattice points of one of the3789facets of the 3-dimensional cube::37903791sage: o = lattice_polytope.octahedron(3)3792sage: cube = o.polar()3793sage: face = cube.facets()[0]3794sage: face.nboundary_points()379583796"""3797return self.npoints() - self.ninterior_points()37983799def nfacets(self):3800r"""3801Return the number of facets containing this face.38023803EXAMPLES: The number of facets containing one of the edges of the38043-dimensional octahedron::38053806sage: o = lattice_polytope.octahedron(3)3807sage: edge = o.faces(dim=1)[0]3808sage: edge.nfacets()380923810"""3811return len(self._facets)38123813def ninterior_points(self):3814r"""3815Return the number of interior lattice points of this face.38163817EXAMPLES: The number of interior lattice points of one of the3818facets of the 3-dimensional cube::38193820sage: o = lattice_polytope.octahedron(3)3821sage: cube = o.polar()3822sage: face = cube.facets()[0]3823sage: face.ninterior_points()382413825"""3826try:3827return self._ninterior_points3828except AttributeError:3829return len(self.interior_points())38303831def npoints(self):3832r"""3833Return the number of lattice points of this face.38343835EXAMPLES: The number of lattice points of one of the facets of the38363-dimensional cube::38373838sage: o = lattice_polytope.octahedron(3)3839sage: cube = o.polar()3840sage: face = cube.facets()[0]3841sage: face.npoints()384293843"""3844try:3845return self._npoints3846except AttributeError:3847return len(self.points())38483849def nvertices(self):3850r"""3851Return the number of vertices generating this face.38523853EXAMPLES: The number of vertices generating one of the facets of3854the 3-dimensional cube::38553856sage: o = lattice_polytope.octahedron(3)3857sage: cube = o.polar()3858sage: face = cube.facets()[0]3859sage: face.nvertices()386043861"""3862return len(self._vertices)38633864def ordered_points(self):3865r"""3866Return the list of indices of lattice points on the edge in their3867geometric order, from one vertex to other.38683869Works only for edges, i.e. faces generated by exactly two3870vertices.38713872EXAMPLE: We find all points along an edge of the cube::38733874sage: o = lattice_polytope.octahedron(3)3875sage: c = o.polar()3876sage: e = c.edges()[0]3877sage: e.vertices()3878[0, 1]3879sage: e.ordered_points()3880[0, 15, 1]3881"""3882if len(self.vertices()) != 2:3883raise ValueError, "Order of points is defined for edges only!"3884pcol = self._polytope.points().columns(copy=False)3885start = pcol[self.vertices()[0]]3886end = pcol[self.vertices()[1]]3887primitive = end - start3888primitive = primitive * (1/integral_length(primitive.list()))3889result = [self.vertices()[0]]3890start = start + primitive3891while start != end:3892for i in self.points():3893if start == pcol[i]:3894result.append(i)3895break3896start = start + primitive3897result.append(self.vertices()[1])3898return result38993900def points(self):3901r"""3902Return a sequence of indices of lattice points of this face.39033904EXAMPLES: The lattice points of one of the facets of the39053-dimensional cube::39063907sage: o = lattice_polytope.octahedron(3)3908sage: cube = o.polar()3909sage: face = cube.facets()[0]3910sage: face.points()3911[0, 2, 4, 6, 8, 9, 10, 11, 12]3912"""3913try:3914return self._points3915except AttributeError:3916self._polytope._face_compute_points(self)3917return self._points39183919def traverse_boundary(self):3920r"""3921Return a list of indices of vertices of a 2-face in their boundary3922order.39233924Needed for plot3d function of polytopes.39253926EXAMPLES::39273928sage: c = lattice_polytope.octahedron(3).polar()3929sage: f = c.facets()[0]3930sage: f.vertices()3931[0, 2, 4, 6]3932sage: f.traverse_boundary()3933[0, 4, 6, 2]3934"""3935if self not in self._polytope.faces(dim=2):3936raise ValueError, "Boundary can be traversed only for 2-faces!"3937edges = [e for e in self._polytope.edges() if e.vertices()[0] in self.vertices() and3938e.vertices()[1] in self.vertices()]3939start = self.vertices()[0]3940l = [start]3941for e in edges:3942if start in e.vertices():3943next = e.vertices()[0] if e.vertices()[0] != start else e.vertices()[1]3944l.append(next)3945prev = start3946while len(l) < self.nvertices():3947for e in edges:3948if next in e.vertices() and prev not in e.vertices():3949prev = next3950next = e.vertices()[0] if e.vertices()[0] != next else e.vertices()[1]3951l.append(next)3952break3953return l39543955def vertices(self):3956r"""3957Return a sequence of indices of vertices generating this face.39583959EXAMPLES: The vertices generating one of the facets of the39603-dimensional cube::39613962sage: o = lattice_polytope.octahedron(3)3963sage: cube = o.polar()3964sage: face = cube.facets()[0]3965sage: face.vertices()3966[0, 2, 4, 6]3967"""3968return self._vertices396939703971def _create_octahedron(dim):3972r"""3973Create an octahedron of the given dimension.39743975Since we know that we are passing vertices, we suppress their3976computation.39773978TESTS::39793980sage: o = lattice_polytope._create_octahedron(3)3981sage: o.vertices()3982[ 1 0 0 -1 0 0]3983[ 0 1 0 0 -1 0]3984[ 0 0 1 0 0 -1]3985"""3986m = matrix(ZZ, dim, 2*dim)3987for i in range(dim):3988m[i,i] = 13989m[i,dim+i] = -13990return LatticePolytope(m, "An octahedron", compute_vertices=False, copy_vertices=False)39913992_octahedrons = dict() # Dictionary for storing created octahedrons39933994_palp_dimension = None39953996def _palp(command, polytopes, reduce_dimension=False):3997r"""3998Run ``command`` on vertices of given3999``polytopes``.40004001Returns the name of the file containing the output of4002``command``. You should delete it after using.40034004.. note::40054006PALP cannot be called for polytopes that do not span the ambient space.4007If you specify ``reduce_dimension=True`` argument, PALP will be4008called for vertices of this polytope in some basis of the affine space4009it spans.40104011TESTS::40124013sage: o = lattice_polytope.octahedron(3)4014sage: result_name = lattice_polytope._palp("poly.x -f", [o])4015sage: f = open(result_name)4016sage: f.readlines()4017['M:7 6 N:27 8 Pic:17 Cor:0\n']4018sage: f.close()4019sage: os.remove(result_name)40204021sage: m = matrix(ZZ, [[1, 0, -1, 0],4022... [0, 1, 0, -1],4023... [0, 0, 0, 0]])4024...4025sage: p = LatticePolytope(m)4026sage: lattice_polytope._palp("poly.x -f", [p])4027Traceback (most recent call last):4028ValueError: Cannot run PALP for a 2-dimensional polytope in a 3-dimensional space!40294030sage: result_name = lattice_polytope._palp("poly.x -f", [p], reduce_dimension=True)4031sage: f = open(result_name)4032sage: f.readlines()4033['M:5 4 F:4\n']4034sage: f.close()4035sage: os.remove(result_name)4036"""4037if _palp_dimension is not None:4038dot = command.find(".")4039command = command[:dot] + "-%dd" % _palp_dimension + command[dot:]4040input_file_name = tmp_filename()4041input_file = open(input_file_name, "w")4042for p in polytopes:4043if p.dim() == 0:4044raise ValueError(("Cannot run \"%s\" for the zero-dimensional "4045+ "polytope!\nPolytope: %s") % (command, p))4046if p.dim() < p.ambient_dim() and not reduce_dimension:4047raise ValueError(("Cannot run PALP for a %d-dimensional polytope " +4048"in a %d-dimensional space!") % (p.dim(), p.ambient_dim()))4049write_palp_matrix(p._pullback(p._vertices), input_file)4050input_file.close()4051output_file_name = tmp_filename()4052c = "%s <%s >%s" % (command, input_file_name, output_file_name)4053p = subprocess.Popen(c, shell=True, bufsize=2048,4054stdin=subprocess.PIPE,4055stdout=subprocess.PIPE,4056stderr=subprocess.PIPE,4057close_fds=True)4058stdin, stdout, stderr = (p.stdin, p.stdout, p.stderr)4059err = stderr.read()4060if len(err) > 0:4061raise RuntimeError, ("Error executing \"%s\" for a polytope sequence!"4062+ "\nOutput:\n%s") % (command, err)4063os.remove(input_file_name)4064try:4065p.terminate()4066except OSError:4067pass4068return output_file_name40694070def _read_nef_x_partitions(data):4071r"""4072Read all nef-partitions for one polytope from a string or an open4073file.40744075``data`` should be an output of nef.x.40764077Returns the sequence of nef-partitions. Each nef-partition is given4078as a sequence of integers.40794080If there are no nef-partitions, returns the empty sequence. If the4081string is empty or EOF is reached, raises ValueError.40824083TESTS::40844085sage: o = lattice_polytope.octahedron(3)4086sage: s = o.nef_x("-N -p")4087sage: print s # random4088M:27 8 N:7 6 codim=2 #part=54089P:0 V:2 4 5 0sec 0cpu4090P:2 V:3 4 5 0sec 0cpu4091P:3 V:4 5 0sec 0cpu4092np=3 d:1 p:1 0sec 0cpu4093sage: lattice_polytope._read_nef_x_partitions(s)4094[[2, 4, 5], [3, 4, 5], [4, 5]]4095"""4096if isinstance(data, str):4097f = StringIO.StringIO(data)4098partitions = _read_nef_x_partitions(f)4099f.close()4100return partitions4101line = data.readline()4102if line == "":4103raise ValueError, "Empty file!"4104partitions = []4105while len(line) > 0 and line.find("np=") == -1:4106if line.find("V:") == -1:4107line = data.readline()4108continue4109start = line.find("V:") + 24110end = line.find(" ", start) # Find DOUBLE space4111partitions.append(Sequence(line[start:end].split(),int))4112line = data.readline()4113# Compare the number of found partitions with np in data.4114start = line.find("np=")4115if start != -1:4116start += 34117end = line.find(" ", start)4118np = int(line[start:end])4119if False and np != len(partitions):4120raise ValueError, ("Found %d partitions, expected %d!" %4121(len(partitions), np))4122else:4123raise ValueError, "Wrong data format, cannot find \"np=\"!"4124return partitions41254126def _read_poly_x_incidences(data, dim):4127r"""4128Convert incidence data from binary numbers to sequences.41294130INPUT:413141324133- ``data`` - an opened file with incidence4134information. The first line will be skipped, each consecutive line4135contains incidence information for all faces of one dimension, the4136first word of each line is a comment and is dropped.41374138- ``dim`` - dimension of the polytope.413941404141OUTPUT: a sequence F, such that F[d][i] is a sequence of vertices4142or facets corresponding to the i-th d-dimensional face.41434144TESTS::41454146sage: o = lattice_polytope.octahedron(2)4147sage: result_name = lattice_polytope._palp("poly.x -fi", [o])4148sage: f = open(result_name)4149sage: f.readlines()4150['Incidences as binary numbers [F-vector=(4 4)]:\n', "v[d][i]: sum_j Incidence(i'th dim-d-face, j-th vertex) x 2^j\n", 'v[0]: 1000 0001 0100 0010 \n', 'v[1]: 1001 1100 0011 0110 \n', "f[d][i]: sum_j Incidence(i'th dim-d-face, j-th facet) x 2^j\n", 'f[0]: 0011 0101 1010 1100 \n', 'f[1]: 0001 0010 0100 1000 \n']4151sage: f.close()4152sage: f = open(result_name)4153sage: l = f.readline()4154sage: lattice_polytope._read_poly_x_incidences(f, 2)4155[[[3], [0], [2], [1]], [[0, 3], [2, 3], [0, 1], [1, 2]]]4156sage: f.close()4157sage: os.remove(result_name)4158"""4159data.readline()4160lines = [data.readline().split() for i in range(dim)]4161if len(lines) != dim:4162raise ValueError, "Not enough data!"4163n = len(lines[0][1]) # Number of vertices or facets4164result = []4165for line in lines:4166line.pop(0)4167subr = []4168for e in line:4169f = Sequence([j for j in range(n) if e[n-1-j] == '1'], int, check=False)4170f.set_immutable()4171subr.append(f)4172result.append(subr)4173return result41744175def all_cached_data(polytopes):4176r"""4177Compute all cached data for all given ``polytopes`` and4178their polars.41794180This functions does it MUCH faster than member functions of4181``LatticePolytope`` during the first run. So it is recommended to4182use this functions if you work with big sets of data. None of the4183polytopes in the given sequence should be constructed as the polar4184polytope to another one.41854186INPUT: a sequence of lattice polytopes.41874188EXAMPLES: This function has no output, it is just a fast way to4189work with long sequences of polytopes. Of course, you can use short4190sequences as well::41914192sage: o = lattice_polytope.octahedron(3)4193sage: lattice_polytope.all_cached_data([o])4194sage: o.faces()4195[4196[[0], [1], [2], [3], [4], [5]],4197[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],4198[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]4199]42004201However, you cannot use it for polytopes that are constructed as4202polar polytopes of others::42034204sage: lattice_polytope.all_cached_data([o.polar()])4205Traceback (most recent call last):4206...4207ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!4208"""4209all_polars(polytopes)4210all_points(polytopes)4211all_faces(polytopes)4212reflexive = [p for p in polytopes if p.is_reflexive()]4213all_nef_partitions(reflexive)4214polar = [p.polar() for p in reflexive]4215all_points(polar)4216all_nef_partitions(polar)4217for p in polytopes + polar:4218for d_faces in p.faces():4219for face in d_faces:4220face.boundary_points()42214222def all_faces(polytopes):4223r"""4224Compute faces for all given ``polytopes``.42254226This functions does it MUCH faster than member functions of4227``LatticePolytope`` during the first run. So it is recommended to4228use this functions if you work with big sets of data.42294230INPUT: a sequence of lattice polytopes.42314232EXAMPLES: This function has no output, it is just a fast way to4233work with long sequences of polytopes. Of course, you can use short4234sequences as well::42354236sage: o = lattice_polytope.octahedron(3)4237sage: lattice_polytope.all_faces([o])4238sage: o.faces()4239[4240[[0], [1], [2], [3], [4], [5]],4241[[1, 5], [0, 5], [0, 1], [3, 5], [1, 3], [4, 5], [0, 4], [3, 4], [1, 2], [0, 2], [2, 3], [2, 4]],4242[[0, 1, 5], [1, 3, 5], [0, 4, 5], [3, 4, 5], [0, 1, 2], [1, 2, 3], [0, 2, 4], [2, 3, 4]]4243]42444245However, you cannot use it for polytopes that are constructed as4246polar polytopes of others::42474248sage: lattice_polytope.all_faces([o.polar()])4249Traceback (most recent call last):4250...4251ValueError: Cannot read face structure for a polytope constructed as polar, use _compute_faces!4252"""4253result_name = _palp("poly.x -fi", polytopes, reduce_dimension=True)4254result = open(result_name)4255for p in polytopes:4256p._read_faces(result)4257result.close()4258os.remove(result_name)42594260def all_nef_partitions(polytopes, keep_symmetric=False):4261r"""4262Compute nef-partitions for all given ``polytopes``.42634264This functions does it MUCH faster than member functions of4265``LatticePolytope`` during the first run. So it is recommended to4266use this functions if you work with big sets of data.42674268Note: member function ``is_reflexive`` will be called4269separately for each polytope. It is strictly recommended to call4270``all_polars`` on the sequence of4271``polytopes`` before using this function.42724273INPUT: a sequence of lattice polytopes.42744275EXAMPLES: This function has no output, it is just a fast way to4276work with long sequences of polytopes. Of course, you can use short4277sequences as well::42784279sage: o = lattice_polytope.octahedron(3)4280sage: lattice_polytope.all_nef_partitions([o])4281sage: o.nef_partitions()4282[4283Nef-partition {0, 1, 3} U {2, 4, 5},4284Nef-partition {0, 1, 3, 4} U {2, 5} (direct product),4285Nef-partition {0, 1, 2} U {3, 4, 5},4286Nef-partition {0, 1, 2, 3} U {4, 5},4287Nef-partition {0, 1, 2, 3, 4} U {5} (projection)4288]42894290You cannot use this function for non-reflexive polytopes::42914292sage: m = matrix(ZZ, [[1, 0, 0, -1, 0, 0],4293... [0, 1, 0, 0, -1, 0],4294... [0, 0, 2, 0, 0, -1]])4295...4296sage: p = LatticePolytope(m)4297sage: lattice_polytope.all_nef_partitions([o, p])4298Traceback (most recent call last):4299...4300ValueError: The given polytope is not reflexive!4301Polytope: A lattice polytope: 3-dimensional, 6 vertices.4302"""4303keys = "-N -V -D -P -p"4304if keep_symmetric:4305keys += " -s"4306result_name = _palp("nef.x -f " + keys, polytopes)4307result = open(result_name)4308for p in polytopes:4309if not p.is_reflexive():4310raise ValueError, "The given polytope is not reflexive!\nPolytope: %s" % p4311p._read_nef_partitions(result)4312p._nef_partitions_s = keep_symmetric4313result.close()4314os.remove(result_name)43154316def all_points(polytopes):4317r"""4318Compute lattice points for all given ``polytopes``.43194320This functions does it MUCH faster than member functions of4321``LatticePolytope`` during the first run. So it is recommended to4322use this functions if you work with big sets of data.43234324INPUT: a sequence of lattice polytopes.43254326EXAMPLES: This function has no output, it is just a fast way to4327work with long sequences of polytopes. Of course, you can use short4328sequences as well::43294330sage: o = lattice_polytope.octahedron(3)4331sage: lattice_polytope.all_points([o])4332sage: o.points()4333[ 1 0 0 -1 0 0 0]4334[ 0 1 0 0 -1 0 0]4335[ 0 0 1 0 0 -1 0]4336"""4337result_name = _palp("poly.x -fp", polytopes, reduce_dimension=True)4338result = open(result_name)4339for p in polytopes:4340p._points = p._embed(read_palp_matrix(result))4341if p._points.nrows() == 0:4342raise RuntimeError, ("Cannot read points of a polytope!"4343+"\nPolytope: %s" % p)4344result.close()4345os.remove(result_name)43464347def all_polars(polytopes):4348r"""4349Compute polar polytopes for all reflexive and equations of facets4350for all non-reflexive ``polytopes``.43514352``all_facet_equations`` and ``all_polars`` are synonyms.43534354This functions does it MUCH faster than member functions of4355``LatticePolytope`` during the first run. So it is recommended to4356use this functions if you work with big sets of data.43574358INPUT: a sequence of lattice polytopes.43594360EXAMPLES: This function has no output, it is just a fast way to4361work with long sequences of polytopes. Of course, you can use short4362sequences as well::43634364sage: o = lattice_polytope.octahedron(3)4365sage: lattice_polytope.all_polars([o])4366sage: o.polar()4367A polytope polar to An octahedron: 3-dimensional, 8 vertices.4368"""4369result_name = _palp("poly.x -fe", polytopes)4370result = open(result_name)4371for p in polytopes:4372p._read_equations(result)4373result.close()4374os.remove(result_name)43754376# Synonym for the above function4377all_facet_equations = all_polars437843794380_always_use_files = True438143824383def always_use_files(new_state=None):4384r"""4385Set or get the way of using PALP for lattice polytopes.43864387INPUT:43884389- ``new_state`` - (default:None) if specified, must be ``True`` or ``False``.43904391OUTPUT: The current state of using PALP. If ``True``, files are used4392for all calls to PALP, otherwise pipes are used for single polytopes.4393While the latter may have some advantage in speed, the first method4394is more reliable when working with large outputs. The initial state4395is ``True``.43964397EXAMPLES::43984399sage: lattice_polytope.always_use_files()4400True4401sage: p = LatticePolytope(matrix([1, 20]))4402sage: p.npoints()44032044044405Now let's use pipes instead of files::44064407sage: lattice_polytope.always_use_files(False)4408False4409sage: p = LatticePolytope(matrix([1, 20]))4410sage: p.npoints()4411204412"""4413global _always_use_files4414if new_state != None:4415_always_use_files = new_state4416return _always_use_files441744184419def convex_hull(points):4420r"""4421Compute the convex hull of the given points.44224423.. note::44244425``points`` might not span the space. Also, it fails for large4426numbers of vertices in dimensions 4 or greater44274428INPUT:442944304431- ``points`` - a list that can be converted into4432vectors of the same dimension over ZZ.443344344435OUTPUT: list of vertices of the convex hull of the given points (as4436vectors).44374438EXAMPLES: Let's compute the convex hull of several points on a line4439in the plane::44404441sage: lattice_polytope.convex_hull([[1,2],[3,4],[5,6],[7,8]])4442[(1, 2), (7, 8)]4443"""4444if len(points) == 0:4445return []4446vpoints = []4447for p in points:4448v = vector(ZZ,p)4449if not v in vpoints:4450vpoints.append(v)4451p0 = vpoints[0]4452vpoints = [p-p0 for p in vpoints]4453N = ZZ**p0.degree()4454H = N.submodule(vpoints)4455if H.rank() == 0:4456return [p0]4457elif H.rank() == N.rank():4458vpoints = LatticePolytope(matrix(ZZ, vpoints).transpose(), compute_vertices=True).vertices().columns(copy=False)4459else:4460H_points = [H.coordinates(p) for p in vpoints]4461H_polytope = LatticePolytope(matrix(ZZ, H_points).transpose(), compute_vertices=True)4462vpoints = (H.basis_matrix().transpose() * H_polytope.vertices()).columns(copy=False)4463vpoints = [p+p0 for p in vpoints]4464return vpoints44654466def filter_polytopes(f, polytopes, subseq=None, print_numbers=False):4467r"""4468Use the function ``f`` to filter polytopes in a list.44694470INPUT:447144724473- ``f`` - filtering function, it must take one4474argument, a lattice polytope, and return True or False.44754476- ``polytopes`` - list of polytopes.44774478- ``subseq`` - (default: None) list of integers. If it4479is specified, only polytopes with these numbers will be4480considered.44814482- ``print_numbers`` - (default: False) if True, the4483number of the current polytope will be printed on the screen before4484calling ``f``.448544864487OUTPUT: a list of integers -- numbers of polytopes in the given4488list, that satisfy the given condition (i.e. function4489``f`` returns True) and are elements of subseq, if it4490is given.44914492EXAMPLES: Consider a sequence of octahedrons::44934494sage: polytopes = Sequence([lattice_polytope.octahedron(n) for n in range(2, 7)], cr=True)4495sage: polytopes4496[4497An octahedron: 2-dimensional, 4 vertices.,4498An octahedron: 3-dimensional, 6 vertices.,4499An octahedron: 4-dimensional, 8 vertices.,4500An octahedron: 5-dimensional, 10 vertices.,4501An octahedron: 6-dimensional, 12 vertices.4502]45034504This filters octahedrons of dimension at least 4::45054506sage: lattice_polytope.filter_polytopes(lambda p: p.dim() >= 4, polytopes)4507[2, 3, 4]45084509For long tests you can see the current progress::45104511sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, print_numbers=True)45120451314514245153451644517[3, 4]45184519Here we consider only some of the polytopes::45204521sage: lattice_polytope.filter_polytopes(lambda p: p.nvertices() >= 10, polytopes, [2, 3, 4], print_numbers=True)4522245233452444525[3, 4]4526"""4527if subseq == []:4528return []4529elif subseq == None:4530subseq = range(len(polytopes))4531result = []4532for n in subseq:4533if print_numbers:4534print n4535os.sys.stdout.flush()4536if f(polytopes[n]):4537result.append(n)4538return result453945404541def integral_length(v):4542"""4543Compute the integral length of a given rational vector.45444545INPUT:45464547- ``v`` - any object which can be converted to a list of rationals45484549OUTPUT: Rational number ``r`` such that ``v = r u``, where ``u`` is the4550primitive integral vector in the direction of ``v``.45514552EXAMPLES::45534554sage: lattice_polytope.integral_length([1, 2, 4])455514556sage: lattice_polytope.integral_length([2, 2, 4])455724558sage: lattice_polytope.integral_length([2/3, 2, 4])45592/34560"""4561data = [QQ(e) for e in list(v)]4562ns = [e.numerator() for e in data]4563ds = [e.denominator() for e in data]4564return gcd(ns)/lcm(ds)456545664567def minkowski_sum(points1, points2):4568r"""4569Compute the Minkowski sum of two convex polytopes.45704571.. note::45724573Polytopes might not be of maximal dimension.45744575INPUT:457645774578- ``points1, points2`` - lists of objects that can be4579converted into vectors of the same dimension, treated as vertices4580of two polytopes.458145824583OUTPUT: list of vertices of the Minkowski sum, given as vectors.45844585EXAMPLES: Let's compute the Minkowski sum of two line segments::45864587sage: lattice_polytope.minkowski_sum([[1,0],[-1,0]],[[0,1],[0,-1]])4588[(1, 1), (1, -1), (-1, 1), (-1, -1)]4589"""4590points1 = [vector(p) for p in points1]4591points2 = [vector(p) for p in points2]4592points = []4593for p1 in points1:4594for p2 in points2:4595points.append(p1+p2)4596return convex_hull(points)459745984599def octahedron(dim):4600r"""4601Return an octahedron of the given dimension.46024603EXAMPLES: Here are 3- and 4-dimensional octahedrons::46044605sage: o = lattice_polytope.octahedron(3)4606sage: o4607An octahedron: 3-dimensional, 6 vertices.4608sage: o.vertices()4609[ 1 0 0 -1 0 0]4610[ 0 1 0 0 -1 0]4611[ 0 0 1 0 0 -1]4612sage: o = lattice_polytope.octahedron(4)4613sage: o4614An octahedron: 4-dimensional, 8 vertices.4615sage: o.vertices()4616[ 1 0 0 0 -1 0 0 0]4617[ 0 1 0 0 0 -1 0 0]4618[ 0 0 1 0 0 0 -1 0]4619[ 0 0 0 1 0 0 0 -1]46204621There exists only one octahedron of each dimension::46224623sage: o is lattice_polytope.octahedron(4)4624True4625"""4626if _octahedrons.has_key(dim):4627return _octahedrons[dim]4628else:4629_octahedrons[dim] = _create_octahedron(dim)4630return _octahedrons[dim]463146324633def positive_integer_relations(points):4634r"""4635Return relations between given points.46364637INPUT:463846394640- ``points`` - lattice points given as columns of a4641matrix464246434644OUTPUT: matrix of relations between given points with non-negative4645integer coefficients46464647EXAMPLES: This is a 3-dimensional reflexive polytope::46484649sage: m = matrix(ZZ,[[1, 0, -1, 0, -1],4650... [0, 1, -1, 0, 0],4651... [0, 0, 0, 1, -1]])4652...4653sage: p = LatticePolytope(m)4654sage: p.points()4655[ 1 0 -1 0 -1 0]4656[ 0 1 -1 0 0 0]4657[ 0 0 0 1 -1 0]46584659We can compute linear relations between its points in the following4660way::46614662sage: p.points().transpose().kernel().echelonized_basis_matrix()4663[ 1 0 0 1 1 0]4664[ 0 1 1 -1 -1 0]4665[ 0 0 0 0 0 1]46664667However, the above relations may contain negative and rational4668numbers. This function transforms them in such a way, that all4669coefficients are non-negative integers::46704671sage: lattice_polytope.positive_integer_relations(p.points())4672[1 0 0 1 1 0]4673[1 1 1 0 0 0]4674[0 0 0 0 0 1]4675sage: lattice_polytope.positive_integer_relations(ReflexivePolytope(2,1).vertices())4676[2 1 1]4677"""4678points = points.transpose().base_extend(QQ)4679relations = points.kernel().echelonized_basis_matrix()4680nonpivots = relations.nonpivots()4681nonpivot_relations = relations.matrix_from_columns(nonpivots)4682n_nonpivots = len(nonpivots)4683n = nonpivot_relations.nrows()4684a = matrix(QQ,n_nonpivots,n_nonpivots)4685for i in range(n_nonpivots):4686a[i, i] = -14687a = nonpivot_relations.stack(a).transpose()4688a = sage_matrix_to_maxima(a)4689maxima.load("simplex")46904691new_relations = []4692for i in range(n_nonpivots):4693# Find a non-negative linear combination of relations,4694# such that all components are non-negative and the i-th one is 14695b = [0]*i + [1] + [0]*(n_nonpivots - i - 1)4696c = [0]*(n+i) + [1] + [0]*(n_nonpivots - i - 1)4697x = maxima.linear_program(a, b, c)4698if x.str() == r'?Problem\not\feasible\!':4699raise ValueError, "cannot find required relations"4700x = x.sage()[0][:n]4701v = relations.linear_combination_of_rows(x)4702new_relations.append(v)47034704relations = relations.stack(matrix(QQ, new_relations))4705# Use the new relation to remove negative entries in non-pivot columns4706for i in range(n_nonpivots):4707for j in range(n):4708coef = relations[j,nonpivots[i]]4709if coef < 0:4710relations.add_multiple_of_row(j, n+i, -coef)4711# Get a new basis4712relations = relations.matrix_from_rows(relations.transpose().pivots())4713# Switch to integers4714for i in range(n):4715relations.rescale_row(i, 1/integral_length(relations[i]))4716return relations.change_ring(ZZ)471747184719def projective_space(dim):4720r"""4721Return a simplex of the given dimension, corresponding to4722`P_{dim}`.47234724EXAMPLES: We construct 3- and 4-dimensional simplexes::47254726sage: p = lattice_polytope.projective_space(3)4727sage: p4728A simplex: 3-dimensional, 4 vertices.4729sage: p.vertices()4730[ 1 0 0 -1]4731[ 0 1 0 -1]4732[ 0 0 1 -1]4733sage: p = lattice_polytope.projective_space(4)4734sage: p4735A simplex: 4-dimensional, 5 vertices.4736sage: p.vertices()4737[ 1 0 0 0 -1]4738[ 0 1 0 0 -1]4739[ 0 0 1 0 -1]4740[ 0 0 0 1 -1]4741"""4742m = matrix(ZZ, dim, dim+1)4743for i in range(dim):4744m[i,i] = 14745m[i,dim] = -14746return LatticePolytope(m, "A simplex", copy_vertices=False)474747484749def read_all_polytopes(file_name, desc=None):4750r"""4751Read all polytopes from the given file.47524753INPUT:475447554756- ``file_name`` - the name of a file with VERTICES of4757polytopes47584759- ``desc`` - a string, that will be used for creating4760polytope descriptions. By default it will be set to 'A lattice4761polytope #%d from "filename"' + and will be used as ``desc %4762n`` where ``n`` is the number of the polytope in4763the file (*STARTING WITH ZERO*).476447654766OUTPUT: a sequence of polytopes47674768EXAMPLES: We use poly.x to compute polar polytopes of 2- and47693-octahedrons and read them::47704771sage: o2 = lattice_polytope.octahedron(2)4772sage: o3 = lattice_polytope.octahedron(3)4773sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])4774sage: f = open(result_name)4775sage: f.readlines()4776['4 2 Vertices of P-dual <-> Equations of P\n', ' -1 1\n', ' 1 1\n', ' -1 -1\n', ' 1 -1\n', '8 3 Vertices of P-dual <-> Equations of P\n', ' -1 -1 1\n', ' 1 -1 1\n', ' -1 1 1\n', ' 1 1 1\n', ' -1 -1 -1\n', ' 1 -1 -1\n', ' -1 1 -1\n', ' 1 1 -1\n']4777sage: f.close()4778sage: lattice_polytope.read_all_polytopes(result_name, desc="Polytope from file %d")4779[4780Polytope from file 0: 2-dimensional, 4 vertices.,4781Polytope from file 1: 3-dimensional, 8 vertices.4782]4783sage: os.remove(result_name)4784"""4785if desc == None:4786desc = r'A lattice polytope #%d from "'+file_name+'"'4787polytopes = Sequence([], LatticePolytope, cr=True)4788f = open(file_name)4789n = 04790m = read_palp_matrix(f)4791while m.nrows() != 0:4792polytopes.append(LatticePolytope(m,4793desc=(desc % n), compute_vertices=False, copy_vertices=False))4794n += 14795m = read_palp_matrix(f)4796f.close()4797return polytopes479847994800def read_palp_matrix(data):4801r"""4802Read and return an integer matrix from a string or an opened file.48034804First input line must start with two integers m and n, the number4805of rows and columns of the matrix. The rest of the first line is4806ignored. The next m lines must contain n numbers each.48074808If m>n, returns the transposed matrix. If the string is empty or EOF4809is reached, returns the empty matrix, constructed by4810``matrix()``.48114812EXAMPLES::48134814sage: lattice_polytope.read_palp_matrix("2 3 comment \n 1 2 3 \n 4 5 6")4815[1 2 3]4816[4 5 6]4817sage: lattice_polytope.read_palp_matrix("3 2 Will be transposed \n 1 2 \n 3 4 \n 5 6")4818[1 3 5]4819[2 4 6]4820"""4821if isinstance(data,str):4822f = StringIO.StringIO(data)4823mat = read_palp_matrix(f)4824f.close()4825return mat4826# If data is not a string, try to treat it as a file.4827line = data.readline()4828if line == "":4829return matrix()4830line = line.split()4831m = int(line[0])4832n = int(line[1])4833seq = []4834for i in range(m):4835seq.extend(int(el) for el in data.readline().split())4836mat = matrix(ZZ,m,n,seq)4837if m <= n:4838return mat4839else:4840return mat.transpose()484148424843def sage_matrix_to_maxima(m):4844r"""4845Convert a Sage matrix to the string representation of Maxima.48464847EXAMPLE::48484849sage: m = matrix(ZZ,2)4850sage: lattice_polytope.sage_matrix_to_maxima(m)4851matrix([0,0],[0,0])4852"""4853return maxima("matrix("+",".join(str(v.list()) for v in m.rows())+")")485448554856def set_palp_dimension(d):4857r"""4858Set the dimension for PALP calls to ``d``.48594860INPUT:48614862- ``d`` -- an integer from the list [4,5,6,11] or ``None``.48634864OUTPUT:48654866- none.48674868PALP has many hard-coded limits, which must be specified before4869compilation, one of them is dimension. Sage includes several versions with4870different dimension settings (which may also affect other limits and enable4871certain features of PALP). You can change the version which will be used by4872calling this function. Such a change is not done automatically for each4873polytope based on its dimension, since depending on what you are doing it4874may be necessary to use dimensions higher than that of the input polytope.48754876EXAMPLES:48774878By default, it is not possible to create the 7-dimensional simplex with4879vertices at the basis of the 8-dimensional space::48804881sage: LatticePolytope(identity_matrix(8))4882Traceback (most recent call last):4883...4884ValueError: Error executing "poly.x -fv" for the given polytope!4885Polytope: A lattice polytope: 7-dimensional, 8 vertices.4886Vertices:4887[ 0 -1 -1 -1 -1 -1 -1 -1]4888[ 0 1 0 0 0 0 0 0]4889[ 0 0 1 0 0 0 0 0]4890[ 0 0 0 1 0 0 0 0]4891[ 0 0 0 0 1 0 0 0]4892[ 0 0 0 0 0 1 0 0]4893[ 0 0 0 0 0 0 1 0]4894Output:4895Please increase POLY_Dmax to at least 748964897However, we can work with this polytope by changing PALP dimension to 11::48984899sage: lattice_polytope.set_palp_dimension(11)4900sage: LatticePolytope(identity_matrix(8))4901A lattice polytope: 7-dimensional, 8 vertices.49024903Let's go back to default settings::49044905sage: lattice_polytope.set_palp_dimension(None)4906"""4907global _palp_dimension4908_palp_dimension = d490949104911def skip_palp_matrix(data, n=1):4912r"""4913Skip matrix data in a file.49144915INPUT:491649174918- ``data`` - opened file with blocks of matrix data in4919the following format: A block consisting of m+1 lines has the4920number m as the first element of its first line.49214922- ``n`` - (default: 1) integer, specifies how many4923blocks should be skipped492449254926If EOF is reached during the process, raises ValueError exception.49274928EXAMPLE: We create a file with vertices of the square and the cube,4929but read only the second set::49304931sage: o2 = lattice_polytope.octahedron(2)4932sage: o3 = lattice_polytope.octahedron(3)4933sage: result_name = lattice_polytope._palp("poly.x -fe", [o2, o3])4934sage: f = open(result_name)4935sage: f.readlines()4936['4 2 Vertices of P-dual <-> Equations of P\n',4937' -1 1\n',4938' 1 1\n',4939' -1 -1\n',4940' 1 -1\n',4941'8 3 Vertices of P-dual <-> Equations of P\n',4942' -1 -1 1\n',4943' 1 -1 1\n',4944' -1 1 1\n',4945' 1 1 1\n',4946' -1 -1 -1\n',4947' 1 -1 -1\n',4948' -1 1 -1\n',4949' 1 1 -1\n']4950sage: f.close()4951sage: f = open(result_name)4952sage: lattice_polytope.skip_palp_matrix(f)4953sage: lattice_polytope.read_palp_matrix(f)4954[-1 1 -1 1 -1 1 -1 1]4955[-1 -1 1 1 -1 -1 1 1]4956[ 1 1 1 1 -1 -1 -1 -1]4957sage: f.close()4958sage: os.remove(result_name)4959"""4960for i in range(n):4961line = data.readline()4962if line == "":4963raise ValueError, "There are not enough data to skip!"4964for j in range(int(line.split()[0])):4965if data.readline() == "":4966raise ValueError, "There are not enough data to skip!"496749684969def write_palp_matrix(m, ofile=None, comment="", format=None):4970r"""4971Write a matrix into a file.49724973INPUT:497449754976- ``m`` - a matrix over integers.49774978- ``ofile`` - a file opened for writing (default:4979stdout)49804981- ``comment`` - a string (default: empty) see output4982description49834984- ``format`` - a format string used to print matrix entries.498549864987OUTPUT: First line: number_of_rows number_of_columns comment4988Next number_of_rows lines: rows of the matrix.49894990EXAMPLES: This functions is used for writing polytope vertices in4991PALP format::49924993sage: o = lattice_polytope.octahedron(3)4994sage: lattice_polytope.write_palp_matrix(o.vertices(), comment="3D Octahedron")49953 6 3D Octahedron49961 0 0 -1 0 049970 1 0 0 -1 049980 0 1 0 0 -14999sage: lattice_polytope.write_palp_matrix(o.vertices(), format="%4d")50003 650011 0 0 -1 0 050020 1 0 0 -1 050030 0 1 0 0 -15004"""5005if format == None:5006n = max(len(str(m[i,j]))5007for i in range(m.nrows()) for j in range(m.ncols()))5008format = "%" + str(n) + "d"5009s = "%d %d %s\n" % (m.nrows(),m.ncols(),comment)5010if ofile is None:5011print s,5012else:5013ofile.write(s)5014for i in range(m.nrows()):5015s = " ".join(format % m[i,j] for j in range(m.ncols()))+"\n"5016if ofile is None:5017print s,5018else:5019ofile.write(s)502050215022