3 import ctypes
, ctypes
.util
6 from fractions
import Fraction
, gcd
8 libisl
= ctypes
.CDLL(ctypes
.util
.find_library('isl'))
10 libisl
.isl_printer_get_str
.restype
= ctypes
.c_char_p
14 'constant', 'symbol', 'symbols',
15 'eq', 'le', 'lt', 'ge', 'gt',
21 _CONTEXT
= isl
.Context()
23 def _polymorphic_method(func
):
24 @functools.wraps(func
)
26 if isinstance(b
, Expression
):
28 if isinstance(b
, numbers
.Rational
):
34 def _polymorphic_operator(func
):
35 # A polymorphic operator should call a polymorphic method, hence we just
36 # have to test the left operand.
37 @functools.wraps(func
)
39 if isinstance(a
, numbers
.Rational
):
42 elif isinstance(a
, Expression
):
44 raise TypeError('arguments must be linear expressions')
50 This class implements linear expressions.
53 def __new__(cls
, coefficients
=None, constant
=0):
54 if isinstance(coefficients
, str):
56 raise TypeError('too many arguments')
57 return cls
.fromstring(coefficients
)
58 self
= super().__new
__(cls
)
59 self
._coefficients
= {}
60 if isinstance(coefficients
, dict):
61 coefficients
= coefficients
.items()
62 if coefficients
is not None:
63 for symbol
, coefficient
in coefficients
:
64 if isinstance(symbol
, Expression
) and symbol
.issymbol():
66 elif not isinstance(symbol
, str):
67 raise TypeError('symbols must be strings')
68 if not isinstance(coefficient
, numbers
.Rational
):
69 raise TypeError('coefficients must be rational numbers')
71 self
._coefficients
[symbol
] = coefficient
72 if not isinstance(constant
, numbers
.Rational
):
73 raise TypeError('constant must be a rational number')
74 self
._constant
= constant
79 yield from sorted(self
._coefficients
)
83 return len(list(self
.symbols()))
85 def coefficient(self
, symbol
):
86 if isinstance(symbol
, Expression
) and symbol
.issymbol():
88 elif not isinstance(symbol
, str):
89 raise TypeError('symbol must be a string')
91 return self
._coefficients
[symbol
]
95 __getitem__
= coefficient
97 def coefficients(self
):
98 for symbol
in self
.symbols():
99 yield symbol
, self
.coefficient(symbol
)
103 return self
._constant
105 def isconstant(self
):
106 return len(self
._coefficients
) == 0
109 for symbol
in self
.symbols():
110 yield self
.coefficient(symbol
)
113 def values_int(self
):
114 for symbol
in self
.symbols():
115 return self
.coefficient(symbol
)
116 return int(self
.constant
)
120 if not self
.issymbol():
121 raise ValueError('not a symbol: {}'.format(self
))
122 for symbol
in self
.symbols():
126 return len(self
._coefficients
) == 1 and self
._constant
== 0
129 return (not self
.isconstant()) or bool(self
.constant
)
138 def __add__(self
, other
):
139 coefficients
= dict(self
.coefficients())
140 for symbol
, coefficient
in other
.coefficients():
141 if symbol
in coefficients
:
142 coefficients
[symbol
] += coefficient
144 coefficients
[symbol
] = coefficient
145 constant
= self
.constant
+ other
.constant
146 return Expression(coefficients
, constant
)
151 def __sub__(self
, other
):
152 coefficients
= dict(self
.coefficients())
153 for symbol
, coefficient
in other
.coefficients():
154 if symbol
in coefficients
:
155 coefficients
[symbol
] -= coefficient
157 coefficients
[symbol
] = -coefficient
158 constant
= self
.constant
- other
.constant
159 return Expression(coefficients
, constant
)
164 def __mul__(self
, other
):
165 if other
.isconstant():
166 coefficients
= dict(self
.coefficients())
167 for symbol
in coefficients
:
168 coefficients
[symbol
] *= other
.constant
169 constant
= self
.constant
* other
.constant
170 return Expression(coefficients
, constant
)
171 if isinstance(other
, Expression
) and not self
.isconstant():
172 raise ValueError('non-linear expression: '
173 '{} * {}'.format(self
._parenstr
(), other
._parenstr
()))
174 return NotImplemented
179 def __truediv__(self
, other
):
180 if other
.isconstant():
181 coefficients
= dict(self
.coefficients())
182 for symbol
in coefficients
:
183 coefficients
[symbol
] = \
184 Fraction(coefficients
[symbol
], other
.constant
)
185 constant
= Fraction(self
.constant
, other
.constant
)
186 return Expression(coefficients
, constant
)
187 if isinstance(other
, Expression
):
188 raise ValueError('non-linear expression: '
189 '{} / {}'.format(self
._parenstr
(), other
._parenstr
()))
190 return NotImplemented
192 def __rtruediv__(self
, other
):
193 if isinstance(other
, self
):
194 if self
.isconstant():
195 constant
= Fraction(other
, self
.constant
)
196 return Expression(constant
=constant
)
198 raise ValueError('non-linear expression: '
199 '{} / {}'.format(other
._parenstr
(), self
._parenstr
()))
200 return NotImplemented
204 symbols
= sorted(self
.symbols())
206 for symbol
in symbols
:
207 coefficient
= self
[symbol
]
212 string
+= ' + {}'.format(symbol
)
213 elif coefficient
== -1:
215 string
+= '-{}'.format(symbol
)
217 string
+= ' - {}'.format(symbol
)
220 string
+= '{}*{}'.format(coefficient
, symbol
)
221 elif coefficient
> 0:
222 string
+= ' + {}*{}'.format(coefficient
, symbol
)
224 assert coefficient
< 0
226 string
+= ' - {}*{}'.format(coefficient
, symbol
)
228 constant
= self
.constant
229 if constant
!= 0 and i
== 0:
230 string
+= '{}'.format(constant
)
232 string
+= ' + {}'.format(constant
)
235 string
+= ' - {}'.format(constant
)
240 def _parenstr(self
, always
=False):
242 if not always
and (self
.isconstant() or self
.issymbol()):
245 return '({})'.format(string
)
248 string
= '{}({{'.format(self
.__class
__.__name
__)
249 for i
, (symbol
, coefficient
) in enumerate(self
.coefficients()):
252 string
+= '{!r}: {!r}'.format(symbol
, coefficient
)
253 string
+= '}}, {!r})'.format(self
.constant
)
257 def fromstring(cls
, string
):
258 raise NotImplementedError
261 def __eq__(self
, other
):
263 # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs
264 return isinstance(other
, Expression
) and \
265 self
._coefficients
== other
._coefficients
and \
266 self
.constant
== other
.constant
269 return hash((self
._coefficients
, self
._constant
))
272 lcm
= functools
.reduce(lambda a
, b
: a
*b
// gcd(a
, b
),
273 [value
.denominator
for value
in self
.values()])
277 def _eq(self
, other
):
278 return Polyhedron(equalities
=[(self
- other
)._canonify
()])
281 def __le__(self
, other
):
282 return Polyhedron(inequalities
=[(self
- other
)._canonify
()])
285 def __lt__(self
, other
):
286 return Polyhedron(inequalities
=[(self
- other
)._canonify
() + 1])
289 def __ge__(self
, other
):
290 return Polyhedron(inequalities
=[(other
- self
)._canonify
()])
293 def __gt__(self
, other
):
294 return Polyhedron(inequalities
=[(other
- self
)._canonify
() + 1])
297 def constant(numerator
=0, denominator
=None):
298 if denominator
is None and isinstance(numerator
, numbers
.Rational
):
299 return Expression(constant
=numerator
)
301 return Expression(constant
=Fraction(numerator
, denominator
))
304 if not isinstance(name
, str):
305 raise TypeError('name must be a string')
306 return Expression(coefficients
={name
: 1})
309 if isinstance(names
, str):
310 names
= names
.replace(',', ' ').split()
311 return (symbol(name
) for name
in names
)
314 @_polymorphic_operator
318 @_polymorphic_operator
322 @_polymorphic_operator
326 @_polymorphic_operator
330 @_polymorphic_operator
337 This class implements polyhedrons.
340 def __new__(cls
, equalities
=None, inequalities
=None):
341 if isinstance(equalities
, str):
342 if inequalities
is not None:
343 raise TypeError('too many arguments')
344 return cls
.fromstring(equalities
)
345 self
= super().__new
__(cls
)
346 self
._equalities
= []
347 if equalities
is not None:
348 for constraint
in equalities
:
349 for value
in constraint
.values():
350 if value
.denominator
!= 1:
351 raise TypeError('non-integer constraint: '
352 '{} == 0'.format(constraint
))
353 self
._equalities
.append(constraint
)
354 self
._inequalities
= []
355 if inequalities
is not None:
356 for constraint
in inequalities
:
357 for value
in constraint
.values():
358 if value
.denominator
!= 1:
359 raise TypeError('non-integer constraint: '
360 '{} <= 0'.format(constraint
))
361 self
._inequalities
.append(constraint
)
362 self
._bset
= self
.to_isl()
367 def equalities(self
):
368 yield from self
._equalities
371 def inequalities(self
):
372 yield from self
._inequalities
376 return self
._constant
378 def isconstant(self
):
379 return len(self
._coefficients
) == 0
383 return bool(libisl
.isl_basic_set_is_empty(self
._bset
))
385 def constraints(self
):
386 yield from self
.equalities
387 yield from self
.inequalities
392 for constraint
in self
.constraints():
393 s
.update(constraint
.symbols
)
396 def symbol_count(self
):
398 for constraint
in self
.constraints():
399 s
.append(constraint
.symbols
)
404 return len(self
.symbols())
407 # return false if the polyhedron is empty, true otherwise
408 if self
._equalities
or self
._inequalities
:
414 def __contains__(self
, value
):
415 # is the value in the polyhedron?
416 raise NotImplementedError
418 def __eq__(self
, other
):
419 raise NotImplementedError
424 def isuniverse(self
):
425 return self
== universe
427 def isdisjoint(self
, other
):
428 # return true if the polyhedron has no elements in common with other
429 raise NotImplementedError
431 def issubset(self
, other
):
432 raise NotImplementedError
434 def __le__(self
, other
):
435 return self
.issubset(other
)
437 def __lt__(self
, other
):
438 raise NotImplementedError
440 def issuperset(self
, other
):
441 # test whether every element in other is in the polyhedron
443 if value
== self
.constraints():
447 raise NotImplementedError
449 def __ge__(self
, other
):
450 return self
.issuperset(other
)
452 def __gt__(self
, other
):
453 raise NotImplementedError
455 def union(self
, *others
):
456 # return a new polyhedron with elements from the polyhedron and all
457 # others (convex union)
458 raise NotImplementedError
460 def __or__(self
, other
):
461 return self
.union(other
)
463 def intersection(self
, *others
):
464 # return a new polyhedron with elements common to the polyhedron and all
466 # a poor man's implementation could be:
467 # equalities = list(self.equalities)
468 # inequalities = list(self.inequalities)
469 # for other in others:
470 # equalities.extend(other.equalities)
471 # inequalities.extend(other.inequalities)
472 # return self.__class__(equalities, inequalities)
473 raise NotImplementedError
475 def __and__(self
, other
):
476 return self
.intersection(other
)
478 def difference(self
, *others
):
479 # return a new polyhedron with elements in the polyhedron that are not
481 raise NotImplementedError
483 def __sub__(self
, other
):
484 return self
.difference(other
)
488 for constraint
in self
.equalities
:
489 constraints
.append('{} == 0'.format(constraint
))
490 for constraint
in self
.inequalities
:
491 constraints
.append('{} <= 0'.format(constraint
))
492 return '{{{}}}'.format(', '.join(constraints
))
495 equalities
= list(self
.equalities
)
496 inequalities
= list(self
.inequalities
)
497 return '{}(equalities={!r}, inequalities={!r})' \
498 ''.format(self
.__class
__.__name
__, equalities
, inequalities
)
501 def fromstring(cls
, string
):
502 raise NotImplementedError
506 ip
= libisl
.isl_printer_to_str(_CONTEXT
)
507 ip
= libisl
.isl_printer_print_val(ip
, self
) #self should be value
508 string
= libisl
.isl_printer_get_str(ip
).decode()
514 space
= libisl
.isl_space_set_alloc(_CONTEXT
, 0, len(self
.symbol_count()))
515 bset
= libisl
.isl_basic_set_empty(libisl
.isl_space_copy(space
))
516 ls
= libisl
.isl_local_space_from_space(libisl
.isl_space_copy(space
))
517 ceq
= libisl
.isl_equality_alloc(libisl
.isl_local_space_copy(ls
))
518 cin
= libisl
.isl_inequality_alloc(libisl
.isl_local_space_copy(ls
))
519 dict_ex
= Expression().__dict
__
522 if there are equalities/inequalities, take each constant and coefficient and add as a constraint to the basic set
523 need to change the symbols method to a lookup table for the integer value for each letter that could be a symbol
526 for _constant
in dict_ex
:
527 value
= dict_ex
.get('_constant')
528 ceq
= libisl
.isl_constraint_set_constant_val(ceq
, value
)
529 for _coefficients
in dict_ex
:
530 value_co
= dict_ex
.get('_coefficients')
532 ceq
= libisl
.isl_constraint_set_coefficient_si(ceq
, libisl
.isl_set_dim
, self
.symbols(), value_co
)
533 bset
= libisl
.isl_set_add_constraint(bset
, ceq
)
534 bset
= libisl
.isl_basic_set_project_out(bset
, libisl
.isl_set_dim
, 1, 1);
535 elif self
.inequalities
:
536 for _constant
in dict_ex
:
537 value
= dict_ex
.get('_constant')
538 cin
= libisl
.isl_constraint_set_constant_val(cin
, value
)
539 for _coefficients
in dict_ex
:
540 value_co
= dict_ex
.get('_coefficients')
542 cin
= libisl
.isl_constraint_set_coefficient_si(cin
, libisl
.isl_set_dim
, self
.symbols(), value_co
)
543 bset
= libisl
.isl_set_add_contraint(bset
, cin
)
545 string
= libisl
.isl_printer_print_basic_set(bset
)
555 universe
= Polyhedron()