MP(Maxpressure)算法的信号控制仿真实例

系列文章目录

利用SUMO进行交叉口速度引导
SUMO使用E2检测器获取信号交叉口车道信息和信号控制方案
SUMO轨迹图的绘制和traci接口的简单使用
SUMO利用转弯率构建车流rou文件

前言

博主最近在进行MP(max_pressure)算法的自适应信号控制的复现,近期完成了初步demo的复现工作,写一篇blog浅浅记录一下。


一、MaxPressure算法

本文中所使用的MaxPressure算法,主要基于以下两篇论文当中的一些算法进行实现:
Wei H, Chen C, Zheng G, et al. Presslight: Learning max pressure control to coordinate traffic signals in arterial network[C]//Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2019: 1290-1298.
[1]张斯钰. 基于网联车数据的城市网络排队估计和最大压强信号控制[D].浙江大学,2020.DOI:10.27461/d.cnki.gzjdx.2020.003073.

1.压强的定义

在max_pressure信号控制算法当中,每一相位的压强基本可以定义为进口道车辆数与出口道车辆数之间的差值,如下图所示。
MP(Maxpressure)算法的信号控制仿真实例_第1张图片
北向南压强可以简单地定义为上游车辆数(4)-下游车辆数(1)=3

2.控制算法

当进行至相位切换或延长判断时刻:
在这里插入图片描述
根据最大压强相位和当前相位进行判断:
如果当前相位任然为最大压强相位,则延长当前相位
如果最大压强相位不是当前相位,则以最小绿灯时间切换至下一相位
在这里插入图片描述
本demo中使用的延长时间为5s一延长,达到最大绿灯时间后,切换下一相位,初始绿灯时间为最小绿灯时间

二、代码实现

1.简单sumo场景构建

为了实现本文所需要复现的MP算法,构建了一个简单的交叉口场景,如下图所示。
MP(Maxpressure)算法的信号控制仿真实例_第2张图片

2.算法构建

构建读取net文件数据的类:

import sys, subprocess, os
import inspect
import numpy as np


if 'SUMO_HOME' in os.environ:
    tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
    sys.path.append(tools)
    import sumolib
else:
    sys.exit("please declare environment variable 'SUMO_HOME'")


