57 def _expr_richcmp(self, other, op):
59 if isinstance(other, Expr)
or isinstance(other, GenExpr):
60 return (self - other) <= 0.0
61 elif _is_number(other):
62 return ExprCons(self, rhs=float(other))
64 raise NotImplementedError
66 if isinstance(other, Expr)
or isinstance(other, GenExpr):
67 return (self - other) >= 0.0
68 elif _is_number(other):
69 return ExprCons(self, lhs=float(other))
71 raise NotImplementedError
73 if isinstance(other, Expr)
or isinstance(other, GenExpr):
74 return (self - other) == 0.0
75 elif _is_number(other):
76 return ExprCons(self, lhs=float(other), rhs=float(other))
78 raise NotImplementedError
80 raise NotImplementedError(
"Can only support constraints with '<=', '>=', or '=='.")
84 '''This is a monomial term'''
86 __slots__ = (
'vartuple',
'ptrtuple',
'hashval')
89 self.
vartuple = tuple(sorted(vartuple, key=
lambda v: v.ptr()))
100 return self.
ptrtuple == other.ptrtuple
106 both = self.
vartuple + other.vartuple
110 return 'Term(%s)' %
', '.join([str(v)
for v
in self.
vartuple])
117 """helper function to generate an object of type GenExpr"""
120 elif isinstance(expr, Expr):
125 for vars, coef
in expr.terms.items():
130 sumexpr += coef * varexpr
136 sumexpr += coef * prodexpr
139 assert isinstance(expr, GenExpr)
147 '''terms is a dict of variables to coefficients.
149 CONST is used as key for the constant term.'''
150 self.
terms = {}
if terms
is None else terms
152 if len(self.
terms) == 0:
153 self.
terms[CONST] = 0.0
156 if not isinstance(key, Term):
158 return self.
terms.get(key, 0.0)
161 return iter(self.
terms)
164 try:
return next(self.
terms)
165 except:
raise StopIteration
175 assert isinstance(other, Expr)
176 left,right = right,left
177 terms = left.terms.copy()
179 if isinstance(right, Expr):
181 for v,c
in right.terms.items():
182 terms[v] = terms.get(v, 0.0) + c
183 elif _is_number(right):
185 terms[CONST] = terms.get(CONST, 0.0) + c
186 elif isinstance(right, GenExpr):
189 raise NotImplementedError
193 if isinstance(other, Expr):
194 for v,c
in other.terms.items():
196 elif _is_number(other):
198 self.
terms[CONST] = self.
terms.get(CONST, 0.0) + c
199 elif isinstance(other, GenExpr):
205 raise NotImplementedError
209 if _is_number(other):
211 return Expr({v:f*c
for v,c
in self.
terms.items()})
212 elif _is_number(self):
214 return Expr({v:f*c
for v,c
in other.terms.items()})
215 elif isinstance(other, Expr):
217 for v1, c1
in self.
terms.items():
218 for v2, c2
in other.terms.items():
220 terms[v] = terms.get(v, 0.0) + c1 * c2
222 elif isinstance(other, GenExpr):
225 raise NotImplementedError
228 if _is_number(other):
232 return selfexpr.__truediv__(other)
240 return otherexpr.__truediv__(self)
243 if float(other).is_integer()
and other >= 0:
254 return Expr({v:-c
for v,c
in self.
terms.items()})
257 return self + (-other)
266 return -1.0 * self + other
269 '''turn it into a constraint'''
270 return _expr_richcmp(self, other, op)
273 '''remove terms with coefficient of 0'''
274 self.
terms = {t:c
for (t,c)
in self.
terms.items()
if c != 0.0}
277 return 'Expr(%s)' % repr(self.
terms)
280 '''computes highest degree of terms'''
281 if len(self.
terms) == 0:
284 return max(len(v)
for v
in self.
terms)
288 '''Constraints with a polynomial expressions and lower/upper bounds.'''
297 assert not (lhs
is None and rhs
is None)
301 '''move constant terms in expression to bounds'''
302 if isinstance(self.
expr, Expr):
305 assert self.
expr[CONST] == 0.0
308 assert isinstance(self.
expr, GenExpr)
311 if not self.
_lhs is None:
313 if not self.
_rhs is None:
318 '''turn it into a constraint'''
320 if not self.
_rhs is None:
321 raise TypeError(
'ExprCons already has upper bound')
322 assert self.
_rhs is None
323 assert not self.
_lhs is None
325 if not _is_number(other):
326 raise TypeError(
'Ranged ExprCons is not well defined!')
330 if not self.
_lhs is None:
331 raise TypeError(
'ExprCons already has lower bound')
332 assert self.
_lhs is None
333 assert not self.
_rhs is None
335 if not _is_number(other):
336 raise TypeError(
'Ranged ExprCons is not well defined!')
343 return 'ExprCons(%s, %s, %s)' % (self.
expr, self.
_lhs, self.
_rhs)
346 '''Make sure that equality of expressions is not asserted with =='''
348 msg =
"""Can't evaluate constraints as booleans.
350 If you want to add a ranged constraint of the form
351 lhs <= expression <= rhs
352 you have to use parenthesis to break the Python syntax for chained comparisons:
353 lhs <= (expression <= rhs)
358 '''add linear expressions and constants much faster than Python's sum
359 by avoiding intermediate data structures and adding terms inplace
362 for term
in termlist:
367 '''multiply linear expressions and constants by avoiding intermediate
368 data structures and multiplying terms inplace
371 for term
in termlist:
379 exp, log, sqrt, sin, cos =
'exp',
'log',
'sqrt',
'sin',
'cos'
380 plus, minus, mul, div, power =
'+',
'-',
'*',
'/',
'**'
412 if left.getOp() == Operator.add:
413 ans.coefs.extend(left.coefs)
414 ans.children.extend(left.children)
415 ans.constant += left.constant
416 elif left.getOp() == Operator.const:
417 ans.constant += left.number
419 ans.coefs.append(1.0)
420 ans.children.append(left)
423 if right.getOp() == Operator.add:
424 ans.coefs.extend(right.coefs)
425 ans.children.extend(right.children)
426 ans.constant += right.constant
427 elif right.getOp() == Operator.const:
428 ans.constant += right.number
430 ans.coefs.append(1.0)
431 ans.children.append(right)
467 if left.getOp() == Operator.prod:
468 ans.children.extend(left.children)
469 ans.constant *= left.constant
470 elif left.getOp() == Operator.const:
471 ans.constant *= left.number
473 ans.children.append(left)
476 if right.getOp() == Operator.prod:
477 ans.children.extend(right.children)
478 ans.constant *= right.constant
479 elif right.getOp() == Operator.const:
480 ans.constant *= right.number
482 ans.children.append(right)
510 if expo.getOp() != Operator.const:
511 raise NotImplementedError(
"exponents must be numbers")
512 if self.
getOp() == Operator.const:
513 return Constant(self.number**expo.number)
515 ans.children.append(self)
516 ans.expo = expo.number
524 if divisor.getOp() == Operator.const
and divisor.number == 0.0:
525 raise ZeroDivisionError(
"cannot divide by 0")
526 return self * divisor**(-1)
531 return otherexpr.__truediv__(self)
537 return self + (-other)
546 return -1.0 * self + other
549 '''turn it into a constraint'''
550 return _expr_richcmp(self, other, op)
553 '''Note: none of these expressions should be polynomial'''
557 '''returns operator of GenExpr'''
571 self.
_op = Operator.add
573 return self.
_op +
"(" + str(self.
constant) +
"," +
",".join(map(
lambda child : child.__repr__(), self.
children)) +
")"
581 self.
_op = Operator.prod
583 return self.
_op +
"(" + str(self.
constant) +
"," +
",".join(map(
lambda child : child.__repr__(), self.
children)) +
")"
590 self.
_op = Operator.varidx
600 self.
_op = Operator.power
618 self.
_op = Operator.const
624 """returns expression with exp-function"""
627 """returns expression with log-function"""
630 """returns expression with sqrt-function"""
633 """returns expression with sin-function"""
636 """returns expression with cos-function"""
640 '''transforms tree to an array of nodes. each node is an operator and the position of the
641 children of that operator (i.e. the other nodes) in the array'''
642 assert isinstance(expr, GenExpr)
648 """adds a given value to an array"""
649 nodes.append(tuple([
'const', [val]]))
650 return len(nodes) - 1
658 """adds expression to array"""
660 if op == Operator.const:
661 nodes.append(tuple([op, [expr.number]]))
662 elif op != Operator.varidx:
664 nchildren = len(expr.children)
665 for child
in expr.children:
668 if op == Operator.power:
671 elif (op == Operator.add
and expr.constant != 0.0)
or (op == Operator.prod
and expr.constant != 1.0):
674 nodes.append( tuple( [op, indices] ) )
676 nodes.append( tuple( [op, expr.children] ) )
677 return len(nodes) - 1