Description
Details for the issue
What did you do?
Creating large problems in PULP is slow, there are a two issues:
- Creating expressions via
pulp.lpSum
is slower than directly creating them viapulp.LpAffineExpression
- Making constraints requires allocating and filling an additional 2 copies of an LpAffineExpression
import pulp, numpy as np, itertools
# Some reward matrix
reward = np.full((300, 200, 50), 10)
prob = pulp.LpProblem("Problem", pulp.constants.LpMaximize)
dv = pulp.LpVariable.dicts("dv",
itertools.product(range(300), range(200), range(50)),
cat=pulp.constants.LpBinary)
Slow objective function:
prob += pulp.lpSum([
dv[a, b, c] * reward[a, b, c]
for a in range(300)
for b in range(200)
for c in range(50)
])
Faster objective function:
prob += pulp.LpAffineExpression(
(dv[a, b, c], reward[a, b, c])
for a in range(300)
for b in range(200)
for c in range(50)
)
Slow constraint:
for c in range(50):
prob += pulp.lpSum([
dv[a, b, c]
for a in range(300)
for b in range(200)
]) == 1
Faster constraint:
for c in range(50):
prob += pulp.LpAffineExpression(
(dv[a, b, c], 1)
for a in range(300)
for b in range(200)
) == 1
Making constraints is slow as self - other
creates the first LpAffineExpression copy at https://github.com/coin-or/pulp/blob/master/pulp/pulp.py#L980, https://github.com/coin-or/pulp/blob/master/pulp/pulp.py#L983 or https://github.com/coin-or/pulp/blob/master/pulp/pulp.py#L986. A second copy is then made when LpConstraint's constructor is called (https://github.com/coin-or/pulp/blob/master/pulp/pulp.py#L1012).
It seems as if one of these copies can be avoided if the rhs
is set when other
is int/float. So in LpAffineExpression.__eq__
this would be:
def __eq__(self, other):
if isinstance(other, (float, int)):
return LpConstraint(self, const.LpConstraintEQ, rhs=other)
else:
return LpConstraint(self - other, const.LpConstraintEQ)
Avoiding the second copy of LpAffineExpression would require that LpConstraint does not inherit from LpAffineExpression, which is a more significant change.
Some performance numbers using the attached script:
Slow Objective: 16.692 seconds
Slow Constraint: 22.000 seconds
Fast Objective: 2.945 seconds
Fast Constraint: 20.278 seconds
Faster Constraint: 15.407 seconds
- Happy to send a PR with the change to use rhs if this looks correct.
- Unsure about the right way forward with lpSum, as LpAffineExpression's are constructed when doing the internal multiplication, ideally something more lightweight (e.g., tuple) would be used as the result of the multiplication.
Useful extra information
The info below often helps, please fill it out if you're able to. :)
What operating system are you using?
- Windows: Microsoft Windows [Version 10.0.19045.4291]
I'm using python version: Python 3.10.12
I installed PuLP via:
- pypi (python -m pip install pulp)
Did you also
- Tried out the latest github version: https://github.com/coin-or/pulp
- Searched for an existing similar issue: https://github.com/coin-or/pulp/issues?utf8=%E2%9C%93&q=is%3Aissue%20