1+ from cutgeneratingfunctionology .spam .basic_semialgebraic import BasicSemialgebraicSet_polyhedral
2+
3+ from pplite import Variable as pplite_Var , Constraint as pplite_Con , Linear_Expression as pplite_Lin_Expr , Affine_Expression as pplite_Aff_expr , NNC_Polyhedron as pplite_NNC_Polyhedron , PPliteGenerator , Polyhedron_Constraint_Rel , Polyhedron_Generator_Rel
4+
5+ poly_is_included_pplite = Polyhedron_Constraint_Rel .is_included ()
6+ point_is_included_pplite = Polyhedron_Generator_Rel .subsumes ()
7+
8+ class BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron (BasicSemialgebraicSet_polyhedral ):
9+
10+ r"""
11+ A (possibly half-open) polyhedral basic semialgebraic set,
12+ represented by a PPLite ``NNC_Polyhedron``
13+
14+ """
15+
16+ def __init__ (self , ambient_dim = None , polyhedron = None , base_ring = None , poly_ring = None , ** options ):
17+ r"""
18+ Initialize a basic semialgebraic set as the universe in
19+ ``ambient_dim``, or, if ``polyhedron`` (an ``NNC_Polyhedron``,
20+ which after that belongs to this object) is provided, as
21+ that.
22+
23+ TEST::
24+
25+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
26+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
27+ sage: P.add_linear_constraint([0,1],0,operator.ge)
28+ sage: P.add_linear_constraint([1,0],0,operator.ge)
29+ sage: P.add_linear_constraint([2,3],-6,operator.lt)
30+ sage: P
31+ BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron([x1>=0, x0>=0, -2*x0-3*x1+6>0], names=[x0, x1])
32+ sage: sorted(P.eq_poly())
33+ []
34+ sage: sorted(P.lt_poly())
35+ [2*x0 + 3*x1 - 6]
36+ sage: sorted(P.le_poly())
37+ [-x1, -x0]
38+ """
39+ if ambient_dim is None and polyhedron is not None :
40+ ambient_dim = polyhedron .space_dimension ()
41+ if base_ring is None and poly_ring is None :
42+ base_ring = QQ
43+ poly_ring , base_ring , ambient_dim , names = self ._poly_ring_from_options (
44+ ambient_dim = ambient_dim , base_ring = base_ring , poly_ring = poly_ring , ** options )
45+ if base_ring is not QQ :
46+ raise ValueError ("only base_ring=QQ is supported" )
47+ super (BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron , self ).__init__ (poly_ring = poly_ring )
48+ if polyhedron is None :
49+ self ._polyhedron = pplite_NNC_Polyhedron (dim_type = int (ambient_dim ), spec_elem = 'universe' , topology = "nnc" ) # To work with pplite, ambient_dim is required to be type int
50+ else :
51+ self ._polyhedron = polyhedron
52+
53+ @staticmethod
54+ def _pplite_constraint (lhs , cst , op ):
55+ r"""
56+ Make a PPL ``Constraint`` ``lhs`` * x + cst ``op`` 0,
57+ where ``lhs`` is be a vector of length ambient_dim.
58+ """
59+ lcd = lcm (lcm (x .denominator () for x in lhs ), cst .denominator ())
60+ lin_expr = sum ([int ((lcd * lhs [i ]))* pplite_Var (i ) for i in range (len (lhs ))])
61+ aff_expr = pplite_Aff_expr (lin_expr , int (lcd * cst ))
62+ #linexpr = pplite_Lin_Expr(lhs * lcd, cst * lcd)
63+ if op == operator .lt :
64+ return (aff_expr < 0 )
65+ elif op == operator .gt :
66+ return (aff_expr > 0 )
67+ elif op == operator .eq :
68+ return (aff_expr == 0 )
69+ elif op == operator .le :
70+ return (aff_expr <= 0 )
71+ elif op == operator .ge :
72+ return (aff_expr >= 0 )
73+ else :
74+ raise ValueError ("{} is not a supported operator" .format (op ))
75+
76+ def __copy__ (self ):
77+ r"""
78+ Make a copy of ``self``.
79+
80+ TESTS:
81+
82+ Test that it is actually making a copy of the (mutable!) NNC_Polyhedron::
83+
84+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
85+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
86+ sage: P._polyhedron is copy(P)._polyhedron
87+ False
88+ """
89+ return self .__class__ (polyhedron = pplite_NNC_Polyhedron (nnc_poly = self ._polyhedron ), poly_ring = self .poly_ring ())
90+
91+ def _repr_ (self ):
92+ constraints = self ._polyhedron .constraints ()
93+ names = list (self .poly_ring ().gens ())
94+ return 'BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron({}, names={})' .format (
95+ constraints , names )
96+
97+ def closure (self , bsa_class = 'formal_closure' ):
98+ r"""
99+ Return the basic semialgebraic set that is the topological closure
100+ of ``self``.
101+ """
102+ # Because our description consists of minimized constraints, the closure is
103+ # just the formal closure.
104+ return self .formal_closure (bsa_class = bsa_class )
105+
106+ def relint (self , bsa_class = 'formal_relint' ):
107+ r"""
108+ Return the basic semialgebraic set that is the topological relative interior
109+ of ``self``.
110+ """
111+ # Because our description consists of minimized constraints, the relint is
112+ # just the formal relint.
113+ return self .formal_relint (bsa_class = bsa_class )
114+
115+ def eq_poly (self ):
116+ r"""
117+ Return a list of the polynomials `f` in equations `f(x) = 0`
118+ in the description of ``self``.
119+
120+ Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``.
121+ """
122+ # add tests
123+ for c in self ._polyhedron .constraints ():
124+ if c .is_equality ():
125+ coeff = [c .coefficient (pplite_Var (i )) for i in range (c .space_dimension ())]
126+ # observe: coeffients in a constraint of NNC_Polyhedron could have gcd != 1.
127+ gcd_c = gcd (gcd (coeff ), c .inhomogeneous_term ())
128+ t = sum (QQ (x )/ gcd_c * y for x , y in zip (coeff , self .poly_ring ().gens ())) + QQ (c .inhomogeneous_term ())/ gcd_c # not type stable, make it type stable
129+ yield self .poly_ring ()(t )
130+
131+ def lt_poly (self ):
132+ r"""
133+ Return a list of the polynomials `f` in strict inequalities `f(x) < 0`
134+ in the description of ``self``.
135+
136+ Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``.
137+ """
138+ for c in self ._polyhedron .constraints ():
139+ if c .is_strict_inequality ():
140+ coeff = [c .coefficient (pplite_Var (i )) for i in range (c .space_dimension ())]
141+ gcd_c = gcd (gcd (coeff ), c .inhomogeneous_term ())
142+ # constraint is written with '>', while lt_poly records '<' relation
143+ t = sum (- QQ (x )/ gcd_c * y for x , y in zip (coeff , self .poly_ring ().gens ())) - QQ (c .inhomogeneous_term ())/ gcd_c
144+ yield self .poly_ring ()(t )
145+
146+ def le_poly (self ):
147+ r"""
148+ Return a list of the polynomials `f` in inequalities `f(x) \leq 0`
149+ in the description of ``self``.
150+
151+ Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``.
152+ """
153+ for c in self ._polyhedron .constraints ():
154+ if c .is_nonstrict_inequality ():
155+ coeff = [c .coefficient (pplite_Var (i )) for i in range (c .space_dimension ())]
156+ gcd_c = gcd (gcd (coeff ), c .inhomogeneous_term ())
157+ # constraint is written with '>=', while lt_poly records '<=' relation
158+ t = sum (- QQ (x )/ gcd_c * y for x , y in zip (coeff , self .poly_ring ().gens ())) - QQ (c .inhomogeneous_term ())/ gcd_c
159+ yield self .poly_ring ()(t )
160+
161+ # override the default implementation
162+ def __contains__ (self , point ):
163+ r"""
164+ Whether the set contains the ``point`` (vector).
165+ """
166+ rational_list = [ QQ (x ) for x in point ]
167+ num_list = [x .numerator () for x in rational_list ]
168+ den_list = [x .denominator () for x in rational_list ]
169+ common_den = lcm (den_list )
170+ coef = [common_den // den_list [i ] * num_list [i ] for i in range (len (rational_list ))]
171+ pt = ppl_point (Linear_Expression (coef , 0 ), common_den )
172+ return self ._polyhedron .relation_with (pt ).implies (point_is_included_pplite )
173+
174+ # override the abstract methods
175+ def find_point (self ):
176+ r"""
177+ Find a point in ``self``.
178+
179+ EXAMPLES::
180+
181+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
182+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
183+ sage: P.add_linear_constraint([0,1],0,operator.ge)
184+ sage: P.add_linear_constraint([1,0],0,operator.ge)
185+ sage: P.add_linear_constraint([2,3],-6,operator.lt)
186+ sage: P.find_point()
187+ (1, 2/3)
188+ """
189+ # pplite has a different representation points, closure points, of NNC polys compared to ppl
190+ # so the find_point method yields different results
191+
192+ def to_point (g , ambient_dim ):
193+ den = g .divisor ()
194+ # g.set_space_dimension(ambient_dim) # PPlite generators have space dim of largest dimension of variables in point expression.
195+ # To sum points as vectors in sagemath vectors need to have the same dimension.
196+ # To fix, update the points space dim to be a defined ambient dimension.
197+ # TODO: update after this gets fixed in pplite.
198+ return vector (QQ , (QQ (x )/ den for x in [g .coefficient (v ) for v in range (ambient_dim )])) # based on email this should in theory works
199+
200+ def to_vector (g , ambient_dim ):
201+ den = g .divisor ()
202+ # g.set_space_dimension(ambient_dim)
203+ return vector (QQ , (QQ (x )/ den for x in [g .coefficient (v ) for v in range (ambient_dim )]))
204+ points = [to_point (g , self ._polyhedron .space_dimension ()) for g in self ._polyhedron .generators ()
205+ if g .is_point () or g .is_closure_point ()]
206+ rays = [to_vector (g , self ._polyhedron .space_dimension ()) for g in self ._polyhedron .generators ()
207+ if g .is_ray ()]
208+ if points :
209+ p = sum (points ) / len (points )
210+ if rays :
211+ p += sum (rays ) / len (rays )
212+ return p
213+ raise NotImplementedError ("find_test_point implementation cannot handle this case" )
214+
215+ def add_space_dimensions_and_embed (self , space_dim_to_add ):
216+ r"""
217+ Mutate ``self`` by injecting it into a higher dimensional space.
218+
219+ EXAMPLES::
220+
221+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
222+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
223+ sage: P.add_linear_constraint([0,1],0,operator.ge)
224+ sage: P.add_linear_constraint([1,0],0,operator.ge)
225+ sage: P.add_linear_constraint([2,3],-6,operator.lt)
226+ sage: P.add_space_dimensions_and_embed(2)
227+ sage: P
228+ BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron([x1>=0, x0>=0, -2*x0-3*x1+6>0], names=[x0, x1, x2, x3])
229+ sage: P.ambient_dim()
230+ 4
231+ """
232+ super (BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron , self ).add_space_dimensions_and_embed (space_dim_to_add )
233+ self ._polyhedron .add_space_dimensions (int (space_dim_to_add ), False )
234+
235+ @staticmethod
236+ def _pplite_constraint (lhs , cst , op ):
237+ r"""
238+ Make a PPLite ``Constraint`` ``lhs`` * x + cst ``op`` 0,
239+ where ``lhs`` is be a vector of length ambient_dim.
240+
241+ TESTS::
242+
243+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
244+ sage: from pplite import Constraint as pplite_Con
245+ sage: test_constraint = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron._pplite_constraint([0,1], 0, operator.ge)
246+ sage: test_constraint
247+ x1>=0
248+ sage: isinstance(test_constraint, pplite_Con)
249+ True
250+
251+ """
252+ lcd = lcm (lcm (x .denominator () for x in lhs ), cst .denominator ())
253+ aff_expr = sum ([int ((lcd * lhs [i ]))* pplite_Var (i ) for i in range (len (lhs ))]) + int (lcd * cst )
254+ if op == operator .lt :
255+ return (aff_expr < 0 )
256+ elif op == operator .gt :
257+ return (aff_expr > 0 )
258+ elif op == operator .eq :
259+ return (aff_expr == 0 )
260+ elif op == operator .le :
261+ return (aff_expr <= 0 )
262+ elif op == operator .ge :
263+ return (aff_expr >= 0 )
264+ else :
265+ raise ValueError ("{} is not a supported operator" .format (op ))
266+
267+ def linear_function_upper_bound (self , form ):
268+ r"""
269+ Find an upper bound for ``form`` (a vector) on ``self``.
270+ This upper bound is the supremum.
271+
272+ If ``self`` is empty, it returns -oo
273+ """
274+
275+ def to_point (g ):
276+ den = g .divisor ()
277+ return vector (QQ , (QQ (x )/ den for x in [g .coefficient (v ) for v in range (g .space_dimision ())]))
278+
279+ def to_vector (g ):
280+ return vector (QQ , (QQ (x ) for x in [g .coefficient (v ) for v in range (g .space_dimision ())]))
281+ if self ._polyhedron .is_empty ():
282+ return - Infinity
283+ form = vector (form )
284+ for g in self ._polyhedron .generators ():
285+ if g .is_line ():
286+ if to_vector (g ) * form != 0 :
287+ return + Infinity
288+ if g .is_ray ():
289+ if to_vector (g ) * form > 0 :
290+ return + Infinity
291+ points = [to_point (g ) for g in self ._polyhedron .generators ()
292+ if g .is_point () or g .is_closure_point ()]
293+ return max (p * form for p in points )
294+
295+ def is_linear_constraint_valid (self , lhs , cst , op ):
296+ r"""
297+ Whether the constraint ``lhs`` * x + cst ``op`` 0
298+ is satisfied for all points of ``self``,
299+ where ``lhs`` is be a vector of length ambient_dim.
300+
301+ EXAMPLES::
302+
303+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
304+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
305+ sage: P.add_linear_constraint([0,1],0,operator.ge)
306+ sage: P.add_linear_constraint([1,0],0,operator.ge)
307+ sage: P.add_linear_constraint([2,3],-6,operator.lt)
308+ sage: P.is_linear_constraint_valid([1,1],-3,operator.lt)
309+ True
310+ sage: P.is_linear_constraint_valid([0,1],0,operator.gt)
311+ False
312+ """
313+ lhs = vector (lhs )
314+ constraint = self ._pplite_constraint (lhs , cst , op )
315+ return self ._polyhedron .relation_with (constraint ).implies (poly_is_included_pplite )
316+
317+ def add_linear_constraint (self , lhs , cst , op ):
318+ r"""
319+ Add the constraint ``lhs`` * x + cst ``op`` 0,
320+ where ``lhs`` is a vector of length ambient_dim, and
321+ ``op`` is one of ``operator.lt``, ``operator.gt``, ``operator.eq``,
322+ ``operator.le``, ``operator.ge``
323+
324+ EXAMPLES::
325+
326+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
327+ sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2)
328+ sage: P.add_linear_constraint([2,3],-6,operator.gt)
329+ sage: sorted(P.lt_poly())
330+ [-2*x0 - 3*x1 + 6]
331+ """
332+ lhs = vector (lhs )
333+ constraint = self ._pplite_constraint (lhs , cst , op )
334+ self ._polyhedron .add_constraint (constraint )
335+
336+ def is_empty (self ):
337+ """
338+ EXAMPLES::
339+
340+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
341+ sage: S = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(1)
342+ sage: S.add_linear_constraint([1], -1, operator.ge)
343+ sage: S.is_empty()
344+ False
345+ sage: S.add_linear_constraint([1], +1, operator.le)
346+ sage: S.is_empty()
347+ True
348+ """
349+ return self ._polyhedron .is_empty ()
350+
351+ def is_universe (self ):
352+ """
353+ EXAMPLES::
354+
355+ sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import *
356+ sage: S = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(1)
357+ sage: S.add_linear_constraint([0], 0, operator.eq)
358+ sage: S.is_universe()
359+ True
360+ sage: S.add_linear_constraint([1], 1, operator.le)
361+ sage: S.is_universe()
362+ False
363+ """
364+ self ._polyhedron .minimize ()
365+ return self ._polyhedron .is_universe ()
0 commit comments