Path: blob/master/src/sage/geometry/polyhedron/ppl_lattice_polytope.py
8817 views
"""1Fast Lattice Polytopes using PPL.23The :func:`LatticePolytope_PPL` class is a thin wrapper around PPL4polyhedra. Its main purpose is to be fast to construct, at the cost of5being much less full-featured than the usual polyhedra. This makes it6possible to iterate with it over the list of all 473800776 reflexive7polytopes in 4 dimensions.89.. NOTE::1011For general lattice polyhedra you should use12:func:`~sage.geometry.polyhedon.constructor.Polyhedron` with13`base_ring=ZZ`.1415The class derives from the PPL :class:`sage.libs.ppl.C_Polyhedron`16class, so you can work with the underlying generator and constraint17objects. However, integral points are generally represented by18`\ZZ`-vectors. In the following, we always use *generator* to refer19the PPL generator objects and *vertex* (or integral point) for the20corresponding `\ZZ`-vector.2122EXAMPLES::2324sage: vertices = [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1), (-9, -6, -1, -1)]25sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL26sage: P = LatticePolytope_PPL(vertices); P27A 4-dimensional lattice polytope in ZZ^4 with 5 vertices28sage: P.integral_points()29((-9, -6, -1, -1), (-3, -2, 0, 0), (-2, -1, 0, 0), (-1, -1, 0, 0),30(-1, 0, 0, 0), (0, 0, 0, 0), (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0))31sage: P.integral_points_not_interior_to_facets()32((-9, -6, -1, -1), (-3, -2, 0, 0), (0, 0, 0, 0), (1, 0, 0, 0),33(0, 1, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0))3435Fibrations of the lattice polytopes are defined as lattice36sub-polytopes and give rise to fibrations of toric varieties for37suitable fan refinements. We can compute them using38:meth:`~LatticePolytope_PPL.fibration_generator` ::3940sage: F = P.fibration_generator(2).next()41sage: F.vertices()42((1, 0, 0, 0), (0, 1, 0, 0), (-3, -2, 0, 0))4344Finally, we can compute automorphisms and identify fibrations that45only differ by a lattice automorphism::4647sage: square = LatticePolytope_PPL((-1,-1),(-1,1),(1,-1),(1,1))48sage: fibers = [ f.vertices() for f in square.fibration_generator(1) ]; fibers49[((1, 0), (-1, 0)), ((0, 1), (0, -1)), ((-1, -1), (1, 1)), ((-1, 1), (1, -1))]50sage: square.pointsets_mod_automorphism(fibers)51(frozenset([(0, 1), (0, -1)]), frozenset([(1, 1), (-1, -1)]))5253AUTHORS:5455- Volker Braun: initial version, 201256"""5758########################################################################59# Copyright (C) 2012 Volker Braun <[email protected]>60#61# Distributed under the terms of the GNU General Public License (GPL)62#63# http://www.gnu.org/licenses/64########################################################################6566import copy67from sage.rings.integer import GCD_list68from sage.rings.integer_ring import ZZ69from sage.misc.all import union, cached_method, prod, uniq70from sage.modules.all import (71vector, zero_vector )72from sage.matrix.constructor import (73matrix, column_matrix, diagonal_matrix )74from sage.libs.ppl import (75C_Polyhedron, Linear_Expression, Variable,76point, ray, line,77Generator, Generator_System, Generator_System_iterator )78from sage.libs.ppl import (79C_Polyhedron, Linear_Expression, Variable,80point, ray, line, Generator, Generator_System,81Constraint_System,82Poly_Con_Relation )8384858687########################################################################88def _class_for_LatticePolytope(dim):89"""90Return the appropriate class in the given dimension.9192Helper function for :func:`LatticePolytope_PPL`. You should not93have to use this function manually.9495INPUT:9697- ``dim`` -- integer. The ambient space dimenson.9899OUTPUT:100101The appropriate class for the lattice polytope.102103EXAMPLES::104105sage: from sage.geometry.polyhedron.ppl_lattice_polytope import _class_for_LatticePolytope106sage: _class_for_LatticePolytope(2)107<class 'sage.geometry.polyhedron.ppl_lattice_polygon.LatticePolygon_PPL_class'>108sage: _class_for_LatticePolytope(3)109<class 'sage.geometry.polyhedron.ppl_lattice_polytope.LatticePolytope_PPL_class'>110"""111if dim <= 2:112from sage.geometry.polyhedron.ppl_lattice_polygon import LatticePolygon_PPL_class113return LatticePolygon_PPL_class114return LatticePolytope_PPL_class115116117########################################################################118def LatticePolytope_PPL(*args):119"""120Construct a new instance of the PPL-based lattice polytope class.121122EXAMPLES::123124sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL125sage: LatticePolytope_PPL((0,0),(1,0),(0,1))126A 2-dimensional lattice polytope in ZZ^2 with 3 vertices127128sage: from sage.libs.ppl import point, Generator_System, C_Polyhedron, Linear_Expression, Variable129sage: p = point(Linear_Expression([2,3],0)); p130point(2/1, 3/1)131sage: LatticePolytope_PPL(p)132A 0-dimensional lattice polytope in ZZ^2 with 1 vertex133134sage: P = C_Polyhedron(Generator_System(p)); P135A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 point136sage: LatticePolytope_PPL(P)137A 0-dimensional lattice polytope in ZZ^2 with 1 vertex138139A ``TypeError`` is raised if the arguments do not specify a lattice polytope::140141sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL142sage: LatticePolytope_PPL((0,0),(1/2,1))143Traceback (most recent call last):144...145TypeError: no conversion of this rational to integer146147sage: from sage.libs.ppl import point, Generator_System, C_Polyhedron, Linear_Expression, Variable148sage: p = point(Linear_Expression([2,3],0), 5); p149point(2/5, 3/5)150sage: LatticePolytope_PPL(p)151Traceback (most recent call last):152...153TypeError: generator is not a lattice polytope generator154155sage: P = C_Polyhedron(Generator_System(p)); P156A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 point157sage: LatticePolytope_PPL(P)158Traceback (most recent call last):159...160TypeError: polyhedron has non-integral generators161"""162polytope_class = LatticePolytope_PPL_class163if len(args)==1 and isinstance(args[0], C_Polyhedron):164polyhedron = args[0]165polytope_class = _class_for_LatticePolytope(polyhedron.space_dimension())166if not all(p.is_point() and p.divisor().is_one() for p in polyhedron.generators()):167raise TypeError('polyhedron has non-integral generators')168return polytope_class(polyhedron)169if len(args)==1 \170and isinstance(args[0], (list, tuple)) \171and isinstance(args[0][0], (list,tuple)):172vertices = args[0]173else:174vertices = args175gs = Generator_System()176for v in vertices:177if isinstance(v, Generator):178if (not v.is_point()) or (not v.divisor().is_one()):179raise TypeError('generator is not a lattice polytope generator')180gs.insert(v)181else:182gs.insert(point(Linear_Expression(v, 0)))183if not gs.empty():184dim = Generator_System_iterator(gs).next().space_dimension()185polytope_class = _class_for_LatticePolytope(dim)186return polytope_class(gs)187188189190########################################################################191class LatticePolytope_PPL_class(C_Polyhedron):192"""193The lattice polytope class.194195You should use :func:LatticePolytope_PPL` to construct instances.196197EXAMPLES::198199sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL200sage: LatticePolytope_PPL((0,0),(1,0),(0,1))201A 2-dimensional lattice polytope in ZZ^2 with 3 vertices202"""203204def _repr_(self):205"""206Return the string representation207208OUTPUT:209210A string.211212EXAMPLES::213214sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL215sage: P = LatticePolytope_PPL((0,0),(1,0),(0,1))216sage: P217A 2-dimensional lattice polytope in ZZ^2 with 3 vertices218sage: P._repr_()219'A 2-dimensional lattice polytope in ZZ^2 with 3 vertices'220221sage: LatticePolytope_PPL()222The empty lattice polytope in ZZ^0223"""224desc = ''225if self.n_vertices()==0:226desc += 'The empty lattice polytope'227else:228desc += 'A ' + repr(self.affine_dimension()) + '-dimensional lattice polytope'229desc += ' in ZZ^' + repr(self.space_dimension())230231if self.n_vertices()>0:232desc += ' with '233desc += repr(self.n_vertices())234if self.n_vertices()==1: desc += ' vertex'235else: desc += ' vertices'236return desc237238239240def is_bounded(self):241"""242Return whether the lattice polytope is compact.243244OUTPUT:245246Always ``True``, since polytopes are by definition compact.247248EXAMPLES::249250sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL251sage: LatticePolytope_PPL((0,0),(1,0),(0,1)).is_bounded()252True253"""254return True255256@cached_method257def n_vertices(self):258"""259Return the number of vertices.260261OUTPUT:262263An integer, the number of vertices.264265EXAMPLES::266267sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL268sage: LatticePolytope_PPL((0,0,0), (1,0,0), (0,1,0)).n_vertices()2693270"""271return len(self.minimized_generators())272273@cached_method274def is_simplex(self):275r"""276Return whether the polyhedron is a simplex.277278OUTPUT:279280Boolean, whether the polyhedron is a simplex (possibly of281strictly smaller dimension than the ambient space).282283EXAMPLES::284285sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL286sage: LatticePolytope_PPL((0,0,0), (1,0,0), (0,1,0)).is_simplex()287True288"""289return self.affine_dimension()+1==self.n_vertices()290291@cached_method292def bounding_box(self):293r"""294Return the coordinates of a rectangular box containing the non-empty polytope.295296OUTPUT:297298A pair of tuples ``(box_min, box_max)`` where ``box_min`` are299the coordinates of a point bounding the coordinates of the300polytope from below and ``box_max`` bounds the coordinates301from above.302303EXAMPLES::304305sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL306sage: LatticePolytope_PPL((0,0),(1,0),(0,1)).bounding_box()307((0, 0), (1, 1))308"""309box_min = []310box_max = []311if self.is_empty():312raise ValueError('empty polytope is not allowed')313for i in range(0, self.space_dimension()):314x = Variable(i)315coords = [ v.coefficient(x) for v in self.generators() ]316max_coord = max(coords)317min_coord = min(coords)318box_max.append(max_coord)319box_min.append(min_coord)320return (tuple(box_min), tuple(box_max))321322@cached_method323def n_integral_points(self):324"""325Return the number of integral points.326327OUTPUT:328329Integer. The number of integral points contained in the330lattice polytope.331332EXAMPLES::333334sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL335sage: LatticePolytope_PPL((0,0),(1,0),(0,1)).n_integral_points()3363337"""338if self.is_empty():339return tuple()340box_min, box_max = self.bounding_box()341from sage.geometry.integral_points import rectangular_box_points342return rectangular_box_points(box_min, box_max, self, count_only=True)343344@cached_method345def integral_points(self):346r"""347Return the integral points in the polyhedron.348349Uses the naive algorithm (iterate over a rectangular bounding350box).351352OUTPUT:353354The list of integral points in the polyhedron. If the355polyhedron is not compact, a ``ValueError`` is raised.356357EXAMPLES::358359sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL360sage: LatticePolytope_PPL((-1,-1),(1,0),(1,1),(0,1)).integral_points()361((-1, -1), (0, 0), (0, 1), (1, 0), (1, 1))362363sage: simplex = LatticePolytope_PPL((1,2,3), (2,3,7), (-2,-3,-11))364sage: simplex.integral_points()365((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7))366367The polyhedron need not be full-dimensional::368369sage: simplex = LatticePolytope_PPL((1,2,3,5), (2,3,7,5), (-2,-3,-11,5))370sage: simplex.integral_points()371((-2, -3, -11, 5), (0, 0, -2, 5), (1, 2, 3, 5), (2, 3, 7, 5))372373sage: point = LatticePolytope_PPL((2,3,7))374sage: point.integral_points()375((2, 3, 7),)376377sage: empty = LatticePolytope_PPL()378sage: empty.integral_points()379()380381Here is a simplex where the naive algorithm of running over382all points in a rectangular bounding box no longer works fast383enough::384385sage: v = [(1,0,7,-1), (-2,-2,4,-3), (-1,-1,-1,4), (2,9,0,-5), (-2,-1,5,1)]386sage: simplex = LatticePolytope_PPL(v); simplex387A 4-dimensional lattice polytope in ZZ^4 with 5 vertices388sage: len(simplex.integral_points())38949390391Finally, the 3-d reflexive polytope number 4078::392393sage: v = [(1,0,0), (0,1,0), (0,0,1), (0,0,-1), (0,-2,1),394... (-1,2,-1), (-1,2,-2), (-1,1,-2), (-1,-1,2), (-1,-3,2)]395sage: P = LatticePolytope_PPL(*v)396sage: pts1 = P.integral_points() # Sage's own code397sage: pts2 = LatticePolytope(v).points().columns() # PALP398sage: for p in pts1: p.set_immutable()399sage: for p in pts2: p.set_immutable()400sage: set(pts1) == set(pts2)401True402403sage: timeit('Polyhedron(v).integral_points()') # random output404sage: timeit('LatticePolytope(v).points()') # random output405sage: timeit('LatticePolytope_PPL(*v).integral_points()') # random output406"""407if self.is_empty():408return tuple()409box_min, box_max = self.bounding_box()410from sage.geometry.integral_points import rectangular_box_points411points = rectangular_box_points(box_min, box_max, self)412if not self.n_integral_points.is_in_cache():413self.n_integral_points.set_cache(len(points))414return points415416@cached_method417def _integral_points_saturating(self):418"""419Return the integral points together with information about420which inequalities are saturated.421422See :func:`~sage.geometry.integral_points.rectangular_box_points`.423424OUTPUT:425426A tuple of pairs (one for each integral point) consisting of a427pair ``(point, Hrep)``, where ``point`` is the coordinate428vector of the intgeral point and ``Hrep`` is the set of429indices of the :meth:`minimized_constraints` that are430saturated at the point.431432EXAMPLES::433434sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL435sage: quad = LatticePolytope_PPL((-1,-1),(0,1),(1,0),(1,1))436sage: quad._integral_points_saturating()437(((-1, -1), frozenset([0, 1])),438((0, 0), frozenset([])),439((0, 1), frozenset([0, 3])),440((1, 0), frozenset([1, 2])),441((1, 1), frozenset([2, 3])))442"""443if self.is_empty():444return tuple()445box_min, box_max = self.bounding_box()446from sage.geometry.integral_points import rectangular_box_points447points= rectangular_box_points(box_min, box_max, self, return_saturated=True)448if not self.n_integral_points.is_in_cache():449self.n_integral_points.set_cache(len(points))450if not self.integral_points.is_in_cache():451self.integral_points.set_cache(tuple(p[0] for p in points))452return points453454@cached_method455def integral_points_not_interior_to_facets(self):456"""457Return the integral points not interior to facets458459OUTPUT:460461A tuple whose entries are the coordinate vectors of integral462points not interior to facets (codimension one faces) of the463lattice polytope.464465EXAMPLES::466467sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL468sage: square = LatticePolytope_PPL((-1,-1),(-1,1),(1,-1),(1,1))469sage: square.n_integral_points()4709471sage: square.integral_points_not_interior_to_facets()472((-1, -1), (-1, 1), (0, 0), (1, -1), (1, 1))473"""474n = 1 + self.space_dimension() - self.affine_dimension()475return tuple(p[0] for p in self._integral_points_saturating() if len(p[1])!=n)476477@cached_method478def vertices(self):479r"""480Return the vertices as a tuple of `\ZZ`-vectors.481482OUTPUT:483484A tuple of `\ZZ`-vectors. Each entry is the coordinate vector485of an integral points of the lattice polytope.486487EXAMPLES::488489sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL490sage: p = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0))491sage: p.vertices()492((-9, -6, -1, -1), (0, 0, 0, 1), (0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0))493sage: p.minimized_generators()494Generator_System {point(-9/1, -6/1, -1/1, -1/1), point(0/1, 0/1, 0/1, 1/1),495point(0/1, 0/1, 1/1, 0/1), point(0/1, 1/1, 0/1, 0/1), point(1/1, 0/1, 0/1, 0/1)}496"""497d = self.space_dimension()498v = vector(ZZ, d)499points = []500for g in self.minimized_generators():501for i in range(0,d):502v[i] = g.coefficient(Variable(i))503v_copy = copy.copy(v)504v_copy.set_immutable()505points.append(v_copy)506return tuple(points)507508def vertices_saturating(self, constraint):509"""510Return the vertices saturating the constraint511512INPUT:513514- ``constraint`` -- a constraint (inequality or equation) of515the polytope.516517OUTPUT:518519The tuple of vertices saturating the constraint. The vertices520are returned as `\ZZ`-vectors, as in :meth:`vertices`.521522EXAMPLES::523524sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL525sage: p = LatticePolytope_PPL((0,0),(0,1),(1,0))526sage: ieq = iter(p.constraints()).next(); ieq527x0>=0528sage: p.vertices_saturating(ieq)529((0, 0), (0, 1))530"""531from sage.libs.ppl import C_Polyhedron, Poly_Con_Relation532result = []533for i,v in enumerate(self.minimized_generators()):534v = C_Polyhedron(v)535if v.relation_with(constraint).implies(Poly_Con_Relation.saturates()):536result.append(self.vertices()[i])537return tuple(result)538539@cached_method540def is_full_dimensional(self):541"""542Return whether the lattice polytope is full dimensional.543544OUTPUT:545546Boolean. Whether the :meth:`affine_dimension` equals the547ambient space dimension.548549EXAMPLES::550551sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL552sage: p = LatticePolytope_PPL((0,0),(0,1))553sage: p.is_full_dimensional()554False555sage: q = LatticePolytope_PPL((0,0),(0,1),(1,0))556sage: q.is_full_dimensional()557True558"""559560return self.affine_dimension() == self.space_dimension()561562def fibration_generator(self, dim):563"""564Generate the lattice polytope fibrations.565566For the purposes of this function, a lattice polytope fiber is567a sub-lattice polytope. Projecting the plane spanned by the568subpolytope to a point yields another lattice polytope, the569base of the fibration.570571INPUT:572573- ``dim`` -- integer. The dimension of the lattice polytope574fiber.575576OUTPUT:577578A generator yielding the distinct lattice polytope fibers of579given dimension.580581EXAMPLES::582583sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL584sage: p = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0))585sage: list( p.fibration_generator(2) )586[A 2-dimensional lattice polytope in ZZ^4 with 3 vertices]587"""588assert self.is_full_dimensional()589codim = self.space_dimension() - dim590# "points" are the potential vertices of the fiber. They are591# in the $codim$-skeleton of the polytope, which is contained592# in the points that saturate at least $dim$ equations.593points = [ p for p in self._integral_points_saturating() if len(p[1])>=dim ]594points = sorted(points, key=lambda x:len(x[1]))595596# iterate over point combinations subject to all points being on one facet.597def point_combinations_iterator(n, i0=0, saturated=None):598for i in range(i0, len(points)):599p, ieqs = points[i]600if saturated is None:601saturated_ieqs = ieqs602else:603saturated_ieqs = saturated.intersection(ieqs)604if len(saturated_ieqs)==0:605continue606if n == 1:607yield [i]608else:609for c in point_combinations_iterator(n-1, i+1, saturated_ieqs):610yield [i] + c611612point_lines = [ line(Linear_Expression(p[0].list(),0)) for p in points ]613origin = point()614fibers = set()615gs = Generator_System()616for indices in point_combinations_iterator(dim):617gs.clear()618gs.insert(origin)619for i in indices:620gs.insert(point_lines[i])621plane = C_Polyhedron(gs)622if plane.affine_dimension() != dim:623continue624plane.intersection_assign(self)625if (not self.is_full_dimensional()) and (plane.affine_dimension() != dim):626continue627try:628fiber = LatticePolytope_PPL(plane)629except TypeError: # not a lattice polytope630continue631fiber_vertices = tuple(sorted(fiber.vertices()))632if fiber_vertices not in fibers:633yield fiber634fibers.update([fiber_vertices])635636def pointsets_mod_automorphism(self, pointsets):637"""638Return ``pointsets`` modulo the automorphisms of ``self``.639640INPUT:641642- ``polytopes`` a tuple/list/iterable of subsets of the643integral points of ``self``.644645OUTPUT:646647Representatives of the point sets modulo the648:meth:`lattice_automorphism_group` of ``self``.649650EXAMPLES::651652sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL653sage: square = LatticePolytope_PPL((-1,-1),(-1,1),(1,-1),(1,1))654sage: fibers = [ f.vertices() for f in square.fibration_generator(1) ]655sage: square.pointsets_mod_automorphism(fibers)656(frozenset([(0, 1), (0, -1)]), frozenset([(1, 1), (-1, -1)]))657658sage: cell24 = LatticePolytope_PPL(659... (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1),660... (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1),661... (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0),662... (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1))663sage: fibers = [ f.vertices() for f in cell24.fibration_generator(2) ]664sage: cell24.pointsets_mod_automorphism(fibers) # long time665(frozenset([(1, 0, -1, 0), (-1, 0, 1, 0), (0, -1, -1, 1), (0, 1, 1, -1)]),666frozenset([(-1, 0, 0, 0), (1, 0, 0, 0), (0, 0, 0, 1),667(1, 0, 0, -1), (0, 0, 0, -1), (-1, 0, 0, 1)]))668"""669points = set()670for ps in pointsets:671points.update(ps)672points = tuple(points)673Aut = self.lattice_automorphism_group(points,674point_labels=tuple(range(len(points))))675indexsets = set([ frozenset([points.index(p) for p in ps]) for ps in pointsets ])676orbits = []677while len(indexsets)>0:678idx = indexsets.pop()679orbits.append(frozenset([points[i] for i in idx]))680for g in Aut:681g_idx = frozenset([g(i) for i in idx])682indexsets.difference_update([g_idx])683return tuple(orbits)684685@cached_method686def ambient_space(self):687r"""688Return the ambient space.689690OUTPUT:691692The free module `\ZZ^d`, where `d` is the ambient space693dimension.694695EXAMPLES::696697sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL698sage: point = LatticePolytope_PPL((1,2,3))699sage: point.ambient_space()700Ambient free module of rank 3 over the principal ideal domain Integer Ring701"""702from sage.modules.free_module import FreeModule703return FreeModule(ZZ, self.space_dimension())704705def contains(self, point_coordinates):706r"""707Test whether point is contained in the polytope.708709INPUT:710711- ``point_coordinates`` -- a list/tuple/iterable of rational712numbers. The coordinates of the point.713714OUTPUT:715716Boolean.717718EXAMPLES::719720sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL721sage: line = LatticePolytope_PPL((1,2,3), (-1,-2,-3))722sage: line.contains([0,0,0])723True724sage: line.contains([1,0,0])725False726"""727p = C_Polyhedron(point(Linear_Expression(list(point_coordinates), 1)))728is_included = Poly_Con_Relation.is_included()729for c in self.constraints():730if not p.relation_with(c).implies(is_included):731return False732return True733734@cached_method735def contains_origin(self):736"""737Test whether the polytope contains the origin738739OUTPUT:740741Boolean.742743EXAMPLES::744745sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL746sage: LatticePolytope_PPL((1,2,3), (-1,-2,-3)).contains_origin()747True748sage: LatticePolytope_PPL((1,2,5), (-1,-2,-3)).contains_origin()749False750"""751return self.contains(self.ambient_space().zero())752753@cached_method754def affine_space(self):755r"""756Return the affine space spanned by the polytope.757758OUTPUT:759760The free module `\ZZ^n`, where `n` is the dimension of the761affine space spanned by the points of the polytope.762763EXAMPLES::764765sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL766sage: point = LatticePolytope_PPL((1,2,3))767sage: point.affine_space()768Free module of degree 3 and rank 0 over Integer Ring769Echelon basis matrix:770[]771sage: line = LatticePolytope_PPL((1,1,1), (1,2,3))772sage: line.affine_space()773Free module of degree 3 and rank 1 over Integer Ring774Echelon basis matrix:775[0 1 2]776"""777vertices = self.vertices()778if not self.contains_origin():779v0 = vertices[0]780vertices = [v-v0 for v in vertices]781return self.ambient_space().span(vertices).saturation()782783def affine_lattice_polytope(self):784"""785Return the lattice polytope restricted to786:meth:`affine_space`.787788OUTPUT:789790A new, full-dimensional lattice polytope.791792EXAMPLES::793794sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL795sage: poly_4d = LatticePolytope_PPL((-9,-6,0,0),(0,1,0,0),(1,0,0,0)); poly_4d796A 2-dimensional lattice polytope in ZZ^4 with 3 vertices797sage: poly_4d.space_dimension()7984799sage: poly_2d = poly_4d.affine_lattice_polytope(); poly_2d800A 2-dimensional lattice polytope in ZZ^2 with 3 vertices801sage: poly_2d.space_dimension()8022803"""804V = self.affine_space()805if self.contains_origin():806vertices = [ V.coordinates(v) for v in self.vertices() ]807else:808v0 = vertices[0]809vertices = [ V.coordinates(v-v0) for v in self.vertices() ]810return LatticePolytope_PPL(*vertices)811812@cached_method813def base_projection(self, fiber):814"""815The projection that maps the sub-polytope ``fiber`` to a816single point.817818OUTPUT:819820The quotient module of the ambient space modulo the821:meth:`affine_space` spanned by the fiber.822823EXAMPLES::824825sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL826sage: poly = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0))827sage: fiber = poly.fibration_generator(2).next()828sage: poly.base_projection(fiber)829Finitely generated module V/W over Integer Ring with invariants (0, 0)830"""831return self.ambient_space().quotient(fiber.affine_space())832833@cached_method834def base_projection_matrix(self, fiber):835"""836The projection that maps the sub-polytope ``fiber`` to a837single point.838839OUTPUT:840841An integer matrix that represents the projection to the842base.843844.. SEEALSO::845846The :meth:`base_projection` yields equivalent information,847and is easier to use. However, just returning the matrix848has lower overhead.849850EXAMPLES::851852sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL853sage: poly = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0))854sage: fiber = poly.fibration_generator(2).next()855sage: poly.base_projection_matrix(fiber)856[0 0 1 0]857[0 0 0 1]858859Note that the basis choice in :meth:`base_projection` for the860quotient is usually different::861862sage: proj = poly.base_projection(fiber)863sage: proj_matrix = poly.base_projection_matrix(fiber)864sage: [ proj(p) for p in poly.integral_points() ]865[(-1, -1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (1, 0), (0, 1)]866sage: [ proj_matrix*p for p in poly.integral_points() ]867[(-1, -1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 1), (1, 0)]868"""869return matrix(ZZ, fiber.vertices()).right_kernel_matrix()870871def base_rays(self, fiber, points):872"""873Return the primitive lattice vectors that generate the874direction given by the base projection of points.875876INPUT:877878- ``fiber`` -- a sub-lattice polytope defining the879:meth:`base_projection`.880881- ``points`` -- the points to project to the base.882883OUTPUT:884885A tuple of primitive `\ZZ`-vectors.886887EXAMPLES::888889sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL890sage: poly = LatticePolytope_PPL((-9,-6,-1,-1),(0,0,0,1),(0,0,1,0),(0,1,0,0),(1,0,0,0))891sage: fiber = poly.fibration_generator(2).next()892sage: poly.base_rays(fiber, poly.integral_points_not_interior_to_facets())893((-1, -1), (0, 1), (1, 0))894895sage: p = LatticePolytope_PPL((1,0),(1,2),(-1,0))896sage: f = LatticePolytope_PPL((1,0),(-1,0))897sage: p.base_rays(f, p.integral_points())898((1),)899"""900quo = self.base_projection(fiber)901vertices = []902for p in points:903v = vector(ZZ,quo(p))904if v.is_zero():905continue906d = GCD_list(v.list())907if d>1:908for i in range(0,v.degree()):909v[i] /= d910v.set_immutable()911vertices.append(v)912return tuple(uniq(vertices))913914@cached_method915def has_IP_property(self):916"""917Whether the lattice polytope has the IP property.918919That is, the polytope is full-dimensional and the origin is a920interior point not on the boundary.921922OUTPUT:923924Boolean.925926EXAMPLES::927928sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL929sage: LatticePolytope_PPL((-1,-1),(0,1),(1,0)).has_IP_property()930True931sage: LatticePolytope_PPL((-1,-1),(1,1)).has_IP_property()932False933"""934origin = C_Polyhedron(point(0*Variable(self.space_dimension())))935is_included = Poly_Con_Relation.is_included()936saturates = Poly_Con_Relation.saturates()937for c in self.constraints():938rel = origin.relation_with(c)939if (not rel.implies(is_included)) or rel.implies(saturates):940return False941return True942943@cached_method944def restricted_automorphism_group(self, vertex_labels=None):945r"""946Return the restricted automorphism group.947948First, let the linear automorphism group be the subgroup of949the Euclidean group `E(d) = GL(d,\RR) \ltimes \RR^d`950preserving the `d`-dimensional polyhedron. The Euclidean group951acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the952ambient space. The restricted automorphism group is the953subgroup of the linear automorphism group generated by954permutations of vertices. If the polytope is full-dimensional,955it is equal to the full (unrestricted) automorphism group.956957INPUT:958959- ``vertex_labels`` -- a tuple or ``None`` (default). The960labels of the vertices that will be used in the output961permutation group. By default, the vertices are used962themselves.963964OUTPUT:965966A967:class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`968acting on the vertices (or the ``vertex_labels``, if969specified).970971REFERENCES:972973.. [BSS]974David Bremner, Mathieu Dutour Sikiric, Achill Schuermann:975Polyhedral representation conversion up to symmetries.976http://arxiv.org/abs/math/0702239977978EXAMPLES::979980sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL981sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3))982sage: Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4))983Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)]984sage: G = Z3square.restricted_automorphism_group(); G985Permutation Group with generators [((1,2),(2,1)),986((0,0),(1,2))((2,1),(3,3)), ((0,0),(3,3))]987sage: tuple(G.domain()) == Z3square.vertices()988True989sage: G.orbit(Z3square.vertices()[0])990((0, 0), (1, 2), (3, 3), (2, 1))991992sage: cell24 = LatticePolytope_PPL(993... (1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1),(1,-1,-1,1),(0,0,-1,1),994... (0,-1,0,1),(-1,0,0,1),(1,0,0,-1),(0,1,0,-1),(0,0,1,-1),(-1,1,1,-1),995... (1,-1,-1,0),(0,0,-1,0),(0,-1,0,0),(-1,0,0,0),(1,-1,0,0),(1,0,-1,0),996... (0,1,1,-1),(-1,1,1,0),(-1,1,0,0),(-1,0,1,0),(0,-1,-1,1),(0,0,0,-1))997sage: cell24.restricted_automorphism_group().cardinality()9981152999"""1000if not self.is_full_dimensional():1001return self.affine_lattice_polytope().\1002restricted_automorphism_group(vertex_labels=vertex_labels)1003if vertex_labels is None:1004vertex_labels = self.vertices()1005from sage.groups.perm_gps.permgroup import PermutationGroup1006from sage.graphs.graph import Graph1007# good coordinates for the vertices1008v_list = []1009for v in self.minimized_generators():1010assert v.divisor().is_one()1011v_coords = (1,) + v.coefficients()1012v_list.append(vector(v_coords))10131014# Finally, construct the graph1015Qinv = sum( v.column() * v.row() for v in v_list ).inverse()1016G = Graph()1017for i in range(0,len(v_list)):1018for j in range(i+1,len(v_list)):1019v_i = v_list[i]1020v_j = v_list[j]1021G.add_edge(vertex_labels[i], vertex_labels[j], v_i * Qinv * v_j)1022return G.automorphism_group(edge_labels=True)10231024@cached_method1025def lattice_automorphism_group(self, points=None, point_labels=None):1026"""1027The integral subgroup of the restricted automorphism group.10281029INPUT:10301031- ``points`` -- A tuple of coordinate vectors or ``None``1032(default). If specified, the points must form complete1033orbits under the lattice automorphism group. If ``None`` all1034vertices are used.10351036- ``point_labels`` -- A tuple of labels for the ``points`` or1037``None`` (default). These will be used as labels for the do1038permutation group. If ``None`` the ``points`` will be used1039themselves.10401041OUTPUT:10421043The integral subgroup of the restricted automorphism group1044acting on the given ``points``, or all vertices if not1045specified.10461047EXAMPLES::10481049sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL1050sage: Z3square = LatticePolytope_PPL((0,0), (1,2), (2,1), (3,3))1051sage: Z3square.lattice_automorphism_group()1052Permutation Group with generators [(), ((1,2),(2,1)),1053((0,0),(3,3)), ((0,0),(3,3))((1,2),(2,1))]10541055sage: G1 = Z3square.lattice_automorphism_group(point_labels=(1,2,3,4)); G11056Permutation Group with generators [(), (2,3), (1,4), (1,4)(2,3)]1057sage: G1.cardinality()1058410591060sage: G2 = Z3square.restricted_automorphism_group(vertex_labels=(1,2,3,4)); G21061Permutation Group with generators [(2,3), (1,2)(3,4), (1,4)]1062sage: G2.cardinality()1063810641065sage: points = Z3square.integral_points(); points1066((0, 0), (1, 1), (1, 2), (2, 1), (2, 2), (3, 3))1067sage: Z3square.lattice_automorphism_group(points, point_labels=(1,2,3,4,5,6))1068Permutation Group with generators [(), (3,4), (1,6)(2,5), (1,6)(2,5)(3,4)]1069"""1070if not self.is_full_dimensional():1071return self.affine_lattice_polytope().lattice_automorphism_group()10721073if points is None:1074points = self.vertices()1075if point_labels is None:1076point_labels = tuple(points)1077points = [ vector(ZZ, [1]+v.list()) for v in points ]1078map(lambda x:x.set_immutable(), points)10791080vertices = [ vector(ZZ, [1]+v.list()) for v in self.vertices() ]1081pivots = matrix(ZZ, vertices).pivot_rows()1082basis = matrix(ZZ, [ vertices[i] for i in pivots ])1083Mat_ZZ = basis.parent()1084basis_inverse = basis.inverse()10851086from sage.groups.perm_gps.permgroup import PermutationGroup1087from sage.groups.perm_gps.permgroup_element import PermutationGroupElement1088lattice_gens = []1089G = self.restricted_automorphism_group(1090vertex_labels=tuple(range(len(vertices))))1091for g in G:1092image = matrix(ZZ, [ vertices[g(i)] for i in pivots ])1093m = basis_inverse*image1094if m not in Mat_ZZ:1095continue1096perm_list = [ point_labels[points.index(p*m)]1097for p in points ]1098lattice_gens.append(perm_list)1099return PermutationGroup(lattice_gens, domain=point_labels)11001101def sub_polytope_generator(self):1102"""1103Generate the maximal lattice sub-polytopes.11041105OUTPUT:11061107A generator yielding the maximal (with respect to inclusion)1108lattice sub polytopes. That is, each can be gotten as the1109convex hull of the integral points of ``self`` with one vertex1110removed.11111112EXAMPLES::11131114sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL1115sage: P = LatticePolytope_PPL((1,0,0), (0,1,0), (0,0,1), (-1,-1,-1))1116sage: for p in P.sub_polytope_generator():1117....: print p.vertices()1118((0, 0, 0), (0, 0, 1), (0, 1, 0), (1, 0, 0))1119((-1, -1, -1), (0, 0, 0), (0, 1, 0), (1, 0, 0))1120((-1, -1, -1), (0, 0, 0), (0, 0, 1), (1, 0, 0))1121((-1, -1, -1), (0, 0, 0), (0, 0, 1), (0, 1, 0))1122"""1123pointset = set(self.integral_points())1124for v in self.vertices():1125sub = list(pointset.difference([v]))1126yield LatticePolytope_PPL(*sub)11271128@cached_method1129def _find_isomorphism_to_subreflexive_polytope(self):1130"""1131Find an isomorphism to a sub-polytope of a maximal reflexive1132polytope.11331134OUTPUT:11351136A tuple consisting of the ambient reflexive polytope, the1137subpolytope, and the embedding of ``self`` into the ambient1138polytope.11391140EXAMPLES::11411142sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL1143sage: polygon = LatticePolytope_PPL((0,0,2,1),(0,1,2,0),(2,3,0,0),(2,0,0,3))1144sage: polygon._find_isomorphism_to_subreflexive_polytope()1145(A 2-dimensional lattice polytope in ZZ^2 with 3 vertices,1146A 2-dimensional lattice polytope in ZZ^2 with 4 vertices,1147The map A*x+b with A=1148[ 1 1]1149[ 0 1]1150[-1 -1]1151[ 1 0]1152b =1153(-1, 0, 3, 0))1154sage: ambient, sub, embedding = _1155sage: ambient.vertices()1156((0, 0), (0, 3), (3, 0))1157sage: sub.vertices()1158((0, 1), (3, 0), (0, 3), (1, 0))1159"""1160from ppl_lattice_polygon import sub_reflexive_polygons1161from sage.geometry.polyhedron.lattice_euclidean_group_element import \1162LatticePolytopesNotIsomorphicError, LatticePolytopeNoEmbeddingError1163for p, ambient in sub_reflexive_polygons():1164try:1165return (ambient, p, p.find_isomorphism(self))1166except LatticePolytopesNotIsomorphicError:1167pass1168from sage.geometry.polyhedron.lattice_euclidean_group_element import \1169LatticePolytopeNoEmbeddingError1170raise LatticePolytopeNoEmbeddingError('not a sub-polytope of a reflexive polygon')11711172def embed_in_reflexive_polytope(self, output='hom'):1173"""1174Find an embedding as a sub-polytope of a maximal reflexive1175polytope.11761177INPUT:11781179- ``hom`` -- string. One of ``'hom'`` (default),1180``'polytope'``, or ``points``. How the embedding is1181returned. See the output section for details.11821183OUTPUT:11841185An embedding into a reflexive polytope. Depending on the1186``output`` option slightly different data is returned.11871188- If ``output='hom'``, a map from a reflexive polytope onto1189``self`` is returned.11901191- If ``output='polytope'``, a reflexive polytope that contains1192``self`` (up to a lattice linear transformation) is1193returned. That is, the domain of the ``output='hom'`` map is1194returned. If the affine span of ``self`` is less or equal11952-dimnsional, the output is one of the following three1196possibilities::1197:func:`~sage.geometry.polyhedron.ppl_lattice_polygon.polar_P2_polytope`,1198:func:`~sage.geometry.polyhedron.ppl_lattice_polygon.polar_P1xP1_polytope`,1199or1200:func:`~sage.geometry.polyhedron.ppl_lattice_polygon.polar_P2_112_polytope`.12011202- If ``output='points'``, a dictionary containing the integral1203points of ``self`` as keys and the corresponding integral1204point of the reflexive polytope as value.12051206If there is no such embedding, a1207:class:`~sage.geometry.polyhedron.lattice_euclidean_group_element.LatticePolytopeNoEmbeddingError`1208is raised. Even if it exists, the ambient reflexive polytope1209is usually not uniquely determined an a random but fixed1210choice will be returned.12111212EXAMPLES::12131214sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL1215sage: polygon = LatticePolytope_PPL((0,0,2,1),(0,1,2,0),(2,3,0,0),(2,0,0,3))1216sage: polygon.embed_in_reflexive_polytope()1217The map A*x+b with A=1218[ 1 1]1219[ 0 1]1220[-1 -1]1221[ 1 0]1222b =1223(-1, 0, 3, 0)1224sage: polygon.embed_in_reflexive_polytope('polytope')1225A 2-dimensional lattice polytope in ZZ^2 with 3 vertices1226sage: polygon.embed_in_reflexive_polytope('points')1227{(0, 0, 2, 1): (1, 0), (2, 1, 0, 2): (2, 1),1228(0, 1, 2, 0): (0, 1), (1, 1, 1, 1): (1, 1),1229(1, 2, 1, 0): (0, 2), (2, 2, 0, 1): (1, 2),1230(2, 3, 0, 0): (0, 3), (1, 0, 1, 2): (2, 0),1231(2, 0, 0, 3): (3, 0)}12321233sage: LatticePolytope_PPL((0,0), (4,0), (0,4)).embed_in_reflexive_polytope()1234Traceback (most recent call last):1235...1236LatticePolytopeNoEmbeddingError: not a sub-polytope of a reflexive polygon1237"""1238if self.affine_dimension() > 2:1239raise NotImplementedError('can only embed in reflexive polygons')1240ambient, subreflexive, hom = self._find_isomorphism_to_subreflexive_polytope()1241if output == 'hom':1242return hom1243elif output == 'polytope':1244return ambient1245elif output == 'points':1246points = dict()1247for p in subreflexive.integral_points():1248points[ tuple(hom(p)) ] = p1249return points1250else:1251raise ValueError('output='+str(output)+' is not valid.')125212531254125512561257