Skip to content

Commit 0ccacce

Browse files
committed
add pplite bsa file
1 parent 00328c7 commit 0ccacce

2 files changed

Lines changed: 366 additions & 0 deletions

File tree

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
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()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ sphinx
44
sphinxcontrib-bibtex
55
sphinxcontrib-websupport
66
pynormaliz
7+
pplitepy

0 commit comments

Comments
 (0)