TypeError when LpAffineExpressions are Specified Using Decimals
When a LpAffineExpression is extended using Decimals, as of pulp 2.9.0 (and before), it would work without raising any exception and returning accurate results. Beginning in 3.0.0, it now raises a TypeError in the addInPlace method:
self.constant += other.constant * sign
A reproducing pytest file which can be run, passing in 2.9.0, and failing in 3.0.2(or 3.0.0): https://gist.github.com/mark92223/e590c21df931eda1927ae4230fec118d .
Is using Decimals in this context unsupported in Pulp?
Thank you!
Details for the issue
What did you do?
What did you expect to see?
The test to pass
What did you see instead?
def test_it(self):
from pulp import LpProblem, LpVariable, LpMaximize, LpAffineExpression
from pulp.apis import GLPK_CMD
m1, m2, extra = 3, Decimal("8.1"), 5
problem = LpProblem("graph", LpMaximize)
y = LpVariable("y", lowBound=0, upBound=Decimal("32.24"), cat="Continuous")
problem += y
expression = LpAffineExpression()
include_extra = LpVariable("include_extra1", cat="Binary")
x = LpVariable("x", lowBound=0,
upBound=random.randint(3, 50), cat="Continuous")
# y = 3x + 5 | y = 3x
expression += x * m1 + include_extra*extra - y
problem += expression == 0
second_expression = LpAffineExpression()
# y = 8.1x - 6
> second_expression += x * m2 - 6 - y
test_pypi_packages_for_wms.py:125:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/home/mark/ve3.13/lib/python3.13/site-packages/pulp/pulp.py:928: in __iadd__
return self.addInPlace(other)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = 0.0, other = 8.1*x + -1*y + -6.0, sign = 1
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):
# if a variable, we add it to the dictionary
self.addterm(other, sign)
elif isinstance(other, (LpAffineExpression, LpConstraint)):
# if an expression, we add each variable and the constant
> self.constant += other.constant * sign
E TypeError: unsupported operand type(s) for +=: 'float' and 'decimal.Decimal'
/home/mark/ve3.13/lib/python3.13/site-packages/pulp/pulp.py:888: TypeError
Useful extra information
The info below often helps, please fill it out if you're able to. :)
What operating system are you using?
- [ ] Windows: ( version: ___ )
- [x] Linux: (Arch Linux)
- [ ] Mac OS: ( version: ___ )
- [ ] Other: ___
I'm using python version:
- [ ] 3.7
- [ ] 3.8
- [ ] 3.9
- [ ] 3.10
- [ ] 3.11
- [x] Other: 3.13
I installed PuLP via:
- [x] pypi (python -m pip install pulp)
- [ ] github (python -m pip install -U git+https://github.com/coin-or/pulp)
- [ ] Other: ___ (conda?)
Did you also
- [ ] Tried out the latest github version: https://github.com/coin-or/pulp
- [x] Searched for an existing similar issue: https://github.com/coin-or/pulp/issues?utf8=%E2%9C%93&q=is%3Aissue%20
@pchtsp this looks like another one I broke, will take a look.
After some investigation, the reason this happens is that Decimal allows operations with ints but not floats.
$ python
Python 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from decimal import Decimal as D
>>> 0 + D("7")
Decimal('7')
>>> 0.0 + D("7")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'
@mark92223 a quick fix to try, instead of declaring your constraints as you have can you either do:
second_expression = LpAffineExpression(constant=Decimal("0"))
# y = 8.1x - 6
second_expression += x * m2 - 6 - y
or
# y = 8.1x - 6
second_expression = x * m2 - 6 - y
I expect this will not fully solve the issue, as in my tests I get an exception in assignConsSlack now.
Taking a look GLPK does not use assignConsSlack, so that is probably why you have not hit an issue there before.
The workaround does seem to resolve the crash, thanks (And obviously, it still returns the correct result)!
I personally still think that this ought to work as is, but appreciate the help regardless.
I'm closing as the issue was with Decimals.
@pchtsp So, are you saying that decimals are not supported in this context? This is not clear in the docs and previously worked successfully.
I had no idea the Decimal library existed. The error seems pretty explicit: floats and Decimals do not seem to get along.
I'm unsure why this worked before. Options:
- cast the Decimal into float (on your side)
- cast all values into floats (on pulp's side). [I imagine something of this was going on before.]
- any other thing?
Option 2 seems easy but then, why would you Decimal if the values are going to be converted to floats under the hood?
If you have any suggestion or better want to make a PR, go ahead!
For this issue, Decimal really will have only worked with a few of the backends. GLPK only works as it does not use assignConsSlack to assign outputs from the solver. GLPK still has an issue as it will read coefficients from the solver in as either int or float (https://github.com/coin-or/pulp/blob/master/pulp/apis/glpk_api.py#L134). So you will not get results in as a Decimal.
One option could be to have a global option which specifies what type the coefficients will be (e.g., int or float/Decimal).