7 from fractions
import Fraction
, gcd
12 'Constant', 'Symbol', 'symbols',
13 'Eq', 'Le', 'Lt', 'Ge', 'Gt',
22 if not input.endswith(';'):
24 proc
= subprocess
.Popen(['iscc'],
25 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
26 universal_newlines
=True)
27 output
, error
= proc
.communicate(input=input)
28 output
= output
.strip()
30 print('ISCC({!r}) = {!r}'.format(input, output
))
34 def _polymorphic_method(func
):
35 @functools.wraps(func
)
37 if isinstance(b
, Expression
):
39 if isinstance(b
, numbers
.Rational
):
45 def _polymorphic_operator(func
):
46 # A polymorphic operator should call a polymorphic method, hence we just
47 # have to test the left operand.
48 @functools.wraps(func
)
50 if isinstance(a
, numbers
.Rational
):
53 elif isinstance(a
, Expression
):
55 raise TypeError('arguments must be linear expressions')
61 This class implements linear expressions.
64 def __new__(cls
, coefficients
=None, constant
=0):
65 self
= super().__new
__(cls
)
66 self
._coefficients
= {}
67 if isinstance(coefficients
, dict):
68 coefficients
= coefficients
.items()
69 if coefficients
is not None:
70 for symbol
, coefficient
in coefficients
:
71 if isinstance(symbol
, Expression
) and symbol
.issymbol():
73 elif not isinstance(symbol
, str):
74 raise TypeError('symbols must be strings')
75 if not isinstance(coefficient
, numbers
.Rational
):
76 raise TypeError('coefficients must be rational numbers')
78 self
._coefficients
[symbol
] = coefficient
79 if not isinstance(constant
, numbers
.Rational
):
80 raise TypeError('constant must be a rational number')
81 self
._constant
= constant
85 def _fromiscc(cls
, symbols
, string
):
86 string
= re
.sub(r
'(\d+)\s*([a-zA-Z_]\w*)',
87 lambda m
: '{}*{}'.format(m
.group(1), m
.group(2)),
90 for symbol
in symbols
:
91 context
[symbol
] = Symbol(symbol
)
92 return eval(string
, context
)
95 yield from sorted(self
._coefficients
)
99 return len(list(self
.symbols()))
101 def coefficient(self
, symbol
):
102 if isinstance(symbol
, Expression
) and symbol
.issymbol():
104 elif not isinstance(symbol
, str):
105 raise TypeError('symbol must be a string')
107 return self
._coefficients
[symbol
]
111 __getitem__
= coefficient
113 def coefficients(self
):
114 for symbol
in self
.symbols():
115 yield symbol
, self
.coefficient(symbol
)
119 return self
._constant
121 def isconstant(self
):
122 return len(self
._coefficients
) == 0
125 for symbol
in self
.symbols():
126 yield self
.coefficient(symbol
)
130 if not self
.issymbol():
131 raise ValueError('not a symbol: {}'.format(self
))
132 for symbol
in self
.symbols():
136 return len(self
._coefficients
) == 1 and self
._constant
== 0
139 return (not self
.isconstant()) or bool(self
.constant
)
148 def __add__(self
, other
):
149 coefficients
= dict(self
.coefficients())
150 for symbol
, coefficient
in other
.coefficients():
151 if symbol
in coefficients
:
152 coefficients
[symbol
] += coefficient
154 coefficients
[symbol
] = coefficient
155 constant
= self
.constant
+ other
.constant
156 return Expression(coefficients
, constant
)
161 def __sub__(self
, other
):
162 coefficients
= dict(self
.coefficients())
163 for symbol
, coefficient
in other
.coefficients():
164 if symbol
in coefficients
:
165 coefficients
[symbol
] -= coefficient
167 coefficients
[symbol
] = -coefficient
168 constant
= self
.constant
- other
.constant
169 return Expression(coefficients
, constant
)
171 def __rsub__(self
, other
):
172 return -(self
- other
)
175 def __mul__(self
, other
):
176 if other
.isconstant():
177 coefficients
= dict(self
.coefficients())
178 for symbol
in coefficients
:
179 coefficients
[symbol
] *= other
.constant
180 constant
= self
.constant
* other
.constant
181 return Expression(coefficients
, constant
)
182 if isinstance(other
, Expression
) and not self
.isconstant():
183 raise ValueError('non-linear expression: '
184 '{} * {}'.format(self
._prepr
(), other
._prepr
()))
185 return NotImplemented
191 symbols
= sorted(self
.symbols())
193 for symbol
in symbols
:
194 coefficient
= self
[symbol
]
199 string
+= ' + {}'.format(symbol
)
200 elif coefficient
== -1:
202 string
+= '-{}'.format(symbol
)
204 string
+= ' - {}'.format(symbol
)
207 string
+= '{}*{}'.format(coefficient
, symbol
)
208 elif coefficient
> 0:
209 string
+= ' + {}*{}'.format(coefficient
, symbol
)
211 assert coefficient
< 0
213 string
+= ' - {}*{}'.format(coefficient
, symbol
)
215 constant
= self
.constant
216 if constant
!= 0 and i
== 0:
217 string
+= '{}'.format(constant
)
219 string
+= ' + {}'.format(constant
)
222 string
+= ' - {}'.format(constant
)
227 def _prepr(self
, always
=False):
229 if not always
and (self
.isconstant() or self
.issymbol()):
232 return '({})'.format(string
)
235 def __eq__(self
, other
):
237 # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs
238 return isinstance(other
, Expression
) and \
239 self
._coefficients
== other
._coefficients
and \
240 self
.constant
== other
.constant
243 return hash((tuple(self
._coefficients
), self
._constant
))
246 def _eq(self
, other
):
247 return Polyhedron(equalities
=[self
- other
])
250 def __le__(self
, other
):
251 return Polyhedron(inequalities
=[self
- other
])
254 def __lt__(self
, other
):
255 return Polyhedron(inequalities
=[self
- other
+ 1])
258 def __ge__(self
, other
):
259 return Polyhedron(inequalities
=[other
- self
])
262 def __gt__(self
, other
):
263 return Polyhedron(inequalities
=[other
- self
+ 1])
266 def Constant(numerator
=0, denominator
=None):
267 if denominator
is None and isinstance(numerator
, numbers
.Rational
):
268 return Expression(constant
=numerator
)
270 return Expression(constant
=Fraction(numerator
, denominator
))
273 if not isinstance(name
, str):
274 raise TypeError('name must be a string')
275 return Expression(coefficients
={name
: 1})
278 if isinstance(names
, str):
279 names
= names
.replace(',', ' ').split()
280 return (Symbol(name
) for name
in names
)
283 @_polymorphic_operator
287 @_polymorphic_operator
291 @_polymorphic_operator
295 @_polymorphic_operator
299 @_polymorphic_operator
306 This class implements polyhedrons.
309 def __new__(cls
, equalities
=None, inequalities
=None):
310 if equalities
is None:
312 if inequalities
is None:
315 for equality
in equalities
:
316 symbols
.update(equality
.symbols())
317 for inequality
in inequalities
:
318 symbols
.update(inequality
.symbols())
319 symbols
= sorted(symbols
)
320 string
= cls
._isccpoly
(symbols
, equalities
, inequalities
)
321 string
= _iscc(string
)
322 return cls
._fromiscc
(symbols
, string
)
325 def _isccpoly(cls
, symbols
, equalities
, inequalities
):
327 for equality
in equalities
:
328 strings
.append('{} = 0'.format(equality
))
329 for inequality
in inequalities
:
330 strings
.append('{} <= 0'.format(inequality
))
331 string
= '{{ [{}] : {} }}'.format(', '.join(symbols
), ' and '.join(strings
))
334 def _toiscc(self
, symbols
):
335 return self
._isccpoly
(symbols
, self
.equalities
, self
.inequalities
)
338 def _fromiscc(cls
, symbols
, string
):
339 if re
.match(r
'^\s*\{\s*\}\s*$', string
):
341 self
= super().__new
__(cls
)
342 self
._symbols
= symbols
343 self
._equalities
= []
344 self
._inequalities
= []
345 string
= re
.sub(r
'^\s*\{\s*(.*?)\s*\}\s*$', lambda m
: m
.group(1), string
)
346 if ':' not in string
:
347 string
= string
+ ':'
348 vstr
, cstr
= re
.split(r
'\s*:\s*', string
)
350 vstr
= re
.sub(r
'^\s*\[\s*(.*?)\s*\]\s*$', lambda m
: m
.group(1), vstr
)
351 toks
= list(filter(None, re
.split(r
'\s*,\s*', vstr
)))
352 assert len(toks
) == len(symbols
)
353 for i
in range(len(symbols
)):
355 if toks
[i
] != symbol
:
356 expr
= Expression
._fromiscc
(symbols
, toks
[i
])
357 self
._equalities
.append(Symbol(symbol
) - expr
)
359 cstrs
= re
.split(r
'\s*and\s*', cstr
)
361 lhs
, op
, rhs
= re
.split(r
'\s*(<=|>=|<|>|=)\s*', cstr
)
362 lhs
= Expression
._fromiscc
(symbols
, lhs
)
363 rhs
= Expression
._fromiscc
(symbols
, rhs
)
365 self
._equalities
.append(lhs
- rhs
)
367 self
._inequalities
.append(lhs
- rhs
)
369 self
._inequalities
.append(rhs
- lhs
)
371 self
._inequalities
.append(lhs
- rhs
+ 1)
373 self
._inequalities
.append(rhs
- lhs
+ 1)
377 def equalities(self
):
378 yield from self
._equalities
381 def inequalities(self
):
382 yield from self
._inequalities
384 def constraints(self
):
385 yield from self
.equalities
386 yield from self
.inequalities
389 yield from self
._symbols
393 return len(self
.symbols())
396 # return false if the polyhedron is empty, true otherwise
397 raise not self
.isempty()
399 def __eq__(self
, other
):
400 symbols
= set(self
.symbols()) |
set(other
.symbols())
401 string
= '{} = {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
402 string
= _iscc(string
)
403 return string
== 'True'
408 def isuniverse(self
):
409 return self
== universe
411 def isdisjoint(self
, other
):
412 # return true if the polyhedron has no elements in common with other
413 return (self
& other
).isempty()
415 def issubset(self
, other
):
416 symbols
= set(self
.symbols()) |
set(other
.symbols())
417 string
= '{} <= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
418 string
= _iscc(string
)
419 return string
== 'True'
421 def __le__(self
, other
):
422 return self
.issubset(other
)
424 def __lt__(self
, other
):
425 symbols
= set(self
.symbols()) |
set(other
.symbols())
426 string
= '{} < {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
427 string
= _iscc(string
)
428 return string
== 'True'
430 def issuperset(self
, other
):
431 # test whether every element in other is in the polyhedron
432 symbols
= set(self
.symbols()) |
set(other
.symbols())
433 string
= '{} >= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
434 string
= _iscc(string
)
435 return string
== 'True'
437 def __ge__(self
, other
):
438 return self
.issuperset(other
)
440 def __gt__(self
, other
):
441 symbols
= set(self
.symbols() + other
.symbols())
442 string
= '{} > {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
443 string
= _iscc(string
)
444 return string
== 'True'
446 def union(self
, *others
):
447 # return a new polyhedron with elements from the polyhedron and all
448 # others (convex union)
449 symbols
= set(self
.symbols())
451 symbols
.update(other
.symbols())
452 symbols
= sorted(symbols
)
453 strings
= [self
._toiscc
(symbols
)]
455 strings
.append(other
._toiscc
(symbols
))
456 string
= ' + '.join(strings
)
457 string
= _iscc('poly ({})'.format(string
))
458 return Polyhedron
._fromiscc
(symbols
, string
)
460 def __or__(self
, other
):
461 return self
.union(other
)
463 def intersection(self
, *others
):
464 symbols
= set(self
.symbols())
466 symbols
.update(other
.symbols())
467 symbols
= sorted(symbols
)
468 strings
= [self
._toiscc
(symbols
)]
470 strings
.append(other
._toiscc
(symbols
))
471 string
= ' * '.join(strings
)
472 string
= _iscc('poly ({})'.format(string
))
473 return Polyhedron
._fromiscc
(symbols
, string
)
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 symbols
= set(self
.symbols())
483 symbols
.update(other
.symbols())
484 symbols
= sorted(symbols
)
485 strings
= [self
._toiscc
(symbols
)]
487 strings
.append(other
._toiscc
(symbols
))
488 string
= ' - '.join(strings
)
489 string
= _iscc('poly ({})'.format(string
))
490 return Polyhedron
._fromiscc
(symbols
, string
)
492 def __sub__(self
, other
):
493 return self
.difference(other
)
497 for constraint
in self
.equalities
:
498 constraints
.append('{} == 0'.format(constraint
))
499 for constraint
in self
.inequalities
:
500 constraints
.append('{} <= 0'.format(constraint
))
501 if len(constraints
) == 0:
503 elif len(constraints
) == 1:
504 string
= constraints
[0]
505 return 'empty' if string
== '1 == 0' else string
507 strings
= ['({})'.format(constraint
) for constraint
in constraints
]
508 return ' & '.join(strings
)
512 universe
= Polyhedron()