class NetworkData:
    def __init__(self, net_fp):
        print(net_fp) 
        self.net = sumolib.net.readNet(net_fp)
        ###get edge data
        self.edge_data = self.get_edge_data(self.net)
        self.lane_data = self.get_lane_data(self.net)
        self.node_data, self.intersection_data = self.get_node_data(self.net)
        print("SUCCESSFULLY GENERATED NET DATA")

    def get_net_data(self):
        return {'lane':self.lane_data, 'edge':self.edge_data, 'origin':self.find_origin_edges(), 'destination':self.find_destination_edges(), 'node':self.node_data, 'inter':self.intersection_data}

    def find_destination_edges(self):
        next_edges = { e:0 for e in self.edge_data }
        for e in self.edge_data:
            for next_e in self.edge_data[e]['incoming']:
                next_edges[next_e] += 1
                                                                 
        destinations = [ e for e in next_edges if next_edges[e] == 0]
        return destinations

    def find_origin_edges(self):
        next_edges = { e:0 for e in self.edge_data }
        for e in self.edge_data:
            for next_e in self.edge_data[e]['outgoing']:
                next_edges[next_e] += 1

        origins = [ e for e in next_edges if next_edges[e] == 0]
        return origins

    def get_edge_data(self, net):
        edges = net.getEdges()
        edge_data = {str(edge.getID()):{} for edge in edges} 

        for edge in edges:
            edge_ID = str(edge.getID())
            edge_data[edge_ID]['lanes'] = [str(lane.getID()) for lane in edge.getLanes()]
            edge_data[edge_ID]['length'] = float(edge.getLength())
            edge_data[edge_ID]['outgoing'] = [str(out.getID()) for out in edge.getOutgoing()]
            edge_data[edge_ID]['noutgoing'] = len(edge_data[edge_ID]['outgoing'])
            edge_data[edge_ID]['nlanes'] = len(edge_data[edge_ID]['lanes'])
            edge_data[edge_ID]['incoming'] = [str(inc.getID()) for inc in edge.getIncoming()]
            edge_data[edge_ID]['outnode'] = str(edge.getFromNode().getID())
            edge_data[edge_ID]['incnode'] = str(edge.getToNode().getID())
            edge_data[edge_ID]['speed'] = float(edge.getSpeed())
          
            ###coords for each edge
            incnode_coord = edge.getFromNode().getCoord()
            outnode_coord = edge.getToNode().getCoord()
            edge_data[edge_ID]['coord'] = np.array([incnode_coord[0], incnode_coord[1], outnode_coord[0], outnode_coord[1]]).reshape(2,2)
            #print edge_data[edge_ID]['coord']
        return edge_data 

    def get_lane_data(self, net):
        #create lane objects from lane_ids
        lane_ids = []
        for edge in self.edge_data:
            lane_ids.extend(self.edge_data[edge]['lanes'])

        lanes = [net.getLane(lane) for lane in lane_ids]
        #lane data dict
        lane_data = {lane:{} for lane in lane_ids}

        for lane in lanes:
            lane_id = lane.getID()
            lane_data[ lane_id ]['length'] = lane.getLength()
            lane_data[ lane_id ]['speed'] = lane.getSpeed()
            lane_data[ lane_id ]['edge'] = str(lane.getEdge().getID())
            #lane_data[ lane_id ]['outgoing'] = []
            lane_data[ lane_id ]['outgoing'] = {}
            ###.getOutgoing() returns a Connection type, which we use to determine the next lane
            moveid = []
            for conn in lane.getOutgoing():
                out_id = str(conn.getToLane().getID())
                lane_data[ lane_id ]['outgoing'][out_id] = {'dir':str(conn.getDirection()), 'index':conn.getTLLinkIndex()}
                moveid.append(str(conn.getDirection()))
            lane_data[ lane_id ]['movement'] = ''.join(sorted(moveid))
            #create empty list for incoming lanes 
            lane_data[ lane_id ]['incoming'] = []
               
        #determine incoming lanes using outgoing lanes data
        for lane in lane_data:
            for inc in lane_data:
                if lane == inc:
                    continue
                else:
                    if inc in lane_data[lane]['outgoing']:
                        lane_data[inc]['incoming'].append(lane)

        return lane_data


    def get_node_data(self, net):
        #read network from .netfile
        nodes = net.getNodes()
        node_data = {node.getID():{} for node in nodes}

        for node in nodes:
            node_id = node.getID()
            #get incoming/outgoing edge
            node_data[node_id]['incoming'] = set(str(edge.getID()) for edge in node.getIncoming())
            node_data[node_id]['outgoing'] = set(str(edge.getID()) for edge in node.getOutgoing())
            node_data[node_id]['tlsindex'] = { conn.getTLLinkIndex():str(conn.getFromLane().getID()) for conn in node.getConnections()}
            node_data[node_id]['tlsindexdir'] = { conn.getTLLinkIndex():str(conn.getDirection()) for conn in node.getConnections()}

            if node_id == '-13968':
                missing = []
                negative = []
                for i in range(len(node_data[node_id]['tlsindex'])):
                    if i not in node_data[node_id]['tlsindex']:
                        missing.append(i)

                for k in node_data[node_id]['tlsindex']:
                    if k < 0  :
                        negative.append(k)
              
                for m,n in zip(missing, negative):
                    node_data[node_id]['tlsindex'][m] = node_data[node_id]['tlsindex'][n]
                    del node_data[node_id]['tlsindex'][n]
                    #for index dir
                    node_data[node_id]['tlsindexdir'][m] = node_data[node_id]['tlsindexdir'][n]
                    del node_data[node_id]['tlsindexdir'][n]
            
            #get XY coords
            pos = node.getCoord()
            node_data[node_id]['x'] = pos[0]
            node_data[node_id]['y'] = pos[1]

        intersection_data = {str(node):node_data[node] for node in node_data if "traffic_light" in net.getNode(node).getType()} 

        return node_data, intersection_data

将车道划分为车道组

def phase_lane_get(net):
''' 
net- data of road
phase_1-4 - four kind of car flow of four phase
'''
    phase_1,phase_2,phase_3,phase_4 = [],[],[],[]
    phase = []
    for i in net.lane_data:
        if net.lane_data[i]['edge'] == 'E2TL' or net.lane_data[i]['edge'] == 'W2TL':
            if net.lane_data[i]['movement'] == 's' or net.lane_data[i]['movement'] == 'rs':
                phase_1.append(i)
            else:
                phase_2.append(i)
        else:
            if net.lane_data[i]['movement'] == 's' or net.lane_data[i]['movement'] == 'rs':
                phase_3.append(i)
            elif net.lane_data[i]['movement'] == 'l':
                phase_4.append(i)
            else:
                continue
            
    phase.append(phase_1)
    phase.append(phase_2)
    phase.append(phase_3)
    phase.append(phase_4)
    
    return phase
