1 # Copyright 2014 MINES ParisTech
3 # This file is part of LinPy.
5 # LinPy is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # LinPy is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with LinPy. If not, see <http://www.gnu.org/licenses/>.
22 from . import islhelper
24 from .islhelper
import mainctx
, libisl
25 from .geometry
import GeometricObject
, Point
26 from .linexprs
import LinExpr
, Rational
27 from .domains
import Domain
32 'Lt', 'Le', 'Eq', 'Ne', 'Ge', 'Gt',
37 class Polyhedron(Domain
):
39 A convex polyhedron (or simply "polyhedron") is the space defined by a
40 system of linear equalities and inequalities. This space can be unbounded. A
41 Z-polyhedron (simply called "polyhedron" in LinPy) is the set of integer
42 points in a convex polyhedron.
52 def __new__(cls
, equalities
=None, inequalities
=None):
54 Return a polyhedron from two sequences of linear expressions: equalities
55 is a list of expressions equal to 0, and inequalities is a list of
56 expressions greater or equal to 0. For example, the polyhedron
57 0 <= x <= 2, 0 <= y <= 2 can be constructed with:
59 >>> x, y = symbols('x y')
60 >>> square1 = Polyhedron([], [x, 2 - x, y, 2 - y])
62 And(0 <= x, x <= 2, 0 <= y, y <= 2)
64 It may be easier to use comparison operators LinExpr.__lt__(),
65 LinExpr.__le__(), LinExpr.__ge__(), LinExpr.__gt__(), or functions Lt(),
66 Le(), Eq(), Ge() and Gt(), using one of the following instructions:
68 >>> x, y = symbols('x y')
69 >>> square1 = (0 <= x) & (x <= 2) & (0 <= y) & (y <= 2)
70 >>> square1 = Le(0, x, 2) & Le(0, y, 2)
72 It is also possible to build a polyhedron from a string.
74 >>> square1 = Polyhedron('0 <= x <= 2, 0 <= y <= 2')
76 Finally, a polyhedron can be constructed from a GeometricObject
77 instance, calling the GeometricObject.aspolyedron() method. This way, it
78 is possible to compute the polyhedral hull of a Domain instance, i.e.,
79 the convex hull of two polyhedra:
81 >>> square1 = Polyhedron('0 <= x <= 2, 0 <= y <= 2')
82 >>> square2 = Polyhedron('1 <= x <= 3, 1 <= y <= 3')
83 >>> Polyhedron(square1 | square2)
84 And(0 <= x, 0 <= y, x <= y + 2, y <= x + 2, x <= 3, y <= 3)
86 if isinstance(equalities
, str):
87 if inequalities
is not None:
88 raise TypeError('too many arguments')
89 return cls
.fromstring(equalities
)
90 elif isinstance(equalities
, GeometricObject
):
91 if inequalities
is not None:
92 raise TypeError('too many arguments')
93 return equalities
.aspolyhedron()
95 if equalities
is not None:
96 for equality
in equalities
:
97 if not isinstance(equality
, LinExpr
):
98 raise TypeError('equalities must be linear expressions')
99 sc_equalities
.append(equality
.scaleint())
101 if inequalities
is not None:
102 for inequality
in inequalities
:
103 if not isinstance(inequality
, LinExpr
):
104 raise TypeError('inequalities must be linear expressions')
105 sc_inequalities
.append(inequality
.scaleint())
106 symbols
= cls
._xsymbols
(sc_equalities
+ sc_inequalities
)
107 islbset
= cls
._toislbasicset
(sc_equalities
, sc_inequalities
, symbols
)
108 return cls
._fromislbasicset
(islbset
, symbols
)
111 def equalities(self
):
113 The tuple of equalities. This is a list of LinExpr instances that are
114 equal to 0 in the polyhedron.
116 return self
._equalities
119 def inequalities(self
):
121 The tuple of inequalities. This is a list of LinExpr instances that are
122 greater or equal to 0 in the polyhedron.
124 return self
._inequalities
127 def constraints(self
):
129 The tuple of constraints, i.e., equalities and inequalities. This is
130 semantically equivalent to: equalities + inequalities.
132 return self
._equalities
+ self
._inequalities
138 def make_disjoint(self
):
141 def isuniverse(self
):
142 islbset
= self
._toislbasicset
(self
.equalities
, self
.inequalities
,
144 universe
= bool(libisl
.isl_basic_set_is_universe(islbset
))
145 libisl
.isl_basic_set_free(islbset
)
148 def aspolyhedron(self
):
151 def convex_union(self
, *others
):
153 Return the convex union of two or more polyhedra.
156 if not isinstance(other
, Polyhedron
):
157 raise TypeError('arguments must be Polyhedron instances')
158 return Polyhedron(self
.union(*others
))
160 def __contains__(self
, point
):
161 if not isinstance(point
, Point
):
162 raise TypeError('point must be a Point instance')
163 if self
.symbols
!= point
.symbols
:
164 raise ValueError('arguments must belong to the same space')
165 for equality
in self
.equalities
:
166 if equality
.subs(point
.coordinates()) != 0:
168 for inequality
in self
.inequalities
:
169 if inequality
.subs(point
.coordinates()) < 0:
173 def subs(self
, symbol
, expression
=None):
174 equalities
= [equality
.subs(symbol
, expression
)
175 for equality
in self
.equalities
]
176 inequalities
= [inequality
.subs(symbol
, expression
)
177 for inequality
in self
.inequalities
]
178 return Polyhedron(equalities
, inequalities
)
180 def asinequalities(self
):
182 Express the polyhedron using inequalities, given as a list of
183 expressions greater or equal to 0.
185 inequalities
= list(self
.equalities
)
186 inequalities
.extend([-expression
for expression
in self
.equalities
])
187 inequalities
.extend(self
.inequalities
)
190 def widen(self
, other
):
192 Compute the standard widening of two polyhedra, à la Halbwachs.
194 In its current implementation, this method is slow and should not be
195 used on large polyhedra.
197 if not isinstance(other
, Polyhedron
):
198 raise TypeError('argument must be a Polyhedron instance')
199 inequalities1
= self
.asinequalities()
200 inequalities2
= other
.asinequalities()
202 for inequality1
in inequalities1
:
203 if other
<= Polyhedron(inequalities
=[inequality1
]):
204 inequalities
.append(inequality1
)
205 for inequality2
in inequalities2
:
206 for i
in range(len(inequalities1
)):
207 inequalities3
= inequalities1
[:i
] + inequalities
[i
+ 1:]
208 inequalities3
.append(inequality2
)
209 polyhedron3
= Polyhedron(inequalities
=inequalities3
)
210 if self
== polyhedron3
:
211 inequalities
.append(inequality2
)
213 return Polyhedron(inequalities
=inequalities
)
216 def _fromislbasicset(cls
, islbset
, symbols
):
217 islconstraints
= islhelper
.isl_basic_set_constraints(islbset
)
220 for islconstraint
in islconstraints
:
221 constant
= libisl
.isl_constraint_get_constant_val(islconstraint
)
222 constant
= islhelper
.isl_val_to_int(constant
)
224 for index
, symbol
in enumerate(symbols
):
225 coefficient
= libisl
.isl_constraint_get_coefficient_val(islconstraint
,
226 libisl
.isl_dim_set
, index
)
227 coefficient
= islhelper
.isl_val_to_int(coefficient
)
229 coefficients
[symbol
] = coefficient
230 expression
= LinExpr(coefficients
, constant
)
231 if libisl
.isl_constraint_is_equality(islconstraint
):
232 equalities
.append(expression
)
234 inequalities
.append(expression
)
235 libisl
.isl_basic_set_free(islbset
)
236 self
= object().__new
__(Polyhedron
)
237 self
._equalities
= tuple(equalities
)
238 self
._inequalities
= tuple(inequalities
)
239 self
._symbols
= cls
._xsymbols
(self
.constraints
)
240 self
._dimension
= len(self
._symbols
)
244 def _toislbasicset(cls
, equalities
, inequalities
, symbols
):
245 dimension
= len(symbols
)
246 indices
= {symbol
: index
for index
, symbol
in enumerate(symbols
)}
247 islsp
= libisl
.isl_space_set_alloc(mainctx
, 0, dimension
)
248 islbset
= libisl
.isl_basic_set_universe(libisl
.isl_space_copy(islsp
))
249 islls
= libisl
.isl_local_space_from_space(islsp
)
250 for equality
in equalities
:
251 isleq
= libisl
.isl_equality_alloc(libisl
.isl_local_space_copy(islls
))
252 for symbol
, coefficient
in equality
.coefficients():
253 islval
= str(coefficient
).encode()
254 islval
= libisl
.isl_val_read_from_str(mainctx
, islval
)
255 index
= indices
[symbol
]
256 isleq
= libisl
.isl_constraint_set_coefficient_val(isleq
,
257 libisl
.isl_dim_set
, index
, islval
)
258 if equality
.constant
!= 0:
259 islval
= str(equality
.constant
).encode()
260 islval
= libisl
.isl_val_read_from_str(mainctx
, islval
)
261 isleq
= libisl
.isl_constraint_set_constant_val(isleq
, islval
)
262 islbset
= libisl
.isl_basic_set_add_constraint(islbset
, isleq
)
263 for inequality
in inequalities
:
264 islin
= libisl
.isl_inequality_alloc(libisl
.isl_local_space_copy(islls
))
265 for symbol
, coefficient
in inequality
.coefficients():
266 islval
= str(coefficient
).encode()
267 islval
= libisl
.isl_val_read_from_str(mainctx
, islval
)
268 index
= indices
[symbol
]
269 islin
= libisl
.isl_constraint_set_coefficient_val(islin
,
270 libisl
.isl_dim_set
, index
, islval
)
271 if inequality
.constant
!= 0:
272 islval
= str(inequality
.constant
).encode()
273 islval
= libisl
.isl_val_read_from_str(mainctx
, islval
)
274 islin
= libisl
.isl_constraint_set_constant_val(islin
, islval
)
275 islbset
= libisl
.isl_basic_set_add_constraint(islbset
, islin
)
279 def fromstring(cls
, string
):
280 domain
= Domain
.fromstring(string
)
281 if not isinstance(domain
, Polyhedron
):
282 raise ValueError('non-polyhedral expression: {!r}'.format(string
))
287 for equality
in self
.equalities
:
288 left
, right
, swap
= 0, 0, False
289 for i
, (symbol
, coefficient
) in enumerate(equality
.coefficients()):
291 left
+= coefficient
* symbol
293 right
-= coefficient
* symbol
296 if equality
.constant
> 0:
297 left
+= equality
.constant
299 right
-= equality
.constant
301 left
, right
= right
, left
302 strings
.append('{} == {}'.format(left
, right
))
303 for inequality
in self
.inequalities
:
305 for symbol
, coefficient
in inequality
.coefficients():
307 left
-= coefficient
* symbol
309 right
+= coefficient
* symbol
310 if inequality
.constant
< 0:
311 left
-= inequality
.constant
313 right
+= inequality
.constant
314 strings
.append('{} <= {}'.format(left
, right
))
315 if len(strings
) == 1:
318 return 'And({})'.format(', '.join(strings
))
320 def _repr_latex_(self
):
322 for equality
in self
.equalities
:
323 strings
.append('{} = 0'.format(equality
._repr
_latex
_().strip('$')))
324 for inequality
in self
.inequalities
:
325 strings
.append('{} \\ge 0'.format(inequality
._repr
_latex
_().strip('$')))
326 return '$${}$$'.format(' \\wedge '.join(strings
))
329 def fromsympy(cls
, expr
):
330 domain
= Domain
.fromsympy(expr
)
331 if not isinstance(domain
, Polyhedron
):
332 raise ValueError('non-polyhedral expression: {!r}'.format(expr
))
338 for equality
in self
.equalities
:
339 constraints
.append(sympy
.Eq(equality
.tosympy(), 0))
340 for inequality
in self
.inequalities
:
341 constraints
.append(sympy
.Ge(inequality
.tosympy(), 0))
342 return sympy
.And(*constraints
)
345 class EmptyType(Polyhedron
):
347 The empty polyhedron, whose set of constraints is not satisfiable.
351 self
= object().__new
__(cls
)
352 self
._equalities
= (Rational(1),)
353 self
._inequalities
= ()
358 def widen(self
, other
):
359 if not isinstance(other
, Polyhedron
):
360 raise ValueError('argument must be a Polyhedron instance')
366 def _repr_latex_(self
):
367 return '$$\\emptyset$$'
372 class UniverseType(Polyhedron
):
374 The universe polyhedron, whose set of constraints is always satisfiable,
379 self
= object().__new
__(cls
)
380 self
._equalities
= ()
381 self
._inequalities
= ()
389 def _repr_latex_(self
):
392 Universe
= UniverseType()
395 def _pseudoconstructor(func
):
396 @functools.wraps(func
)
397 def wrapper(expr1
, expr2
, *exprs
):
398 exprs
= (expr1
, expr2
) + exprs
400 if not isinstance(expr
, LinExpr
):
401 if isinstance(expr
, numbers
.Rational
):
402 expr
= Rational(expr
)
404 raise TypeError('arguments must be rational numbers '
405 'or linear expressions')
412 Create the polyhedron with constraints expr1 < expr2 < expr3 ...
415 for left
, right
in zip(exprs
, exprs
[1:]):
416 inequalities
.append(right
- left
- 1)
417 return Polyhedron([], inequalities
)
422 Create the polyhedron with constraints expr1 <= expr2 <= expr3 ...
425 for left
, right
in zip(exprs
, exprs
[1:]):
426 inequalities
.append(right
- left
)
427 return Polyhedron([], inequalities
)
432 Create the polyhedron with constraints expr1 == expr2 == expr3 ...
435 for left
, right
in zip(exprs
, exprs
[1:]):
436 equalities
.append(left
- right
)
437 return Polyhedron(equalities
, [])
442 Create the domain such that expr1 != expr2 != expr3 ... The result is a
443 Domain object, not a Polyhedron.
446 for left
, right
in zip(exprs
, exprs
[1:]):
447 domain
&= ~
Eq(left
, right
)
453 Create the polyhedron with constraints expr1 >= expr2 >= expr3 ...
456 for left
, right
in zip(exprs
, exprs
[1:]):
457 inequalities
.append(left
- right
)
458 return Polyhedron([], inequalities
)
463 Create the polyhedron with constraints expr1 > expr2 > expr3 ...
466 for left
, right
in zip(exprs
, exprs
[1:]):
467 inequalities
.append(left
- right
- 1)
468 return Polyhedron([], inequalities
)