Path: blob/master/src/sage/geometry/integral_points.pyx
8815 views
r"""1Cython helper methods to compute integral points in polyhedra.2"""34#*****************************************************************************5# Copyright (C) 2010 Volker Braun <[email protected]>6#7# Distributed under the terms of the GNU General Public License (GPL)8#9# http://www.gnu.org/licenses/10#*****************************************************************************1112from sage.matrix.constructor import matrix, column_matrix, vector, diagonal_matrix13from sage.rings.all import QQ, RR, ZZ, gcd, lcm14from sage.combinat.permutation import Permutation15from sage.combinat.cartesian_product import CartesianProduct16from sage.misc.all import prod, uniq17import copy1819##############################################################################20# The basic idea to enumerate the lattice points in the parallelotope21# is to use the Smith normal form of the ray coordinate matrix to22# bring the lattice into a "nice" basis. Here is the straightforward23# implementation. Note that you do not need to reduce to the24# full-dimensional case, the Smith normal form takes care of that for25# you.26#27## def parallelotope_points(spanning_points, lattice):28## # compute points in the open parallelotope, see [BrunsKoch]29## R = matrix(spanning_points).transpose()30## D,U,V = R.smith_form()31## e = D.diagonal() # the elementary divisors32## d = prod(e) # the determinant33## u = U.inverse().columns() # generators for gp(semigroup)34##35## # "inverse" of the ray matrix as far as possible over ZZ36## # R*Rinv == diagonal_matrix([d]*D.ncols() + [0]*(D.nrows()-D.ncols()))37## # If R is full rank, this is Rinv = matrix(ZZ, R.inverse() * d)38## Dinv = D.transpose()39## for i in range(0,D.ncols()):40## Dinv[i,i] = d/D[i,i]41## Rinv = V * Dinv * U42##43## gens = []44## for b in CartesianProduct(*[ range(0,i) for i in e ]):45## # this is our generator modulo the lattice spanned by the rays46## gen_mod_rays = sum( b_i*u_i for b_i, u_i in zip(b,u) )47## q_times_d = Rinv * gen_mod_rays48## q_times_d = vector(ZZ,[ q_i % d for q_i in q_times_d ])49## gen = lattice(R*q_times_d / d)50## gen.set_immutable()51## gens.append(gen)52## assert(len(gens) == d)53## return tuple(gens)54#55# The problem with the naive implementation is that it is slow:56#57# 1. You can simplify some of the matrix multiplications58#59# 2. The inner loop keeps creating new matrices and vectors, which60# is slow. It needs to recycle objects. Instead of creating a new61# lattice point again and again, change the entries of an62# existing lattice point and then copy it!636465def parallelotope_points(spanning_points, lattice):66r"""67Return integral points in the parallelotope starting at the origin68and spanned by the ``spanning_points``.6970See :meth:`~ConvexRationalPolyhedralCone.semigroup_generators` for a description of the71algorithm.7273INPUT:7475- ``spanning_points`` -- a non-empty list of linearly independent76rays (`\ZZ`-vectors or :class:`toric lattice77<sage.geometry.toric_lattice.ToricLatticeFactory>` elements),78not necessarily primitive lattice points.7980OUTPUT:8182The tuple of all lattice points in the half-open parallelotope83spanned by the rays `r_i`,8485.. math::8687\mathop{par}(\{r_i\}) =88\sum_{0\leq a_i < 1} a_i r_i8990By half-open parallelotope, we mean that the91points in the facets not meeting the origin are omitted.9293EXAMPLES:9495Note how the points on the outward-facing factes are omitted::9697sage: from sage.geometry.integral_points import parallelotope_points98sage: rays = map(vector, [(2,0), (0,2)])99sage: parallelotope_points(rays, ZZ^2)100((0, 0), (1, 0), (0, 1), (1, 1))101102The rays can also be toric lattice points::103104sage: rays = map(ToricLattice(2), [(2,0), (0,2)])105sage: parallelotope_points(rays, ToricLattice(2))106(N(0, 0), N(1, 0), N(0, 1), N(1, 1))107108A non-smooth cone::109110sage: c = Cone([ (1,0), (1,2) ])111sage: parallelotope_points(c.rays(), c.lattice())112(N(0, 0), N(1, 1))113114A ``ValueError`` is raised if the ``spanning_points`` are not115linearly independent::116117sage: rays = map(ToricLattice(2), [(1,1)]*2)118sage: parallelotope_points(rays, ToricLattice(2))119Traceback (most recent call last):120...121ValueError: The spanning points are not linearly independent!122123TESTS::124125sage: rays = map(vector,[(-3, -2, -3, -2), (-2, -1, -8, 5), (1, 9, -7, -4), (-3, -1, -2, 2)])126sage: len(parallelotope_points(rays, ZZ^4))127967128"""129R = matrix(spanning_points).transpose()130e, d, VDinv = ray_matrix_normal_form(R)131points = loop_over_parallelotope_points(e, d, VDinv, R, lattice)132for p in points:133p.set_immutable()134return points135136137def ray_matrix_normal_form(R):138r"""139Compute the Smith normal form of the ray matrix for140:func:`parallelotope_points`.141142INPUT:143144- ``R`` -- `\ZZ`-matrix whose columns are the rays spanning the145parallelotope.146147OUTPUT:148149A tuple containing ``e``, ``d``, and ``VDinv``.150151EXAMPLES::152153sage: from sage.geometry.integral_points import ray_matrix_normal_form154sage: R = column_matrix(ZZ,[3,3,3])155sage: ray_matrix_normal_form(R)156([3], 3, [1])157"""158D,U,V = R.smith_form()159e = D.diagonal() # the elementary divisors160d = prod(e) # the determinant161if d==0:162raise ValueError('The spanning points are not linearly independent!')163Dinv = diagonal_matrix(ZZ,[ d/D[i,i] for i in range(0,D.ncols()) ])164VDinv = V * Dinv165return (e,d,VDinv)166167168169# The optimized version avoids constructing new matrices, vectors, and lattice points170cpdef loop_over_parallelotope_points(e, d, VDinv, R, lattice, A=None, b=None):171r"""172The inner loop of :func:`parallelotope_points`.173174INPUT:175176See :meth:`parallelotope_points` for ``e``, ``d``, ``VDinv``, ``R``, ``lattice``.177178- ``A``, ``b``: Either both ``None`` or a vector and number. If179present, only the parallelotope points satisfying `A x \leq b`180are returned.181182OUTPUT:183184The points of the half-open parallelotope as a tuple of lattice185points.186187EXAMPLES:188189sage: e = [3]190sage: d = prod(e)191sage: VDinv = matrix(ZZ, [[1]])192sage: R = column_matrix(ZZ,[3,3,3])193sage: lattice = ZZ^3194sage: from sage.geometry.integral_points import loop_over_parallelotope_points195sage: loop_over_parallelotope_points(e, d, VDinv, R, lattice)196((0, 0, 0), (1, 1, 1), (2, 2, 2))197198sage: A = vector(ZZ, [1,0,0])199sage: b = 1200sage: loop_over_parallelotope_points(e, d, VDinv, R, lattice, A, b)201((0, 0, 0), (1, 1, 1))202"""203cdef int i, j204cdef int dim = VDinv.nrows()205cdef int ambient_dim = R.nrows()206gens = []207s = ZZ.zero() # summation variable208gen = lattice(0)209q_times_d = vector(ZZ, dim)210for base in CartesianProduct(*[ range(0,i) for i in e ]):211for i in range(0, dim):212s = ZZ.zero()213for j in range(0, dim):214s += VDinv[i,j] * base[j]215q_times_d[i] = s % d216for i in range(0, ambient_dim):217s = ZZ.zero()218for j in range(0, dim):219s += R[i,j] * q_times_d[j]220gen[i] = s / d221if A is not None:222s = ZZ.zero()223for i in range(0, ambient_dim):224s += A[i] * gen[i]225if s>b:226continue227gens.append(copy.copy(gen))228if A is None:229assert(len(gens) == d)230return tuple(gens)231232233234##############################################################################235def simplex_points(vertices):236r"""237Return the integral points in a lattice simplex.238239INPUT:240241- ``vertices`` -- an iterable of integer coordinate vectors. The242indices of vertices that span the simplex under243consideration.244245OUTPUT:246247A tuple containing the integral point coordinates as `\ZZ`-vectors.248249EXAMPLES::250251sage: from sage.geometry.integral_points import simplex_points252sage: simplex_points([(1,2,3), (2,3,7), (-2,-3,-11)])253((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7))254255The simplex need not be full-dimensional::256257sage: simplex = Polyhedron([(1,2,3,5), (2,3,7,5), (-2,-3,-11,5)])258sage: simplex_points(simplex.Vrepresentation())259((2, 3, 7, 5), (0, 0, -2, 5), (-2, -3, -11, 5), (1, 2, 3, 5))260261sage: simplex_points([(2,3,7)])262((2, 3, 7),)263264TESTS::265266sage: v = [(1,0,7,-1), (-2,-2,4,-3), (-1,-1,-1,4), (2,9,0,-5), (-2,-1,5,1)]267sage: simplex = Polyhedron(v); simplex268A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices269sage: pts = simplex_points(simplex.Vrepresentation())270sage: len(pts)27149272sage: for p in pts: p.set_immutable()273sage: len(set(pts))27449275276sage: all(simplex.contains(p) for p in pts)277True278279sage: v = [(4,-1,-1,-1), (-1,4,-1,-1), (-1,-1,4,-1), (-1,-1,-1,4), (-1,-1,-1,-1)]280sage: P4mirror = Polyhedron(v); P4mirror281A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices282sage: len(simplex_points(P4mirror.Vrepresentation()))283126284285sage: vertices = map(vector, [(1,2,3), (2,3,7), (-2,-3,-11)])286sage: for v in vertices: v.set_immutable()287sage: simplex_points(vertices)288((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7))289"""290rays = [vector(ZZ, list(v)) for v in vertices]291if len(rays)==0:292return tuple()293origin = rays.pop()294origin.set_immutable()295if len(rays)==0:296return tuple([origin])297translate_points(rays, origin)298299# Find equation Ax<=b that cuts out simplex from parallelotope300Rt = matrix(rays)301R = Rt.transpose()302if R.is_square():303b = abs(R.det())304A = R.solve_left(vector([b]*len(rays)))305else:306RtR = Rt * R307b = abs(RtR.det())308A = RtR.solve_left(vector([b]*len(rays))) * Rt309310# e, d, VDinv = ray_matrix_normal_form(R)311# print origin312# print rays313# print parallelotope_points(rays, origin.parent())314# print 'A = ', A315# print 'b = ', b316317e, d, VDinv = ray_matrix_normal_form(R)318lattice = origin.parent()319points = loop_over_parallelotope_points(e, d, VDinv, R, lattice, A, b) + tuple(rays)320translate_points(points, -origin)321return points322323324cdef translate_points(v_list, delta):325r"""326Add ``delta`` to each vector in ``v_list``.327"""328cdef int dim = delta.degree()329for v in v_list:330for i in range(0,dim):331v[i] -= delta[i]332333334335##############################################################################336# For points with "small" coordinates (that is, fitting into a small337# rectangular bounding box) it is faster to naively enumerate the338# points. This saves the overhead of triangulating the polytope etc.339340def rectangular_box_points(box_min, box_max, polyhedron=None,341count_only=False, return_saturated=False):342r"""343Return the integral points in the lattice bounding box that are344also contained in the given polyhedron.345346INPUT:347348- ``box_min`` -- A list of integers. The minimal value for each349coordinate of the rectangular bounding box.350351- ``box_max`` -- A list of integers. The maximal value for each352coordinate of the rectangular bounding box.353354- ``polyhedron`` -- A355:class:`~sage.geometry.polyhedron.base.Polyhedron_base`, a PPL356:class:`~sage.libs.ppl.C_Polyhedron`, or ``None`` (default).357358- ``count_only`` -- Boolean (default: ``False``). Whether to359return only the total number of vertices, and not their360coordinates. Enabling this option speeds up the361enumeration. Cannot be combined with the ``return_saturated``362option.363364- ``return_saturated`` -- Boolean (default: ``False``. Whether to365also return which inequalities are saturated for each point of366the polyhedron. Enabling this slows down the enumeration. Cannot367be combined with the ``count_only`` option.368369OUTPUT:370371By default, this function returns a tuple containing the integral372points of the rectangular box spanned by `box_min` and `box_max`373and that lie inside the ``polyhedron``. For sufficiently large374bounding boxes, this are all integral points of the polyhedron.375376If no polyhedron is specified, all integral points of the377rectangular box are returned.378379If ``count_only`` is specified, only the total number (an integer)380of found lattice points is returned.381382If ``return_saturated`` is enabled, then for each integral point a383pair ``(point, Hrep)`` is returned where ``point`` is the point384and ``Hrep`` is the set of indices of the H-representation objects385that are saturated at the point.386387ALGORITHM:388389This function implements the naive algorithm towards counting390integral points. Given min and max of vertex coordinates, it391iterates over all points in the bounding box and checks whether392they lie in the polyhedron. The following optimizations are393implemented:394395* Cython: Use machine integers and optimizing C/C++ compiler396where possible, arbitrary precision integers where necessary.397Bounds checking, no compile time limits.398399* Unwind inner loop (and next-to-inner loop):400401.. math::402403Ax\leq b404\quad \Leftrightarrow \quad405a_1 x_1 ~\leq~ b - \sum_{i=2}^d a_i x_i406407so we only have to evaluate `a_1 * x_1` in the inner loop.408409* Coordinates are permuted to make the longest box edge the410inner loop. The inner loop is optimized to run very fast, so411its best to do as much work as possible there.412413* Continuously reorder inequalities and test the most414restrictive inequalities first.415416* Use convexity and only find first and last allowed point in417the inner loop. The points in-between must be points of the418polyhedron, too.419420EXAMPLES::421422sage: from sage.geometry.integral_points import rectangular_box_points423sage: rectangular_box_points([0,0,0],[1,2,3])424((0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3),425(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3),426(0, 2, 0), (0, 2, 1), (0, 2, 2), (0, 2, 3),427(1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 0, 3),428(1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3),429(1, 2, 0), (1, 2, 1), (1, 2, 2), (1, 2, 3))430431sage: from sage.geometry.integral_points import rectangular_box_points432sage: rectangular_box_points([0,0,0],[1,2,3], count_only=True)43324434435sage: cell24 = polytopes.twenty_four_cell()436sage: rectangular_box_points([-1]*4, [1]*4, cell24)437((-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0), (0, 0, 0, -1),438(0, 0, 0, 0),439(0, 0, 0, 1), (0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0))440sage: d = 3441sage: dilated_cell24 = d*cell24442sage: len( rectangular_box_points([-d]*4, [d]*4, dilated_cell24) )443305444445sage: d = 6446sage: dilated_cell24 = d*cell24447sage: len( rectangular_box_points([-d]*4, [d]*4, dilated_cell24) )4483625449450sage: rectangular_box_points([-d]*4, [d]*4, dilated_cell24, count_only=True)4513625452453sage: polytope = Polyhedron([(-4,-3,-2,-1),(3,1,1,1),(1,2,1,1),(1,1,3,0),(1,3,2,4)])454sage: pts = rectangular_box_points([-4]*4, [4]*4, polytope); pts455((-4, -3, -2, -1), (-1, 0, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (1, 1, 3, 0),456(1, 2, 1, 1), (1, 2, 2, 2), (1, 3, 2, 4), (2, 1, 1, 1), (3, 1, 1, 1))457sage: all(polytope.contains(p) for p in pts)458True459460sage: set(map(tuple,pts)) == \461... set([(-4,-3,-2,-1),(3,1,1,1),(1,2,1,1),(1,1,3,0),(1,3,2,4),462... (0,1,1,1),(1,2,2,2),(-1,0,0,1),(1,1,1,1),(2,1,1,1)]) # computed with PALP463True464465Long ints and non-integral polyhedra are explictly allowed::466467sage: polytope = Polyhedron([[1], [10*pi.n()]], base_ring=RDF)468sage: len( rectangular_box_points([-100], [100], polytope) )46931470471sage: halfplane = Polyhedron(ieqs=[(-1,1,0)])472sage: rectangular_box_points([0,-1+10^50], [0,1+10^50])473((0, 99999999999999999999999999999999999999999999999999),474(0, 100000000000000000000000000000000000000000000000000),475(0, 100000000000000000000000000000000000000000000000001))476sage: len( rectangular_box_points([0,-100+10^50], [1,100+10^50], halfplane) )477201478479Using a PPL polyhedron::480481sage: from sage.libs.ppl import Variable, Generator_System, C_Polyhedron, point482sage: gs = Generator_System()483sage: x = Variable(0); y = Variable(1); z = Variable(2)484sage: gs.insert(point(0*x + 1*y + 0*z))485sage: gs.insert(point(0*x + 1*y + 3*z))486sage: gs.insert(point(3*x + 1*y + 0*z))487sage: gs.insert(point(3*x + 1*y + 3*z))488sage: poly = C_Polyhedron(gs)489sage: rectangular_box_points([0]*3, [3]*3, poly)490((0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3),491(2, 1, 0), (2, 1, 1), (2, 1, 2), (2, 1, 3), (3, 1, 0), (3, 1, 1), (3, 1, 2), (3, 1, 3))492493Optionally, return the information about the saturated inequalities as well::494495sage: cube = polytopes.n_cube(3)496sage: cube.Hrepresentation(0)497An inequality (0, 0, -1) x + 1 >= 0498sage: cube.Hrepresentation(1)499An inequality (0, -1, 0) x + 1 >= 0500sage: cube.Hrepresentation(2)501An inequality (-1, 0, 0) x + 1 >= 0502sage: rectangular_box_points([0]*3, [1]*3, cube, return_saturated=True)503(((0, 0, 0), frozenset([])),504((0, 0, 1), frozenset([0])),505((0, 1, 0), frozenset([1])),506((0, 1, 1), frozenset([0, 1])),507((1, 0, 0), frozenset([2])),508((1, 0, 1), frozenset([0, 2])),509((1, 1, 0), frozenset([1, 2])),510((1, 1, 1), frozenset([0, 1, 2])))511"""512assert len(box_min)==len(box_max)513assert not (count_only and return_saturated)514cdef int d = len(box_min)515diameter = sorted([ (box_max[i]-box_min[i], i) for i in range(0,d) ], reverse=True)516diameter_value = [ x[0] for x in diameter ]517diameter_index = [ x[1] for x in diameter ]518519sort_perm = Permutation([ i+1 for i in diameter_index])520orig_perm = sort_perm.inverse()521orig_perm_list = [ i-1 for i in orig_perm ]522box_min = sort_perm.action(box_min)523box_max = sort_perm.action(box_max)524inequalities = InequalityCollection(polyhedron, sort_perm, box_min, box_max)525526if count_only:527return loop_over_rectangular_box_points(box_min, box_max, inequalities, d, count_only)528529points = []530v = vector(ZZ, d)531if not return_saturated:532for p in loop_over_rectangular_box_points(box_min, box_max, inequalities, d, count_only):533# v = vector(ZZ, orig_perm.action(p)) # too slow534for i in range(0,d):535v.set(i, p[orig_perm_list[i]])536v_copy = copy.copy(v)537v_copy.set_immutable()538points.append(v_copy)539else:540for p, saturated in \541loop_over_rectangular_box_points_saturated(box_min, box_max, inequalities, d):542for i in range(0,d):543v.set(i, p[orig_perm_list[i]])544v_copy = copy.copy(v)545v_copy.set_immutable()546points.append( (v_copy, saturated) )547548return tuple(points)549550551cdef loop_over_rectangular_box_points(box_min, box_max, inequalities, int d, bint count_only):552"""553The inner loop of :func:`rectangular_box_points`.554555INPUT:556557- ``box_min``, ``box_max`` -- the bounding box.558559- ``inequalities`` -- a :class:`InequalityCollection` containing560the inequalities defining the polyhedron.561562- ``d`` -- the ambient space dimension.563564- ``count_only`` -- whether to only return the total number of565lattice points.566567OUTPUT:568569The integral points in the bounding box satisfying all570inequalities.571"""572cdef int inc573if count_only:574points = 0575else:576points = []577p = copy.copy(box_min)578inequalities.prepare_next_to_inner_loop(p)579while True:580inequalities.prepare_inner_loop(p)581i_min = box_min[0]582i_max = box_max[0]583# Find the lower bound for the allowed region584while i_min <= i_max:585if inequalities.are_satisfied(i_min):586break587i_min += 1588# Find the upper bound for the allowed region589while i_min <= i_max:590if inequalities.are_satisfied(i_max):591break592i_max -= 1593# The points i_min .. i_max are contained in the polyhedron594if count_only:595if i_max>=i_min:596points += i_max-i_min+1597else:598i = i_min599while i <= i_max:600p[0] = i601points.append(tuple(p))602i += 1603# finally increment the other entries in p to move on to next inner loop604inc = 1605if d==1: return points606while True:607if p[inc]==box_max[inc]:608p[inc] = box_min[inc]609inc += 1610if inc==d:611return points612else:613p[inc] += 1614break615if inc>1:616inequalities.prepare_next_to_inner_loop(p)617618619620cdef loop_over_rectangular_box_points_saturated(box_min, box_max, inequalities, int d):621"""622The analog of :func:`rectangular_box_points` except that it keeps623track of which inequalities are saturated.624625INPUT:626627See :func:`rectangular_box_points`.628629OUTPUT:630631The integral points in the bounding box satisfying all632inequalities, each point being returned by a coordinate vector and633a set of H-representation object indices.634"""635cdef int inc636points = []637p = copy.copy(box_min)638inequalities.prepare_next_to_inner_loop(p)639while True:640inequalities.prepare_inner_loop(p)641i_min = box_min[0]642i_max = box_max[0]643# Find the lower bound for the allowed region644while i_min <= i_max:645if inequalities.are_satisfied(i_min):646break647i_min += 1648# Find the upper bound for the allowed region649while i_min <= i_max:650if inequalities.are_satisfied(i_max):651break652i_max -= 1653# The points i_min .. i_max are contained in the polyhedron654i = i_min655while i <= i_max:656p[0] = i657saturated = inequalities.satisfied_as_equalities(i)658points.append( (tuple(p), saturated) )659i += 1660# finally increment the other entries in p to move on to next inner loop661inc = 1662if d==1: return points663while True:664if p[inc]==box_max[inc]:665p[inc] = box_min[inc]666inc += 1667if inc==d:668return points669else:670p[inc] += 1671break672if inc>1:673inequalities.prepare_next_to_inner_loop(p)674675676677cdef class Inequality_generic:678"""679An inequality whose coefficients are arbitrary Python/Sage objects680681INPUT:682683- ``A`` -- list of integers.684685- ``b`` -- integer686687OUTPUT:688689Inequality `A x + b \geq 0`.690691EXAMPLES::692693sage: from sage.geometry.integral_points import Inequality_generic694sage: Inequality_generic([2*pi,sqrt(3),7/2], -5.5)695generic: (2*pi, sqrt(3), 7/2) x + -5.50000000000000 >= 0696"""697698cdef object A699cdef object b700cdef object coeff701cdef object cache702# The index of the inequality in the polyhedron H-representation703cdef int index704705def __cinit__(self, A, b, index=-1):706"""707The Cython constructor708709INPUT:710711See :class:`Inequality_generic`.712713EXAMPLES::714715sage: from sage.geometry.integral_points import Inequality_generic716sage: Inequality_generic([2*pi,sqrt(3),7/2], -5.5)717generic: (2*pi, sqrt(3), 7/2) x + -5.50000000000000 >= 0718"""719self.A = A720self.b = b721self.coeff = 0722self.cache = 0723self.index = int(index)724725def __repr__(self):726"""727Return a string representation.728729OUTPUT:730731String.732733EXAMPLES::734735sage: from sage.geometry.integral_points import Inequality_generic736sage: Inequality_generic([2,3,7], -5).__repr__()737'generic: (2, 3, 7) x + -5 >= 0'738"""739s = 'generic: ('740s += ', '.join([str(self.A[i]) for i in range(0,len(self.A))])741s += ') x + ' + str(self.b) + ' >= 0'742return s743744cdef prepare_next_to_inner_loop(self, p):745"""746In :class:`Inequality_int` this method is used to peel of the747next-to-inner loop.748749See :meth:`InequalityCollection.prepare_inner_loop` for more details.750"""751pass752753cdef prepare_inner_loop(self, p):754"""755Peel off the inner loop.756757See :meth:`InequalityCollection.prepare_inner_loop` for more details.758"""759cdef int j760self.coeff = self.A[0]761self.cache = self.b762for j in range(1,len(self.A)):763self.cache += self.A[j] * p[j]764765cdef bint is_not_satisfied(self, inner_loop_variable):766r"""767Test the inequality, using the cached value from :meth:`prepare_inner_loop`768769OUTPUT:770771Boolean. Whether the inequality is not satisfied.772"""773return inner_loop_variable*self.coeff + self.cache < 0774775cdef bint is_equality(Inequality_generic self, int inner_loop_variable):776r"""777Test the inequality, using the cached value from :meth:`prepare_inner_loop`778779OUTPUT:780781Boolean. Given the inequality `Ax+b\geq 0`, this method782returns whether the equality `Ax+b=0` is satisfied.783"""784return inner_loop_variable*self.coeff + self.cache == 0785786787# if dim>20 then we always use the generic inequalities (Inequality_generic)788DEF INEQ_INT_MAX_DIM = 20789790cdef class Inequality_int:791"""792Fast version of inequality in the case that all coefficient fit793into machine ints.794795INPUT:796797- ``A`` -- list of integers.798799- ``b`` -- integer800801- ``max_abs_coordinates`` -- the maximum of the coordinates that802one wants to evalate the coordinates on. Used for overflow803checking.804805OUTPUT:806807Inequality `A x + b \geq 0`. A ``OverflowError`` is raised if a808machine integer is not long enough to hold the results. A809``ValueError`` is raised if some of the input is not integral.810811EXAMPLES::812813sage: from sage.geometry.integral_points import Inequality_int814sage: Inequality_int([2,3,7], -5, [10]*3)815integer: (2, 3, 7) x + -5 >= 0816817sage: Inequality_int([1]*21, -5, [10]*21)818Traceback (most recent call last):819...820OverflowError: Dimension limit exceeded.821822sage: Inequality_int([2,3/2,7], -5, [10]*3)823Traceback (most recent call last):824...825ValueError: Not integral.826827sage: Inequality_int([2,3,7], -5.2, [10]*3)828Traceback (most recent call last):829...830ValueError: Not integral.831832sage: Inequality_int([2,3,7], -5*10^50, [10]*3) # actual error message can differ between 32 and 64 bit833Traceback (most recent call last):834...835OverflowError: ...836"""837cdef int A[INEQ_INT_MAX_DIM]838cdef int b839cdef int dim840# the innermost coefficient841cdef int coeff842cdef int cache843# the next-to-innermost coefficient844cdef int coeff_next845cdef int cache_next846# The index of the inequality in the polyhedron H-representation847cdef int index848849cdef int _to_int(self, x) except? -999:850if not x in ZZ: raise ValueError('Not integral.')851return int(x) # raises OverflowError in Cython if necessary852853def __cinit__(self, A, b, max_abs_coordinates, index=-1):854"""855The Cython constructor856857See :class:`Inequality_int` for input.858859EXAMPLES::860861sage: from sage.geometry.integral_points import Inequality_int862sage: Inequality_int([2,3,7], -5, [10]*3)863integer: (2, 3, 7) x + -5 >= 0864"""865cdef int i866self.dim = self._to_int(len(A))867self.index = int(index)868if self.dim<1 or self.dim>INEQ_INT_MAX_DIM:869raise OverflowError('Dimension limit exceeded.')870for i in range(0,self.dim):871self.A[i] = self._to_int(A[i])872self.b = self._to_int(b)873self.coeff = self.A[0]874if self.dim>0:875self.coeff_next = self.A[1]876# finally, make sure that there cannot be any overflow during the enumeration877self._to_int(sum( [ZZ(b)]+[ZZ(A[i])*ZZ(max_abs_coordinates[i]) for i in range(0,self.dim)] ))878879def __repr__(self):880"""881Return a string representation.882883OUTPUT:884885String.886887EXAMPLES::888889sage: from sage.geometry.integral_points import Inequality_int890sage: Inequality_int([2,3,7], -5, [10]*3).__repr__()891'integer: (2, 3, 7) x + -5 >= 0'892"""893s = 'integer: ('894s += ', '.join([str(self.A[i]) for i in range(0,self.dim)])895s += ') x + ' + str(self.b) + ' >= 0'896return s897898cdef prepare_next_to_inner_loop(Inequality_int self, p):899"""900Peel off the next-to-inner loop.901902See :meth:`InequalityCollection.prepare_inner_loop` for more details.903"""904cdef int j905self.cache_next = self.b906for j in range(2,self.dim):907self.cache_next += self.A[j] * p[j]908909cdef prepare_inner_loop(Inequality_int self, p):910"""911Peel off the inner loop.912913See :meth:`InequalityCollection.prepare_inner_loop` for more details.914"""915cdef int j916if self.dim>1:917self.cache = self.cache_next + self.coeff_next*p[1]918else:919self.cache = self.cache_next920921cdef bint is_not_satisfied(Inequality_int self, int inner_loop_variable):922return inner_loop_variable*self.coeff + self.cache < 0923924cdef bint is_equality(Inequality_int self, int inner_loop_variable):925return inner_loop_variable*self.coeff + self.cache == 0926927928929cdef class InequalityCollection:930"""931A collection of inequalities.932933INPUT:934935- ``polyhedron`` -- a polyhedron defining the inequalities.936937- ``permutation`` -- a permution of the coordinates. Will be used938to permute the coordinates of the inequality.939940- ``box_min``, ``box_max`` -- the (not permuted) minimal and maximal941coordinates of the bounding box. Used for bounds checking.942943EXAMPLES::944945sage: from sage.geometry.integral_points import InequalityCollection946sage: P_QQ = Polyhedron(identity_matrix(3).columns() + [(-2, -1,-1)], base_ring=QQ)947sage: ieq = InequalityCollection(P_QQ, Permutation([1,2,3]), [0]*3,[1]*3); ieq948The collection of inequalities949integer: (3, -2, -2) x + 2 >= 0950integer: (-1, 4, -1) x + 1 >= 0951integer: (-1, -1, 4) x + 1 >= 0952integer: (-1, -1, -1) x + 1 >= 0953954sage: P_RR = Polyhedron(identity_matrix(2).columns() + [(-2.7, -1)], base_ring=RDF)955sage: InequalityCollection(P_RR, Permutation([1,2]), [0]*2, [1]*2)956The collection of inequalities957integer: (-1, -1) x + 1 >= 0958generic: (-1.0, 3.7) x + 1.0 >= 0959generic: (1.0, -1.35) x + 1.35 >= 0960961sage: line = Polyhedron(eqns=[(2,3,7)])962sage: InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 )963The collection of inequalities964integer: (3, 7) x + 2 >= 0965integer: (-3, -7) x + -2 >= 0966967TESTS::968969sage: TestSuite(ieq).run(skip='_test_pickling')970"""971cdef object ineqs_int972cdef object ineqs_generic973974def __repr__(self):975r"""976Return a string representation.977978OUTPUT:979980String.981982EXAMPLES::983984sage: from sage.geometry.integral_points import InequalityCollection985sage: line = Polyhedron(eqns=[(2,3,7)])986sage: InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 ).__repr__()987'The collection of inequalities\ninteger: (3, 7) x + 2 >= 0\ninteger: (-3, -7) x + -2 >= 0'988"""989s = 'The collection of inequalities\n'990for ineq in self.ineqs_int:991s += str(<Inequality_int>ineq) + '\n'992for ineq in self.ineqs_generic:993s += str(<Inequality_generic>ineq) + '\n'994return s.strip()995996def _make_A_b(self, Hrep_obj, permutation):997r"""998Return the coefficients and constant of the H-representation999object.10001001INPUT:10021003- ``Hrep_obj`` -- a H-representation object of the polyhedron.10041005- ``permutation`` -- the permutation of the coordinates to1006apply to ``A``.10071008OUTPUT:10091010A pair ``(A,b)``.10111012EXAXMPLES::10131014sage: from sage.geometry.integral_points import InequalityCollection1015sage: line = Polyhedron(eqns=[(2,3,7)])1016sage: ieq = InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 )1017sage: ieq._make_A_b(line.Hrepresentation(0), Permutation([1,2]))1018([3, 7], 2)1019sage: ieq._make_A_b(line.Hrepresentation(0), Permutation([2,1]))1020([7, 3], 2)1021"""1022v = list(Hrep_obj)1023A = permutation.action(v[1:])1024b = v[0]1025try:1026x = lcm([a.denominator() for a in A] + [b.denominator()])1027A = [ a*x for a in A ]1028b = b*x1029except AttributeError:1030pass1031return (A,b)10321033def __cinit__(self, polyhedron, permutation, box_min, box_max):1034"""1035The Cython constructor10361037See the class documentation for the desrciption of the arguments.10381039EXAMPLES::10401041sage: from sage.geometry.integral_points import InequalityCollection1042sage: line = Polyhedron(eqns=[(2,3,7)])1043sage: InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 )1044The collection of inequalities1045integer: (3, 7) x + 2 >= 01046integer: (-3, -7) x + -2 >= 01047"""1048max_abs_coordinates = [ max(abs(c_min), abs(c_max))1049for c_min, c_max in zip(box_min, box_max) ]1050max_abs_coordinates = permutation.action(max_abs_coordinates)1051self.ineqs_int = []1052self.ineqs_generic = []1053if polyhedron is None:1054return10551056try:1057# polyhedron is a PPL C_Polyhedron class?1058self._cinit_from_PPL(max_abs_coordinates, permutation, polyhedron)1059except AttributeError:1060try:1061# polyhedron is a Polyhedron class?1062self._cinit_from_Polyhedron(max_abs_coordinates, permutation, polyhedron)1063except AttributeError:1064raise TypeError('Cannot extract Hrepresentation data from polyhedron.')10651066cdef _cinit_from_PPL(self, max_abs_coordinates, permutation, polyhedron):1067"""1068Initialize the inequalities from a PPL C_Polyhedron10691070See __cinit__ for a description of the arguments.10711072EXAMPLES::10731074sage: from sage.libs.ppl import Variable, Generator_System, C_Polyhedron, point1075sage: gs = Generator_System()1076sage: x = Variable(0); y = Variable(1); z = Variable(2)1077sage: gs.insert(point(0*x + 0*y + 1*z))1078sage: gs.insert(point(0*x + 3*y + 1*z))1079sage: gs.insert(point(3*x + 0*y + 1*z))1080sage: gs.insert(point(3*x + 3*y + 1*z))1081sage: poly = C_Polyhedron(gs)1082sage: from sage.geometry.integral_points import InequalityCollection1083sage: InequalityCollection(poly, Permutation([1,3,2]), [0]*3, [3]*3 )1084The collection of inequalities1085integer: (0, 1, 0) x + -1 >= 01086integer: (0, -1, 0) x + 1 >= 01087integer: (1, 0, 0) x + 0 >= 01088integer: (0, 0, 1) x + 0 >= 01089integer: (-1, 0, 0) x + 3 >= 01090integer: (0, 0, -1) x + 3 >= 01091"""1092for index,c in enumerate(polyhedron.minimized_constraints()):1093A = permutation.action(c.coefficients())1094b = c.inhomogeneous_term()1095try:1096H = Inequality_int(A, b, max_abs_coordinates, index)1097self.ineqs_int.append(H)1098except (OverflowError, ValueError):1099H = Inequality_generic(A, b, index)1100self.ineqs_generic.append(H)1101if c.is_equality():1102A = [ -a for a in A ]1103b = -b1104try:1105H = Inequality_int(A, b, max_abs_coordinates, index)1106self.ineqs_int.append(H)1107except (OverflowError, ValueError):1108H = Inequality_generic(A, b, index)1109self.ineqs_generic.append(H)11101111cdef _cinit_from_Polyhedron(self, max_abs_coordinates, permutation, polyhedron):1112"""1113Initialize the inequalities from a Sage Polyhedron11141115See __cinit__ for a description of the arguments.11161117EXAMPLES::11181119sage: from sage.geometry.integral_points import InequalityCollection1120sage: line = Polyhedron(eqns=[(2,3,7)])1121sage: InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 )1122The collection of inequalities1123integer: (3, 7) x + 2 >= 01124integer: (-3, -7) x + -2 >= 01125"""1126for Hrep_obj in polyhedron.inequality_generator():1127A, b = self._make_A_b(Hrep_obj, permutation)1128try:1129H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())1130self.ineqs_int.append(H)1131except (OverflowError, ValueError):1132H = Inequality_generic(A, b, Hrep_obj.index())1133self.ineqs_generic.append(H)1134for Hrep_obj in polyhedron.equation_generator():1135A, b = self._make_A_b(Hrep_obj, permutation)1136# add inequality1137try:1138H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())1139self.ineqs_int.append(H)1140except (OverflowError, ValueError):1141H = Inequality_generic(A, b, Hrep_obj.index())1142self.ineqs_generic.append(H)1143# add sign-reversed inequality1144A = [ -a for a in A ]1145b = -b1146try:1147H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())1148self.ineqs_int.append(H)1149except (OverflowError, ValueError):1150H = Inequality_generic(A, b, Hrep_obj.index())1151self.ineqs_generic.append(H)11521153def prepare_next_to_inner_loop(self, p):1154r"""1155Peel off the next-to-inner loop.11561157In the next-to-inner loop of :func:`rectangular_box_points`,1158we have to repeatedly evaluate `A x-A_0 x_0+b`. To speed up1159computation, we pre-evaluate11601161.. math::11621163c = b + sum_{i=2} A_i x_i11641165and only compute `A x-A_0 x_0+b = A_1 x_1 +c \geq 0` in the1166next-to-inner loop.11671168INPUT:11691170- ``p`` -- the point coordinates. Only ``p[2:]`` coordinates1171are potentially used by this method.11721173EXAMPLES::11741175sage: from sage.geometry.integral_points import InequalityCollection, print_cache1176sage: P = Polyhedron(ieqs=[(2,3,7,11)])1177sage: ieq = InequalityCollection(P, Permutation([1,2,3]), [0]*3,[1]*3); ieq1178The collection of inequalities1179integer: (3, 7, 11) x + 2 >= 01180sage: ieq.prepare_next_to_inner_loop([2,1,3])1181sage: ieq.prepare_inner_loop([2,1,3])1182sage: print_cache(ieq)1183Cached inner loop: 3 * x_0 + 42 >= 01184Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 35 >= 01185"""1186for ineq in self.ineqs_int:1187(<Inequality_int>ineq).prepare_next_to_inner_loop(p)1188for ineq in self.ineqs_generic:1189(<Inequality_generic>ineq).prepare_next_to_inner_loop(p)11901191def prepare_inner_loop(self, p):1192r"""1193Peel off the inner loop.11941195In the inner loop of :func:`rectangular_box_points`, we have1196to repeatedly evaluate `A x+b\geq 0`. To speed up computation, we pre-evaluate11971198.. math::11991200c = A x - A_0 x_0 +b = b + sum_{i=1} A_i x_i12011202and only test `A_0 x_0 +c \geq 0` in the inner loop.12031204You must call :meth:`prepare_next_to_inner_loop` before1205calling this method.12061207INPUT:12081209- ``p`` -- the coordinates of the point to loop over. Only the1210``p[1:]`` entries are used.12111212EXAMPLES::12131214sage: from sage.geometry.integral_points import InequalityCollection, print_cache1215sage: P = Polyhedron(ieqs=[(2,3,7,11)])1216sage: ieq = InequalityCollection(P, Permutation([1,2,3]), [0]*3,[1]*3); ieq1217The collection of inequalities1218integer: (3, 7, 11) x + 2 >= 01219sage: ieq.prepare_next_to_inner_loop([2,1,3])1220sage: ieq.prepare_inner_loop([2,1,3])1221sage: print_cache(ieq)1222Cached inner loop: 3 * x_0 + 42 >= 01223Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 35 >= 01224"""1225for ineq in self.ineqs_int:1226(<Inequality_int>ineq).prepare_inner_loop(p)1227for ineq in self.ineqs_generic:1228(<Inequality_generic>ineq).prepare_inner_loop(p)12291230def swap_ineq_to_front(self, i):1231r"""1232Swap the ``i``-th entry of the list to the front of the list of inequalities.12331234INPUT:12351236- ``i`` -- Integer. The :class:`Inequality_int` to swap to the1237beginnig of the list of integral inequalities.12381239EXAMPLES::12401241sage: from sage.geometry.integral_points import InequalityCollection1242sage: P_QQ = Polyhedron(identity_matrix(3).columns() + [(-2, -1,-1)], base_ring=QQ)1243sage: iec = InequalityCollection(P_QQ, Permutation([1,2,3]), [0]*3,[1]*3)1244sage: iec1245The collection of inequalities1246integer: (3, -2, -2) x + 2 >= 01247integer: (-1, 4, -1) x + 1 >= 01248integer: (-1, -1, 4) x + 1 >= 01249integer: (-1, -1, -1) x + 1 >= 01250sage: iec.swap_ineq_to_front(3)1251sage: iec1252The collection of inequalities1253integer: (-1, -1, -1) x + 1 >= 01254integer: (3, -2, -2) x + 2 >= 01255integer: (-1, 4, -1) x + 1 >= 01256integer: (-1, -1, 4) x + 1 >= 01257"""1258i_th_entry = self.ineqs_int.pop(i)1259self.ineqs_int.insert(0, i_th_entry)12601261def are_satisfied(self, inner_loop_variable):1262r"""1263Return whether all inequalities are satisfied.12641265You must call :meth:`prepare_inner_loop` before calling this1266method.12671268INPUT:12691270- ``inner_loop_variable`` -- Integer. the 0-th coordinate of1271the lattice point.12721273OUTPUT:12741275Boolean. Whether the lattice point is in the polyhedron.12761277EXAMPLES::12781279sage: from sage.geometry.integral_points import InequalityCollection1280sage: line = Polyhedron(eqns=[(2,3,7)])1281sage: ieq = InequalityCollection(line, Permutation([1,2]), [0]*2, [1]*2 )1282sage: ieq.prepare_next_to_inner_loop([3,4])1283sage: ieq.prepare_inner_loop([3,4])1284sage: ieq.are_satisfied(3)1285False1286"""1287cdef int i1288for i in range(0,len(self.ineqs_int)):1289ineq = self.ineqs_int[i]1290if (<Inequality_int>ineq).is_not_satisfied(inner_loop_variable):1291if i>0:1292self.swap_ineq_to_front(i)1293return False1294for i in range(0,len(self.ineqs_generic)):1295ineq = self.ineqs_generic[i]1296if (<Inequality_generic>ineq).is_not_satisfied(inner_loop_variable):1297return False1298return True12991300def satisfied_as_equalities(self, inner_loop_variable):1301"""1302Return the inequalities (by their index) that are satisfied as1303equalities.13041305INPUT:13061307- ``inner_loop_variable`` -- Integer. the 0-th coordinate of1308the lattice point.13091310OUTPUT:13111312A set of integers in ascending order. Each integer is the1313index of a H-representation object of the polyhedron (either a1314inequality or an equation).13151316EXAMPLES::13171318sage: from sage.geometry.integral_points import InequalityCollection1319sage: quadrant = Polyhedron(rays=[(1,0), (0,1)])1320sage: ieqs = InequalityCollection(quadrant, Permutation([1,2]), [-1]*2, [1]*2 )1321sage: ieqs.prepare_next_to_inner_loop([-1,0])1322sage: ieqs.prepare_inner_loop([-1,0])1323sage: ieqs.satisfied_as_equalities(-1)1324frozenset([1])1325sage: ieqs.satisfied_as_equalities(0)1326frozenset([0, 1])1327sage: ieqs.satisfied_as_equalities(1)1328frozenset([1])1329"""1330cdef int i1331result = []1332for i in range(0,len(self.ineqs_int)):1333ineq = self.ineqs_int[i]1334if (<Inequality_int>ineq).is_equality(inner_loop_variable):1335result.append( (<Inequality_int>ineq).index )1336for i in range(0,len(self.ineqs_generic)):1337ineq = self.ineqs_generic[i]1338if (<Inequality_generic>ineq).is_equality(inner_loop_variable):1339result.append( (<Inequality_generic>ineq).index )1340return frozenset(result)1341134213431344cpdef print_cache(InequalityCollection inequality_collection):1345r"""1346Print the cached values in :class:`Inequality_int` (for1347debugging/doctesting only).13481349EXAMPLES::13501351sage: from sage.geometry.integral_points import InequalityCollection, print_cache1352sage: P = Polyhedron(ieqs=[(2,3,7)])1353sage: ieq = InequalityCollection(P, Permutation([1,2]), [0]*2,[1]*2); ieq1354The collection of inequalities1355integer: (3, 7) x + 2 >= 01356sage: ieq.prepare_next_to_inner_loop([3,5])1357sage: ieq.prepare_inner_loop([3,5])1358sage: print_cache(ieq)1359Cached inner loop: 3 * x_0 + 37 >= 01360Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 2 >= 01361"""1362cdef Inequality_int ieq = <Inequality_int>(inequality_collection.ineqs_int[0])1363print 'Cached inner loop: ' + \1364str(ieq.coeff) + ' * x_0 + ' + str(ieq.cache) + ' >= 0'1365print 'Cached next-to-inner loop: ' + \1366str(ieq.coeff) + ' * x_0 + ' + \1367str(ieq.coeff_next) + ' * x_1 + ' + str(ieq.cache_next) + ' >= 0'13681369137013711372