|
4 | 4 | import random |
5 | 5 | import time |
6 | 6 | import warnings |
| 7 | +from pprint import pprint |
| 8 | +from collections import defaultdict |
| 9 | + |
7 | 10 | import matplotlib.pyplot as plt |
8 | 11 | import numpy as np |
9 | | -from collections import defaultdict |
| 12 | + |
10 | 13 | from ..Utilities import enumerate_k_shortest_routes, enumerate_k_random_routes |
11 | 14 | from ..utils import * |
| 15 | +from . import ALNS |
12 | 16 |
|
13 | 17 | import warnings |
14 | 18 |
|
@@ -601,7 +605,7 @@ def plot_vehicle_stats(s, orig=None, dest=None): |
601 | 605 | class SolverDSO_GA: |
602 | 606 | def __init__(s, func_World): |
603 | 607 | """ |
604 | | - Solve Dynamic System Optimum (DSO) problem using genetic algorithm. WIP |
| 608 | + Solve Dynamic System Optimum (DSO) problem using genetic algorithm. |
605 | 609 |
|
606 | 610 | Parameters |
607 | 611 | ---------- |
@@ -981,6 +985,203 @@ def plot_vehicle_stats(s, orig=None, dest=None): |
981 | 985 | plt.legend() |
982 | 986 | plt.show() |
983 | 987 |
|
| 988 | + |
| 989 | +class SolverDSO_ALNS: |
| 990 | + def __init__(s, func_World): |
| 991 | + |
| 992 | + """ |
| 993 | + Solve Dynamic System Optimum (DSO) problem using a customized Adaptive Large Neighborhood Search (ADLS). |
| 994 | +
|
| 995 | + Parameters |
| 996 | + ---------- |
| 997 | + func_World : function |
| 998 | + function that returns a World object with nodes, links, and demand specifications |
| 999 | + """ |
| 1000 | + s.func_World = func_World |
| 1001 | + s.W_sol = None #final solution |
| 1002 | + s.W_intermid_solution = None |
| 1003 | + s.dfs_link = [] |
| 1004 | + |
| 1005 | + def solve(s, max_iter, n_routes_per_od=10, k_max=None, p0=0.2, print_progress=True, initial_solution_World=None): |
| 1006 | + |
| 1007 | + s.print_progress = print_progress |
| 1008 | + s.n_routes_per_od = n_routes_per_od |
| 1009 | + s.max_iter = max_iter |
| 1010 | + if k_max == None: |
| 1011 | + k_max = max([1, int(len(initial_solution_World.VEHICLES)*0.01)]) |
| 1012 | + |
| 1013 | + departure_times = [] |
| 1014 | + |
| 1015 | + s.start_time = time.time() |
| 1016 | + |
| 1017 | + ############################################################## |
| 1018 | + # initial solution |
| 1019 | + |
| 1020 | + if initial_solution_World == None: |
| 1021 | + W = s.func_World() |
| 1022 | + W.exec_simulation() |
| 1023 | + if print_progress: |
| 1024 | + print(W.analyzer.basic_to_pandas()) |
| 1025 | + else: |
| 1026 | + W = initial_solution_World |
| 1027 | + dict_od_to_routes = W.dict_od_to_routes |
| 1028 | + |
| 1029 | + if print_progress: |
| 1030 | + W.print_scenario_stats() |
| 1031 | + |
| 1032 | + dict_od_to_vehid = defaultdict(lambda: []) |
| 1033 | + for key, veh in W.VEHICLES.items(): |
| 1034 | + o = veh.orig.name |
| 1035 | + d = veh.dest.name |
| 1036 | + dict_od_to_vehid[o,d].append(key) |
| 1037 | + departure_times.append(veh.departure_time) |
| 1038 | + |
| 1039 | + departure_times = np.array(departure_times)/max(departure_times) |
| 1040 | + |
| 1041 | + ############################################################## |
| 1042 | + # enumerate some routes between each OD pair |
| 1043 | + |
| 1044 | + dict_od_to_vehid = defaultdict(lambda: []) |
| 1045 | + for key, veh in W.VEHICLES.items(): |
| 1046 | + o = veh.orig.name |
| 1047 | + d = veh.dest.name |
| 1048 | + dict_od_to_vehid[o,d].append(key) |
| 1049 | + |
| 1050 | + if initial_solution_World != None and hasattr(initial_solution_World, "dict_od_to_routes"): |
| 1051 | + print("Initial solusion is used") |
| 1052 | + routes = initial_solution_World.dict_od_to_routes |
| 1053 | + xx0 = [0 for i in range(len(W.VEHICLES))] |
| 1054 | + for i,veh in enumerate(W.VEHICLES.values()): |
| 1055 | + route_id = 0 |
| 1056 | + for j,r in enumerate(routes[(veh.orig.name, veh.dest.name)]): |
| 1057 | + if W.defRoute(r) == veh.traveled_route()[0]: |
| 1058 | + route_id = j |
| 1059 | + break |
| 1060 | + xx0[i] = route_id |
| 1061 | + else: |
| 1062 | + print("No initial solution") |
| 1063 | + routes = enumerate_k_random_routes(W, k=n_routes_per_od) |
| 1064 | + xx0 = [random.randint(0, len(xx_domain[i])) for i in range(len(W.VEHICLES))] |
| 1065 | + |
| 1066 | + ############################################################## |
| 1067 | + # Prepare ALNS |
| 1068 | + |
| 1069 | + # specify routing based on x vector |
| 1070 | + def specify_routes(W, xx): |
| 1071 | + veh_list = list(W.VEHICLES.values()) |
| 1072 | + for i, value in enumerate(xx): |
| 1073 | + veh = veh_list[i] |
| 1074 | + if value < len(routes[(veh.orig.name, veh.dest.name)]): |
| 1075 | + veh.set_links_prefer(routes[(veh.orig.name, veh.dest.name)][value]) |
| 1076 | + else: |
| 1077 | + veh.set_links_prefer(routes[(veh.orig.name, veh.dest.name)][-1]) |
| 1078 | + |
| 1079 | + xx_domain = [] |
| 1080 | + for i, veh in enumerate(W.VEHICLES.values()): |
| 1081 | + actual_n_route = len(routes[(veh.orig.name, veh.dest.name)]) |
| 1082 | + xx_domain.append([i for i in range(actual_n_route)]) |
| 1083 | + |
| 1084 | + s.W_intermid_solution = W |
| 1085 | + s.best_obj_value = None |
| 1086 | + s.iter = 0 |
| 1087 | + s.ttts = [] |
| 1088 | + s.ttts_best = [] |
| 1089 | + s.route_log = [] |
| 1090 | + s.cost_log = [] |
| 1091 | + s.dfs_link = [] |
| 1092 | + |
| 1093 | + s.best_iter = [] |
| 1094 | + s.best_obj = [] |
| 1095 | + |
| 1096 | + print("solving DSO by ALNS...") |
| 1097 | + print("k_max:", k_max) |
| 1098 | + |
| 1099 | + def objective_function_by_UXsim(xx): |
| 1100 | + W = s.func_World() |
| 1101 | + specify_routes(W, xx) |
| 1102 | + W.exec_simulation() |
| 1103 | + obj_value = W.analyzer.total_travel_time - (W.analyzer.trip_all-W.analyzer.trip_completed)*W.TMAX |
| 1104 | + |
| 1105 | + route_actual = {} |
| 1106 | + cost_actual = {} |
| 1107 | + for key,veh in W.VEHICLES.items(): |
| 1108 | + if veh.state != "end": |
| 1109 | + continue |
| 1110 | + |
| 1111 | + route, ts = veh.traveled_route() |
| 1112 | + travel_time = ts[-1]-ts[0] |
| 1113 | + |
| 1114 | + route_actual[key] = [rr.name for rr in route] |
| 1115 | + cost_actual[key] = travel_time |
| 1116 | + s.route_log.append(route_actual) |
| 1117 | + s.cost_log.append(cost_actual) |
| 1118 | + s.ttts.append(int(W.analyzer.total_travel_time)) |
| 1119 | + s.dfs_link.append(W.analyzer.link_to_pandas()) |
| 1120 | + |
| 1121 | + |
| 1122 | + if s.best_obj_value == None or obj_value < s.best_obj_value: |
| 1123 | + s.best_obj_value = obj_value |
| 1124 | + s.W_intermid_solution = W |
| 1125 | + s.W_intermid_solution.dict_od_to_routes = dict_od_to_routes |
| 1126 | + |
| 1127 | + s.ttts_best.append(int(W.analyzer.total_travel_time)) |
| 1128 | + |
| 1129 | + s.best_iter.append(s.iter) |
| 1130 | + s.best_obj.append(s.best_obj_value) |
| 1131 | + |
| 1132 | + if s.iter%20 == 0: |
| 1133 | + print(f"iter: {s.iter} - TTT: {W.analyzer.total_travel_time}, trips:{W.analyzer.trip_completed}/{W.analyzer.trip_all}, Obj:{obj_value}, Best Obj:{s.best_obj_value}") |
| 1134 | + |
| 1135 | + s.iter += 1 |
| 1136 | + return obj_value |
| 1137 | + |
| 1138 | + |
| 1139 | + state = ALNS.init_alns( |
| 1140 | + xx0, objective_function_by_UXsim, |
| 1141 | + domains=xx_domain, |
| 1142 | + k_max=k_max, |
| 1143 | + total_iters=max_iter, |
| 1144 | + p0=p0, |
| 1145 | + seed=0, |
| 1146 | + additional_info={"departure_times":departure_times, "W":W} |
| 1147 | + ) |
| 1148 | + |
| 1149 | + ALNS.run_auto(state, n=max_iter) |
| 1150 | + |
| 1151 | + s.xx_star, s.run = ALNS.finalize(state) |
| 1152 | + |
| 1153 | + s.W_sol = s.W_intermid_solution |
| 1154 | + |
| 1155 | + s.end_time = time.time() |
| 1156 | + print("DSO summary:") |
| 1157 | + print(f" total travel time: initial {s.ttts[0]:.1f} -> last {s.ttts_best[-1]:.1f}") |
| 1158 | + print(f" computation time: {s.end_time - s.start_time:.1f} seconds") |
| 1159 | + |
| 1160 | + def print_solution_process(s, full=False): |
| 1161 | + df = s.run.to_dataframe() |
| 1162 | + df = df.drop(columns=["changed_idx"]) |
| 1163 | + if full: |
| 1164 | + print(df.to_string()) |
| 1165 | + else: |
| 1166 | + print(df[df["accepted"]==True].to_string()) |
| 1167 | + pprint(s.run.operator_stats) |
| 1168 | + |
| 1169 | + def plot_convergence(s): |
| 1170 | + plt.figure() |
| 1171 | + plt.plot(s.ttts, "b-", lw=0.5, label="search trace") |
| 1172 | + plt.plot(s.best_iter, s.best_obj, "r.", label="updated solution") |
| 1173 | + obj_init = s.best_obj[0] |
| 1174 | + obj_min = s.best_obj[-1] |
| 1175 | + plt.ylim([obj_min-(obj_init-obj_min)*0.1, obj_init+(obj_init-obj_min)*0.5]) |
| 1176 | + plt.xlabel("iteration") |
| 1177 | + plt.ylabel("total travel time (s)") |
| 1178 | + plt.legend() |
| 1179 | + plt.grid() |
| 1180 | + plt.show() |
| 1181 | + |
| 1182 | + |
| 1183 | + |
| 1184 | + |
984 | 1185 | # does not work very well |
985 | 1186 | # class SolverDUE_departure_time_choice: |
986 | 1187 | # def __init__(s, func_World, desired_arrival_time, time_windows, alpha=1, beta=0.3, gamma=0.9, insensitive_ratio=0.333): |
|
0 commit comments