Skip to content

Commit

Permalink
Add tests for operators on LpConstraint (#796)
Browse files Browse the repository at this point in the history
* Add tests for operators on LpConstraint

* Add test based on sample provided in #794

* Format with black
  • Loading branch information
MBradbury authored Feb 20, 2025
1 parent cd28bdd commit bf20e7c
Show file tree
Hide file tree
Showing 2 changed files with 320 additions and 66 deletions.
123 changes: 58 additions & 65 deletions pulp/pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
import warnings
import math
from time import time
from typing import Any
from typing import Any, Literal

from .apis import LpSolverDefault, PULP_CBC_CMD
from .apis.core import clock
Expand Down Expand Up @@ -770,7 +770,9 @@ def copy(self):
# Will not copy the name
return LpAffineExpression(self)

def __str__(self, constant=1):
def __str__(
self, include_constant: bool = True, override_constant: float | None = None
):
s = ""
for v in self.sorted_keys():
val = self[v]
Expand All @@ -786,14 +788,15 @@ def __str__(self, constant=1):
s += str(v)
else:
s += str(val) + "*" + str(v)
if constant:
if include_constant:
constant = self.constant if override_constant is None else override_constant
if s == "":
s = str(self.constant)
s = str(constant)
else:
if self.constant < 0:
s += " - " + str(-self.constant)
elif self.constant > 0:
s += " + " + str(self.constant)
if constant < 0:
s += " - " + str(-constant)
elif constant > 0:
s += " + " + str(constant)
elif s == "":
s = "0"
return s
Expand All @@ -806,9 +809,12 @@ def sorted_keys(self) -> list[LpElement]:
result.sort(key=lambda v: v.name)
return result

def __repr__(self):
def __repr__(self, override_constant: float | None = None):
constant = constant = (
self.constant if override_constant is None else override_constant
)
l = [str(self[v]) + "*" + str(v) for v in self.sorted_keys()]
l.append(str(self.constant))
l.append(str(constant))
s = " + ".join(l)
return s

Expand Down Expand Up @@ -881,7 +887,7 @@ def asCplexLpAffineExpression(
result = "%s\n" % "\n".join(result)
return result

def addInPlace(self, other, sign=1):
def addInPlace(self, other, sign: Literal[+1, -1] = 1):
"""
:param int sign: the sign of the operation to do other.
if we add other => 1
Expand Down Expand Up @@ -1059,7 +1065,7 @@ def __init__(self, e=None, sense=const.LpConstraintEQ, name=None, rhs=None):
self.expr = (
e if isinstance(e, LpAffineExpression) else LpAffineExpression(e, name=name)
)
self.constant: float = 0.0
self.constant: float = self.expr.constant
if rhs is not None:
self.constant -= rhs
self.sense = sense
Expand All @@ -1080,11 +1086,17 @@ def getUb(self):
return None

def __str__(self):
s = self.expr.__str__(0)
s = self.expr.__str__(include_constant=False, override_constant=self.constant)
if self.sense is not None:
s += " " + const.LpConstraintSenses[self.sense] + " " + str(-self.constant)
return s

def __repr__(self):
s = self.expr.__repr__(override_constant=self.constant)
if self.sense is not None:
s += " " + const.LpConstraintSenses[self.sense] + " 0"
return s

def asCplexLpConstraint(self, name):
"""
Returns a constraint as a string
Expand Down Expand Up @@ -1113,58 +1125,53 @@ def asCplexLpAffineExpression(self, name: str, include_constant: bool = True):
name, include_constant, override_constant=self.constant
)

def changeRHS(self, RHS):
def changeRHS(self, RHS: float):
"""
alters the RHS of a constraint so that it can be modified in a resolve
"""
self.constant = -RHS
self.modified = True

def __repr__(self):
s = repr(self.expr)
if self.sense is not None:
s += " " + const.LpConstraintSenses[self.sense] + " 0"
return s

def copy(self):
"""Make a copy of self"""
return LpConstraint(self, self.sense, rhs=-self.constant)
return LpConstraint(
self.expr.copy(), self.sense, rhs=-self.constant + self.expr.constant
)

def emptyCopy(self):
return LpConstraint(sense=self.sense)

def addInPlace(self, other, sign=1):
def addInPlace(self, other, sign: Literal[+1, -1] = 1):
"""
:param int sign: the sign of the operation to do other.
if we add other => 1
if we subtract other => -1
"""
if isinstance(other, LpConstraint):
if self.sense * other.sense >= 0:
self.constant += other.constant
self.expr.addInPlace(other.expr, 1)
self.sense |= other.sense
else:
self.constant -= other.constant
self.expr.addInPlace(other.expr, -1)
self.sense |= -other.sense
elif isinstance(other, list):
for e in other:
self.addInPlace(e, sign)
else:
if isinstance(other, (int, float)):
self.constant += other * sign
if not (self.sense * other.sense >= 0):
sign = -sign
self.constant += other.constant * sign
self.expr.addInPlace(other.expr, sign)
self.sense |= other.sense * sign
elif isinstance(other, (int, float)):
self.constant += other * sign
self.expr.addInPlace(other, sign)
elif isinstance(other, LpAffineExpression):
self.constant += other.constant * sign
self.expr.addInPlace(other, sign)
elif isinstance(other, LpVariable):
self.expr.addInPlace(other, sign)
# raise TypeError, "Constraints and Expressions cannot be added"
else:
raise TypeError(f"Constraints and {type(other)} cannot be added")
return self

def subInPlace(self, other):
return self.addInPlace(other, -1)

def __neg__(self):
c = self.copy()
c.constant = -c.constant
c.expr = -c.expr
c.sense = -c.sense
return c

def __add__(self, other):
Expand All @@ -1180,51 +1187,37 @@ def __rsub__(self, other):
return (-self).addInPlace(other)

def __mul__(self, other):
if isinstance(other, LpConstraint):
if isinstance(other, (int, float)):
c = self.copy()
c.constant = c.constant * other
c.expr = c.expr * other
if c.sense == 0:
c.sense = other.sense
elif other.sense != 0:
c.sense *= other.sense
return c
else:
elif isinstance(other, LpAffineExpression):
c = self.copy()
c.constant = c.constant * other.constant
c.expr = c.expr * other
return c
else:
raise TypeError(f"Cannot multiple LpConstraint by {type(other)}")

def __rmul__(self, other):
return self * other

def __div__(self, other):
if isinstance(other, LpConstraint):
c = self.copy()
c.expr = c.expr / other
if c.sense == 0:
c.sense = other.sense
elif other.sense != 0:
c.sense *= other.sense
return c
else:
def __truediv__(self, other):
if isinstance(other, (int, float)):
c = self.copy()
c.constant = c.constant / other
c.expr = c.expr / other
return c

def __rdiv__(self, other):
if isinstance(other, LpConstraint):
elif isinstance(other, LpAffineExpression):
c = self.copy()
c.constant = c.constant / other.constant
c.expr = c.expr / other
if c.sense == 0:
c.sense = other.sense
elif other.sense != 0:
c.sense *= other.sense
return c
else:
c = self.copy()
c.expr = c.expr / other
return
raise TypeError(f"Cannot divide LpConstraint by {type(other)}")

def valid(self, eps=0) -> bool:
def valid(self, eps: float = 0) -> bool:
val = self.value()
if self.sense == const.LpConstraintEQ:
return abs(val) <= eps
Expand Down
Loading

0 comments on commit bf20e7c

Please sign in to comment.