Skip to content

Commit

Permalink
Merge pull request #103 from toruseo/develop
Browse files Browse the repository at this point in the history
Add hard deterministic mode
  • Loading branch information
toruseo authored Aug 15, 2024
2 parents 9d98584 + 9a4e43f commit 54bf92d
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 11 deletions.
129 changes: 129 additions & 0 deletions tests/test_verification_exceptional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
41 changes: 30 additions & 11 deletions uxsim/uxsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
#受け入れ可能な場合,リンク優先度に応じて選択
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 54bf92d

Please sign in to comment.