Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
Romain MONTAGNÉ committed Sep 18, 2021
2 parents 96cc550 + 305c050 commit 9047979
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 162 deletions.
15 changes: 15 additions & 0 deletions docs/vrp_variants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ where each item of the list is the maximum load per vehicle type. For example, i
Note how the dimensions of ``load_capacity`` and ``cost`` are consistent: each list must have as many items as vehicle types, and the
order of the items of the ``load_capacity`` list is consistent with the order of the ``cost`` list on every edge of the graph.

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


VRP options
~~~~~~~~~~~
Expand Down Expand Up @@ -254,7 +256,20 @@ will add to the total travel cost each time a node is dropped. For example, if t
>>> prob.drop_penalty = 1000
This problem is sometimes referred to as the `capacitated profitable tour problem` or the `prize collecting tour problem.`

Minimizing the global time span
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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
by setting the ``minimize_global_span`` to ``True``. Of course this assumes edges have a ``time`` argument:

.. code-block:: python
>>> prob.minimize_global_span = True
.. note::

This may lead to poor computation times.

Other VRPs
~~~~~~~~~~
Expand Down
18 changes: 12 additions & 6 deletions examples/cvrp_drop.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@

if __name__ == "__main__":

prob = VehicleRoutingProblem(G,
load_capacity=15,
drop_penalty=1000,
num_vehicles=4)
prob.solve()
prob = VehicleRoutingProblem(G, load_capacity=15, drop_penalty=1000, num_vehicles=4)
prob.solve(
preassignments=[ # locking these routes should yield prob.best_value == 7936
# [9, 14, 16],
# [12, 11, 4, 3, 1],
# [7, 13],
# [8, 10, 2, 5],
],
)
print(prob.best_value)
print(prob.best_routes)
print(prob.best_routes_cost)
print(prob.best_routes_load)
print(prob.node_load)
assert prob.best_value == 7548
assert prob.best_value == 8096

# why doesn't vrpy find 7936 ?
19 changes: 19 additions & 0 deletions tests/test_issue110.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from networkx import DiGraph
from vrpy import VehicleRoutingProblem


class TestIssue110:
def setup(self):
G = DiGraph()
G.add_edge("Source", 1, cost=[1, 2])
G.add_edge("Source", 2, cost=[2, 4])
G.add_edge(1, "Sink", cost=[0, 0])
G.add_edge(2, "Sink", cost=[2, 4])
G.add_edge(1, 2, cost=[1, 2])
G.nodes[1]["demand"] = 13
G.nodes[2]["demand"] = 13
self.prob = VehicleRoutingProblem(G, mixed_fleet=True, load_capacity=[10, 15])

def test_node_load(self):
self.prob.solve()
assert self.prob.best_routes_type == {1: 1, 2: 1}
63 changes: 27 additions & 36 deletions tests/test_toy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@


class TestsToy:

