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
)
140 return tuple(sorted(self
._coefficients
))
144 return len(list(self
.symbols
))
146 def coefficient(self
, symbol
):
147 symbol
= _strsymbol(symbol
)
149 return self
._coefficients
[symbol
]
153 __getitem__
= coefficient
155 def coefficients(self
):
156 for symbol
in self
.symbols
:
157 yield symbol
, self
.coefficient(symbol
)
161 return self
._constant
163 def isconstant(self
):
164 return len(self
._coefficients
) == 0
167 for symbol
in self
.symbols
:
168 yield self
.coefficient(symbol
)
172 if not self
.issymbol():
173 raise ValueError('not a symbol: {}'.format(self
))
174 for symbol
in self
.symbols
:
178 return len(self
._coefficients
) == 1 and self
._constant
== 0
181 return (not self
.isconstant()) or bool(self
.constant
)
190 def __add__(self
, other
):
191 coefficients
= dict(self
.coefficients())
192 for symbol
, coefficient
in other
.coefficients():
193 if symbol
in coefficients
:
194 coefficients
[symbol
] += coefficient
196 coefficients
[symbol
] = coefficient
197 constant
= self
.constant
+ other
.constant
198 return Expression(coefficients
, constant
)
203 def __sub__(self
, other
):
204 coefficients
= dict(self
.coefficients())
205 for symbol
, coefficient
in other
.coefficients():
206 if symbol
in coefficients
:
207 coefficients
[symbol
] -= coefficient
209 coefficients
[symbol
] = -coefficient
210 constant
= self
.constant
- other
.constant
211 return Expression(coefficients
, constant
)
213 def __rsub__(self
, other
):
214 return -(self
- other
)
217 def __mul__(self
, other
):
218 if other
.isconstant():
219 coefficients
= dict(self
.coefficients())
220 for symbol
in coefficients
:
221 coefficients
[symbol
] *= other
.constant
222 constant
= self
.constant
* other
.constant
223 return Expression(coefficients
, constant
)
224 if isinstance(other
, Expression
) and not self
.isconstant():
225 raise ValueError('non-linear expression: '
226 '{} * {}'.format(self
._prepr
(), other
._prepr
()))
227 return NotImplemented
233 symbols
= self
.symbols
235 for symbol
in symbols
:
236 coefficient
= self
[symbol
]
241 string
+= ' + {}'.format(symbol
)
242 elif coefficient
== -1:
244 string
+= '-{}'.format(symbol
)
246 string
+= ' - {}'.format(symbol
)
249 string
+= '{}*{}'.format(coefficient
, symbol
)
250 elif coefficient
> 0:
251 string
+= ' + {}*{}'.format(coefficient
, symbol
)
253 assert coefficient
< 0
255 string
+= ' - {}*{}'.format(coefficient
, symbol
)
257 constant
= self
.constant
258 if constant
!= 0 and i
== 0:
259 string
+= '{}'.format(constant
)
261 string
+= ' + {}'.format(constant
)
264 string
+= ' - {}'.format(constant
)
269 def _prepr(self
, always
=False):
271 if not always
and (self
.isconstant() or self
.issymbol()):
274 return '({})'.format(string
)
277 def __eq__(self
, other
):
279 # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs
280 return isinstance(other
, Expression
) and \
281 self
._coefficients
== other
._coefficients
and \
282 self
.constant
== other
.constant
285 return hash((tuple(self
._coefficients
), self
._constant
))
288 def _eq(self
, other
):
289 return Polyhedron(equalities
=[self
- other
])
292 def __le__(self
, other
):
293 return Polyhedron(inequalities
=[self
- other
])
296 def __lt__(self
, other
):
297 return Polyhedron(inequalities
=[self
- other
+ 1])
300 def __ge__(self
, other
):
301 return Polyhedron(inequalities
=[other
- self
])
304 def __gt__(self
, other
):
305 return Polyhedron(inequalities
=[other
- self
+ 1])
308 def Constant(numerator
=0, denominator
=None):
309 if denominator
is None and isinstance(numerator
, numbers
.Rational
):
310 return Expression(constant
=numerator
)
312 return Expression(constant
=Fraction(numerator
, denominator
))
315 name
= _strsymbol(name
)
316 return Expression(coefficients
={name
: 1})
319 names
= _strsymbols(names
)
320 return (Symbol(name
) for name
in names
)
323 @_polymorphic_operator
327 @_polymorphic_operator
331 @_polymorphic_operator
335 @_polymorphic_operator
339 @_polymorphic_operator
346 This class implements polyhedrons.
349 def __new__(cls
, equalities
=None, inequalities
=None):
350 if equalities
is None:
352 if inequalities
is None:
355 for equality
in equalities
:
356 symbols
.update(equality
.symbols
)
357 for inequality
in inequalities
:
358 symbols
.update(inequality
.symbols
)
359 symbols
= list(symbols
)
360 string
= _iscc
.set(symbols
, equalities
, inequalities
)
361 string
= _iscc
.eval(string
)
362 return cls
._fromiscc
(symbols
, string
)
364 def _toiscc(self
, symbols
):
365 return _iscc
.set(symbols
, self
.equalities
, self
.inequalities
)
368 def _fromiscc(cls
, symbols
, string
):
369 if re
.match(r
'^\s*\{\s*\}\s*$', string
):
371 self
= super().__new
__(cls
)
372 self
._symbols
= sorted(_strsymbols(symbols
))
373 self
._equalities
= []
374 self
._inequalities
= []
375 string
= re
.sub(r
'^\s*\{\s*(.*?)\s*\}\s*$', lambda m
: m
.group(1), string
)
376 if ':' not in string
:
377 string
= string
+ ':'
378 vstr
, cstr
= re
.split(r
'\s*:\s*', string
)
380 vstr
= re
.sub(r
'^\s*\[\s*(.*?)\s*\]\s*$', lambda m
: m
.group(1), vstr
)
381 toks
= list(filter(None, re
.split(r
'\s*,\s*', vstr
)))
382 assert len(toks
) == len(symbols
)
383 for i
in range(len(symbols
)):
385 if toks
[i
] != symbol
:
386 expr
= Expression
._fromiscc
(symbols
, toks
[i
])
387 self
._equalities
.append(Symbol(symbol
) - expr
)
389 cstrs
= re
.split(r
'\s*and\s*', cstr
)
391 lhs
, op
, rhs
= re
.split(r
'\s*(<=|>=|<|>|=)\s*', cstr
)
392 lhs
= Expression
._fromiscc
(symbols
, lhs
)
393 rhs
= Expression
._fromiscc
(symbols
, rhs
)
395 self
._equalities
.append(lhs
- rhs
)
397 self
._inequalities
.append(lhs
- rhs
)
399 self
._inequalities
.append(rhs
- lhs
)
401 self
._inequalities
.append(lhs
- rhs
+ 1)
403 self
._inequalities
.append(rhs
- lhs
+ 1)
407 def equalities(self
):
408 yield from self
._equalities
411 def inequalities(self
):
412 yield from self
._inequalities
414 def constraints(self
):
415 yield from self
.equalities
416 yield from self
.inequalities
420 return tuple(self
._symbols
)
424 return len(self
.symbols
)
427 # return false if the polyhedron is empty, true otherwise
428 raise not self
.isempty()
430 def _symbolunion(self
, *others
):
431 symbols
= set(self
.symbols
)
433 symbols
.update(other
.symbols
)
434 return sorted(symbols
)
436 def __eq__(self
, other
):
437 symbols
= self
._symbolunion
(other
)
438 string
= '{} = {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
439 string
= _iscc
.eval(string
)
440 return string
== 'True'
445 def isuniverse(self
):
446 return self
== universe
448 def isdisjoint(self
, other
):
449 # return true if the polyhedron has no elements in common with other
450 return (self
& other
).isempty()
452 def issubset(self
, other
):
453 symbols
= self
._symbolunion
(other
)
454 string
= '{} <= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
455 string
= _iscc
.eval(string
)
456 return string
== 'True'
458 def __le__(self
, other
):
459 return self
.issubset(other
)
461 def __lt__(self
, other
):
462 symbols
= self
._symbolunion
(other
)
463 string
= '{} < {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
464 string
= _iscc
.eval(string
)
465 return string
== 'True'
467 def issuperset(self
, other
):
468 # test whether every element in other is in the polyhedron
469 symbols
= self
._symbolunion
(other
)
470 string
= '{} >= {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
471 string
= _iscc
.eval(string
)
472 return string
== 'True'
474 def __ge__(self
, other
):
475 return self
.issuperset(other
)
477 def __gt__(self
, other
):
478 symbols
= self
._symbolunion
(other
)
479 string
= '{} > {}'.format(self
._toiscc
(symbols
), other
._toiscc
(symbols
))
480 string
= _iscc
.eval(string
)
481 return string
== 'True'
483 def union(self
, *others
):
484 # return a new polyhedron with elements from the polyhedron and all
485 # others (convex union)
486 symbols
= self
._symbolunion
(*others
)
487 strings
= [self
._toiscc
(symbols
)]
489 strings
.append(other
._toiscc
(symbols
))
490 string
= ' + '.join(strings
)
491 string
= _iscc
.eval('poly ({})'.format(string
))
492 return Polyhedron
._fromiscc
(symbols
, string
)
494 def __or__(self
, other
):
495 return self
.union(other
)
497 def intersection(self
, *others
):
498 symbols
= self
._symbolunion
(*others
)
499 strings
= [self
._toiscc
(symbols
)]
501 strings
.append(other
._toiscc
(symbols
))
502 string
= ' * '.join(strings
)
503 string
= _iscc
.eval('poly ({})'.format(string
))
504 return Polyhedron
._fromiscc
(symbols
, string
)
506 def __and__(self
, other
):
507 return self
.intersection(other
)
509 def difference(self
, *others
):
510 # return a new polyhedron with elements in the polyhedron that are not
512 symbols
= self
._symbolunion
(*others
)
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
])