Skip to content

Commit

Permalink
fixes #674 (#724)
Browse files Browse the repository at this point in the history
  • Loading branch information
pchtsp authored Jan 16, 2024
1 parent f211372 commit cfde9c6
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 59 deletions.
101 changes: 44 additions & 57 deletions pulp/pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
from collections import Counter
import sys
import warnings
import math
from time import time

from .apis import LpSolverDefault, PULP_CBC_CMD
Expand Down Expand Up @@ -657,6 +658,8 @@ class LpAffineExpression(_DICT_TYPE):
1*x_0 + -3*x_1 + 4*x_2 + 0
"""

constant: float
name: str
# to remove illegal characters from the names
trans = maketrans("-+[] ", "_____")

Expand Down Expand Up @@ -842,47 +845,43 @@ def asCplexLpAffineExpression(self, name, constant=1):
result = "%s\n" % "\n".join(result)
return result

def addInPlace(self, other):
def addInPlace(self, other, sign=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, int) and (other == 0):
return self
if other is None:
return self
if isinstance(other, LpElement):
self.addterm(other, 1)
# if a variable, we add it to the dictionary
self.addterm(other, sign)
elif isinstance(other, LpAffineExpression):
self.constant += other.constant
# if an expression, we add each variable and the constant
self.constant += other.constant * sign
for v, x in other.items():
self.addterm(v, x)
self.addterm(v, x * sign)
elif isinstance(other, dict):
# if a dictionary, we add each value
for e in other.values():
self.addInPlace(e)
self.addInPlace(e, sign=sign)
elif isinstance(other, list) or isinstance(other, Iterable):
# if a list, we add each element of the list
for e in other:
self.addInPlace(e)
self.addInPlace(e, sign=sign)
# if we're here, other must be a number
# we check if it's an actual number:
elif not math.isfinite(other):
raise const.PulpError("Cannot add/subtract NaN/inf values")
# if it's indeed a number, we add it to the constant
else:
self.constant += other
self.constant += other * sign
return self

def subInPlace(self, other):
if isinstance(other, int) and (other == 0):
return self
if other is None:
return self
if isinstance(other, LpElement):
self.addterm(other, -1)
elif isinstance(other, LpAffineExpression):
self.constant -= other.constant
for v, x in other.items():
self.addterm(v, -x)
elif isinstance(other, dict):
for e in other.values():
self.subInPlace(e)
elif isinstance(other, list) or isinstance(other, Iterable):
for e in other:
self.subInPlace(e)
else:
self.constant -= other
return self
return self.addInPlace(other, sign=-1)

def __neg__(self):
e = self.emptyCopy()
Expand Down Expand Up @@ -932,7 +931,9 @@ def __mul__(self, other):
elif isinstance(other, LpVariable):
return self * LpAffineExpression(other)
else:
if other != 0:
if not math.isfinite(other):
raise const.PulpError("Cannot multiply variables with NaN/inf values")
elif other != 0:
e.constant = self.constant * other
for v, x in self.items():
e[v] = other * x
Expand All @@ -948,24 +949,16 @@ def __div__(self, other):
"Expressions cannot be divided by a non-constant expression"
)
other = other.constant
if not math.isfinite(other):
raise const.PulpError("Cannot divide variables with NaN/inf values")
e = self.emptyCopy()
e.constant = self.constant / other
for v, x in self.items():
e[v] = x / other
return e

def __truediv__(self, other):
if isinstance(other, LpAffineExpression) or isinstance(other, LpVariable):
if len(other):
raise TypeError(
"Expressions cannot be divided by a non-constant expression"
)
other = other.constant
e = self.emptyCopy()
e.constant = self.constant / other
for v, x in self.items():
e[v] = x / other
return e
return self.__div__(other)

def __rdiv__(self, other):
e = self.emptyCopy()
Expand All @@ -978,6 +971,8 @@ def __rdiv__(self, other):
e.constant = other.constant / c
for v, x in other.items():
e[v] = x / c
elif not math.isfinite(other):
raise const.PulpError("Cannot divide variables with NaN/inf values")
else:
e.constant = other / c
return e
Expand Down Expand Up @@ -1080,37 +1075,29 @@ def copy(self):
def emptyCopy(self):
return LpConstraint(sense=self.sense)

def addInPlace(self, other):
def addInPlace(self, other, sign=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:
LpAffineExpression.addInPlace(self, other)
LpAffineExpression.addInPlace(self, other, 1)
self.sense |= other.sense
else:
LpAffineExpression.subInPlace(self, other)
LpAffineExpression.addInPlace(self, other, -1)
self.sense |= -other.sense
elif isinstance(other, list):
for e in other:
self.addInPlace(e)
self.addInPlace(e, sign)
else:
LpAffineExpression.addInPlace(self, other)
LpAffineExpression.addInPlace(self, other, sign)
# raise TypeError, "Constraints and Expressions cannot be added"
return self

def subInPlace(self, other):
if isinstance(other, LpConstraint):
if self.sense * other.sense <= 0:
LpAffineExpression.subInPlace(self, other)
self.sense |= -other.sense
else:
LpAffineExpression.addInPlace(self, other)
self.sense |= other.sense
elif isinstance(other, list):
for e in other:
self.subInPlace(e)
else:
LpAffineExpression.subInPlace(self, other)
# raise TypeError, "Constraints and Expressions cannot be added"
return self
return self.addInPlace(other, -1)

def __neg__(self):
c = LpAffineExpression.__neg__(self)
Expand Down
19 changes: 17 additions & 2 deletions pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ def tearDown(self):

def test_pulp_001(self):
"""
Test that a variable is deleted when it is suptracted to 0
Test that a variable is deleted when it is subtracted to 0
"""
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
c1 = x + y <= 5
c2 = c1 + z - z
print("\t Testing zero subtraction")
assert str(c2) # will raise an exception
assert str(c2)
assert c2[z] == 0

def test_pulp_009(self):
# infeasible
Expand Down Expand Up @@ -1498,6 +1499,20 @@ def test_options_parsing_SCIP_HIGHS(self):
{x: 4, y: -1, z: 6, w: 0},
)

def test_sum_nan_values(self):
import math

a = math.nan
x = LpVariable("x")
self.assertRaises(PulpError, lambda: x + a)

def test_multiply_nan_values(self):
import math

a = math.nan
x = LpVariable("x")
self.assertRaises(PulpError, lambda: x * a)


class PULP_CBC_CMDTest(BaseSolverTest.PuLPTest):
solveInst = PULP_CBC_CMD
Expand Down

0 comments on commit cfde9c6

Please sign in to comment.