Skip to content

Commit

Permalink
Merge pull request #153 from toruseo/develop
Browse files Browse the repository at this point in the history
add `adddemand_nodes2nodes2` and `adddemand_area2area2`
  • Loading branch information
toruseo authored Oct 9, 2024
2 parents dc954d3 + a794132 commit 280cb71
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 14 deletions.
119 changes: 112 additions & 7 deletions tests/test_other_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,27 +413,27 @@ def test_shortest_path_costs():
assert spd["orig", "mid2"] == 1000
assert spd["mid1", "dest"] == 2000
assert spd["mid2", "dest"] == 1000
assert spd["dest", "orig"] == np.Inf
assert spd["dest", "orig"] == np.inf
assert spd[orig, dest] == 2000
assert spd[dest, orig] == np.Inf
assert spd[dest, orig] == np.inf

spd = get_shortest_path_distance_between_all_nodes(W, return_matrix=True)
assert spd[0, 3] == 2000
assert spd[3, 0] == np.Inf
assert spd[3, 0] == np.inf

spt = get_shortest_path_instantaneous_travel_time_between_all_nodes(W)
assert equal_tolerance(spt["orig", "dest"], 150)
assert equal_tolerance(spt["orig", "mid1"], 50, rel_tol=0.2)
assert equal_tolerance(spt["orig", "mid2"], 150)
assert equal_tolerance(spt["mid1", "dest"], 100)
assert equal_tolerance(spt["mid2", "dest"], 50, rel_tol=0.2)
assert spt["dest", "orig"] == np.Inf
assert spt["dest", "orig"] == np.inf
assert equal_tolerance(spt[orig, dest], 150)
assert spt[dest, orig] == np.Inf
assert spt[dest, orig] == np.inf

spt = get_shortest_path_instantaneous_travel_time_between_all_nodes(W, return_matrix=True)
assert equal_tolerance(spt[0, 3], 150)
assert spt[3, 0] == np.Inf
assert spt[3, 0] == np.inf

spt0 = get_shortest_path_instantaneous_travel_time_between_all_nodes_on_t(W,0)
assert equal_tolerance(spt0["orig", "dest"], 100)
Expand All @@ -458,7 +458,7 @@ def test_shortest_path_costs():

spt600_mat = get_shortest_path_instantaneous_travel_time_between_all_nodes_on_t(W, 600, return_matrix=True)
assert equal_tolerance(spt600_mat[0, 3], 150)
assert spt600_mat[3, 0] == np.Inf
assert spt600_mat[3, 0] == np.inf

def test_util_catch_exceptions_and_warn():
with pytest.warns(UserWarning, match=r".*network().*"):
Expand Down Expand Up @@ -592,6 +592,111 @@ def test_area2area_demand_and_stats():
assert df["average_free_travel_time"][(df["origin_area"] == "areaNW") & (df["destination_area"] == "areaSE")].values[0] == 400.0
assert df["average_shortest_distance"][(df["origin_area"] == "areaNW") & (df["destination_area"] == "areaSE")].values[0] == 8000.0

def test_adddemand_area2area2_nodes2nodes2():
W = World(
name="",
deltan=5,
tmax=7200,
print_mode=1, save_mode=1, show_mode=0,
random_seed=0
)

# scenario
#automated network generation
#deploy nodes as an imax x jmax grid
imax = 5
jmax = 5
nodes = {}
for i in range(imax):
for j in range(jmax):
nodes[i,j] = W.addNode(f"n{(i,j)}", i, j)

#create links between neighborhood nodes
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)

W.adddemand_nodes2nodes2(nodes.values(), nodes.values(), 0, 3600, volume=5000)
W.adddemand_area2area2(0, 0, 1.1, 5, 5, 1.1, 0, 3600, volume=5000)

W.finalize_scenario()

W.exec_simulation()

W.analyzer.print_simple_stats()

assert equal_tolerance(10000, len(W.VEHICLES)*W.DELTAT)

def test_adddemand_area2area2_nodes2nodes2_scenario():
# simulation world
W = World(
name="",
deltan=5,
tmax=7200,
print_mode=1, save_mode=1, show_mode=0,
random_seed=0
)

# scenario
#automated network generation
#deploy nodes as an imax x jmax grid
imax = 5
jmax = 5
nodes = {}
for i in range(imax):
for j in range(jmax):
nodes[i,j] = W.addNode(f"n{(i,j)}", i, j)

#create links between neighborhood nodes
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)
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, free_flow_speed=20, jam_density=0.2)

W.adddemand_nodes2nodes2(nodes.values(), nodes.values(), 0, 3600, volume=5000)
W.adddemand_area2area2(0, 0, 1.1, 5, 5, 1.1, 0, 3600, volume=5000)

W.save_scenario("out/demandtest.uxsim_scenario")

W.exec_simulation()

