Add files via upload
This commit is contained in:
+417
@@ -0,0 +1,417 @@
|
||||
"""
|
||||
Traffic network simulator w/ defined sumo files
|
||||
@author: Tianshu Chu
|
||||
"""
|
||||
from envs.functions import getState, getStockDataVec, formatPrice, formatPercent
|
||||
import xml.etree.cElementTree as ET
|
||||
import subprocess
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
os.environ['SUMO_HOME'] = "/usr/share/sumo"
|
||||
sys.path.append(os.path.join(os.environ.get("SUMO_HOME"), 'tools'))
|
||||
|
||||
|
||||
DEFAULT_PORT = 8000
|
||||
SEC_IN_MS = 1000
|
||||
|
||||
# hard code real-net reward norm
|
||||
REALNET_REWARD_NORM = 20
|
||||
|
||||
|
||||
class PhaseSet:
|
||||
def __init__(self, phases):
|
||||
self.num_phase = len(phases)
|
||||
self.num_lane = len(phases[0])
|
||||
self.phases = phases
|
||||
# self._init_phase_set()
|
||||
|
||||
@staticmethod
|
||||
def _get_phase_lanes(phase, signal='r'):
|
||||
phase_lanes = []
|
||||
for i, l in enumerate(phase):
|
||||
if l == signal:
|
||||
phase_lanes.append(i)
|
||||
return phase_lanes
|
||||
|
||||
def _init_phase_set(self):
|
||||
self.red_lanes = []
|
||||
# self.green_lanes = []
|
||||
for phase in self.phases:
|
||||
self.red_lanes.append(self._get_phase_lanes(phase))
|
||||
# self.green_lanes.append(self._get_phase_lanes(phase, signal='G'))
|
||||
|
||||
|
||||
class PhaseMap:
|
||||
def __init__(self):
|
||||
self.phases = {}
|
||||
|
||||
def get_phase(self, phase_id, action):
|
||||
# phase_type is either green or yellow
|
||||
return self.phases[phase_id].phases[int(action)]
|
||||
|
||||
def get_phase_num(self, phase_id):
|
||||
return self.phases[phase_id].num_phase
|
||||
|
||||
def get_lane_num(self, phase_id):
|
||||
# the lane number is link number
|
||||
return self.phases[phase_id].num_lane
|
||||
|
||||
def get_red_lanes(self, phase_id, action):
|
||||
# the lane number is link number
|
||||
return self.phases[phase_id].red_lanes[int(action)]
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, name, neighbor=[], control=False):
|
||||
self.control = control # disabled
|
||||
self.lanes_in = []
|
||||
self.ilds_in = [] # for state
|
||||
self.fingerprint = [] # local policy
|
||||
self.name = name
|
||||
self.neighbor = neighbor
|
||||
self.num_state = 0 # wave and wait should have the same dim
|
||||
self.num_fingerprint = 0
|
||||
self.wave_state = [] # local state
|
||||
self.wait_state = [] # local state
|
||||
self.phase_id = -1
|
||||
self.n_a = 0
|
||||
# self.prev_action = -1
|
||||
|
||||
|
||||
class TrafficSimulator:
|
||||
def __init__(self, config, output_path, is_record, record_stats, port=0):
|
||||
self.name = config.get('scenario')
|
||||
self.control_interval_sec = config.getint('control_interval_sec')
|
||||
self.yellow_interval_sec = config.getint('yellow_interval_sec')
|
||||
self.port = DEFAULT_PORT + port
|
||||
self.sim_thread = port
|
||||
self.obj = config.get('objective')
|
||||
self.data_path = config.get('data_path')
|
||||
self.price_data = config.get('price_data')
|
||||
self.key = config.get('key')
|
||||
self.window_size = config.getint('window_size')
|
||||
self.balance = config.getint('balance')
|
||||
self.agent = config.get('agent')
|
||||
self.coop_gamma = config.getfloat('coop_gamma')
|
||||
self.cur_episode = 0
|
||||
self.norms = {'wave': config.getfloat('norm_wave'),
|
||||
'wait': config.getfloat('norm_wait')}
|
||||
self.clips = {'wave': config.getfloat('clip_wave'),
|
||||
'wait': config.getfloat('clip_wait')}
|
||||
self.coef_wait = config.getfloat('coef_wait')
|
||||
self.train_mode = True
|
||||
test_seeds = config.get('test_seeds').split(',')
|
||||
test_seeds = [int(s) for s in test_seeds]
|
||||
|
||||
self.data = getStockDataVec(self.price_data, self.key)
|
||||
self.l = len(self.data) - 1
|
||||
self.t = 0
|
||||
self.n = self.window_size + 1
|
||||
self.inventory = {}
|
||||
self.agent_type = {'long': 1, 'short': -1}
|
||||
self.total_profit = 0
|
||||
# self.drawdown = []
|
||||
self.balance_list = []
|
||||
self.cur_balance = self.balance
|
||||
|
||||
self._init_map()
|
||||
self.init_data(is_record, record_stats, output_path)
|
||||
self.init_test_seeds(test_seeds)
|
||||
self._init_nodes()
|
||||
|
||||
def _get_node_phase_id(self, node_name):
|
||||
# needs to be overwriteen
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_state(self):
|
||||
# hard code the state ordering as wave, wait, fp
|
||||
state = []
|
||||
# measure the most recent state
|
||||
self._measure_state_step()
|
||||
|
||||
# get the appropriate state vectors
|
||||
for node_name in self.node_names:
|
||||
# node_name 10026
|
||||
node = self.nodes[node_name]
|
||||
# wave is required in state
|
||||
cur_state = [node.wave_state]
|
||||
# include wave states of neighbors
|
||||
for nnode_name in node.neighbor:
|
||||
# discount the neigboring states
|
||||
cur_state.append(
|
||||
self.nodes[nnode_name].wave_state * self.coop_gamma)
|
||||
# include wait state
|
||||
if 'wait' in self.state_names:
|
||||
cur_state.append(node.wait_state)
|
||||
# include fingerprints of neighbors
|
||||
for nnode_name in node.neighbor:
|
||||
cur_state.append(self.nodes[nnode_name].fingerprint)
|
||||
state.append(np.concatenate(cur_state))
|
||||
|
||||
return state
|
||||
|
||||
def _init_nodes(self):
|
||||
nodes = {}
|
||||
trafficlight_id = ['long', 'short']
|
||||
for node_name in trafficlight_id:
|
||||
if node_name in self.neighbor_map:
|
||||
neighbor = self.neighbor_map[node_name]
|
||||
else:
|
||||
logging.info('node %s can not be found!' % node_name)
|
||||
neighbor = []
|
||||
nodes[node_name] = Node(node_name,
|
||||
neighbor=neighbor,
|
||||
control=True)
|
||||
# Init inventory
|
||||
self.inventory[node_name] = []
|
||||
|
||||
self.nodes = nodes
|
||||
self.node_names = sorted(list(nodes.keys()))
|
||||
s = 'Env: init %d node information:\n' % len(self.node_names)
|
||||
for node in self.nodes.values():
|
||||
s += node.name + ':\n'
|
||||
s += '\tneigbor: %r\n' % node.neighbor
|
||||
# s += '\tlanes_in: %r\n' % node.lanes_in
|
||||
s += '\tilds_in: %r\n' % node.ilds_in
|
||||
# s += '\tedges_in: %r\n' % node.edges_in
|
||||
logging.info(s)
|
||||
self._init_action_space()
|
||||
self._init_state_space()
|
||||
|
||||
def _init_action_space(self):
|
||||
# for local and neighbor coop level
|
||||
self.n_a_ls = []
|
||||
for node_name in self.node_names:
|
||||
node = self.nodes[node_name]
|
||||
# phase_id 3.0
|
||||
phase_id = self._get_node_phase_id(node_name)
|
||||
node.phase_id = phase_id
|
||||
node.n_a = self.phase_map.get_phase_num(phase_id)
|
||||
self.n_a_ls.append(node.n_a)
|
||||
# for global coop level
|
||||
self.n_a = np.prod(np.array(self.n_a_ls))
|
||||
|
||||
def _init_map(self):
|
||||
# needs to be overwriteen
|
||||
self.neighbor_map = None
|
||||
self.phase_map = None
|
||||
self.state_names = None
|
||||
raise NotImplementedError()
|
||||
|
||||
def _init_policy(self):
|
||||
policy = []
|
||||
for node_name in self.node_names:
|
||||
phase_num = self.nodes[node_name].n_a
|
||||
p = 1. / phase_num
|
||||
policy.append(np.array([p] * phase_num))
|
||||
return policy
|
||||
|
||||
def _init_state_space(self):
|
||||
self._reset_state()
|
||||
self.n_s_ls = []
|
||||
self.n_w_ls = []
|
||||
self.n_f_ls = []
|
||||
for node_name in self.node_names:
|
||||
node = self.nodes[node_name]
|
||||
# num_wave = node.num_state
|
||||
num_wave = self.window_size
|
||||
num_fingerprint = 0
|
||||
for nnode_name in node.neighbor:
|
||||
if self.agent not in ['a2c', 'greedy']:
|
||||
# all marl agents have neighborhood communication
|
||||
num_wave += self.nodes[nnode_name].num_state
|
||||
if self.agent == 'ma2c':
|
||||
# only ma2c uses neighbor's policy
|
||||
num_fingerprint += self.nodes[nnode_name].num_fingerprint
|
||||
num_wait = 0 if 'wait' not in self.state_names else node.num_state
|
||||
self.n_s_ls.append(num_wave + num_wait + num_fingerprint)
|
||||
self.n_f_ls.append(num_fingerprint)
|
||||
self.n_w_ls.append(num_wait)
|
||||
self.n_s = np.sum(np.array(self.n_s_ls))
|
||||
|
||||
def _measure_reward_step(self, action):
|
||||
rewards = []
|
||||
for node_name, a in zip(self.node_names, list(action)):
|
||||
reward = 0
|
||||
if a == 1: # buy
|
||||
self.inventory[node_name].append(self.data[self.t])
|
||||
# print("Buy: " + formatPrice(self.data[self.t]))
|
||||
|
||||
elif a == 2 and len(self.inventory[node_name]) > 0: # sell
|
||||
order_price = self.inventory[node_name].pop(0)
|
||||
profit = (self.data[self.t] - order_price) * \
|
||||
self.agent_type[node_name]
|
||||
reward = max(profit, 0)
|
||||
# if node_name == 'short':
|
||||
self.total_profit += profit
|
||||
|
||||
# if profit < 0:
|
||||
# self.drawdown.append(-profit/self.cur_balance)
|
||||
self.cur_balance += profit
|
||||
self.balance_list.append(round(self.cur_balance, 2))
|
||||
# print("Sell: " + formatPrice(self.data[self.t]) + " | Profit: " + formatPrice(
|
||||
# self.data[self.t] - bought_price))
|
||||
rewards.append(reward)
|
||||
|
||||
return np.array(rewards)
|
||||
|
||||
def _measure_state_step(self):
|
||||
for node_name in self.node_names:
|
||||
node = self.nodes[node_name]
|
||||
for state_name in self.state_names:
|
||||
cur_state = getState(self.data, self.t, self.n)
|
||||
if self.record_stats:
|
||||
self.state_stat[state_name] += list(cur_state)
|
||||
# normalization
|
||||
norm_cur_state = self._norm_clip_state(cur_state,
|
||||
self.norms[state_name],
|
||||
self.clips[state_name])
|
||||
node.wave_state = norm_cur_state
|
||||
|
||||
def _measure_traffic_step(self):
|
||||
cur_traffic = {'episode': self.cur_episode,
|
||||
'time_sec': self.t
|
||||
}
|
||||
|
||||
self.traffic_data.append(cur_traffic)
|
||||
|
||||
@staticmethod
|
||||
def _norm_clip_state(x, norm, clip=-1):
|
||||
x = x / norm
|
||||
return x if clip < 0 else np.clip(x, 0, clip)
|
||||
|
||||
def _reset_state(self):
|
||||
for node_name in self.node_names:
|
||||
node = self.nodes[node_name]
|
||||
# prev action for yellow phase before each switch
|
||||
node.prev_action = 0
|
||||
# fingerprint is previous policy[:-1]
|
||||
node.num_fingerprint = node.n_a - 1
|
||||
# node.num_state = self._get_node_state_num(node)
|
||||
|
||||
def _simulate(self):
|
||||
self.t += 1
|
||||
if self.is_record:
|
||||
self._measure_traffic_step()
|
||||
|
||||
def _transfer_action(self, action):
|
||||
'''Transfer global action to a list of local actions'''
|
||||
phase_nums = []
|
||||
for node in self.control_node_names:
|
||||
phase_nums.append(self.nodes[node].phase_num)
|
||||
action_ls = []
|
||||
for i in range(len(phase_nums) - 1):
|
||||
action, cur_action = divmod(action, phase_nums[i])
|
||||
action_ls.append(cur_action)
|
||||
action_ls.append(action)
|
||||
return action_ls
|
||||
|
||||
def _update_waits(self, action):
|
||||
for node_name, a in zip(self.node_names, action):
|
||||
red_lanes = set()
|
||||
node = self.nodes[node_name]
|
||||
for i in self.phase_map.get_red_lanes(node.phase_id, a):
|
||||
red_lanes.add(node.lanes_in[i])
|
||||
for i in range(len(node.waits)):
|
||||
lane = node.ilds_in[i]
|
||||
if lane in red_lanes:
|
||||
node.waits[i] += self.control_interval_sec
|
||||
else:
|
||||
node.waits[i] = 0
|
||||
|
||||
def init_data(self, is_record, record_stats, output_path):
|
||||
self.is_record = is_record
|
||||
self.record_stats = record_stats
|
||||
self.output_path = output_path
|
||||
if self.is_record:
|
||||
self.traffic_data = []
|
||||
self.control_data = []
|
||||
self.trip_data = []
|
||||
if self.record_stats:
|
||||
self.state_stat = {}
|
||||
for state_name in self.state_names:
|
||||
self.state_stat[state_name] = []
|
||||
|
||||
def init_test_seeds(self, test_seeds):
|
||||
self.test_num = len(test_seeds)
|
||||
self.test_seeds = test_seeds
|
||||
|
||||
def output_data(self):
|
||||
if not self.is_record:
|
||||
logging.error('Env: no record to output!')
|
||||
control_data = pd.DataFrame(self.control_data)
|
||||
control_data.to_csv(self.output_path +
|
||||
('%s_%s_control.csv' % (self.name, self.agent)))
|
||||
traffic_data = pd.DataFrame(self.traffic_data)
|
||||
traffic_data.to_csv(self.output_path +
|
||||
('%s_%s_traffic.csv' % (self.name, self.agent)))
|
||||
trip_data = pd.DataFrame(self.trip_data)
|
||||
trip_data.to_csv(self.output_path + ('%s_%s_trip.csv' %
|
||||
(self.name, self.agent)))
|
||||
|
||||
def reset(self):
|
||||
self._reset_state()
|
||||
self.t = 0
|
||||
self.cur_episode += 1
|
||||
# initialize fingerprint
|
||||
self.update_fingerprint(self._init_policy())
|
||||
# next environment random condition should be different
|
||||
self.inventory = dict.fromkeys(self.inventory, [])
|
||||
self.total_profit = 0
|
||||
# self.drawdown = []
|
||||
self.balance_list = []
|
||||
self.cur_balance = self.balance
|
||||
return self._get_state()
|
||||
|
||||
def step(self, action):
|
||||
state = self._get_state()
|
||||
reward = self._measure_reward_step(action)
|
||||
global_reward = np.sum(reward) # for fair comparison
|
||||
# New t for new price
|
||||
self.t += 1
|
||||
# self._simulate()
|
||||
done = True if self.t == self.l - 1 else False
|
||||
|
||||
if done:
|
||||
print("--------------------------------")
|
||||
print("Total Profit: " + formatPrice(self.total_profit))
|
||||
# print('Max Drawdown: ' + formatPercent(max(self.drawdown)))
|
||||
print('Balance List', self.balance_list)
|
||||
print('Final Balance: ' + formatPrice(self.cur_balance))
|
||||
print("--------------------------------")
|
||||
|
||||
if self.is_record:
|
||||
action_r = ','.join(['%d' % a for a in action])
|
||||
cur_control = {'episode': self.cur_episode,
|
||||
'time_sec': self.t,
|
||||
'total_profit': formatPrice(self.total_profit),
|
||||
'action': action_r,
|
||||
'reward': global_reward}
|
||||
self.control_data.append(cur_control)
|
||||
|
||||
# use local rewards in test
|
||||
if not self.train_mode:
|
||||
return state, reward, done, global_reward
|
||||
|
||||
# discounted global reward for ma2c
|
||||
new_reward = []
|
||||
for node_name, r in zip(self.node_names, reward):
|
||||
cur_reward = r
|
||||
for nnode_name in self.nodes[node_name].neighbor:
|
||||
i = self.node_names.index(nnode_name)
|
||||
cur_reward += self.coop_gamma * reward[i]
|
||||
if self.name != 'real_net':
|
||||
new_reward.append(cur_reward)
|
||||
else:
|
||||
n_node = 1 + len(self.nodes[node_name].neighbor)
|
||||
new_reward.append(
|
||||
cur_reward / (n_node * REALNET_REWARD_NORM))
|
||||
reward = np.array(new_reward)
|
||||
return state, reward, done, global_reward
|
||||
|
||||
def update_fingerprint(self, policy):
|
||||
for node_name, pi in zip(self.node_names, policy):
|
||||
self.nodes[node_name].fingerprint = np.array(pi)[:-1]
|
||||
@@ -0,0 +1,48 @@
|
||||
from inspect import indentsize
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
# prints formatted price
|
||||
|
||||
|
||||
def formatPrice(n):
|
||||
return ("-$" if n < 0 else "$") + "{0:.2f}".format(abs(n))
|
||||
|
||||
|
||||
def formatPercent(n):
|
||||
return "{0:.2f}".format(n*100) + "%"
|
||||
|
||||
# returns the vector containing stock data from a fixed file
|
||||
|
||||
|
||||
def getStockDataVec(data_dir, key, index=1000):
|
||||
vec = []
|
||||
lines = open(data_dir + key + ".csv", "r").read().splitlines()
|
||||
|
||||
for line in lines[1:]:
|
||||
l1 = line.split(",")[4] # [1:7] --> not use by no have " "
|
||||
# vec.append(float(line.split(",")[1])*volume)
|
||||
vec.append(float(l1)*index)
|
||||
# list1 = [float(x) for x in l1]
|
||||
# print('num', float(l1)*volume)
|
||||
|
||||
return vec
|
||||
|
||||
# returns the sigmoid
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
return 1 / (1 + math.exp(-x))
|
||||
|
||||
# returns an an n-day state representation ending at time t
|
||||
|
||||
|
||||
def getState(data, t, n):
|
||||
d = t - n + 1
|
||||
block = data[d:t + 1] if d >= 0 else -d * \
|
||||
[data[0]] + data[0:t + 1] # pad with t0
|
||||
res = []
|
||||
for i in range(n - 1):
|
||||
res.append(sigmoid(block[i + 1] - block[i]))
|
||||
|
||||
return np.array(res) # [res]
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Particular class of real traffic network
|
||||
@author: Tianshu Chun
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import seaborn as sns
|
||||
from envs.env import PhaseMap, PhaseSet, TrafficSimulator
|
||||
# from real_net.data.build_file import gen_rou_file
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(
|
||||
"/home/smartcube/tuannguyen/deeprl_signal_control/envs/"))
|
||||
sys.path.append(os.path.abspath(
|
||||
"/home/smartcube/tuannguyen/deeprl_signal_control/real_net/data"))
|
||||
|
||||
sns.set_color_codes()
|
||||
|
||||
STATE_NAMES = ['wave']
|
||||
# node: (phase key, neighbor list)
|
||||
NODES = {'long': ('3.0', []),
|
||||
'short': ('3.1', [])}
|
||||
|
||||
PHASES = {
|
||||
'3.0': ['hold', 'buy', 'sell'],
|
||||
'3.1': ['hold', 'sell', 'buy']
|
||||
}
|
||||
|
||||
|
||||
class RealNetPhase(PhaseMap):
|
||||
def __init__(self):
|
||||
self.phases = {}
|
||||
for key, val in PHASES.items():
|
||||
self.phases[key] = PhaseSet(val)
|
||||
|
||||
|
||||
class RealNetController:
|
||||
def __init__(self, node_names, nodes):
|
||||
self.name = 'greedy'
|
||||
self.node_names = node_names
|
||||
self.nodes = nodes
|
||||
|
||||
def forward(self, obs):
|
||||
actions = []
|
||||
for ob, node_name in zip(obs, self.node_names):
|
||||
actions.append(self.greedy(ob, node_name))
|
||||
return actions
|
||||
|
||||
def greedy(self, ob, node_name):
|
||||
# get the action space
|
||||
phases = PHASES[NODES[node_name][0]]
|
||||
flows = []
|
||||
node = self.nodes[node_name]
|
||||
# get the green waves
|
||||
for phase in phases:
|
||||
wave = 0
|
||||
visited_ilds = set()
|
||||
for i, signal in enumerate(phase):
|
||||
if signal == 'G':
|
||||
# find controlled lane
|
||||
lane = node.lanes_in[i]
|
||||
# ild = 'ild:' + lane
|
||||
ild = lane
|
||||
# if it has not been counted, add the wave
|
||||
if ild not in visited_ilds:
|
||||
j = node.ilds_in.index(ild)
|
||||
wave += ob[j]
|
||||
visited_ilds.add(ild)
|
||||
flows.append(wave)
|
||||
return np.argmax(np.array(flows))
|
||||
|
||||
|
||||
class RealNetEnv(TrafficSimulator):
|
||||
def __init__(self, config, port=0, output_path='', is_record=False, record_stat=False):
|
||||
super().__init__(config, output_path, is_record, record_stat, port=port)
|
||||
|
||||
def _get_node_phase_id(self, node_name):
|
||||
return self.phase_node_map[node_name]
|
||||
|
||||
def _init_neighbor_map(self):
|
||||
return dict([(key, val[1]) for key, val in NODES.items()])
|
||||
|
||||
def _init_map(self):
|
||||
self.neighbor_map = self._init_neighbor_map()
|
||||
self.phase_map = RealNetPhase()
|
||||
self.phase_node_map = dict([(key, val[0])
|
||||
for key, val in NODES.items()])
|
||||
self.state_names = STATE_NAMES
|
||||
|
||||
# def _init_sim_config(self, seed):
|
||||
# # comment out to call build_file.py
|
||||
# return gen_rou_file(self.data_path,
|
||||
# self.flow_rate,
|
||||
# seed=seed,
|
||||
# thread=self.sim_thread)
|
||||
|
||||
def plot_stat(self, rewards):
|
||||
self.state_stat['reward'] = rewards
|
||||
for name, data in self.state_stat.items():
|
||||
fig = plt.figure(figsize=(8, 6))
|
||||
plot_cdf(data)
|
||||
plt.ylabel(name)
|
||||
fig.savefig(self.output_path + self.name + '_' + name + '.png')
|
||||
|
||||
|
||||
def plot_cdf(X, c='b', label=None):
|
||||
sorted_data = np.sort(X)
|
||||
yvals = np.arange(len(sorted_data))/float(len(sorted_data)-1)
|
||||
# print('sorted_data', sorted_data)
|
||||
plt.plot(sorted_data, yvals, color=c, label=label)
|
||||
Reference in New Issue
Block a user