Skip to content

Commit 9047979

Browse files
author
Romain MONTAGNÉ
committed
Merge branch 'dev'
2 parents 96cc550 + 305c050 commit 9047979

File tree

7 files changed

+244
-162
lines changed

7 files changed

+244
-162
lines changed

docs/vrp_variants.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ where each item of the list is the maximum load per vehicle type. For example, i
192192
Note how the dimensions of ``load_capacity`` and ``cost`` are consistent: each list must have as many items as vehicle types, and the
193193
order of the items of the ``load_capacity`` list is consistent with the order of the ``cost`` list on every edge of the graph.
194194

195+
Once the problem is solved, the type of vehicle per route can be queried with ``prob.best_routes_type``.
196+
195197

196198
VRP options
197199
~~~~~~~~~~~
@@ -254,7 +256,20 @@ will add to the total travel cost each time a node is dropped. For example, if t
254256
>>> prob.drop_penalty = 1000
255257
256258
This problem is sometimes referred to as the `capacitated profitable tour problem` or the `prize collecting tour problem.`
259+
260+
Minimizing the global time span
261+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
262+
263+
It is possible to modify the objective function in order to solve a min-max problem. More specifically, the total time span can be minimized
264+
by setting the ``minimize_global_span`` to ``True``. Of course this assumes edges have a ``time`` argument:
265+
266+
.. code-block:: python
267+
268+
>>> prob.minimize_global_span = True
257269
270+
.. note::
271+
272+
This may lead to poor computation times.
258273

259274
Other VRPs
260275
~~~~~~~~~~

examples/cvrp_drop.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,20 @@
1717

1818
if __name__ == "__main__":
1919

20-
prob = VehicleRoutingProblem(G,
21-
load_capacity=15,
22-
drop_penalty=1000,
23-
num_vehicles=4)
24-
prob.solve()
20+
prob = VehicleRoutingProblem(G, load_capacity=15, drop_penalty=1000, num_vehicles=4)
21+
prob.solve(
22+
preassignments=[ # locking these routes should yield prob.best_value == 7936
23+
# [9, 14, 16],
24+
# [12, 11, 4, 3, 1],
25+
# [7, 13],
26+
# [8, 10, 2, 5],
27+
],
28+
)
2529
print(prob.best_value)
2630
print(prob.best_routes)
2731
print(prob.best_routes_cost)
2832
print(prob.best_routes_load)
2933
print(prob.node_load)
30-
assert prob.best_value == 7548
34+
assert prob.best_value == 8096
35+
36+
# why doesn't vrpy find 7936 ?

tests/test_issue110.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from networkx import DiGraph
2+
from vrpy import VehicleRoutingProblem
3+
4+
5+
class TestIssue110:
6+
def setup(self):
7+
G = DiGraph()
8+
G.add_edge("Source", 1, cost=[1, 2])
9+
G.add_edge("Source", 2, cost=[2, 4])
10+
G.add_edge(1, "Sink", cost=[0, 0])
11+
G.add_edge(2, "Sink", cost=[2, 4])
12+
G.add_edge(1, 2, cost=[1, 2])
13+
G.nodes[1]["demand"] = 13
14+
G.nodes[2]["demand"] = 13
15+
self.prob = VehicleRoutingProblem(G, mixed_fleet=True, load_capacity=[10, 15])
16+
17+
def test_node_load(self):
18+
self.prob.solve()
19+
assert self.prob.best_routes_type == {1: 1, 2: 1}

tests/test_toy.py

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88

