7

I'm using the PuLP module in Python to formulate a mixed integer program. I am trying to work out how to set a MIP start (i.e. a feasible solution for the program to start from) via the PuLP interface.

Details on how to set MIP start are given here

And the developer of the PuLP package claims that you can access the full Gurobi model via the PuLP interface here

Pasted below are two complete models. I have made these as small as possible whilst preventing the gurobi solver from finding the optimal value using a heuristic.

I have attempted to set an initial solution (to the optimal values) in both models, but in the PuLP model it is ignored, but in the gurobipy model it works as expected.

How do you set an initial solution for the Gurobi solve via the PuLP interface?

from pulp import *

prob = LpProblem("min example",LpMinimize)

x1=LpVariable("x1",0,None,LpInteger)
x2=LpVariable("x2",0,None,LpInteger)
x3=LpVariable("x3",0,None,LpInteger)
x4=LpVariable("x4",0,None,LpInteger)

# Objective function
prob += 3*x1 + 5*x2 + 6*x3 + 9*x4

# A constraint
prob += -2*x1 + 6*x2 -3*x3 + 4*x4 >= 2, "Con1"
prob += -5*x1 + 3*x2 + x3 + 3*x4 >= -2, "Con2"
prob += 5*x1 - x2 + 4*x3 - 2*x4 >= 3, "Con3"

# Choose solver, and set it to problem, and build the Gurobi model
solver = pulp.GUROBI()
prob.setSolver(solver)
prob.solver.buildSolverModel(prob)

# Attempt to set an initial feasible solution (in this case to an optimal solution)
prob.solverModel.getVars()[0].start = 1
prob.solverModel.getVars()[1].start = 1
prob.solverModel.getVars()[2].start = 0
prob.solverModel.getVars()[3].start = 0

# Solve model
prob.solve()

# Status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]

# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
    print v.name, "=", v.varValue

# Optimised objective function value is printed to the screen
print "OF = ", value(prob.objective)

Which returns:

Optimize a model with 3 rows, 4 columns and 12 nonzeros
Coefficient statistics:
  Matrix range    [1e+00, 6e+00]
  Objective range [3e+00, 9e+00]
  Bounds range    [0e+00, 0e+00]
  RHS range       [2e+00, 3e+00]
Found heuristic solution: objective 12
Presolve removed 0 rows and 1 columns
Presolve time: 0.00s
Presolved: 3 rows, 3 columns, 9 nonzeros
Variable types: 0 continuous, 3 integer (0 binary)

Root relaxation: objective 7.400000e+00, 1 iterations, 0.00 seconds

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

     0     0    7.40000    0    1   12.00000    7.40000  38.3%     -    0s
H    0     0                       8.0000000    7.40000  7.50%     -    0s

Explored 0 nodes (1 simplex iterations) in 0.00 seconds
Thread count was 8 (of 8 available processors)

Optimal solution found (tolerance 1.00e-04)
Best objective 8.000000000000e+00, best bound 8.000000000000e+00, gap 0.0%
('Gurobi status=', 2)
Status: Optimal
x1 = 1.0
x2 = 1.0
x3 = -0.0
x4 = -0.0
OF =  8.0

Secondly I can implement the same model using the gurobipy module, but in this case the MIP start is actually used:

from gurobipy import *

m = Model("min example")
m.modelSense = GRB.MINIMIZE

objFcnCoeffs = [3, 5, 6, 9]
xVars = []
for i in range(4):
    xVars.append(m.addVar(vtype=GRB.INTEGER, obj=objFcnCoeffs[i], name="Open%d" % i))

# Update model to integrate new variables
m.update()

# Constraints
m.addConstr(-2*xVars[0] + 6*xVars[1] -3*xVars[2] + 4*xVars[3] >= 2, "Con1")
m.addConstr(-5*xVars[0] + 3*xVars[1] + xVars[2] + 3*xVars[3] >= -2, "Con2")
m.addConstr(5*xVars[0] - xVars[1] + 4*xVars[2] - 2*xVars[3] >= 3, "Con3")


# Attempt to set an initial feasible solution (in this case to an optimal solution)
startValues = [1, 1, 0, 0]
for i in range(4):
    xVars[i].start = startValues[i]

# Solve model
m.optimize()

# Print solution
print('\nTOTAL COSTS: %g' % m.objVal)
for i in range(4):
    print('\n xVar[%s] = %g' % i, xVars[i])

Which returns:

Optimize a model with 3 rows, 4 columns and 12 nonzeros
Coefficient statistics:
  Matrix range    [1e+00, 6e+00]
  Objective range [3e+00, 9e+00]
  Bounds range    [0e+00, 0e+00]
  RHS range       [2e+00, 3e+00]
Found heuristic solution: objective 12
Presolve removed 0 rows and 1 columns
Presolve time: 0.00s
Presolved: 3 rows, 3 columns, 9 nonzeros

Loaded MIP start with objective 8

Variable types: 0 continuous, 3 integer (0 binary)

Root relaxation: infeasible, 0 iterations, 0.00 seconds

Explored 0 nodes (0 simplex iterations) in 0.00 seconds
Thread count was 8 (of 8 available processors)

Optimal solution found (tolerance 1.00e-04)
Best objective 8.000000000000e+00, best bound 8.000000000000e+00, gap 0.0%

TOTAL COSTS: 8

 xVar[0] = 1

 xVar[1] = 1

 xVar[2] = 0

 xVar[3] = 0
kabdulla
  • 4,549
  • 1
  • 12
  • 28
  • Can you explain what is the use of "xVars[i].start" over here? I referred to the documentation but it does not really seem to make much sense to me. – Akshay Sapra Nov 27 '19 at 07:47
  • It is attempting to set an initial solution for the solver to search from. But see answer below on how to get this to work properly and also comment on lack of documentation. – kabdulla Nov 27 '19 at 08:57

2 Answers2

8

You are setting the start values like this

prob.solverModel.getVars()[0].start = 1

and you are then solving the model with this call

prob.solve().

The oritinal prob is not changed, if you call

prob.solver.callSolver(prob)

Gurobi will use the start vector.

Sonja Mars
  • 321
  • 1
  • 7
  • This works perfectly thanks. I think my understanding of the `pulp.pulp.LpProblem` and `gurobipy.Model` classes is a little limited. Are there any docs could you point me to, to brush up? – kabdulla Oct 20 '16 at 21:46
  • 1
    Great thanks for answering that. The interaction between Pulp and Gurobi is not well documented but if you look at the code in solvers.py you will see that after the model is built the gurobi variables and model are attached to the pulp variables and model. If you are doing this level of solver specific modelling I would recommend you take the 30minutes or so and convert your pulp model to gurobi proper (the syntax is very similar) and continue from there. Stu – Stuart Mitchell Oct 20 '16 at 22:37
1

Very late to the question but hopefully this will help new visitors. Starting in version 2.3 of PuLP, the common warmStart interface supports the GUROBI api. By following the instructions here you should be able to warm start the gurobi solver without having to tinker with the pulp internals or the gurobi package.

pchtsp
  • 574
  • 3
  • 14