From 9a4e43f30faff37086df28e85501a53674cf0de8 Mon Sep 17 00:00:00 2001 From: Toru Seo <34780089+toruseo@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:21:11 +0900 Subject: [PATCH] Add hard deterministic mode --- tests/test_verification_exceptional.py | 129 +++++++++++++++++++++++++ uxsim/uxsim.py | 41 +++++--- 2 files changed, 159 insertions(+), 11 deletions(-) diff --git a/tests/test_verification_exceptional.py b/tests/test_verification_exceptional.py index 8761d7a..7578cce 100644 --- a/tests/test_verification_exceptional.py +++ b/tests/test_verification_exceptional.py @@ -144,3 +144,132 @@ def test_random_numbers_are_reproducible_by_fixing_random_seeds(): ttt[itr] = W.analyzer.basic_to_pandas()["total_travel_time"][0] assert ttt[0] == ttt[1] + +def test_hard_deterministic_mode_merge(): + + ########################### ITER 1 ########################### + + W = World( + name="", # Scenario name + deltan=5, # Simulation aggregation unit delta n + tmax=1200, # Total simulation time (s) + print_mode=1, save_mode=1, show_mode=1, # Various options + random_seed=None, # Set the random seed + hard_deterministic_mode=True + ) + + # Define the scenario + ## Create nodes + W.addNode(name="orig1", x=0, y=0) + W.addNode("orig2", 0, 2) + W.addNode("merge", 1, 1) + W.addNode("dest", 2, 1) + ## Create links between nodes + W.addLink(name="link1", start_node="orig1", end_node="merge", + length=1000, free_flow_speed=20, number_of_lanes=1) + W.addLink("link2", "orig2", "merge", length=1000, free_flow_speed=20, number_of_lanes=1) + W.addLink("link3", "merge", "dest", length=1000, free_flow_speed=20, number_of_lanes=1) + ## Create OD traffic demand between nodes + W.adddemand(orig="orig1", dest="dest", t_start=0, t_end=1000, flow=0.45) + W.adddemand("orig2", "dest", 400, 1000, 0.6) + + # Run the simulation to the end + W.exec_simulation() + + # Print summary of simulation result + W.analyzer.print_simple_stats() + + + df = W.analyzer.link_to_pandas() + + average_tt_link1 = df["average_travel_time"][df["link"]=="link1"].values[0] + average_tt_link2 = df["average_travel_time"][df["link"]=="link2"].values[0] + + assert equal_tolerance(average_tt_link1, 50) + assert equal_tolerance(average_tt_link2, 150) + + + ########################### ITER 2 ########################### + + W = World( + name="", # Scenario name + deltan=5, # Simulation aggregation unit delta n + tmax=1200, # Total simulation time (s) + print_mode=1, save_mode=1, show_mode=1, # Various options + random_seed=None, # Set the random seed + hard_deterministic_mode=True + ) + + # Define the scenario + ## Create nodes + W.addNode(name="orig1", x=0, y=0) + W.addNode("orig2", 0, 2) + W.addNode("merge", 1, 1) + W.addNode("dest", 2, 1) + ## Create links between nodes + W.addLink(name="link1", start_node="orig1", end_node="merge", + length=1000, free_flow_speed=20, number_of_lanes=1) + W.addLink("link2", "orig2", "merge", length=1000, free_flow_speed=20, number_of_lanes=1) + W.addLink("link3", "merge", "dest", length=1000, free_flow_speed=20, number_of_lanes=1) + ## Create OD traffic demand between nodes + W.adddemand(orig="orig1", dest="dest", t_start=0, t_end=1000, flow=0.45) + W.adddemand("orig2", "dest", 400, 1000, 0.6) + + # Run the simulation to the end + W.exec_simulation() + + # Print summary of simulation result + W.analyzer.print_simple_stats() + + assert average_tt_link1 == df["average_travel_time"][df["link"]=="link1"].values[0] + assert average_tt_link2 == df["average_travel_time"][df["link"]=="link2"].values[0] + + +def test_hard_deterministic_mode_grid_network(): + ttt = {} + for itr in range(2): + print(itr, "========="*3) + W = World( + name="", + deltan=10, + tmax=3600, + print_mode=1, save_mode=1, show_mode=0, + random_seed=None, + hard_deterministic_mode=True + ) + + n_nodes = 5 + imax = n_nodes + jmax = n_nodes + nodes = {} + for i in range(imax): + for j in range(jmax): + nodes[i,j] = W.addNode(f"n{(i,j)}", i, j, flow_capacity=1.6) + + links = {} + for i in range(imax): + for j in range(jmax): + if i != imax-1: + links[i,j,i+1,j] = W.addLink(f"l{(i,j,i+1,j)}", nodes[i,j], nodes[i+1,j], length=1000) + if i != 0: + links[i,j,i-1,j] = W.addLink(f"l{(i,j,i-1,j)}", nodes[i,j], nodes[i-1,j], length=1000) + if j != jmax-1: + links[i,j,i,j+1] = W.addLink(f"l{(i,j,i,j+1)}", nodes[i,j], nodes[i,j+1], length=1000) + if j != 0: + links[i,j,i,j-1] = W.addLink(f"l{(i,j,i,j-1)}", nodes[i,j], nodes[i,j-1], length=1000) + + od_pairs = [ + (f"n(0, 0)", f"n({n_nodes-1}, {n_nodes-1})"), + (f"n({n_nodes-1}, 0)", f"n(0, {n_nodes-1})"), + (f"n(0, {n_nodes-1})", f"n({n_nodes-1}, 0)"), + (f"n({n_nodes-1}, {n_nodes-1})", f"n(0, 0)"), + ] + for od_pair in od_pairs: + W.adddemand(od_pair[0], od_pair[1], 0, 3000, 0.6) + + W.exec_simulation() + W.analyzer.print_simple_stats() + + ttt[itr] = W.analyzer.basic_to_pandas()["total_travel_time"][0] + + assert ttt[0] == ttt[1] \ No newline at end of file diff --git a/uxsim/uxsim.py b/uxsim/uxsim.py index 2d83318..43870fa 100644 --- a/uxsim/uxsim.py +++ b/uxsim/uxsim.py @@ -169,10 +169,13 @@ def generate(s): outlinks = sorted(set(outlinks) - set(veh.links_avoid)) preference = np.array([veh.route_pref[l.id] for l in outlinks], dtype=float) - if sum(preference) > 0: - outlink = s.W.rng.choice(outlinks, p=preference/sum(preference)) + if s.W.hard_deterministic_mode == False: + if sum(preference) > 0: + outlink = s.W.rng.choice(outlinks, p=preference/sum(preference)) + else: + outlink = s.W.rng.choice(outlinks) else: - outlink = s.W.rng.choice(outlinks) + outlink = max(zip(preference, outlinks), key=lambda x:x[0])[1] if (len(outlink.vehicles) < outlink.number_of_lanes or outlink.vehicles[-outlink.number_of_lanes].x > outlink.delta_per_lane*s.W.DELTAN) and outlink.capacity_in_remain >= s.W.DELTAN: #受け入れ可能な場合,リンク優先度に応じて選択 @@ -225,7 +228,9 @@ def transfer(s): for outlink in outlink_candidates.keys(): for i in range(outlink.number_of_lanes):#車線の数だけ受け入れ試行回数あり outlinks.append(outlink) - s.W.rng.shuffle(outlinks) + + if s.W.hard_deterministic_mode == False: + s.W.rng.shuffle(outlinks) for outlink in outlinks: if (len(outlink.vehicles) < outlink.number_of_lanes or outlink.vehicles[-outlink.number_of_lanes].x > outlink.delta_per_lane*s.W.DELTAN) and outlink.capacity_in_remain >= s.W.DELTAN and s.flow_capacity_remain >= s.W.DELTAN: @@ -242,7 +247,10 @@ def transfer(s): merge_priorities = np.array([veh.link.merge_priority for veh in vehs], dtype=float) if sum(merge_priorities) == 0: merge_priorities = np.ones(len(merge_priorities)) - veh = s.W.rng.choice(vehs, p=merge_priorities/sum(merge_priorities)) #車線の少ないリンクは,車線の多いリンクの試行回数の恩恵を受けて少し有利になる.大きな差はでないので許容する + if s.W.hard_deterministic_mode == False: + veh = s.W.rng.choice(vehs, p=merge_priorities/sum(merge_priorities)) #車線の少ないリンクは,車線の多いリンクの試行回数の恩恵を受けて少し有利になる.大きな差はでないので許容する + else: + veh = max(zip(merge_priorities, vehs), key=lambda x:x[0])[1] inlink = veh.link @@ -884,7 +892,7 @@ def __init__(s, W, orig, dest, departure_time, name=None, route_pref=None, route else: s.route_pref = {l.id:route_pref[l] for l in route_pref.keys()} - #好むリンクと避けるリンク(近視眼的) + #好むリンクと避けるリンク(近視眼的)1 s.links_prefer = [s.W.get_link(l) for l in links_prefer] s.links_avoid = [s.W.get_link(l) for l in links_avoid] @@ -1094,10 +1102,14 @@ def route_next_link_choice(s): outlinks = sorted(set(outlinks) - set(s.links_avoid)) preference = np.array([s.route_pref[l.id] for l in outlinks], dtype=float) - if sum(preference) > 0: - s.route_next_link = s.W.rng.choice(outlinks, p=preference/sum(preference)) + if s.W.hard_deterministic_mode == False: + if sum(preference) > 0: + s.route_next_link = s.W.rng.choice(outlinks, p=preference/sum(preference)) + else: + s.route_next_link = s.W.rng.choice(outlinks) else: - s.route_next_link = s.W.rng.choice(outlinks) + s.route_next_link = max(zip(preference, outlinks), key=lambda x:x[0])[1] + else: s.route_next_link = None @@ -1291,7 +1303,10 @@ def route_search_all(s, infty=np.inf, noise=0): i = link.start_node.id j = link.end_node.id if s.W.ADJ_MAT[i,j]: - new_link_tt = link.traveltime_instant[-1]*s.W.rng.uniform(1, 1+noise) + link.route_choice_penalty + if s.W.hard_deterministic_mode == False: + new_link_tt = link.traveltime_instant[-1]*s.W.rng.uniform(1, 1+noise) + link.route_choice_penalty + else: + new_link_tt = link.traveltime_instant[-1] + link.route_choice_penalty n = adj_mat_link_count[i,j] s.adj_mat_time[i,j] = s.adj_mat_time[i,j]*n/(n+1) + new_link_tt/(n+1) # if there are multiple links between the same nodes, average the travel time # s.adj_mat_time[i,j] = new_link_tt #if there is only one link between the nodes, this line is fine, but for generality we use the above line @@ -1414,7 +1429,7 @@ class World: World (i.e., simulation environment). A World object is consistently referred to as `W` in this code. """ - def __init__(W, name="", deltan=5, reaction_time=1, duo_update_time=600, duo_update_weight=0.5, duo_noise=0.01, eular_dt=120, eular_dx=100, random_seed=None, print_mode=1, save_mode=1, show_mode=0, route_choice_principle="homogeneous_DUO", route_choice_update_gradual=False, show_progress=1, show_progress_deltat=600, tmax=None, vehicle_logging_timestep_interval=1, instantaneous_TT_timestep_interval=5, meta_data={}): + def __init__(W, name="", deltan=5, reaction_time=1, duo_update_time=600, duo_update_weight=0.5, duo_noise=0.01, eular_dt=120, eular_dx=100, random_seed=None, print_mode=1, save_mode=1, show_mode=0, route_choice_principle="homogeneous_DUO", route_choice_update_gradual=False, show_progress=1, show_progress_deltat=600, tmax=None, vehicle_logging_timestep_interval=1, instantaneous_TT_timestep_interval=5, hard_deterministic_mode=False, meta_data={}): """ Create a World. @@ -1461,6 +1476,8 @@ def __init__(W, name="", deltan=5, reaction_time=1, duo_update_time=600, duo_upd instantaneous_TT_timestep_interval : int, optional The interval for computing instantaneous travel time of each link. Default is 5. If it is longer than the DUO update timestep interval, it is substituted by DUO update timestep interval to maintain reasonable route choice behavior. + hard_deterministic_mode : bool, optional + If True, the simulation will not use any random variables. At a merging node, a link with higher merge_priority will be always prioritized, and vehicles always choose the shortest path. This may be useful for analysis that need strict predictability. Be aware that the simulation results will be significantly different from ones with `hard_deterministic_mode=False`. meta_data : dict, optinal Meta data for simulation scenario. Can store arbitrary data, such as licences and simulation explanation. @@ -1513,6 +1530,8 @@ def __init__(W, name="", deltan=5, reaction_time=1, duo_update_time=600, duo_upd ## system setting W.name = name + W.hard_deterministic_mode = hard_deterministic_mode + W.meta_data = meta_data W.network_info = ddict(list) W.demand_info = ddict(list)