6 from collections
import OrderedDict
7 from fractions
import Fraction
, gcd
17 def _polymorphic(func
):
18 @functools.wraps(func
)
19 def wrapper(left
, right
):
20 if isinstance(right
, Expression
):
21 return func(left
, right
)
22 elif isinstance(right
, numbers
.Rational
):
23 right
= Constant(right
)
24 return func(left
, right
)
31 This class implements linear expressions.
41 def __new__(cls
, coefficients
=None, constant
=0):
42 if isinstance(coefficients
, str):
44 raise TypeError('too many arguments')
45 return cls
.fromstring(coefficients
)
46 if isinstance(coefficients
, dict):
47 coefficients
= coefficients
.items()
48 if coefficients
is None:
49 return Constant(constant
)
50 coefficients
= [(symbol
, coefficient
)
51 for symbol
, coefficient
in coefficients
if coefficient
!= 0]
52 if len(coefficients
) == 0:
53 return Constant(constant
)
54 elif len(coefficients
) == 1 and constant
== 0:
55 symbol
, coefficient
= coefficients
[0]
58 self
= object().__new
__(cls
)
59 self
._coefficients
= {}
60 for symbol
, coefficient
in coefficients
:
61 if isinstance(symbol
, Symbol
):
63 elif not isinstance(symbol
, str):
64 raise TypeError('symbols must be strings or Symbol instances')
65 if isinstance(coefficient
, Constant
):
66 coefficient
= coefficient
.constant
67 if not isinstance(coefficient
, numbers
.Rational
):
68 raise TypeError('coefficients must be rational numbers '
69 'or Constant instances')
70 self
._coefficients
[symbol
] = coefficient
71 self
._coefficients
= OrderedDict(sorted(self
._coefficients
.items()))
72 if isinstance(constant
, Constant
):
73 constant
= constant
.constant
74 if not isinstance(constant
, numbers
.Rational
):
75 raise TypeError('constant must be a rational number '
76 'or a Constant instance')
77 self
._constant
= constant
78 self
._symbols
= tuple(self
._coefficients
)
79 self
._dimension
= len(self
._symbols
)
82 def coefficient(self
, symbol
):
83 if isinstance(symbol
, Symbol
):
85 elif not isinstance(symbol
, str):
86 raise TypeError('symbol must be a string or a Symbol instance')
88 return self
._coefficients
[symbol
]
92 __getitem__
= coefficient
94 def coefficients(self
):
95 yield from self
._coefficients
.items()
107 return self
._dimension
109 def isconstant(self
):
116 for symbol
in self
.symbols
:
117 yield self
.coefficient(symbol
)
130 def __add__(self
, other
):
131 coefficients
= dict(self
.coefficients())
132 for symbol
, coefficient
in other
.coefficients():
133 if symbol
in coefficients
:
134 coefficients
[symbol
] += coefficient
136 coefficients
[symbol
] = coefficient
137 constant
= self
.constant
+ other
.constant
138 return Expression(coefficients
, constant
)
143 def __sub__(self
, other
):
144 coefficients
= dict(self
.coefficients())
145 for symbol
, coefficient
in other
.coefficients():
146 if symbol
in coefficients
:
147 coefficients
[symbol
] -= coefficient
149 coefficients
[symbol
] = -coefficient
150 constant
= self
.constant
- other
.constant
151 return Expression(coefficients
, constant
)
153 def __rsub__(self
, other
):
154 return -(self
- other
)
157 def __mul__(self
, other
):
158 if other
.isconstant():
159 coefficients
= dict(self
.coefficients())
160 for symbol
in coefficients
:
161 coefficients
[symbol
] *= other
.constant
162 constant
= self
.constant
* other
.constant
163 return Expression(coefficients
, constant
)
164 if isinstance(other
, Expression
) and not self
.isconstant():
165 raise ValueError('non-linear expression: '
166 '{} * {}'.format(self
._parenstr
(), other
._parenstr
()))
167 return NotImplemented
172 def __truediv__(self
, other
):
173 if other
.isconstant():
174 coefficients
= dict(self
.coefficients())
175 for symbol
in coefficients
:
176 coefficients
[symbol
] = \
177 Fraction(coefficients
[symbol
], other
.constant
)
178 constant
= Fraction(self
.constant
, other
.constant
)
179 return Expression(coefficients
, constant
)
180 if isinstance(other
, Expression
):
181 raise ValueError('non-linear expression: '
182 '{} / {}'.format(self
._parenstr
(), other
._parenstr
()))
183 return NotImplemented
185 def __rtruediv__(self
, other
):
186 if isinstance(other
, self
):
187 if self
.isconstant():
188 constant
= Fraction(other
, self
.constant
)
189 return Expression(constant
=constant
)
191 raise ValueError('non-linear expression: '
192 '{} / {}'.format(other
._parenstr
(), self
._parenstr
()))
193 return NotImplemented
196 def __eq__(self
, other
):
198 # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs
199 return isinstance(other
, Expression
) and \
200 self
._coefficients
== other
._coefficients
and \
201 self
.constant
== other
.constant
204 def __le__(self
, other
):
205 from .polyhedra
import Le
206 return Le(self
, other
)
209 def __lt__(self
, other
):
210 from .polyhedra
import Lt
211 return Lt(self
, other
)
214 def __ge__(self
, other
):
215 from .polyhedra
import Ge
216 return Ge(self
, other
)
219 def __gt__(self
, other
):
220 from .polyhedra
import Gt
221 return Gt(self
, other
)
224 return hash((tuple(self
.coefficients()), self
._constant
))
227 lcm
= functools
.reduce(lambda a
, b
: a
*b
// gcd(a
, b
),
228 [value
.denominator
for value
in self
.values()])
232 def _fromast(cls
, node
):
233 if isinstance(node
, ast
.Module
) and len(node
.body
) == 1:
234 return cls
._fromast
(node
.body
[0])
235 elif isinstance(node
, ast
.Expr
):
236 return cls
._fromast
(node
.value
)
237 elif isinstance(node
, ast
.Name
):
238 return Symbol(node
.id)
239 elif isinstance(node
, ast
.Num
):
240 return Constant(node
.n
)
241 elif isinstance(node
, ast
.UnaryOp
) and isinstance(node
.op
, ast
.USub
):
242 return -cls
._fromast
(node
.operand
)
243 elif isinstance(node
, ast
.BinOp
):
244 left
= cls
._fromast
(node
.left
)
245 right
= cls
._fromast
(node
.right
)
246 if isinstance(node
.op
, ast
.Add
):
248 elif isinstance(node
.op
, ast
.Sub
):
250 elif isinstance(node
.op
, ast
.Mult
):
252 elif isinstance(node
.op
, ast
.Div
):
254 raise SyntaxError('invalid syntax')
256 _RE_NUM_VAR
= re
.compile(r
'(\d+|\))\s*([^\W\d_]\w*|\()')
259 def fromstring(cls
, string
):
260 # add implicit multiplication operators, e.g. '5x' -> '5*x'
261 string
= cls
._RE
_NUM
_VAR
.sub(r
'\1*\2', string
)
262 tree
= ast
.parse(string
, 'eval')
263 return cls
._fromast
(tree
)
268 for symbol
in self
.symbols
:
269 coefficient
= self
.coefficient(symbol
)
274 string
+= ' + {}'.format(symbol
)
275 elif coefficient
== -1:
277 string
+= '-{}'.format(symbol
)
279 string
+= ' - {}'.format(symbol
)
282 string
+= '{}*{}'.format(coefficient
, symbol
)
283 elif coefficient
> 0:
284 string
+= ' + {}*{}'.format(coefficient
, symbol
)
286 assert coefficient
< 0
288 string
+= ' - {}*{}'.format(coefficient
, symbol
)
290 constant
= self
.constant
291 if constant
!= 0 and i
== 0:
292 string
+= '{}'.format(constant
)
294 string
+= ' + {}'.format(constant
)
297 string
+= ' - {}'.format(constant
)
302 def _parenstr(self
, always
=False):
304 if not always
and (self
.isconstant() or self
.issymbol()):
307 return '({})'.format(string
)
310 return '{}({!r})'.format(self
.__class
__.__name
__, str(self
))
313 def fromsympy(cls
, expr
):
317 for symbol
, coefficient
in expr
.as_coefficients_dict().items():
318 coefficient
= Fraction(coefficient
.p
, coefficient
.q
)
319 if symbol
== sympy
.S
.One
:
320 constant
= coefficient
321 elif isinstance(symbol
, sympy
.Symbol
):
323 coefficients
[symbol
] = coefficient
325 raise ValueError('non-linear expression: {!r}'.format(expr
))
326 return cls(coefficients
, constant
)
331 for symbol
, coefficient
in self
.coefficients():
332 term
= coefficient
* sympy
.Symbol(symbol
)
334 expr
+= self
.constant
338 class Symbol(Expression
):
340 __slots__
= Expression
.__slots
__ + (
344 def __new__(cls
, name
):
345 if isinstance(name
, Symbol
):
347 elif not isinstance(name
, str):
348 raise TypeError('name must be a string or a Symbol instance')
350 self
= object().__new
__(cls
)
351 self
._coefficients
= {name
: 1}
353 self
._symbols
= tuple(name
)
366 def _fromast(cls
, node
):
367 if isinstance(node
, ast
.Module
) and len(node
.body
) == 1:
368 return cls
._fromast
(node
.body
[0])
369 elif isinstance(node
, ast
.Expr
):
370 return cls
._fromast
(node
.value
)
371 elif isinstance(node
, ast
.Name
):
372 return Symbol(node
.id)
373 raise SyntaxError('invalid syntax')
376 return '{}({!r})'.format(self
.__class
__.__name
__, self
._name
)
379 def fromsympy(cls
, expr
):
381 if isinstance(expr
, sympy
.Symbol
):
382 return cls(expr
.name
)
384 raise TypeError('expr must be a sympy.Symbol instance')
388 if isinstance(names
, str):
389 names
= names
.replace(',', ' ').split()
390 return (Symbol(name
) for name
in names
)
393 class Constant(Expression
):
395 def __new__(cls
, numerator
=0, denominator
=None):
396 self
= object().__new
__(cls
)
397 if denominator
is None and isinstance(numerator
, Constant
):
398 self
._constant
= numerator
.constant
400 self
._constant
= Fraction(numerator
, denominator
)
401 self
._coefficients
= {}
406 def isconstant(self
):
410 return self
.constant
!= 0
413 def fromstring(cls
, string
):
414 if isinstance(string
, str):
415 return Constant(Fraction(string
))
417 raise TypeError('string must be a string instance')
420 if self
.constant
.denominator
== 1:
421 return '{}({!r})'.format(self
.__class
__.__name
__,
422 self
.constant
.numerator
)
424 return '{}({!r}, {!r})'.format(self
.__class
__.__name
__,
425 self
.constant
.numerator
, self
.constant
.denominator
)
428 def fromsympy(cls
, expr
):
430 if isinstance(expr
, sympy
.Rational
):
431 return cls(expr
.p
, expr
.q
)
432 elif isinstance(expr
, numbers
.Rational
):
435 raise TypeError('expr must be a sympy.Rational instance')