W.analyzer.print_simple_stats()

#W.analyzer.network_fancy(animation_speed_inverse=15, sample_ratio=0.2, interval=5, trace_length=10, figsize=6, antialiasing=False)
#display_image_in_notebook("out/anim_network_fancy.gif")


### ITER2

W2 = World(
name="",
deltan=5,
tmax=7200,
print_mode=1, save_mode=1, show_mode=0,
random_seed=0
)

W2.load_scenario("out/demandtest.uxsim_scenario")

W2.exec_simulation()
W2.analyzer.print_simple_stats()

assert W.analyzer.average_travel_time == W2.analyzer.average_travel_time

@pytest.mark.flaky(reruns=10)
def test_area_stats():
Expand Down
2 changes: 1 addition & 1 deletion uxsim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .analyzer import *
from .scenario_reader_writer import *

__version__ = "1.6.0"
__version__ = "1.7.0"
__author__ = "Toru Seo"
__copyright__ = "Copyright (c) 2023 Toru Seo"
__license__ = "MIT License"
16 changes: 15 additions & 1 deletion uxsim/scenario_reader_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,24 @@ def load_scenario(W, fname, network=True, demand=True):
for dem in dat[demand_type]:
W.adddemand_point2point(**dem)
print(f" Number of loaded `{demand_type}`s:", len( dat[demand_type]))
if demand_type == 'adddemand_area2area':
elif demand_type == 'adddemand_area2area':
for dem in dat[demand_type]:
W.adddemand_area2area(**dem)
print(f" Number of loaded `{demand_type}`s:", len( dat[demand_type]))
elif demand_type == 'adddemand_nodes2nodes':
for dem in dat[demand_type]:
W.adddemand_nodes2nodes(**dem)
print(f" Number of loaded `{demand_type}`s:", len( dat[demand_type]))
elif demand_type == 'adddemand_area2area2':
for dem in dat[demand_type]:
W.adddemand_area2area2(**dem)
print(f" Number of loaded `{demand_type}`s:", len( dat[demand_type]))
elif demand_type == 'adddemand_nodes2nodes2':
for dem in dat[demand_type]:
W.adddemand_nodes2nodes2(**dem)
print(f" Number of loaded `{demand_type}`s:", len( dat[demand_type]))
else:
pass



Expand Down
130 changes: 125 additions & 5 deletions uxsim/uxsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import csv, time, math, string, warnings, copy
from collections import deque, OrderedDict
from collections import defaultdict as ddict
import warnings

