Linear Programming with Python (Pulp) - Scheduling

3.9k views Asked by At

I'm trying to optimize a schedule by formulating it as an (integer) linear problem. I've run into some problems with the coding part (mostly due to Pulp), which is fairly separate from the formulation.

The Problem:

Schedule n ships over a period of 5 years (timesteps in months), subject to the following constraints:

  • one and only one ship must be on "Local Patrol" (state = 'C') each month
  • one and only one ship must go on "Extended Patrol" (state = 'A') each year, which is a four month block from July to October inclusive
  • each ship must have a 4 month long inspection once during the 5 year period (state = '4')
  • each ship must have an 8 month long break once during the 5 year period (state = '8')
  • each ship must have regular maintenance approx every 5 months (state = 'M')
  • a ship can be in only one state during a given month, but it can also have no state (open/vacant, '_')

Objective Function formulation:

Define slack variables that count the number of times (and the amount) the schedule deviates from the 5 month ('M') maintenance plan.

I can explain the setup more if needed, but for now I have some basic questions that might be the source of my problems.

Here is one of my constraints (blockList stores the start of the 4 and 8 month block for each ship):

# Each ship must have one 8block and one m4block in a 5 year schedule, which must be between 2 and 3 years apart
for n in ship_list:
    prob += blockList[n][0] - blockList[n][1] >= 24
    prob += blockList[n][0] - blockList[n][1] <= 36

I want to take the absolute value of the LHS of the above expressions, but I can't because the objects are initialized as pulp.LpVariable. Any recommendations for a workaround?

Anyways, that may or may not fix my problem. If it doesn't, my next question is for anyone familiar with pulp. How can I know if the solution true, or if there was some sort of elasticity added by the solver in the background? (Running the code as is with that constraint gives incorrect results, and using a different solver like GLPK gives an error).

1

There are 1 answers

2
Ram Narasimhan On

Here are a few suggestions:

  1. Taking Absolute Values

    In PuLP, you can do that by relying on the underlying Python commands to do that.

    Something along the lines of:

    if blockList[n][0] > blockList[n][1]:
        prob += blockList[n][0] - blockList[n][1] >= 24
    else:
        prob += blockList[n][1] - blockList[n][0] >= 24
    
  2. To see if the Solver that to "add elasticity" you'd examine the value of pulp.constants.LpStatusOptimal. If this value is 1, then the problem was solved to optimality. Note that the typical practice is to add a dummy slack or a surplus variable, give it a small penalty in the objective function. If in the solution that dummy variable is non-zero, that means that the problem needed the extra 'elasticity.'

  3. Finally, my main suggestion is that you start small, with a minimal example that works in PuLP. You can even start with one of the case studies here. At each step, write down your LP to a file and examine it. This will right away tell you which constraints or variables are off, and you can alter that.

    class pulp.LpProblem(name='outfile.lp', sense=1)
    

Hope this gets you moving forward.