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
84 '''This is a monomial term''' 86 __slots__ = (
'vartuple',
'ptrtuple',
'hashval')
88 def __init__(self, *vartuple):
89 self.
vartuple = tuple(sorted(vartuple, key=
lambda v: v.ptr()))
93 def __getitem__(self, idx):
99 def __eq__(self, other):
100 return self.
ptrtuple == other.ptrtuple
105 def __add__(self, other):
106 both = self.
vartuple + other.vartuple
110 return 'Term(%s)' %
', '.join([str(v)
for v
in self.
vartuple])
115 def buildGenExprObj(expr):
116 """helper function to generate an object of type GenExpr""" 119 elif isinstance(expr, Expr):
124 for vars, coef
in expr.terms.items():
129 sumexpr += coef * varexpr
135 sumexpr += coef * prodexpr
138 assert isinstance(expr, GenExpr)
142 '''Polynomial expressions of variables with operator overloading.''' 145 def __init__(self, terms=None):
146 '''terms is a dict of variables to coefficients. 148 CONST is used as key for the constant term.''' 149 self.
terms = {}
if terms
is None else terms
151 if len(self.
terms) == 0:
152 self.
terms[CONST] = 0.0
154 def __getitem__(self, key):
155 if not isinstance(key, Term):
157 return self.terms.get(key, 0.0)
160 return iter(self.
terms)
163 try:
return next(self.
terms)
164 except:
raise StopIteration
167 return abs(buildGenExprObj(self))
169 def __add__(self, other):
174 assert isinstance(other, Expr)
175 left,right = right,left
176 terms = left.terms.copy()
178 if isinstance(right, Expr):
180 for v,c
in right.terms.items():
181 terms[v] = terms.get(v, 0.0) + c
182 elif _is_number(right):
184 terms[CONST] = terms.get(CONST, 0.0) + c
185 elif isinstance(right, GenExpr):
186 return buildGenExprObj(left) + right
188 raise NotImplementedError
191 def __iadd__(self, other):
192 if isinstance(other, Expr):
193 for v,c
in other.terms.items():
194 self.
terms[v] = self.terms.get(v, 0.0) + c
195 elif _is_number(other):
197 self.
terms[CONST] = self.terms.get(CONST, 0.0) + c
198 elif isinstance(other, GenExpr):
202 return buildGenExprObj(self) + other
204 raise NotImplementedError
207 def __mul__(self, other):
208 if _is_number(other):
210 return Expr({v:f*c
for v,c
in self.terms.items()})
211 elif _is_number(self):
213 return Expr({v:f*c
for v,c
in other.terms.items()})
214 elif isinstance(other, Expr):
216 for v1, c1
in self.terms.items():
217 for v2, c2
in other.terms.items():
219 terms[v] = terms.get(v, 0.0) + c1 * c2
221 elif isinstance(other, GenExpr):
222 return buildGenExprObj(self) * other
224 raise NotImplementedError
226 def __div__(self, other):
227 ''' transforms Expr into GenExpr''' 228 if _is_number(other):
231 selfexpr = buildGenExprObj(self)
232 return selfexpr.__div__(other)
234 def __rdiv__(self, other):
239 otherexpr = buildGenExprObj(other)
240 return otherexpr.__div__(self)
242 def __truediv__(self,other):
243 if _is_number(other):
246 selfexpr = buildGenExprObj(self)
247 return selfexpr.__truediv__(other)
249 def __rtruediv__(self, other):
254 otherexpr = buildGenExprObj(other)
255 return otherexpr.__truediv__(self)
257 def __pow__(self, other, modulo):
258 if float(other).is_integer()
and other >= 0:
261 return buildGenExprObj(self)**other
269 return Expr({v:-c
for v,c
in self.terms.items()})
271 def __sub__(self, other):
272 return self + (-other)
274 def __radd__(self, other):
277 def __rmul__(self, other):
280 def __rsub__(self, other):
281 return -1.0 * self + other
283 def __richcmp__(self, other, op):
284 '''turn it into a constraint''' 285 return _expr_richcmp(self, other, op)
288 '''remove terms with coefficient of 0''' 289 self.
terms = {t:c
for (t,c)
in self.terms.items()
if c != 0.0}
292 return 'Expr(%s)' % repr(self.
terms)
295 '''computes highest degree of terms''' 296 if len(self.
terms) == 0:
299 return max(len(v)
for v
in self.
terms)
303 '''Constraints with a polynomial expressions and lower/upper bounds.''' 308 def __init__(self, expr, lhs=None, rhs=None):
312 assert not (lhs
is None and rhs
is None)
316 '''move constant terms in expression to bounds''' 317 if isinstance(self.
expr, Expr):
320 assert self.
expr[CONST] == 0.0
321 self.expr.normalize()
323 assert isinstance(self.
expr, GenExpr)
326 if not self.
lhs is None:
328 if not self.
rhs is None:
332 def __richcmp__(self, other, op):
333 '''turn it into a constraint''' 335 if not self.
rhs is None:
336 raise TypeError(
'ExprCons already has upper bound')
337 assert self.
rhs is None 338 assert not self.
lhs is None 340 if not _is_number(other):
341 raise TypeError(
'Ranged ExprCons is not well defined!')
345 if not self.
lhs is None:
346 raise TypeError(
'ExprCons already has lower bound')
347 assert self.
lhs is None 348 assert not self.
rhs is None 350 if not _is_number(other):
351 raise TypeError(
'Ranged ExprCons is not well defined!')
358 return 'ExprCons(%s, %s, %s)' % (self.
expr, self.
lhs, self.
rhs)
360 def __nonzero__(self):
361 '''Make sure that equality of expressions is not asserted with ==''' 363 msg =
"""Can't evaluate constraints as booleans. 365 If you want to add a ranged constraint of the form 366 lhs <= expression <= rhs 367 you have to use parenthesis to break the Python syntax for chained comparisons: 368 lhs <= (expression <= rhs) 372 def quicksum(termlist):
373 '''add linear expressions and constants much faster than Python's sum 374 by avoiding intermediate data structures and adding terms inplace 377 for term
in termlist:
381 def quickprod(termlist):
382 '''multiply linear expressions and constants by avoiding intermediate 383 data structures and multiplying terms inplace 386 for term
in termlist:
394 exp, log, sqrt =
'exp',
'log',
'sqrt' 395 plus, minus, mul, div, power =
'+',
'-',
'*',
'/',
'**' 400 varidx:SCIP_EXPR_VARIDX,
401 const:SCIP_EXPR_CONST,
403 minus:SCIP_EXPR_MINUS,
407 power:SCIP_EXPR_REALPOWER,
412 prod:SCIP_EXPR_PRODUCT
415 '''returns operator index''' 416 return Op.operatorIndexDic[op];
421 '''General expressions of variables with operator overloading. 424 - this expressions are not smart enough to identify equal terms 425 - in constrast to polynomial expressions, __getitem__ is not implemented 426 so expr[x] will generate an error instead of returning the coefficient of x 428 cdef public operatorIndex
439 def __add__(self, other):
440 left = buildGenExprObj(self)
441 right = buildGenExprObj(other)
445 if left.getOp() == Operator.add:
446 ans.coefs.extend(left.coefs)
447 ans.children.extend(left.children)
448 ans.constant += left.constant
449 elif left.getOp() == Operator.const:
450 ans.constant += left.number
452 ans.coefs.append(1.0)
453 ans.children.append(left)
456 if right.getOp() == Operator.add:
457 ans.coefs.extend(right.coefs)
458 ans.children.extend(right.children)
459 ans.constant += right.constant
460 elif right.getOp() == Operator.const:
461 ans.constant += right.number
463 ans.coefs.append(1.0)
464 ans.children.append(right)
494 def __mul__(self, other):
495 left = buildGenExprObj(self)
496 right = buildGenExprObj(other)
500 if left.getOp() == Operator.prod:
501 ans.children.extend(left.children)
502 ans.constant *= left.constant
503 elif left.getOp() == Operator.const:
504 ans.constant *= left.number
506 ans.children.append(left)
509 if right.getOp() == Operator.prod:
510 ans.children.extend(right.children)
511 ans.constant *= right.constant
512 elif right.getOp() == Operator.const:
513 ans.constant *= right.number
515 ans.children.append(right)
541 def __pow__(self, other, modulo):
542 expo = buildGenExprObj(other)
543 if expo.getOp() != Operator.const:
544 raise NotImplementedError(
"exponents must be numbers")
545 if self.getOp() == Operator.const:
546 return Constant(self.number**expo.number)
548 ans.children.append(self)
549 ans.expo = expo.number
554 def __div__(self, other):
555 divisor = buildGenExprObj(other)
557 if divisor.getOp() == Operator.const
and divisor.number == 0.0:
558 raise ZeroDivisionError(
"cannot divide by 0")
559 return self * divisor**(-1)
561 def __rdiv__(self, other):
563 otherexpr = buildGenExprObj(other)
564 return otherexpr.__div__(self)
566 def __truediv__(self,other):
567 divisor = buildGenExprObj(other)
569 if divisor.getOp() == Operator.const
and divisor.number == 0.0:
570 raise ZeroDivisionError(
"cannot divide by 0")
571 return self * divisor**(-1)
573 def __rtruediv__(self, other):
575 otherexpr = buildGenExprObj(other)
576 return otherexpr.__truediv__(self)
581 def __sub__(self, other):
582 return self + (-other)
584 def __radd__(self, other):
585 return self.__add__(other)
587 def __rmul__(self, other):
588 return self.__mul__(other)
590 def __rsub__(self, other):
591 return -1.0 * self + other
593 def __richcmp__(self, other, op):
594 '''turn it into a constraint''' 595 return _expr_richcmp(self, other, op)
598 '''Note: none of these expressions should be polynomial''' 602 '''returns operator of GenExpr''' 616 self.
op = Operator.add
619 return self.
op +
"(" + str(self.
constant) +
"," +
",".join(map(
lambda child : child.__repr__(), self.
children)) +
")" 627 self.
op = Operator.prod
630 return self.
op +
"(" + str(self.
constant) +
"," +
",".join(map(
lambda child : child.__repr__(), self.
children)) +
")" 635 def __init__(self, var):
637 self.
op = Operator.varidx
648 self.
op = Operator.power
651 return self.
op +
"(" + self.
children[0].__repr__() +
"," + str(self.
expo) +
")" 655 def __init__(self, op, expr):
657 self.children.append(expr)
661 return self.
op +
"(" + self.
children[0].__repr__() +
")" 666 def __init__(self,number):
668 self.
op = Operator.const
675 """returns expression with exp-function""" 676 return UnaryExpr(Operator.exp, buildGenExprObj(expr))
678 """returns expression with log-function""" 679 return UnaryExpr(Operator.log, buildGenExprObj(expr))
681 """returns expression with sqrt-function""" 682 return UnaryExpr(Operator.sqrt, buildGenExprObj(expr))
684 def expr_to_nodes(expr):
685 '''transforms tree to an array of nodes. each node is an operator and the position of the 686 children of that operator (i.e. the other nodes) in the array''' 687 assert isinstance(expr, GenExpr)
689 expr_to_array(expr, nodes)
692 def value_to_array(val, nodes):
693 """adds a given value to an array""" 694 nodes.append(tuple([
'const', [val]]))
695 return len(nodes) - 1
702 def expr_to_array(expr, nodes):
703 """adds expression to array""" 705 if op == Operator.const:
706 nodes.append(tuple([op, [expr.number]]))
707 elif op != Operator.varidx:
709 nchildren = len(expr.children)
710 for child
in expr.children:
711 pos = expr_to_array(child, nodes)
713 if op == Operator.power:
714 pos = value_to_array(expr.expo, nodes)
716 elif (op == Operator.add
and expr.constant != 0.0)
or (op == Operator.prod
and expr.constant != 1.0):
717 pos = value_to_array(expr.constant, nodes)
719 nodes.append( tuple( [op, indices] ) )
721 nodes.append( tuple( [op, expr.children] ) )
722 return len(nodes) - 1