How to edit constraints

Once a constraint is added to a problem, the problem keeps its own copy of it. You change that copy through its LpConstraint, the object the problem gives back for each of its constraints. Editing the expression you added does not change the problem.

Getting the LpConstraint

There are three ways to get the constraint as stored in the problem:

from pulp import LpProblem, LpMinimize

prob = LpProblem("edit", LpMinimize)
x = prob.add_variable("x", 0, 10)
y = prob.add_variable("y", 0, 10)

# 1. keep the one returned by addConstraint
cap = prob.addConstraint(x + y <= 5, "cap")

# 2. look it up by name (None if there is no such constraint)
cap = prob.get_constraint_by_name("cap")

# 3. take it from the list of all constraints, in the order they were added
cap = prob.constraints()[0]

prob += x + y <= 5, "cap" adds the constraint too, but returns the problem, not the constraint. Use addConstraint when you want to keep the constraint, or give it a name and look it up afterwards:

prob += x + y <= 5, "cap"
cap = prob.get_constraint_by_name("cap")
cap[x] = 2              # the problem now has 2*x + y <= 5

All three give an object for the same row of the problem: an edit made through one is visible through the others.

What you can change

cap = prob.addConstraint(x + y <= 5, "cap")   # x + y <= 5

cap[x] = 2              # 2*x + y <= 5     set a coefficient
del cap[y]              # 2*x <= 5         remove a term
cap[y] = 0              # same as del: a coefficient of 0 removes the term
cap.changeRHS(8)        # 2*x <= 8         set the right-hand side
cap += 3 * y            # 2*x + 3*y <= 8   add terms to the left-hand side
cap -= 1                # 2*x + 3*y <= 9   (the left-hand side gets -1)
cap.sense = LpConstraintGE   # 2*x + 3*y >= 9
  • cap[x] = value and cap.set_coefficient(x, value) are the same. If x is not in the constraint yet, it is added.

  • changeRHS(rhs) sets the right-hand side. You can also set cap.constant, which is the constant on the left-hand side, so cap.constant = -8 is the same as cap.changeRHS(8).

  • += and -= accept anything you can add to an expression: a variable, an expression, a number, or a list or dict of them. Numbers go to the left-hand side, so they move the right-hand side the other way.

  • The sense is LpConstraintLE, LpConstraintGE or LpConstraintEQ (from pulp).

  • The name can be changed with cap.name = "new_name".

Each change is checked before it is applied. An invalid change, such as an infinite or NaN coefficient, a variable from another problem, or cap.sense = None, raises PulpError and leaves the constraint as it was.

The dual value (cap.pi) and the slack (cap.slack) of the last solve are kept after an edit, and the next solve replaces them.

Example: re-solving with new data

A common use is to solve a model several times with a different right-hand side:

demand = prob.addConstraint(lpSum(flow[w]["bar_1"] for w in warehouses) >= 500, "demand_bar_1")

for value in range(500, 601, 10):
    demand.changeRHS(value)
    stats = prob.solve()
    print(value, stats.objective)

See examples/BeerDistributionProblem_resolve.py for the complete script.

What does not work: editing the expression

addConstraint (and prob += ...) copies the expression into the problem. Changing the expression afterwards changes only the expression:

c = x + y <= 5
prob += c, "c"

c[x] = 3               # UserWarning; the problem still has x + y <= 5
c.changeRHS(8)         # UserWarning; same

PuLP warns you when you edit an expression that has a sense (one created with <=, >= or ==), because that is usually this mistake:

UserWarning: Editing an LpAffineExpression with a sense does not change any
problem it was added to. To edit a constraint in a problem, edit its
LpConstraint: get it with prob.get_constraint_by_name(name) or
prob.constraints(), or keep the one returned by prob.addConstraint(...);
then e.g. con[x] = 2 or con.changeRHS(3).

To fix the code, edit the LpConstraint instead:

c = prob.addConstraint(x + y <= 5, "c")
c[x] = 3               # the problem now has 3*x + y <= 5

or, if the constraint was added with prob += ..., look it up by name:

prob += x + y <= 5, "c"
c = prob.get_constraint_by_name("c")
c[x] = 3               # the problem now has 3*x + y <= 5

The expression does not know whether it was added to a problem, so the warning also appears when you edit an expression with a sense before adding it:

c = x <= 5
c += y                 # UserWarning, although c was not added yet

Build the whole expression before turning it into a constraint, or edit the LpConstraint after adding it:

c = x + y <= 5                         # no warning
c = prob.addConstraint(x <= 5, "c")
c += y                                 # no warning: edits the problem

Expressions without a sense, such as an objective or x + 2 * y, never warn. Operators that build a new constraint (x + y <= 5, -c, c + y) do not warn either, because they leave c unchanged.

Note

In PuLP 3.x the problem stored the expression object itself, so editing it changed the problem. Code written that way still runs in PuLP 4, but the edits no longer reach the problem. The warning shows you where.

Adding variables to existing constraints

cap[x] = value also works for a variable created after the constraint. To add a new variable to several constraints and to the objective in one call, use prob.add_column; see How to model column-wise.