import numpy as np
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -941,7 +942,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 @@ -1777,7 +1778,7 @@ def adddemand_point2point(W, x_orig, y_orig, x_dest, y_dest, t_start, t_end, flo
@demand_info_record
def adddemand_area2area(W, x_orig, y_orig, radious_orig, x_dest, y_dest, radious_dest, t_start, t_end, flow=-1, volume=-1, attribute=None, direct_call=True):
"""
Generate vehicles by specifying time-dependent origin-destination demand by specifying circular areas.
Generate vehicles by specifying time-dependent origin-destination demand by specifying circular areas. `adddemand_area2area()` is not recommended as it may truncate demand. Consider to use new `adddemand_area2area2()` which is more accurate, smooth, and fast.
Parameters
----------
Expand All @@ -1804,6 +1805,11 @@ def adddemand_area2area(W, x_orig, y_orig, radious_orig, x_dest, y_dest, radiou
attribute : any, optinonal
Additional (meta) attributes defined by users.
"""
warnings.warn(
"`adddemand_area2area()` is not recommended as it may truncate demand. Consider to use new `adddemand_area2area2()` which is more accurate, smooth, and fast.",
FutureWarning
)

origs = W.get_nodes_in_area(x_orig, y_orig, radious_orig)
dests = W.get_nodes_in_area(x_dest, y_dest, radious_dest)

Expand All @@ -1830,9 +1836,10 @@ def adddemand_area2area(W, x_orig, y_orig, radious_orig, x_dest, y_dest, radiou
for d in dests:
W.adddemand(o, d, t_start, t_end, flow, volume, attribute, direct_call=False)

def adddemand_nodes2nodes(W, origs, dests, t_start, t_end, flow=-1, volume=-1, attribute=None):
@demand_info_record
def adddemand_nodes2nodes(W, origs, dests, t_start, t_end, flow=-1, volume=-1, attribute=None, direct_call=True):
"""
Generate vehicles by specifying time-dependent origin-destination demand by specifying origin area (i.e., list of nodes) and destination one.
Generate vehicles by specifying time-dependent origin-destination demand by specifying origin area (i.e., list of nodes) and destination one. `adddemand_nodes2nodes()` is not recommended as it may truncate demand. Consider to use new `adddemand_nodes2nodes2() which is more accurate, smooth, and fast.
Parameters
----------
Expand All @@ -1851,6 +1858,10 @@ def adddemand_nodes2nodes(W, origs, dests, t_start, t_end, flow=-1, volume=-1, a
attribute : any, optinonal
Additional (meta) attributes defined by users.
"""
warnings.warn(
"`adddemand_nodes2nodes()` is not recommended as it may truncate demand. Consider to use new `adddemand_nodes2nodes2() which is more accurate, smooth, and fast.`",
FutureWarning
)

origs_new = []
dests_new = []
Expand All @@ -1871,7 +1882,116 @@ def adddemand_nodes2nodes(W, origs, dests, t_start, t_end, flow=-1, volume=-1, a
volume = volume/(len(origs)*len(dests))
for o in origs:
for d in dests:
W.adddemand(o, d, t_start, t_end, flow, volume, attribute, direct_call=True)
W.adddemand(o, d, t_start, t_end, flow, volume, attribute, direct_call=False)

@demand_info_record
def adddemand_area2area2(W, x_orig, y_orig, radious_orig, x_dest, y_dest, radious_dest, t_start, t_end, flow=-1, volume=-1, attribute=None, direct_call=True):
"""
Generate vehicles by specifying time-dependent origin-destination demand by specifying circular areas. This is new version of `adddemand_area2area`, more efficient, more smooth, and more accurate. However, it introduces some randomness.
Parameters
----------
x_orig : float
The x-coordinate of the center of the origin area.
y_orig : float
The y-coordinate of the center of the origin area.
radious_orig : float
The radious of the origin area. Note that too large radious may generate too sparse demand that is rounded to zero.
x_dest : float
The x-coordinate of the center of the destination area.
y_dest : float
The y-coordinate of the center of the destination area.
radious_dest : float
The radious of the destination area.
t_start : float
The start time for the demand in seconds.
t_end : float
The end time for the demand in seconds.
flow : float, optional
The flow rate from the origin to the destination in vehicles per second.
volume: float, optional
The demand volume from the origin to the destination. If volume is specified, the flow is ignored.
attribute : any, optinonal
Additional (meta) attributes defined by users.
"""
origs = W.get_nodes_in_area(x_orig, y_orig, radious_orig)
dests = W.get_nodes_in_area(x_dest, y_dest, radious_dest)

if flow >= 0 and volume == -1:
volume = flow*(t_end-t_start)
size = int(volume/W.DELTAN)

origs_new = []
dests_new = []
for oo in origs:
o = W.get_node(oo)
if len(o.outlinks) != 0:
origs_new.append(o)
for dd in dests:
d = W.get_node(dd)
if len(d.inlinks) != 0:
dests_new.append(d)
origs = origs_new
dests = dests_new
if len(origs) == 0:
origs.append(W.get_nearest_node(x_orig, y_orig))
if len(dests) == 0:
dests.append(W.get_nearest_node(x_dest, y_dest))

ts = np.linspace(t_start, t_end, size)

os = W.rng.choice(origs, size=size)
ds = W.rng.choice(dests, size=size)

for t, o, d, in zip(ts, os, ds):
W.addVehicle(o, d, t, attribute=attribute, direct_call=False)

@demand_info_record
def adddemand_nodes2nodes2(W, origs, dests, t_start, t_end, flow=-1, volume=-1, attribute=None, direct_call=True):
"""
Generate vehicles by specifying time-dependent origin-destination demand by specifying origin area (i.e., list of nodes) and destination one. This is new version of `adddemand_nodes2nodes`, more efficient, more smooth, and more accurate. However, it introduces some randomness.
Parameters
----------
origs : list
The list of origin nodes. The items can be Node objects or names of Nodes.
dests : list
The list of destination nodes. The items can be Node objects or names of Nodes.
t_start : float
The start time for the demand in seconds.
t_end : float
The end time for the demand in seconds.
flow : float, optional
The flow rate from the origin to the destination in vehicles per second.
volume: float, optional
The demand volume from the origin to the destination. If volume is specified, the flow is ignored.
attribute : any, optinonal
Additional (meta) attributes defined by users.
"""

if flow >= 0 and volume == -1:
volume = flow*(t_end-t_start)
size = int(volume/W.DELTAN)

origs_new = []
dests_new = []
for oo in origs:
o = W.get_node(oo)
if len(o.outlinks) != 0:
origs_new.append(o)
for dd in dests:
d = W.get_node(dd)
if len(d.inlinks) != 0:
dests_new.append(d)
origs = origs_new
dests = dests_new
ts = np.linspace(t_start, t_end, size)

os = W.rng.choice(origs, size=size)
ds = W.rng.choice(dests, size=size)

for t, o, d, in zip(ts, os, ds):
W.addVehicle(o, d, t, attribute=attribute, direct_call=False)

def finalize_scenario(W, tmax=None):
"""
Expand Down

0 comments on commit 280cb71

Please sign in to comment.