def setup(self):
"""
Creates a toy graph.
Expand Down Expand Up @@ -58,10 +57,7 @@ def test_cspy_stops_capacity_duration(self):
"""Tests column generation procedure on toy graph
with stop, capacity and duration constraints
"""
prob = VehicleRoutingProblem(self.G,
num_stops=3,
load_capacity=10,
duration=62)
prob = VehicleRoutingProblem(self.G, num_stops=3, load_capacity=10, duration=62)
prob.solve(exact=False)
assert prob.best_value == 85
assert set(prob.best_routes_duration.values()) == {41, 62}
Expand All @@ -82,8 +78,7 @@ def test_cspy_stops_time_windows(self):
assert prob.arrival_time[1]["Sink"] in [41, 62]

def test_cspy_schedule(self):
"""Tests if final schedule is time-window feasible
"""
"""Tests if final schedule is time-window feasible"""
prob = VehicleRoutingProblem(
self.G,
num_stops=3,
Expand All @@ -93,13 +88,13 @@ def test_cspy_schedule(self):
# Check departure times
for k1, v1 in prob.departure_time.items():
for k2, v2 in v1.items():
assert (self.G.nodes[k2]["lower"] <= v2)
assert (v2 <= self.G.nodes[k2]["upper"])
assert self.G.nodes[k2]["lower"] <= v2
assert v2 <= self.G.nodes[k2]["upper"]
# Check arrival times
for k1, v1 in prob.arrival_time.items():
for k2, v2 in v1.items():
assert (self.G.nodes[k2]["lower"] <= v2)
assert (v2 <= self.G.nodes[k2]["upper"])
assert self.G.nodes[k2]["lower"] <= v2
assert v2 <= self.G.nodes[k2]["upper"]

###############
# subsolve lp #
Expand Down Expand Up @@ -151,13 +146,13 @@ def test_LP_schedule(self):
# Check departure times
for k1, v1 in prob.departure_time.items():
for k2, v2 in v1.items():
assert (self.G.nodes[k2]["lower"] <= v2)
assert (v2 <= self.G.nodes[k2]["upper"])
assert self.G.nodes[k2]["lower"] <= v2
assert v2 <= self.G.nodes[k2]["upper"]
# Check arrival times
for k1, v1 in prob.arrival_time.items():
for k2, v2 in v1.items():
assert (self.G.nodes[k2]["lower"] <= v2)
assert (v2 <= self.G.nodes[k2]["upper"])
assert self.G.nodes[k2]["lower"] <= v2
assert v2 <= self.G.nodes[k2]["upper"]

def test_LP_stops_elementarity(self):
"""Tests column generation procedure on toy graph"""
Expand Down Expand Up @@ -188,11 +183,9 @@ def test_clarke_wright(self):
#########

def test_all(self):
prob = VehicleRoutingProblem(self.G,
num_stops=3,
time_windows=True,
duration=63,
load_capacity=10)
prob = VehicleRoutingProblem(
self.G, num_stops=3, time_windows=True, duration=63, load_capacity=10
)
prob.solve(cspy=False)
lp_best = prob.best_value
prob.solve(cspy=True)
Expand All @@ -217,9 +210,7 @@ def test_knapsack(self):

def test_pricing_strategies(self):
sol = []
for strategy in [
"Exact", "BestPaths", "BestEdges1", "BestEdges2", "Hyper"
]:
for strategy in ["Exact", "BestPaths", "BestEdges1", "BestEdges2", "Hyper"]:
prob = VehicleRoutingProblem(self.G, num_stops=4)
prob.solve(pricing_strategy=strategy)
sol.append(prob.best_value)
Expand Down Expand Up @@ -294,22 +285,25 @@ def test_fixed_cost(self):
assert set(prob.best_routes_cost.values()) == {30 + 100, 40 + 100}

def test_drop_nodes(self):
prob = VehicleRoutingProblem(self.G,
num_stops=3,
num_vehicles=1,
drop_penalty=100)
prob = VehicleRoutingProblem(
self.G, num_stops=3, num_vehicles=1, drop_penalty=100
)
prob.solve()
assert prob.best_value == 240
assert prob.best_routes == {1: ["Source", 1, 2, 3, "Sink"]}

def test_num_vehicles_use_all(self):
prob = VehicleRoutingProblem(self.G,
num_stops=3,
num_vehicles=2,
use_all_vehicles=True,
drop_penalty=100)
prob = VehicleRoutingProblem(
self.G, num_stops=3, num_vehicles=2, use_all_vehicles=True, drop_penalty=100
)
prob.solve()
assert len(prob.best_routes) == 2
prob.num_vehicles = 3
prob.solve()
assert len(prob.best_routes) == 3
prob.num_vehicles = 4
prob.solve()
assert len(prob.best_routes) == 4

def test_periodic(self):
self.G.nodes[2]["frequency"] = 2
Expand All @@ -322,10 +316,7 @@ def test_periodic(self):
frequency += 1
assert frequency == 2
assert prob.schedule[0] in [[1], [1, 2]]
prob = VehicleRoutingProblem(self.G,
num_stops=2,
periodic=2,
num_vehicles=1)
prob = VehicleRoutingProblem(self.G, num_stops=2, periodic=2, num_vehicles=1)
prob.solve()
assert prob.schedule == {}

Expand Down
8 changes: 4 additions & 4 deletions vrpy/master_solve_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def solve(self, relax, time_limit):
logger.debug("master problem relax %s" % relax)
logger.debug("Status: %s" % pulp.LpStatus[self.prob.status])
logger.debug("Objective: %s" % pulp.value(self.prob.objective))
# self.prob.writeLP("master.lp")

if pulp.LpStatus[self.prob.status] != "Optimal":
raise Exception("problem " + str(pulp.LpStatus[self.prob.status]))
Expand Down Expand Up @@ -194,8 +195,6 @@ def _solve(self, relax: bool, time_limit: Optional[int]):
if "artificial_bound_" in var.name:
var.upBound = 0
var.lowBound = 0
# self.prob.writeLP("master.lp")
# print(self.prob)
# Solve with appropriate solver
if self.solver == "cbc":
self.prob.solve(
Expand All @@ -210,7 +209,8 @@ def _solve(self, relax: bool, time_limit: Optional[int]):
pulp.CPLEX_CMD(
msg=False,
timeLimit=time_limit,
options=["set lpmethod 4", "set barrier crossover -1"],
# options=["set lpmethod 4", "set barrier crossover -1"], # set barrier crossover -1 is deprecated
options=["set lpmethod 4", "set solutiontype 2"],
)
)
elif self.solver == "gurobi":
Expand Down Expand Up @@ -360,7 +360,7 @@ def _add_drop_variables(self):
drop[v] takes value 1 if and only if node v is dropped.
"""
for node in self.G.nodes():
if self.G.nodes[node]["demand"] > 0 and node != "Source":
if node not in ["Source", "Sink"]:
self.drop[node] = pulp.LpVariable(
"drop_%s" % node,
lowBound=0,
Expand Down
2 changes: 1 addition & 1 deletion vrpy/masterproblem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class _MasterProblemBase:
routes (list): Current routes/variables/columns.
drop_penalty (int, optional): Value of penalty if node is dropped. Defaults to None.
num_vehicles (int, optional): Maximum number of vehicles. Defaults to None.
use_all_vehicles (bool, optional): True if all vehicles specified by num_vehicles should be used. Defaults to False
use_all_vehicles (bool, optional): True if all vehicles specified by num_vehicles should be used. Defaults to False.
periodic (bool, optional): True if vertices are to be visited periodically. Defaults to False.
minimize_global_span (bool, optional): True if global span (maximum distance) is minimized. Defaults to False.
solver (str): Name of solver to use.
Expand Down

0 comments on commit 9047979

Please sign in to comment.