Solving the CVRP variant problem
ユーザーの入力を待っています。I want to implement a variant of the CVRP problem using gurobi. Specifically, he has n customer nodes (1..n) and a warehouse node depot=0. The capacity of the warehouse is set to Q=1. And assume that the distance between two points is known, and the distance between any two points has two values d^{+} and d^{-}. The mathematical model is modeled as:
the data is:
n:5
distance:(i,j,d_ij^{+},d_ij^{-})
0 1 0.74 0.37
0 2 0.68 0.44
0 3 0.26 0.2
0 4 0.7 0.49
0 5 0.64 0.28
1 2 0.87 0.36
1 3 0.68 0.17
1 4 0.86 0.69
1 5 0.68 0.48
2 3 0.42 0.27
2 4 0.88 0.36
2 5 0.51 0.25
3 4 0.66 0.63
3 5 0.66 0.48
4 5 0.57 0.21
node_demand:1..n) depot_demand=0
0.4
0.2
0.1
0.6
0.1
the code is as follows, But the current situation is that even for 5 points, the optimal solution cannot be found.
import gurobipy as gp
from gurobipy import GRB
import time
EPS = 0.0001
def get_wst_scenario(n, sol, d_down, d_up):
wst = dict()
for i in range(n+1):
for j in range(i+1,n+1):
if sol[(i,j)]>0.5 or sol[(j,i)]>0.5:
wst[(i, j)] = wst[(j, i)] = d_up[(i,j)]
else:
wst[(i, j)] = wst[(j, i)] = d_down[(i,j)]
return wst
def get_regret(N, Q, q, d_down, d_up, sol):
d_wst = get_wst_scenario(n=len(N), sol=sol, d_down=d_down, d_up=d_up)
y_val, y_sol = solve_cvrp_bigM(N=N, E=d_down.keys(), d=d_wst, Q=Q, q=q)
cost_x = sum(d_up[e] * sol[e] for e in sol)
regret = sum(d_up[e] * sol[e] for e in sol) - y_val
return regret, cost_x, y_val, y_sol
#---V:include depot node 0 N:all customer node(i..n) E:all edges
# d: cost q:demand Q:capacity limitation for the vehicle---#
def solve_cvrp_bigM(N, E, d, Q, q, time_limit=None):
V = [0] + N
model = gp.Model("CVRP")
### Variable
# x 0-1
x = {e: model.addVar(vtype=GRB.BINARY, name="x[{}]".format(e)) for e in E}
u = model.addVars(V,vtype=GRB.CONTINUOUS)
u[0] = 0 # 在仓库节点的已服务量为 0
model.update()
### Objective
model.setObjective(gp.quicksum(d[e] * x[e] for e in E), GRB.MINIMIZE)
### Constraints
#
model.addConstrs(gp.quicksum(x[i,j] for j in V if i != j) == 1 for i in N)
model.addConstrs(gp.quicksum(x[i,j] for i in V if i != j) == 1 for j in N)
#
model.addConstrs( ( (u[i] + q[j]) <= (u[j] + Q*(1-x[i,j])) ) for i in V for j in N if i!=j)
model.addConstrs( ( (u[i] + q[j])
>= ( u[j] - (Q - q[i] - q[j])*(1-x[i,j]) ) ) for i in V for j in N if i!=j)
model.Params.outputFlag = False
model.Params.threads = 1
model.Params.MIPGap = 0.0
model.Params.lazyConstraints = 1
if time_limit is not None:
model.Params.timeLimit = time_limit
model.optimize()
if model.SolCount <= 0:
return None, None
x_sol = {e: round(x[e].x) for e in x}
return (model.ObjVal), x_sol
def set_bd_model(N, E, Q, q, d_down, d_up):
V = [0] + N
model = gp.Model("BD")
# x:
x = {e: model.addVar(vtype=GRB.BINARY, name="x[{}]".format(e)) for e in E}
# r: r<=sum(yl+sum y(u-l)x
r = model.addVar(vtype=GRB.CONTINUOUS, ub=sum(d_up.values() )/2 , name="r")
u = model.addVars(V, vtype=GRB.CONTINUOUS, name="u")
u[0] = 0 #
model.update()
model.setObjective(gp.quicksum(d_up[e] * x[e] for e in x) - r, GRB.MINIMIZE)
# flow cut
#
model.addConstrs(gp.quicksum(x[i, j] for j in V if j != i) == 1 for i in N)
model.addConstrs(gp.quicksum(x[i, j] for i in V if i != j) == 1 for j in N)
#
model.addConstrs(((u[i] + q[j]) <= (u[j] + Q * (1 - x[i, j]))) for i in V for j in N if i != j)
model.addConstrs(((u[i] + q[j])
>= (u[j] - (Q - q[i] - q[j]) * (1 - x[i, j]))) for i in V for j in N if i != j)
model.Params.outputFlag = False
model.Params.threads = 1
model.Params.MIPGap = 0.0
model._x, model._r = x, r
model._N = N
model._Q = Q
model._q = q
model._d_down, model._d_up = d_down, d_up
#
model.Params.lazyConstraints = 1
model.update()
return model, x, r
def gen_cut(mod, where): # where 哪个阶段触发callback
"""Callback to add a cut for branch-and-cut framework for benders mod"""
# Execute the function when an incumbent is found
if where != GRB.Callback.MIPSOL:
return
x_sol = mod.cbGetSolution(mod._x)
# check Benders cut
ttb = mod.cbGet(GRB.Callback.RUNTIME)
# Obtain the incumbent solution
r_sol = mod.cbGetSolution(mod._r)
d_up, d_down = mod._d_up, mod._d_down
# Prepare worst-case scenario
####
d_wst = get_wst_scenario(n=len(mod._N), sol=x_sol, d_down=d_down, d_up=d_up)
y_val, y_sol = solve_cvrp_bigM(N=mod._N, E=d_down.keys(), d=d_wst, Q=mod._Q, q=mod._q)
####
y_sol = {e for e in d_down if y_sol[e] > 0.5} # set对象
for e in x_sol:
if x_sol[e]>0.5:
i = e[0]
j = e[1]
x_sol[i,j] = x_sol[j,i]
if y_val + EPS < r_sol:
# Add bd cuts
mod.cbLazy(gp.quicksum(d_down[e] + (d_up[e] - d_down[e]) * x_sol[e] for e in y_sol) >= mod._r)
return
mod._ttb = ttb
return
def solve_bc(n, Q, q, d_down, d_up, time_limit):
N = [i for i in range(1, n + 1)]
model, x, _ = set_bd_model(N, d_down.keys(), Q, q, d_down, d_up) # return model, x, r
model.Params.timeLimit = time_limit
model.optimize(gen_cut)
if model.SolCount <= 0:
return None, None, None
sol = [e for e in x if x[e].x > 0.5]
x_e = dict()
for i in range(n+1):
for j in range(n+1):
if i!=j:
x_e[(i,j)] = 0
for i in range(n+1):
for j in range(n+1):
if (i,j) in sol:
x_e[(i,j)] = 1
# x_e[(j, i)] = 1
obj = (model.objVal)
bound = (model.objBound + 1 - EPS)
return (obj, bound, sol, x_e)
-
Hi Anja,
Your Benders approach is quite complex to debug. What exactly happened? Does it give you wrong solutions or does it take too long to solve? An instance with 5 nodes should be rather easy to solve, independent of the input data and the VRP variant. If it takes too long, could you post a log of your solve?
Best regards,
Mario0 -
Yes, he got the wrong solution. Taking the example of the 5 points mentioned above, the solution obtained by the current model is 1.42. But when I calculated the obtained sequence, it should be 1.09.
the log is as follows:
Set parameter Threads to value 1
Set parameter MIPGap to value 0
Set parameter LazyConstraints to value 1
Set parameter TimeLimit to value 3600
Gurobi Optimizer version 10.0.0 build v10.0.0rc2 (win64)CPU model: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz, instruction set [SSE2|AVX|AVX2]
Thread count: 4 physical cores, 8 logical processors, using up to 1 threadsOptimize a model with 60 rows, 37 columns and 188 nonzeros
Model fingerprint: 0x3b140fac
Variable types: 7 continuous, 30 integer (30 binary)
Coefficient statistics:
Matrix range [2e-01, 1e+00]
Objective range [3e-01, 1e+00]
Bounds range [1e+00, 2e+01]
RHS range [4e-01, 1e+00]
Presolve removed 7 rows and 0 columns
Presolve time: 0.00s
Presolved: 53 rows, 37 columns, 219 nonzeros
Variable types: 7 continuous, 30 integer (30 binary)Root relaxation: objective -9.514286e-02, 20 iterations, 0.00 seconds (0.00 work units)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time0 0 -0.09514 0 9 - -0.09514 - - 0s
H 0 0 2.3000000 -0.09514 104% - 0s
H 0 0 2.2900000 -0.09514 104% - 0s
H 0 0 1.6700000 -0.09514 106% - 0s
0 0 0.46410 0 14 1.67000 0.46410 72.2% - 0s
0 0 0.52996 0 12 1.67000 0.52996 68.3% - 0s
0 0 0.55260 0 12 1.67000 0.55260 66.9% - 0s
0 0 0.55260 0 12 1.67000 0.55260 66.9% - 0s
0 0 0.63448 0 16 1.67000 0.63448 62.0% - 0s
H 0 0 1.6400000 0.63448 61.3% - 0s
H 0 0 1.4200000 0.63448 55.3% - 0s
0 0 0.65632 0 15 1.42000 0.65632 53.8% - 0s
0 0 0.66232 0 17 1.42000 0.66232 53.4% - 0s
0 0 0.70098 0 16 1.42000 0.70098 50.6% - 0s
0 0 0.70248 0 16 1.42000 0.70248 50.5% - 0s
0 0 0.70248 0 16 1.42000 0.70248 50.5% - 0s
0 0 0.71012 0 20 1.42000 0.71012 50.0% - 0s
0 0 0.71480 0 20 1.42000 0.71480 49.7% - 0s
0 0 0.71569 0 20 1.42000 0.71569 49.6% - 0s
0 0 0.71569 0 20 1.42000 0.71569 49.6% - 0s
0 0 0.72210 0 18 1.42000 0.72210 49.1% - 0s
0 0 0.72333 0 18 1.42000 0.72333 49.1% - 0s
0 0 0.75916 0 20 1.42000 0.75916 46.5% - 0s
0 0 0.76583 0 19 1.42000 0.76583 46.1% - 0s
0 0 0.76583 0 19 1.42000 0.76583 46.1% - 0s
0 0 0.76670 0 19 1.42000 0.76670 46.0% - 0s
0 0 0.76700 0 21 1.42000 0.76700 46.0% - 0s
0 0 0.76835 0 21 1.42000 0.76835 45.9% - 0s
0 0 0.76839 0 22 1.42000 0.76839 45.9% - 0s
0 0 0.77138 0 21 1.42000 0.77138 45.7% - 0s
0 0 0.77175 0 21 1.42000 0.77175 45.7% - 0s
0 0 0.77206 0 21 1.42000 0.77206 45.6% - 0s
0 0 0.77225 0 22 1.42000 0.77225 45.6% - 0s
0 0 0.77225 0 22 1.42000 0.77225 45.6% - 0s
0 2 0.77451 0 22 1.42000 0.77451 45.5% - 0sCutting planes:
Learned: 1
Gomory: 2
Cover: 1
MIR: 9
StrongCG: 1
GUB cover: 1
Relax-and-lift: 4
Lazy constraints: 3Explored 38 nodes (443 simplex iterations) in 0.44 seconds (0.02 work units)
Thread count was 1 (of 8 available processors)Solution count 5: 1.42 1.64 1.67 ... 2.3
Optimal solution found (tolerance 0.00e+00)
Best objective 1.420000000000e+00, best bound 1.420000000000e+00, gap 0.0000%User-callback calls 554, time in user-callback 0.37 sec
regret:1.4199999999999962
sol:[(1, 0), (0, 2), (0, 3), (4, 0), (3, 1), (2, 5), (5, 4)]
******************************
xe:{(0, 1): 0, (0, 2): 1, (0, 3): 1, (0, 4): 0, (0, 5): 0, (1, 0): 1, (1, 2): 0, (1, 3): 0, (1, 4): 0, (1, 5): 0, (2, 0): 0, (2, 1): 0, (2, 3): 0, (2, 4): 0, (2, 5): 1, (3, 0): 0, (3, 1): 1, (3, 2): 0, (3, 4): 0, (3, 5): 0, (4, 0): 1, (4, 1): 0, (4, 2): 0, (4, 3): 0, (4, 5): 0, (5, 0): 0, (5, 1): 0, (5, 2): 0, (5, 3): 0, (5, 4): 1}
original y_sol:{(0, 1): 0, (1, 0): 0, (0, 2): 0, (2, 0): 0, (0, 3): 0, (3, 0): 1, (0, 4): 1, (4, 0): 1, (0, 5): 1, (5, 0): 0, (1, 2): 1, (2, 1): 0, (1, 3): 0, (3, 1): 0, (1, 4): 0, (4, 1): 0, (1, 5): 0, (5, 1): 1, (2, 3): 1, (3, 2): 0, (2, 4): 0, (4, 2): 0, (2, 5): 0, (5, 2): 0, (3, 4): 0, (4, 3): 0, (3, 5): 0, (5, 3): 0, (4, 5): 0, (5, 4): 0}
regret:1.0900000000000007
y_sol:{(1, 2), (3, 0), (0, 5), (2, 3), (0, 4), (5, 1), (4, 0)}
y_val:3.05
cost_x:4.1400000000000010 -
So, what you are saying is that the solution from the solver is valid (x and y variables), but the corresponding objective value is computed incorrectly?
In this example, you added 3 lazy constraints that seem to affect the objective function. Could you print them when you add them in the callback?
It also helps debug model issues when you write out the initial model (without lazy constraints) to an LP file immediately before calling optimize() and take a closer look at it. You can do this with model.write("model.lp").
0
サインインしてコメントを残してください。
コメント
3件のコメント