计算相位压强
def pressure_cal(phase,net):
'''
income_pressure - the pressure of income lane
outcome_pressure - the pressure of outcome lane
phase - the set of lane in same phase
'''
    total_pressure = 0
    for income_lane in phase:
        income_pressure = traci.lane.getLastStepVehicleNumber(income_lane)
        outcome_pressure = traci.lane.getLastStepVehicleNumber(next(iter(net.lane_data[income_lane]['outgoing'])))
        
        total_pressure += income_pressure - outcome_pressure
        #print(total_pressure)
    return total_pressure/len(phase)
获取最大压强所对应的相位
def max_pressure_phase(phase):
    pressure = []
    for j in phase:
        #print(j)
        pressure.append(pressure_cal(j,net))

    pressure = np.array(pressure)
    phase_index = pressure.argmax()
    if phase_index == 0:
        phase_index = 4
    elif phase_index == 1:
        phase_index = 6
    elif phase_index == 2:
        phase_index = 0
    else :
        phase_index = 2
    return phase_index
构建信号控制器
def max_pressure_controller(phase_pressure_max,phase_now,current_green_time,current_time):
'''
min_green - min green time of green phase
max_green - max green time of one green phase
phase_pressure_max - current max pressure phase
phase_now - current phase
phase_extend_time - the time of extend in one time
phase_next - next phase 
yellow_phase - the index of yellow phase between two green phase
next_swith_time - the time stamp of judge extend or swith
'''

    min_green = 16
    max_green = 45
    phase_extend_time = 5
    if phase_pressure_max == phase_now:
        phase_next = phase_now
    else:
        phase_next = phase_now + 2
    if phase_next > 7:
        phase_next -=8
    if phase_now == phase_next and current_green_time <max_green:
        traci.trafficlight.setPhase('TL',phase_now)
        traci.trafficlight.setPhaseDuration('TL',phase_extend_time)
        current_green_time = current_green_time + phase_extend_time
        next_swith_time = current_time + phase_extend_time -1
        
        print(current_time,'相位延长',next_swith_time,phase_now)
    elif (phase_now == phase_next and current_green_time >=max_green) or (phase_now != phase_next):
        if phase_now + 1 <=7 :
            yellow_phase = phase_now + 1
        else:
            yellow_phase = 0
        
        traci.trafficlight.setPhase('TL',yellow_phase)
        traci.trafficlight.setPhaseDuration('TL',3)
        for step in range(3):
            traci.simulationStep()
        traci.trafficlight.setPhase('TL',phase_next)
        traci.trafficlight.setPhaseDuration('TL',min_green) 
        current_green_time = min_green -1
        next_swith_time = current_time + min_green + 3
        print(current_time,'切换相位',next_swith_time,phase_now)
        
    print(phase_next)
        
    return current_green_time , next_swith_time
仿真主程序
def main():
    current_green_time = 15
    next_swith_time = 200
    phase = phase_lane_get(net)
    traci.start(["sumo-gui", "-c", "intersection/sumo_config.sumocfg"])
    for step in range(3600):
        traci.simulationStep()
        current_time = traci.simulation.getTime()
        if current_time < 200 :
            continue
        elif current_time >3600:
            break
        else:
            if current_time == next_swith_time:
                phase_pressure_max = max_pressure_phase(phase)
                phase_now = traci.trafficlight.getPhase('TL')
                current_time = traci.simulation.getTime()
                current_green_time,next_swith_time = max_pressure_controller(phase_pressure_max,phase_now,current_green_time,current_time)
            else:
                continue
    traci.close()

2.运行实例

MP(Maxpressure)算法的信号控制仿真实例_第3张图片

总结

本文对于MP算法进行了简单的复现,并未实现高适用性的MP算法控制。未来还有待进一步对算法进行优化和完善,例如完善压强的算法、延长时间的优化等待。本文所构建的算法、代码仅当作本人的学习记录资料使用,如有不足,欢迎大家指正并多多交流。

你可能感兴趣的:(sumo,人工智能,算法,python)