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 __eq__(self
, other
):
429 symbols
= set(self
.symbols()) |
set(other
.symbols())
430 string
= '{} = {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
431 string
= _iscc
.eval(string
)
432 return string
== 'True'
437 def isuniverse(self
):
438 return self
== universe
440 def isdisjoint(self
, other
):
441 # return true if the polyhedron has no elements in common with other
442 return (self
& other
).isempty()
444 def issubset(self
, other
):
445 symbols
= set(self
.symbols()) |
set(other
.symbols())
446 string
= '{} <= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
447 string
= _iscc
.eval(string
)
448 return string
== 'True'
450 def __le__(self
, other
):
451 return self
.issubset(other
)
453 def __lt__(self
, other
):
454 symbols
= set(self
.symbols()) |
set(other
.symbols())
455 string
= '{} < {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
456 string
= _iscc
.eval(string
)
457 return string
== 'True'
459 def issuperset(self
, other
):
460 # test whether every element in other is in the polyhedron
461 symbols
= set(self
.symbols()) |
set(other
.symbols())
462 string
= '{} >= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
463 string
= _iscc
.eval(string
)
464 return string
== 'True'
466 def __ge__(self
, other
):
467 return self
.issuperset(other
)
469 def __gt__(self
, other
):
470 symbols
= set(self
.symbols() + other
.symbols())
471 string
= '{} > {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
472 string
= _iscc
.eval(string
)
473 return string
== 'True'
475 def union(self
, *others
):
476 # return a new polyhedron with elements from the polyhedron and all
477 # others (convex union)
478 symbols
= set(self
.symbols())
480 symbols
.update(other
.symbols())
481 symbols
= sorted(symbols
)
482 strings
= [self
._toiscc
(symbols
)]
484 strings
.append(other
._toiscc
(symbols
))
485 string
= ' + '.join(strings
)
486 string
= _iscc
.eval('poly ({})'.format(string
))
487 return Polyhedron
._fromiscc
(symbols
, string
)
489 def __or__(self
, other
):
490 return self
.union(other
)
492 def intersection(self
, *others
):
493 symbols
= set(self
.symbols())
495 symbols
.update(other
.symbols())
496 symbols
= sorted(symbols
)
497 strings
= [self
._toiscc
(symbols
)]
499 strings
.append(other
._toiscc
(symbols
))
500 string
= ' * '.join(strings
)
501 string
= _iscc
.eval('poly ({})'.format(string
))
502 return Polyhedron
._fromiscc
(symbols
, string
)
504 def __and__(self
, other
):
505 return self
.intersection(other
)
507 def difference(self
, *others
):
508 # return a new polyhedron with elements in the polyhedron that are not
510 symbols
= set(self
.symbols())
512 symbols
.update(other
.symbols())
513 symbols
= sorted(symbols
)
514 strings
= [self
._toiscc
(symbols
)]
516 strings
.append(other
._toiscc
(symbols
))
517 string
= ' - '.join(strings
)
518 string
= _iscc
.eval('poly ({})'.format(string
))
519 return Polyhedron
._fromiscc
(symbols
, string
)
521 def __sub__(self
, other
):
522 return self
.difference(other
)
524 def projection(self
, symbols
):
525 symbols
= _strsymbols(symbols
)
526 string
= _iscc
.map(symbols
, self
.symbols(),
527 self
.equalities
, self
.inequalities
)
528 string
= _iscc
.eval('poly (dom ({}))'.format(string
))
529 return Polyhedron
._fromiscc
(symbols
, string
)
531 def __getitem__(self
, symbol
):
532 return self
.projection([symbol
])
536 for constraint
in self
.equalities
:
537 constraints
.append('{} == 0'.format(constraint
))
538 for constraint
in self
.inequalities
:
539 constraints
.append('{} <= 0'.format(constraint
))
540 if len(constraints
) == 0:
542 elif len(constraints
) == 1:
543 string
= constraints
[0]
544 return 'empty' if string
== '1 == 0' else string
546 strings
= ['({})'.format(constraint
) for constraint
in constraints
]
547 return ' & '.join(strings
)
551 universe
= Polyhedron()
554 if __name__
== '__main__':
555 x
, y
, z
= symbols('x y z')
556 sq0
= (0 <= x
) & (x
<= 1) & (0 <= y
) & (y
<= 1)
557 sq1
= (1 <= x
) & (x
<= 2) & (1 <= y
) & (y
<= 2)
558 print('sq0 ∪ sq1 =', sq0 | sq1
)
559 print('sq0 ∩ sq1 =', sq0
& sq1
)
560 print('sq0[x] =', sq0
[x
])