1

I have a linear model for a capacity vehicle routing model. Now I want to put in a constraint on the maximum number of active edges which will result in the fact that not each node can be visited. However each route should start and end at the depot (node 0). I have the following model:

Input:

n = Number of Clients
N = List of Nodes
V = List of nodes plus depot
Q = Vehicle Capacity
q = Demands per Client Dictionary

A = All Possible Roads (eg. [(0,1),(1,2),(2,3),(3,0),(2,0)])
c = All Distances Dictionary (eg. {(0, 1): 90, (1,2): 50, …})

Model:

mdl = Model('CVRP')

x = mdl.binary_var_dict(A, name='x')
u = mdl.continuous_var_dict(N, ub=Q, name='u')

# Objective: Maximize Profit (profit - cost)
mdl.maximize(mdl.sum(q[i]*x[i,j] - c[i,j]*x[i,j] for i,j in A))

# (1) Constraints: Make sure end once in each node
mdl.add_constraints(mdl.sum(x[i,j] for j in V if j!=i) == 1 for i in N)

# (2) Constraints: Make sure leave each node once
mdl.add_constraints(mdl.sum(x[i,j] for i in V if i!=j) == 1 for j in N)

# (3) Constraints: Fill of container is waste current contianer + past containers
mdl.add_indicator_constraints(mdl.indicator_constraint(x[i,j], u[i]+q[j]==u[j]) for i,j in A if i!=0 and j!=0)

# (4) Constraints: Have to take all waste from a container
mdl.add_constraints(u[i]>=q[i] for i in N)

solution = mdl.solve(log_output=True)

To implement the constraint of a maximum of active edges I added the following constraint:

# (5) Constraint: Set maximum of active edges
mdl.add_constraint(mdl.sum(x[i,j] for i,j in A) <= 6)

Thereby I should adjust the '==' operator to '<=' in constraint (1) and (2). However, the result is that also node 0, the depot, is not mandatory to visit anymore. Can anyone help me a bit further with this? Thank you in advance!

Marty
  • 43
  • 8

1 Answers1

3

In order to force the depot being entered and exited you must not relax the == for the depot. So you have to split constraints (1) and (2) for the depot and non-depot nodes:

# Depot
mdl.add_constraints(mdl.sum(x[0,j] for j in V if j!=i))
mdl.add_constraints(mdl.sum(x[i,0] for i in V if i!=j))
# Non-Depot
mdl.add_constraints(mdl.sum(x[i,j] for j in V if j!=i) <= 1 for i in N if N != 0)
mdl.add_constraints(mdl.sum(x[i,j] for i in V if i!=j) <= 1 for j in N if N != 0)

I did not think about this a lot but now you may also need a constraint that requires for all nodes the number of incoming selected arcs to be equal to the number of outgoing selected arcs. That is, if a route enters a node then it must also exit that node.

Daniel Junglas
  • 5,830
  • 1
  • 5
  • 22
  • Thanks for the comment! I think that applying the separate constraints for the depot are not necessary since in 'V' the depot is not included, therefore the constraint doesn't apply to the depot. (Correct me if I'm wrong).The second challenge you mention is indeed the one I deal with now. The constraint of each node with an 'active' incoming edge must have an 'active' outgoing edge and vice versa. So there should be a check **if** a node is visited or left, **then** a node should be left and visited as well. I tried to implement this, but the question raised if this is actually possible in LO? – Marty May 08 '19 at 15:36
  • The constraint should be pretty simple: the sum of all variables corresponding to entering arcs must be equal to the sum of all variables corresponding to outgoing arcs. I missed the difference between V and N. So if you want to force something out of or into the depot then you just have to add a constraint the sum of variables that correspond to arcs leaving/entering the depot is >= 1. – Daniel Junglas May 08 '19 at 18:42
  • Thanks again. Just comparing the sum of incoming and outgoing in general can also lead to entering one node and leaving another which doesn't lead to connected routes. It should thus be checked for each node individually. So if x[2,5] is 1, than there should be one arc in the set x[5,*] be one as well where * can be any other node or the depot. I'm new to Docplex, so I have a hard time to implement this constraint. Do you know how I could implement this? – Marty May 09 '19 at 11:51
  • In addition: I created an extra decision variable to keep track of the visited nodes with `g = mdl.binary_var_dict(N, name='g')`. Second I added indicator constraints for visited nodes with `mdl.add_indicator_constraints(mdl.indicator_constraint(g[i], mdl.sum(x[i,j] for j in V if j!= i) == 1) for i in N if i!=0 and j!=0)`. More concrete question is how could I set g[i] to 1 if the node is visited and thus a x[i,node] is 1? – Marty May 09 '19 at 12:21
  • Yes, of course the "number of inbound arcs equals number of outbound arcs" constraints must be per node. I thought that was what I said (at least I tried). For this constraint something as simple as this should work: `mdl.add_constraints(mdl.sum(x[j, i] for j in N if j != i) == mdl.sum(x[i, j] for j in N if j != i) for i in N)`. For the rest I think you want to look up something called "subtour elimination constraints". Also I tihnk you don't need those additional variables. A node `i` is visited if `sum(x[i,j] for j in N j != i)` is 1 (i.e., if there is an arc leaving the node). – Daniel Junglas May 09 '19 at 18:25
  • Thank you this worked for me! I added the constraint as you proposed, but changed N in V since this also applies to the depot because each route has to start and end at the depot: `mdl.add_constraints(mdl.sum(x[j, i] for j in V if j != i) == mdl.sum(x[i, j] for j in V if j != i) for i in V)`. For me it is okey to find multiple routes that can be driven after each other, so the basic problem is now solved! It was easier than I thought on first hand. Thanks again @Daniel! – Marty May 12 '19 at 09:23