7 from fractions
import Fraction
, gcd
12 'Constant', 'Symbol', 'symbols',
13 'Eq', 'Le', 'Lt', 'Ge', 'Gt',
19 def _strsymbol(symbol
):
20 if isinstance(symbol
, str):
22 elif isinstance(symbol
, Expression
) and symbol
.issymbol():
25 raise TypeError('symbol must be a string or a symbol')
27 def _strsymbols(symbols
):
28 if isinstance(symbols
, str):
29 return symbols
.replace(',', ' ').split()
31 return [_strsymbol(symbol
) for symbol
in symbols
]
41 if not input.endswith(';'):
43 proc
= subprocess
.Popen(cls
.command
,
44 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
45 universal_newlines
=True)
46 output
, error
= proc
.communicate(input=input)
47 output
= output
.strip()
49 print('iscc: {!r} -> {!r}'.format(input, output
))
53 def symbols(cls
, symbols
):
54 symbols
= _strsymbols(symbols
)
55 return '[{}]'.format(', '.join(symbols
))
58 def constraints(cls
, equalities
, inequalities
):
60 for equality
in equalities
:
61 strings
.append('{} = 0'.format(equality
))
62 for inequality
in inequalities
:
63 strings
.append('{} <= 0'.format(inequality
))
64 return ' and '.join(strings
)
67 def set(cls
, symbols
, equalities
, inequalities
):
68 return '{{ {} : {} }}'.format(cls
.symbols(symbols
),
69 cls
.constraints(equalities
, inequalities
))
72 def map(cls
, lsymbols
, rsymbols
, equalities
, inequalities
):
73 return '{{ {} -> {} : {} }}'.format(
74 cls
.symbols(lsymbols
), cls
.symbols(rsymbols
),
75 cls
.constraints(equalities
, inequalities
))
80 def _polymorphic_method(func
):
81 @functools.wraps(func
)
83 if isinstance(b
, Expression
):
85 if isinstance(b
, numbers
.Rational
):
91 def _polymorphic_operator(func
):
92 # A polymorphic operator should call a polymorphic method, hence we just
93 # have to test the left operand.
94 @functools.wraps(func
)
96 if isinstance(a
, numbers
.Rational
):
99 elif isinstance(a
, Expression
):
101 raise TypeError('arguments must be linear expressions')
107 This class implements linear expressions.
110 def __new__(cls
, coefficients
=None, constant
=0):
111 self
= super().__new
__(cls
)
112 self
._coefficients
= {}
113 if isinstance(coefficients
, dict):
114 coefficients
= coefficients
.items()
115 if coefficients
is not None:
116 for symbol
, coefficient
in coefficients
:
117 symbol
= _strsymbol(symbol
)
118 if not isinstance(coefficient
, numbers
.Rational
):
119 raise TypeError('coefficients must be rational numbers')
121 self
._coefficients
[symbol
] = coefficient
122 if not isinstance(constant
, numbers
.Rational
):
123 raise TypeError('constant must be a rational number')
124 self
._constant
= constant
128 def _fromiscc(cls
, symbols
, string
):
129 symbols
= _strsymbols(symbols
)
130 string
= re
.sub(r
'(\d+)\s*([a-zA-Z_]\w*)',
131 lambda m
: '{}*{}'.format(m
.group(1), m
.group(2)),
134 for symbol
in symbols
:
135 context
[symbol
] = Symbol(symbol
)
136 return eval(string
, context
)
139 yield from sorted(self
._coefficients
)
143 return len(list(self
.symbols()))
145 def coefficient(self
, symbol
):
146 symbol
= _strsymbol(symbol
)
148 return self
._coefficients
[symbol
]
152 __getitem__
= coefficient
154 def coefficients(self
):
155 for symbol
in self
.symbols():
156 yield symbol
, self
.coefficient(symbol
)
160 return self
._constant
162 def isconstant(self
):
163 return len(self
._coefficients
) == 0
166 for symbol
in self
.symbols():
167 yield self
.coefficient(symbol
)
171 if not self
.issymbol():
172 raise ValueError('not a symbol: {}'.format(self
))
173 for symbol
in self
.symbols():
177 return len(self
._coefficients
) == 1 and self
._constant
== 0
180 return (not self
.isconstant()) or bool(self
.constant
)
189 def __add__(self
, other
):
190 coefficients
= dict(self
.coefficients())
191 for symbol
, coefficient
in other
.coefficients():
192 if symbol
in coefficients
:
193 coefficients
[symbol
] += coefficient
195 coefficients
[symbol
] = coefficient
196 constant
= self
.constant
+ other
.constant
197 return Expression(coefficients
, constant
)
202 def __sub__(self
, other
):
203 coefficients
= dict(self
.coefficients())
204 for symbol
, coefficient
in other
.coefficients():
205 if symbol
in coefficients
:
206 coefficients
[symbol
] -= coefficient
208 coefficients
[symbol
] = -coefficient
209 constant
= self
.constant
- other
.constant
210 return Expression(coefficients
, constant
)
212 def __rsub__(self
, other
):
213 return -(self
- other
)
216 def __mul__(self
, other
):
217 if other
.isconstant():
218 coefficients
= dict(self
.coefficients())
219 for symbol
in coefficients
:
220 coefficients
[symbol
] *= other
.constant
221 constant
= self
.constant
* other
.constant
222 return Expression(coefficients
, constant
)
223 if isinstance(other
, Expression
) and not self
.isconstant():
224 raise ValueError('non-linear expression: '
225 '{} * {}'.format(self
._prepr
(), other
._prepr
()))
226 return NotImplemented
232 symbols
= sorted(self
.symbols())
234 for symbol
in symbols
:
235 coefficient
= self
[symbol
]
240 string
+= ' + {}'.format(symbol
)
241 elif coefficient
== -1:
243 string
+= '-{}'.format(symbol
)
245 string
+= ' - {}'.format(symbol
)
248 string
+= '{}*{}'.format(coefficient
, symbol
)
249 elif coefficient
> 0:
250 string
+= ' + {}*{}'.format(coefficient
, symbol
)
252 assert coefficient
< 0
254 string
+= ' - {}*{}'.format(coefficient
, symbol
)
256 constant
= self
.constant
257 if constant
!= 0 and i
== 0:
258 string
+= '{}'.format(constant
)
260 string
+= ' + {}'.format(constant
)
263 string
+= ' - {}'.format(constant
)
268 def _prepr(self
, always
=False):
270 if not always
and (self
.isconstant() or self
.issymbol()):
273 return '({})'.format(string
)
276 def __eq__(self
, other
):
278 # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs
279 return isinstance(other
, Expression
) and \
280 self
._coefficients
== other
._coefficients
and \
281 self
.constant
== other
.constant
284 return hash((tuple(self
._coefficients
), self
._constant
))
287 def _eq(self
, other
):
288 return Polyhedron(equalities
=[self
- other
])
291 def __le__(self
, other
):
292 return Polyhedron(inequalities
=[self
- other
])
295 def __lt__(self
, other
):
296 return Polyhedron(inequalities
=[self
- other
+ 1])
299 def __ge__(self
, other
):
300 return Polyhedron(inequalities
=[other
- self
])
303 def __gt__(self
, other
):
304 return Polyhedron(inequalities
=[other
- self
+ 1])
307 def Constant(numerator
=0, denominator
=None):
308 if denominator
is None and isinstance(numerator
, numbers
.Rational
):
309 return Expression(constant
=numerator
)
311 return Expression(constant
=Fraction(numerator
, denominator
))
314 name
= _strsymbol(name
)
315 return Expression(coefficients
={name
: 1})
318 names
= _strsymbols(names
)
319 return (Symbol(name
) for name
in names
)
322 @_polymorphic_operator
326 @_polymorphic_operator
330 @_polymorphic_operator
334 @_polymorphic_operator
338 @_polymorphic_operator
345 This class implements polyhedrons.
348 def __new__(cls
, equalities
=None, inequalities
=None):
349 if equalities
is None:
351 if inequalities
is None:
354 for equality
in equalities
:
355 symbols
.update(equality
.symbols())
356 for inequality
in inequalities
:
357 symbols
.update(inequality
.symbols())
358 symbols
= sorted(symbols
)
359 string
= _iscc
.set(symbols
, equalities
, inequalities
)
360 string
= _iscc
.eval(string
)
361 return cls
._fromiscc
(symbols
, string
)
363 def _toiscc(self
, symbols
):
364 return _iscc
.set(symbols
, self
.equalities
, self
.inequalities
)
367 def _fromiscc(cls
, symbols
, string
):
368 if re
.match(r
'^\s*\{\s*\}\s*$', string
):
370 self
= super().__new
__(cls
)
371 self
._symbols
= _strsymbols(symbols
)
372 self
._equalities
= []
373 self
._inequalities
= []
374 string
= re
.sub(r
'^\s*\{\s*(.*?)\s*\}\s*$', lambda m
: m
.group(1), string
)
375 if ':' not in string
:
376 string
= string
+ ':'
377 vstr
, cstr
= re
.split(r
'\s*:\s*', string
)
379 vstr
= re
.sub(r
'^\s*\[\s*(.*?)\s*\]\s*$', lambda m
: m
.group(1), vstr
)
380 toks
= list(filter(None, re
.split(r
'\s*,\s*', vstr
)))
381 assert len(toks
) == len(symbols
)
382 for i
in range(len(symbols
)):
384 if toks
[i
] != symbol
:
385 expr
= Expression
._fromiscc
(symbols
, toks
[i
])
386 self
._equalities
.append(Symbol(symbol
) - expr
)
388 cstrs
= re
.split(r
'\s*and\s*', cstr
)
390 lhs
, op
, rhs
= re
.split(r
'\s*(<=|>=|<|>|=)\s*', cstr
)
391 lhs
= Expression
._fromiscc
(symbols
, lhs
)
392 rhs
= Expression
._fromiscc
(symbols
, rhs
)
394 self
._equalities
.append(lhs
- rhs
)
396 self
._inequalities
.append(lhs
- rhs
)
398 self
._inequalities
.append(rhs
- lhs
)
400 self
._inequalities
.append(lhs
- rhs
+ 1)
402 self
._inequalities
.append(rhs
- lhs
+ 1)
406 def equalities(self
):
407 yield from self
._equalities
410 def inequalities(self
):
411 yield from self
._inequalities
413 def constraints(self
):
414 yield from self
.equalities
415 yield from self
.inequalities
418 yield from self
._symbols
422 return len(self
.symbols())
425 # return false if the polyhedron is empty, true otherwise
426 raise not self
.isempty()
428 def _symbolunion(self
, *others
):
429 symbols
= set(self
.symbols())
431 symbols
.update(other
.symbols())
432 return sorted(symbols
)
434 def __eq__(self
, other
):
435 symbols
= self
._symbolunion
(other
)
436 string
= '{} = {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
437 string
= _iscc
.eval(string
)
438 return string
== 'True'
443 def isuniverse(self
):
444 return self
== universe
446 def isdisjoint(self
, other
):
447 # return true if the polyhedron has no elements in common with other
448 return (self
& other
).isempty()
450 def issubset(self
, other
):
451 symbols
= self
._symbolunion
(other
)
452 string
= '{} <= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
453 string
= _iscc
.eval(string
)
454 return string
== 'True'
456 def __le__(self
, other
):
457 return self
.issubset(other
)
459 def __lt__(self
, other
):
460 symbols
= self
._symbolunion
(other
)
461 string
= '{} < {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
462 string
= _iscc
.eval(string
)
463 return string
== 'True'
465 def issuperset(self
, other
):
466 # test whether every element in other is in the polyhedron
467 symbols
= self
._symbolunion
(other
)
468 string
= '{} >= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
469 string
= _iscc
.eval(string
)
470 return string
== 'True'
472 def __ge__(self
, other
):
473 return self
.issuperset(other
)
475 def __gt__(self
, other
):
476 symbols
= self
._symbolunion
(other
)
477 string
= '{} > {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
478 string
= _iscc
.eval(string
)
479 return string
== 'True'
481 def union(self
, *others
):
482 # return a new polyhedron with elements from the polyhedron and all
483 # others (convex union)
484 symbols
= self
._symbolunion
(*others
)
485 strings
= [self
._toiscc
(symbols
)]
487 strings
.append(other
._toiscc
(symbols
))
488 string
= ' + '.join(strings
)
489 string
= _iscc
.eval('poly ({})'.format(string
))
490 return Polyhedron
._fromiscc
(symbols
, string
)
492 def __or__(self
, other
):
493 return self
.union(other
)
495 def intersection(self
, *others
):
496 symbols
= self
._symbolunion
(*others
)
497 symbols
= sorted(symbols
)
498 strings
= [self
._toiscc
(symbols
)]
500 strings
.append(other
._toiscc
(symbols
))
501 string
= ' * '.join(strings
)
502 string
= _iscc
.eval('poly ({})'.format(string
))
503 return Polyhedron
._fromiscc
(symbols
, string
)
505 def __and__(self
, other
):
506 return self
.intersection(other
)
508 def difference(self
, *others
):
509 # return a new polyhedron with elements in the polyhedron that are not
511 symbols
= self
._symbolunion
(*others
)
512 symbols
= sorted(symbols
)
513 strings
= [self
._toiscc
(symbols
)]
515 strings
.append(other
._toiscc
(symbols
))
516 string
= ' - '.join(strings
)
517 string
= _iscc
.eval('poly ({})'.format(string
))
518 return Polyhedron
._fromiscc
(symbols
, string
)
520 def __sub__(self
, other
):
521 return self
.difference(other
)
523 def projection(self
, symbols
):
524 symbols
= _strsymbols(symbols
)
525 string
= _iscc
.map(symbols
, self
.symbols(),
526 self
.equalities
, self
.inequalities
)
527 string
= _iscc
.eval('poly (dom ({}))'.format(string
))
528 return Polyhedron
._fromiscc
(symbols
, string
)
530 def __getitem__(self
, symbol
):
531 return self
.projection([symbol
])
535 for constraint
in self
.equalities
:
536 constraints
.append('{} == 0'.format(constraint
))
537 for constraint
in self
.inequalities
:
538 constraints
.append('{} <= 0'.format(constraint
))
539 if len(constraints
) == 0:
541 elif len(constraints
) == 1:
542 string
= constraints
[0]
543 return 'empty' if string
== '1 == 0' else string
545 strings
= ['({})'.format(constraint
) for constraint
in constraints
]
546 return ' & '.join(strings
)
550 universe
= Polyhedron()
553 if __name__
== '__main__':
554 x
, y
, z
= symbols('x y z')
555 sq0
= (0 <= x
) & (x
<= 1) & (0 <= y
) & (y
<= 1)
556 sq1
= (1 <= x
) & (x
<= 2) & (1 <= y
) & (y
<= 2)
557 print('sq0 ∪ sq1 =', sq0 | sq1
)
558 print('sq0 ∩ sq1 =', sq0
& sq1
)
559 print('sq0[x] =', sq0
[x
])