99
class TestsToy:
10-
1110
def setup(self):
1211
"""
1312
Creates a toy graph.
@@ -58,10 +57,7 @@ def test_cspy_stops_capacity_duration(self):
5857
"""Tests column generation procedure on toy graph
5958
with stop, capacity and duration constraints
6059
"""
61-
prob = VehicleRoutingProblem(self.G,
62-
num_stops=3,
63-
load_capacity=10,
64-
duration=62)
60+
prob = VehicleRoutingProblem(self.G, num_stops=3, load_capacity=10, duration=62)
6561
prob.solve(exact=False)
6662
assert prob.best_value == 85
6763
assert set(prob.best_routes_duration.values()) == {41, 62}
@@ -82,8 +78,7 @@ def test_cspy_stops_time_windows(self):
8278
assert prob.arrival_time[1]["Sink"] in [41, 62]
8379

8480
def test_cspy_schedule(self):
85-
"""Tests if final schedule is time-window feasible
86-
"""
81+
"""Tests if final schedule is time-window feasible"""
8782
prob = VehicleRoutingProblem(
8883
self.G,
8984
num_stops=3,
@@ -93,13 +88,13 @@ def test_cspy_schedule(self):
9388
# Check departure times
9489
for k1, v1 in prob.departure_time.items():
9590
for k2, v2 in v1.items():
96-
assert (self.G.nodes[k2]["lower"] <= v2)
97-
assert (v2 <= self.G.nodes[k2]["upper"])
91+
assert self.G.nodes[k2]["lower"] <= v2
92+
assert v2 <= self.G.nodes[k2]["upper"]
9893
# Check arrival times
9994
for k1, v1 in prob.arrival_time.items():
10095
for k2, v2 in v1.items():
101-
assert (self.G.nodes[k2]["lower"] <= v2)
102-
assert (v2 <= self.G.nodes[k2]["upper"])
96+
assert self.G.nodes[k2]["lower"] <= v2
97+
assert v2 <= self.G.nodes[k2]["upper"]
10398

10499
###############
105100
# subsolve lp #
@@ -151,13 +146,13 @@ def test_LP_schedule(self):
151146
# Check departure times
152147
for k1, v1 in prob.departure_time.items():
153148
for k2, v2 in v1.items():
154-
assert (self.G.nodes[k2]["lower"] <= v2)
155-
assert (v2 <= self.G.nodes[k2]["upper"])
149+
assert self.G.nodes[k2]["lower"] <= v2
150+
assert v2 <= self.G.nodes[k2]["upper"]
156151
# Check arrival times
157152
for k1, v1 in prob.arrival_time.items():
158153
for k2, v2 in v1.items():
159-
assert (self.G.nodes[k2]["lower"] <= v2)
160-
assert (v2 <= self.G.nodes[k2]["upper"])
154+
assert self.G.nodes[k2]["lower"] <= v2
155+
assert v2 <= self.G.nodes[k2]["upper"]
161156

162157
def test_LP_stops_elementarity(self):
163158
"""Tests column generation procedure on toy graph"""
@@ -188,11 +183,9 @@ def test_clarke_wright(self):
188183
#########
189184

190185
def test_all(self):
191-
prob = VehicleRoutingProblem(self.G,
192-
num_stops=3,
193-
time_windows=True,
194-
duration=63,
195-
load_capacity=10)
186+
prob = VehicleRoutingProblem(
187+
self.G, num_stops=3, time_windows=True, duration=63, load_capacity=10
188+
)
196189
prob.solve(cspy=False)
197190
lp_best = prob.best_value
198191
prob.solve(cspy=True)
@@ -217,9 +210,7 @@ def test_knapsack(self):
217210

218211
def test_pricing_strategies(self):
219212
sol = []
220-
for strategy in [
221-
"Exact", "BestPaths", "BestEdges1", "BestEdges2", "Hyper"
222-
]:
213+
for strategy in ["Exact", "BestPaths", "BestEdges1", "BestEdges2", "Hyper"]:
223214
prob = VehicleRoutingProblem(self.G, num_stops=4)
224215
prob.solve(pricing_strategy=strategy)
225216
sol.append(prob.best_value)
@@ -294,22 +285,25 @@ def test_fixed_cost(self):
294285
assert set(prob.best_routes_cost.values()) == {30 + 100, 40 + 100}
295286

296287
def test_drop_nodes(self):
297-
prob = VehicleRoutingProblem(self.G,
298-
num_stops=3,
299-
num_vehicles=1,
300-
drop_penalty=100)
288+
prob = VehicleRoutingProblem(
289+
self.G, num_stops=3, num_vehicles=1, drop_penalty=100
290+
)
301291
prob.solve()
302292
assert prob.best_value == 240
303293
assert prob.best_routes == {1: ["Source", 1, 2, 3, "Sink"]}
304294

305295
def test_num_vehicles_use_all(self):
306-
prob = VehicleRoutingProblem(self.G,
307-
num_stops=3,
308-
num_vehicles=2,
309-
use_all_vehicles=True,
310-
drop_penalty=100)
296+
prob = VehicleRoutingProblem(
297+
self.G, num_stops=3, num_vehicles=2, use_all_vehicles=True, drop_penalty=100
298+
)
311299
prob.solve()
312300
assert len(prob.best_routes) == 2
301+
prob.num_vehicles = 3
302+
prob.solve()
303+
assert len(prob.best_routes) == 3
304+
prob.num_vehicles = 4
305+
prob.solve()
306+
assert len(prob.best_routes) == 4
313307

314308
def test_periodic(self):
315309
self.G.nodes[2]["frequency"] = 2
@@ -322,10 +316,7 @@ def test_periodic(self):
322316
frequency += 1
323317
assert frequency == 2
324318
assert prob.schedule[0] in [[1], [1, 2]]
325-
prob = VehicleRoutingProblem(self.G,
326-
num_stops=2,
327-
periodic=2,
328-
num_vehicles=1)
319+
prob = VehicleRoutingProblem(self.G, num_stops=2, periodic=2, num_vehicles=1)
329320
prob.solve()
330321
assert prob.schedule == {}
331322

vrpy/master_solve_pulp.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def solve(self, relax, time_limit):
4646
logger.debug("master problem relax %s" % relax)
4747
logger.debug("Status: %s" % pulp.LpStatus[self.prob.status])
4848
logger.debug("Objective: %s" % pulp.value(self.prob.objective))
49+
# self.prob.writeLP("master.lp")
4950

5051
if pulp.LpStatus[self.prob.status] != "Optimal":
5152
raise Exception("problem " + str(pulp.LpStatus[self.prob.status]))
@@ -194,8 +195,6 @@ def _solve(self, relax: bool, time_limit: Optional[int]):
194195
if "artificial_bound_" in var.name:
195196
var.upBound = 0
196197
var.lowBound = 0
197-
# self.prob.writeLP("master.lp")
198-
# print(self.prob)
199198
# Solve with appropriate solver
200199
if self.solver == "cbc":
201200
self.prob.solve(
@@ -210,7 +209,8 @@ def _solve(self, relax: bool, time_limit: Optional[int]):
210209
pulp.CPLEX_CMD(
211210
msg=False,
212211
timeLimit=time_limit,
213-
options=["set lpmethod 4", "set barrier crossover -1"],
212+
# options=["set lpmethod 4", "set barrier crossover -1"], # set barrier crossover -1 is deprecated
213+
options=["set lpmethod 4", "set solutiontype 2"],
214214
)
215215
)
216216
elif self.solver == "gurobi":
@@ -360,7 +360,7 @@ def _add_drop_variables(self):
360360
drop[v] takes value 1 if and only if node v is dropped.
361361
"""
362362
for node in self.G.nodes():
363-
if self.G.nodes[node]["demand"] > 0 and node != "Source":
363+
if node not in ["Source", "Sink"]:
364364
self.drop[node] = pulp.LpVariable(
365365
"drop_%s" % node,
366366
lowBound=0,

vrpy/masterproblem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class _MasterProblemBase:
77
routes (list): Current routes/variables/columns.
88
drop_penalty (int, optional): Value of penalty if node is dropped. Defaults to None.
99
num_vehicles (int, optional): Maximum number of vehicles. Defaults to None.
10-
use_all_vehicles (bool, optional): True if all vehicles specified by num_vehicles should be used. Defaults to False
10+
use_all_vehicles (bool, optional): True if all vehicles specified by num_vehicles should be used. Defaults to False.
1111
periodic (bool, optional): True if vertices are to be visited periodically. Defaults to False.
1212
minimize_global_span (bool, optional): True if global span (maximum distance) is minimized. Defaults to False.
1313
solver (str): Name of solver to use.

0 commit comments

Comments
 (0)