From cd0637ebae1309dd8b3727a006df7c66b04b66b9 Mon Sep 17 00:00:00 2001 From: torressa <23246013+torressa@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:49:11 +0100 Subject: [PATCH] Change dual signs for upper_bound_vehicles #97 --- tests/test_issue97.py | 245 ++++++++++++++++++++++++++++++++++++++ vrpy/master_solve_pulp.py | 34 +++--- vrpy/subproblem.py | 14 +-- 3 files changed, 268 insertions(+), 25 deletions(-) create mode 100644 tests/test_issue97.py diff --git a/tests/test_issue97.py b/tests/test_issue97.py new file mode 100644 index 0000000..1b3aa40 --- /dev/null +++ b/tests/test_issue97.py @@ -0,0 +1,245 @@ +from numpy import array +from vrpy import VehicleRoutingProblem +from networkx import DiGraph, from_numpy_matrix, relabel_nodes, set_node_attributes + + +class TestIssue97: + + def setup(self): + # Distance matrix + DISTANCES = [ + [ + 0, 2095.7400000000002, 1691.865516829233, 862.6229483760624, + 1601.5608041588769, 1249.374, 1578.2344525273484, + 1668.0624056689408, 1110.684, 1388.5990193830364, + 841.3631824321101, 492.1374650840197, 1184.5397511638025, + 1862.9229137819339, 1122.5363001867227, 1687.79201970507, + 1429.297, 1352.9250000000002, 1265.5129469050848, + 931.262209888035, 1446.9970000000003, 0 + ], + [ + 0, 0, 1569.7435168292332, 2372.6569483760627, 2346.338804158877, + 2759.408000000001, 3088.268452527349, 3178.0964056689418, + 2241.4875976812646, 2898.633019383037, 1285.57518243211, + 2356.3494650840207, 2954.9737511638027, 3633.6599137819335, + 2016.7367195183472, 2267.6773484827677, 3200.034, + 2829.574597681264, 1046.940946905085, 1865.058807569299, + 3253.6065976812642, 2095.7400000000007 + ], + [ + 0, 1569.7435168292332, 0, 1968.782465205296, 1561.8476358124115, + 2209.7228316535343, 2612.5582841808828, 2774.2219224981754, + 1837.6131145104976, 2348.9478510365707, 881.7006992613432, + 1952.4749819132528, 2551.0992679930364, 3229.785430611167, + 1232.245551171882, 697.9338316535344, 2796.159516829234, + 2425.7001145104973, 1305.850463734318, 1461.1843243985325, + 2849.732114510497, 1691.8655168292332 + ], + [ + 0, 2372.656948376062, 1968.7824652052955, 0, 1367.8317999868807, + 547.8209958280038, 737.6974483553521, 805.4394572928785, + 1516.7959483760626, 687.0460152110402, 1118.2801308081723, + 817.3944134600821, 610.7397469918063, 1293.480909609937, + 1027.0752485627852, 1592.3309680811326, 861.4079958280038, + 1360.0159483760626, 1542.4298952811473, 1407.6951582640975, + 1215.9829958280038, 862.6229483760626 + ], + [ + 0, 2346.3388041588764, 1561.8476358124115, 1367.8317999868807, + 0, 1165.657804158877, 1568.4932566862253, 1755.6072656237518, + 2480.716804158877, 1304.8828235419135, 1634.306986590987, + 1829.4652692428965, 1813.235206949606, 2545.8189565363064, + 550.4835236772244, 894.8578041588769, 2132.8538041588768, + 2373.2668041588777, 2058.456751063962, 2213.790611728176, + 2457.842804158877, 1601.5608041588769 + ], + [ + 0, 2759.4080000000004, 2209.7228316535347, 547.8209958280038, + 1165.657804158877, 0, 626.8304525273484, 813.9444614648747, + 2064.6169442040664, 139.22501938303637, 1505.0311824321104, + 1365.2154092880858, 871.5724027907286, 1604.1561523774294, + 1054.1913001867229, 1542.7330000000002, 1312.8429999999998, + 1907.8369442040662, 1929.1809469050854, 1949.108209888035, + 1653.9621539545312, 1249.374 + ], + [ + 0, 3088.2684525273485, 2612.558284180883, 737.6974483553521, + 1568.4932566862253, 626.8304525273484, 0, 613.4389139922231, + 2185.2496064818797, 766.0554719103848, 1833.8916349594585, + 1488.756917611368, 671.0668553180769, 1403.650604904778, + 1457.026752714071, 1945.5684525273482, 1163.3506064818794, + 2028.4696064818795, 2258.0413994324335, 2079.057662415383, + 1453.4566064818796, 1578.2344525273481 + ], + [ + 0, 3178.0964056689413, 2774.2219224981745, 805.4394572928785, + 1755.6072656237518, 813.9444614648747, 613.4389139922231, 0, + 2032.8699310851523, 953.1694808479111, 1923.7195881010512, + 1518.3283961691718, 518.6871799213496, 790.2116909125546, + 1644.1407616515976, 2132.682461464875, 1010.9709310851521, + 1876.0899310851523, 2347.8693525740264, 1940.252140973187, + 1023.831777130621, 1668.062405668941 + ], + [ + 0, 2241.487597681264, 1837.6131145104973, 1516.7959483760624, + 2480.716804158878, 2064.616944204066, 2185.2496064818797, + 2032.869931085152, 0, 2203.8419635871023, 955.9124152491543, + 1103.7514650840199, 1514.1827511638023, 1836.8009137819336, + 2001.6923001867235, 2535.5469461640314, 1386.884, 608.554, + 1411.2605445863492, 656.516209888035, 1202.097, + 1110.6840000000002 + ], + [ + 0, 2898.6330193830363, 2348.9478510365707, 687.0460152110401, + 1304.8828235419132, 139.22501938303637, 766.0554719103848, + 953.1694808479111, 2203.841963587103, 0, 1644.2562018151468, + 1504.4404286711222, 1010.797422173765, 1743.3811717604658, + 1193.4163195697593, 1681.9580193830363, 1452.0680193830362, + 2047.0619635871026, 2068.405966288122, 2088.333229271071, + 1793.1871733375674, 1388.5990193830364 + ], + [ + 0, 1285.57518243211, 881.7006992613432, 1118.2801308081725, + 1634.3069865909868, 1505.03118243211, 1833.8916349594583, + 1923.719588101051, 955.9124152491543, 1644.2562018151464, 0, + 1101.9726475161299, 1700.5969335959126, 2379.2830962140433, + 1304.7049019504573, 1579.6345309148776, 1945.65718243211, + 1543.9994152491545, 455.3481293371951, 579.4836251371892, + 1968.0314152491546, 841.3631824321101 + ], + [ + 0, 2356.349465084019, 1952.4749819132526, 817.3944134600821, + 1829.4652692428965, 1365.2154092880858, 1488.756917611368, + 1518.3283961691718, 1103.7514650840196, 1504.4404286711222, + 1101.9726475161294, 0, 999.6412162478221, 1678.327378865953, + 1350.4407652707423, 1915.6964847890897, 1244.7014650840197, + 1268.4004650840197, 1526.1224119891044, 924.3296749720547, + 1362.4724650840199, 492.13746508401965 + ], + [ + 0, 2954.9737511638023, 2551.0992679930364, 610.7397469918062, + 1813.2352069496058, 871.5724027907286, 671.0668553180769, + 518.6871799213496, 1514.1827511638025, 1010.797422173765, + 1700.5969335959126, 999.6412162478222, 0, 855.558664945736, + 1604.917051350525, 2170.1727708688722, 492.28375116380255, + 1357.4027511638026, 2124.746698068888, 1421.5649610518374, + 782.3897511638024, 1184.5397511638023 + ], + [ + 0, 3633.6599137819335, 3229.7854306111667, 1293.4809096099373, + 2545.818956536307, 1604.1561523774294, 1403.6506049047775, + 790.2116909125546, 1836.8009137819333, 1743.3811717604658, + 2379.2830962140433, 1678.3273788659533, 855.5586649457362, 0, + 2287.6582139686557, 2852.9139334870033, 1157.7129137819334, + 1680.0209137819334, 2803.4328606870185, 2089.9161236699683, + 634.7039137819336, 1862.9229137819334 + ], + [ + 0, 2016.7367195183474, 1232.245551171882, 1027.0752485627852, + 550.4835236772244, 1054.1913001867226, 1457.0267527140713, + 1644.1407616515976, 2001.692300186723, 1193.416319569759, + 1304.7049019504573, 1350.4407652707423, 1604.917051350525, + 2287.658213968656, 0, 565.2557195183474, 1855.5853001867226, + 1894.2423001867228, 1728.8546664234323, 1822.2705100747578, + 1978.8183001867228, 1122.5363001867229 + ], + [ + 0, 2267.6773484827677, 697.9338316535345, 1592.3309680811326, + 894.857804158877, 1542.733, 1945.5684525273482, + 2132.682461464875, 2535.5469461640328, 1681.9580193830363, + 1579.6345309148778, 1915.6964847890897, 2170.1727708688727, + 2852.9139334870033, 565.2557195183474, 0, 2420.84101970507, + 2459.498019705071, 2003.784295387853, 2159.1181560520668, + 2544.07401970507, 1687.79201970507 + ], + [ + 0, 3200.0340000000006, 2796.159516829234, 861.4079958280038, + 2132.853804158877, 1312.843, 1163.3506064818794, + 1010.9709310851523, 1386.8840000000002, 1452.0680193830365, + 1945.6571824321104, 1244.7014650840194, 492.28375116380255, + 1157.7129137819338, 1855.5853001867226, 2420.84101970507, 0, + 1230.1040000000003, 2369.8069469050856, 1656.2902098880352, + 718.8679999999999, 1429.297 + ], + [ + 0, 2829.5745976812636, 2425.7001145104978, 1360.0159483760622, + 2373.2668041588777, 1907.836944204066, 2028.4696064818795, + 1876.0899310851519, 608.554, 2047.0619635871024, + 1543.999415249154, 1268.4004650840195, 1357.4027511638023, + 1680.0209137819338, 1894.2423001867232, 2459.49801970507, + 1230.104, 0, 1999.3475445863492, 1244.6032098880348, 1045.317, + 1352.9249999999997 + ], + [ + 0, 1046.940946905085, 1305.850463734318, 1542.4298952811473, + 2058.456751063962, 1929.1809469050852, 2258.0413994324335, + 2347.869352574026, 1411.2605445863494, 2068.4059662881214, + 455.348129337195, 1526.1224119891042, 2124.746698068888, + 2803.4328606870185, 1728.8546664234325, 2003.7842953878524, + 2369.806946905085, 1999.3475445863496, 0, 1034.8317544743843, + 2423.379544586349, 1265.5129469050846 + ], + [ + 0, 1865.0588075692995, 1461.1843243985327, 1407.6951582640977, + 2213.7906117281764, 1949.1082098880354, 2079.057662415383, + 1940.2521409731876, 656.516209888035, 2088.3332292710716, + 579.4836251371893, 924.3296749720548, 1421.5649610518378, + 2089.9161236699692, 1822.2705100747585, 2159.118156052067, + 1656.2902098880354, 1244.603209888035, 1034.8317544743845, 0, + 1673.9902098880352, 931.2622098880352 + ], + [ + 0, 3253.606597681264, 2849.7321145104975, 1215.9829958280036, + 2457.8428041588772, 1653.9621539545315, 1453.4566064818796, + 1023.8317771306209, 1202.097, 1793.1871733375679, + 1968.031415249154, 1362.4724650840196, 782.3897511638025, + 634.7039137819335, 1978.8183001867228, 2544.07401970507, + 718.8679999999999, 1045.317, 2423.379544586349, + 1673.990209888035, 0, 1446.9969999999998 + ], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ] + + # Demands (key: node, value: amount) + DEMAND = { + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + 6: 1, + 7: 1, + 8: 1, + 9: 1, + 10: 1, + 11: 1, + 12: 1, + 13: 1, + 14: 1, + 15: 1, + 16: 1, + 17: 1, + 18: 1, + 19: 1, + 20: 1 + } + + # The matrix is transformed into a DiGraph + A = array(DISTANCES, dtype=[("cost", int)]) + self.G = from_numpy_matrix(A, create_using=DiGraph()) + # The demands are stored as node attributes + set_node_attributes(self.G, values=DEMAND, name="demand") + # The depot is relabeled as Source and Sink + self.G = relabel_nodes(self.G, {0: "Source", 21: "Sink"}) + self.prob = VehicleRoutingProblem(self.G, load_capacity=5) + self.prob.num_vehicles = 5 + self.prob.use_all_vehicles = True + + def test_cspy(self): + self.prob.solve() + assert (self.prob.best_value == 22749) + + def test_lp(self): + # This takes a few seconds (close to 9) + self.prob.solve(cspy=False) + assert (self.prob.best_value == 22749) diff --git a/vrpy/master_solve_pulp.py b/vrpy/master_solve_pulp.py index f43f138..919d509 100644 --- a/vrpy/master_solve_pulp.py +++ b/vrpy/master_solve_pulp.py @@ -82,39 +82,37 @@ def get_duals(self, relax: pulp.LpProblem = None): duals = {} # set covering duals for node in self.G.nodes(): - if ( - node not in ["Source", "Sink"] - and "depot_from" not in self.G.nodes[node] - and "depot_to" not in self.G.nodes[node] - ): + if (node not in ["Source", "Sink"] and + "depot_from" not in self.G.nodes[node] and + "depot_to" not in self.G.nodes[node]): constr_name = "visit_node_%s" % node if not relax: duals[node] = self.prob.constraints[constr_name].pi else: duals[node] = relax.constraints[constr_name].pi # num vehicles dual - if self.num_vehicles and not self.periodic: + if (self.num_vehicles and not self.periodic) or self.use_all_vehicles: duals["upper_bound_vehicles"] = {} for k in range(len(self.num_vehicles)): + sign = 1 if self.use_all_vehicles else -1 if not relax: - duals["upper_bound_vehicles"][k] = self.prob.constraints[ - "upper_bound_vehicles_%s" % k - ].pi + duals["upper_bound_vehicles"][ + k] = sign * self.prob.constraints[ + "upper_bound_vehicles_%s" % k].pi else: - duals["upper_bound_vehicles"][k] = relax.constraints[ - "upper_bound_vehicles_%s" % k - ].pi + duals["upper_bound_vehicles"][k] = sign * relax.constraints[ + "upper_bound_vehicles_%s" % k].pi # global span duals if self.minimize_global_span: for route in self.routes: if not relax: - duals["makespan_%s" % route.graph["name"]] = self.prob.constraints[ - "makespan_%s" % route.graph["name"] - ].pi + duals["makespan_%s" % + route.graph["name"]] = self.prob.constraints[ + "makespan_%s" % route.graph["name"]].pi else: - duals["makespan_%s" % route.graph["name"]] = relax.constraints[ - "makespan_%s" % route.graph["name"] - ].pi + duals["makespan_%s" % + route.graph["name"]] = relax.constraints[ + "makespan_%s" % route.graph["name"]].pi return duals def get_total_cost_and_routes(self, relax: bool): diff --git a/vrpy/subproblem.py b/vrpy/subproblem.py index a25b294..ecb48cd 100644 --- a/vrpy/subproblem.py +++ b/vrpy/subproblem.py @@ -112,24 +112,24 @@ def __init__( elif pricing_strategy == "Exact": # The graph remains as is self.sub_G = self.G - logger.debug("Pricing strategy %s, %s" % (pricing_strategy, pricing_parameter)) + logger.debug("Pricing strategy %s, %s" % + (pricing_strategy, pricing_parameter)) def add_reduced_cost_attribute(self): """Substracts the dual values to compute reduced cost on each edge.""" for edge in self.G.edges(data=True): edge[2]["weight"] = edge[2]["cost"][self.vehicle_type] if self.route: - edge[2]["weight"] *= -self.duals[ - "makespan_%s" % self.route.graph["name"] - ] + edge[2]["weight"] *= -self.duals["makespan_%s" % + self.route.graph["name"]] for v in self.duals: if edge[0] == v: edge[2]["weight"] -= self.duals[v] if "upper_bound_vehicles" in self.duals: for v in self.G.successors("Source"): - self.G.edges["Source", v]["weight"] -= self.duals[ - "upper_bound_vehicles" - ][self.vehicle_type] + self.G.edges["Source", + v]["weight"] += self.duals["upper_bound_vehicles"][ + self.vehicle_type] def discard_nodes(self): """Removes nodes with marginal cost = 0."""