Skip to main content

Solving VRP with time window doesn't converge but it makes plausible solution

Ongoing

Comments

2 comments

  • Ed Klotz
    • Gurobi Staff Gurobi Staff

    As you noted, the performance challenge here appears to involve progress in the dual  bound (aka best bound in the node log).   You have set the MIP Focus parameter to 1, which will cause Gurobi to work more on finding good feasible solution (as will your setting of Heuristics=0.5).   Those all appear to be good settings to use, but you might want to add some settings that are well suited to moving the dual bound more.   Specifically

    • Depending on how they are formulated and the input data, VRPs can have a fair bit of symmetry, so try setting the Symmetry parameter to 2
    • Aggressive cuts can sometimes move the dual bound parameter, so try setting Gurobi's Cuts parameter to 2

     

    Beyond that, perhaps you can use your knowledge of the model to derive a better dual bound and show that the MIPgap for the solutions Gurobi finds is actually significantly better than what is reported in the node log. What exactly is the objective in the model measuring, and can you derive your own lower bound for any integer feasible solution that is larger than the dual bound that is based on LP relaxations?   This subject will be discussed in our tech talk next week on strong vs weak MIP formulations; you can register here:

    https://www.gurobi.com/event/tech-talk-converting-weak-to-strong-mip-formulations/

    0
  • Daisuke Asaki
    • Gurobi-versary
    • First Comment
    • First Question

    Dear Ed,

    Thank you for your reply.

     

    I tried the symmetry and the cut parameter, but it didn't work well...

    Here is the entire code. The objective is the total working hours of all drivers.

    (An explanation: Even if a driver arrives at a node early he/she has to wait until the time window start time. Also, he/she has to arrive at a node within the time window end time. The total working hours is a sum of each driver's arrival time to the depot.)

    import pandas as pd
    import numpy as np
    from matplotlib import pyplot as plt
    from gurobipy import *
    import sys

    # prerequisit setting here
    capacity = 100
    num_vehicles = 13
    least_vehicles = 2
    df = pd.read_csv('ex.csv')
    df = df[0:15]
    coordinates = np.column_stack((list(df["X"]), list(df["Y"])))

    start_time, end_time, service_time = list(df["start_time"]), list(df["end_time"]), list(df["service_time"])
    demand = list(df["Demand"])
    n = len(coordinates)
    depot, customers = coordinates[0, :], coordinates[1:, :]
    inf = 100

    all_nodes = [i for i in range(n)]
    edges_index = [(i, j) for i in all_nodes for j in all_nodes if i != j] 

    model = Model("MVRP")
    model.Params.NoRelHeurTime=20
    model.Params.Heuristics=1.0
    model.Params.MIPFocus=1
    model.Params.Method=0
    model.Params.Symmetry=2
    model.Params.Cuts=2
    quantity, arrTime, depTime = {}, {}, {}  #intialize the decision variables

    X, Y = list(df["X"]), list(df["Y"])
    dist_matrix = np.empty([n, n])
    for i in range(len(X)):
        for j in range(len(Y)):
            dist_matrix[i, j] = np.sqrt((X[i] - X[j]) ** 2 + (Y[i] - Y[j]) ** 2)
            if i == j:
                dist_matrix[i, j] = inf
            continue

    num_drivers = model.addVar(vtype=GRB.INTEGER, ub=num_vehicles, lb=least_vehicles)
    edges = model.addVars(edges_index, vtype=GRB.BINARY)
    quantity = model.addVars(n, vtype=GRB.INTEGER, lb=0, ub=capacity)   # cumulative demand
    arrTime = model.addVars(n, vtype=GRB.CONTINUOUS, lb=0, ub=100) # arrival time
    depTime = model.addVars(n, vtype=GRB.CONTINUOUS, lb=0, ub=100) # depature time

    # visit node exact once == sum(x) == 1 
    for i in range(1, n):
        model.addConstr(quicksum(edges[i, j] for j in range(n) if i != j) == 1 )

    # visit node exact once == sum(x) == 1
    for j in range(1, n):
        model.addConstr(quicksum(edges[i, j] for i in range(n) if i != j) == 1)

    # leave depo once
    model.addConstr(quicksum(edges[0, j] for j in range(1, n)) == num_drivers)
    # arriv depot once
    model.addConstr(quicksum(edges[i, 0] for i in range(1, n)) == num_drivers)

    # capacity of vehicle and eliminate sub-tours
    model.addConstrs(quantity[j] <= capacity for j in range(n))
    model.addConstrs(quantity[j] >= demand[j] for j in range(n))
    model.addConstrs(
        quantity[j] >= quantity[i] + demand[j] * (edges[i, j]) - capacity * (1 - (edges[i, j]))
        for i in range(1, n)
        for j in range(1, n) if i != j
    )

    # time window(supposed to start at 9:00 am)
    for i in range(0,n):
      # arrival time at depot should be 0 which means a tour starts at 0
        if i == 0:
               depTime[i] = 0
        model.addConstr(depTime[i] >= arrTime[i])
        # arrival time should be after previous node arrival time + travel time
        model.addConstrs(arrTime[j] - dist_matrix[j, i] - depTime[i] >= inf * (edges[i, j] - 1) for j in range(1, n) if i != j)
        # arrival time should be after start time of timewindows
        model.addConstr(arrTime[i] >= start_time[i])
        # arrival time should be before end time of timewindows
        model.addConstr(arrTime[i] <= end_time[i])

    # objective
    # sum arrTime at a node right before depot and distance between the node and depot so that it indicate the arrival time of the depot.
    model.setObjective(
        quicksum((arrTime[i] + dist_matrix[i,0]) * edges[i, 0] for i in range(1, n)),
        GRB.MINIMIZE
    )

    model.optimize()

    # get solution
    sol_quantity, sol_edges, sol_arrtime = model.getAttr('x', quantity), model.getAttr('x', edges), model.getAttr('x', arrTime)
    solEdges, solQuantity, solArrTime = np.empty([n, n]), np.empty([n]), np.empty([n])
    for i in range(n):
        solQuantity[i] = sol_quantity[i]
        solArrTime[i] = sol_arrtime[i]
        for j in range(n):
            if i != j:
                solEdges[i, j] = int(sol_edges[i, j])
            else:
                solEdges[i, j] = 0

    print("route: ", [[i, j] for i in range(solEdges.shape[0]) for j in range(solEdges.shape[1]) if solEdges[i, j] ==1])
    print("arrival time of each nodes: ", solArrTime)

    The data is something like this.

    >> can you derive your own lower bound for any integer feasible solution that is larger than the dual bound that is based on LP relaxations?

    Does it mean if I can set a lower bound or an upper bound to integer variables in the source code so that the dual bound increases?  I tried setting some bound for integer variables but it didn't work well.

    Even after I fixed the num_drivers value and eliminated quantity capacity, it wasn't improved...

    >>This subject will be discussed in our tech talk next week on strong vs weak MIP formulations; you can register here:

    Thank you very much for the information.

     

    0

Please sign in to leave a comment.