NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

虽然交通并不总是畅通无阻,但汽车无缝穿越交叉路口,在交通信号灯处转弯和停车看起来相当壮观。这种沉思让我思考交通流对人类文明的重要性。

在此之后,内心的书呆子特质让我忍不住思考一种模拟交通流的方法。我在一个涉及交通流量的本科项目上投入了几个星期的时间,深入到不同的模拟技术,最终选择了一个。

在本文中,我将解释为什么交通流模拟很重要,比较不同的方法来模拟交通流,并呈现仿真结果(以及源代码)。

1、为什么要模拟交通流?

模拟交通流的主要原因是在没有真实世界的情况下生成数据。你可以使用软件运行模型来预测交通流,而不需要在现实世界中安装传感器来测试新的管理思路如何影响现实世界。这有助于加速交通流系统的优化和数据收集。仿真是实际测试更便宜、更快捷的替代方案。

训练机器学习模型需要巨大的数据集,这些数据集的收集和处理成本可能很高。通过模拟交通流在程序上生成数据可以很容易地适应所需的数据类型。

2、建 模

要分析和优化交通系统,我们首先必须对交通系统进行数学建模。这种模型应根据输入参数(路网几何、每分钟车辆、车速等)真实地表示交通流量。

交通流系统模型通常分为三类,具体取决于它们在哪个级别上运行:

  • 微型模型:分别代表每辆车,并尝试复现驾驶员行为。
  • 宏观模型:从交通密度(每公里车辆)和交通流量(车辆每分钟)的角度描述车辆的整体移动。它们通常类似于流体的流动。
  • 中观模型:是结合微观和宏观模型特点的混合模型,将流量建模为车辆的"包"。

在本文中,我将使用微观模型。

3、微观模型

在微观模型中使用驾驶员模型描述单个驾驶员/车辆的行为。因此,它必须是一个多代理系统,即每辆车都使用来自其环境的输入自行运行。

在微观模型中,每辆车都编号为i。i辆车跟随第(i-1)辆车。对于第i辆车,我们将用xi表示其沿路的位置vi表示其速度,以及li表示其长度。每辆车都是如此。

我们将用si表示保险杠到保险杠的距离,用Δvᵢ 表示第i辆车和前面的第i-1辆车之间的速度差异。

4、智能驾驶员模型 (IDM)

2000年,Treiber, Hennecke 和 Helbing开发了智能驾驶员模型,它描述第i辆车的加速度是其变量和前面车辆的变量的函数。动态方程的定义为:


在我解释这个模型背后的直觉之前,我应该先解释下这些符号代表什么。

我们已经谈到了sᵢ, vᵢ, andΔvᵢ。其他参数包括:

  • s0i:是第i辆车和第i-1辆车之间的最小期望距离。
  • v0i :是第i辆车的最大期望速度.
  • δ:是加速度指数,它控制着加速度的"平滑度"。
  • Ti:第i辆车的驾驶员的反应时间。
  • ai :是第i辆车的最大加速度
  • bi :是第i辆车的舒适减速度.
  • s* : 是第i辆车和第i-1辆车之间的实际期望距离。

首先,我们将看看s*,这是一个距离,它由三部分组成。

  • s0i:如前所言,是所需的最小距离。
  • viTi:是反应时间的安全距离。这是车辆在驾驶员反应(刹车)前行驶的距离。
    由于速度是随着时间推移而保持的距离,距离是速度乘以时间。
  • (vᵢ Δvᵢ)/√(2aᵢ bᵢ):这个有点复杂,这是一个基于速度差的安全距离。它表示车辆在非紧急刹车时(减速度应小于bi),不撞到前面的车辆的前提下所需的距离

5、智能驾驶员模型的工作原理

车辆假定沿着一条直道行驶,并假定遵守以下方程:

为了更好地了解这个等式,我们可以将其一分为二。我们有一个自由道路加速度交互加速度

自由道路加速度是自由道路上的加速,即没有车辆的空路。如果我们绘制加速度作为速度vi的函数,可以得到

我们注意到,当车辆静止(vi=0)时,加速是最大的。当车速接近最高速度,加速变为 0。这表明,自由道路加速度将加速车辆到最高速度。

如果我们绘制不同值的δ的 v-a 图,我们会注意到它控制驾驶员在接近最大速度时减速的速度。这反过来又控制了加速度/减速度的平滑度。

交互加速度与与前方车辆的交互有关。为了更好地了解它是如何工作的,让我们考虑以下情况:

  • 在自由路上(si >>s*):
    当前面的车辆很远时,即si远远大于车辆之间所需的安全距离s*,交互加速度几乎为0。这意味着车辆将受到自由道路加速的制约。
  • 在高接近速率((Δvᵢ):
    当速度差很大时,交互加速度试图通过分子中的(vᵢΔvᵢ)²项进行制动补偿,但过于困难。这是通过分母中4bᵢsᵢ²实现的老实说,我不知道它如何精确限制减速到bᵢ)。
  • 在小距离差(si <<1和Δvᵢ≈0):
    加速度成为一个简单的排斥力。

6、交通道路网络模型

我们需要模拟道路网络。为此,我们将使用有向图 G =(V、E),其中:

  • V是一组顶点(或节点)。
  • E是代表道路的边的几何。

每辆车将沿着一条由多个路段(边)组成的路径形式。我们将在同一路段(边)上的车辆应用智能驾驶员模型。当车辆到达路的尽头时,我们会将其从该路段移开并附加到下一路段上。

在仿真中,我们不会保留一组节点(阵列),相反,每条道路都将由其开始和结束节点的值明确定义。

7、随机车辆发生器

为了将车辆添加到我们的模拟中,我们有两个选项:

  • 通过创建新的Vehicle类实例并将其添加到车辆列表中,手动将每辆车添加到模拟中。
  • 根据预先定义的概率,随机添加车辆。

对于第二个选项,我们必须定义一个随机车辆发生器。

随机车辆发生器由两个约束定义:

  • 车辆生成速率(τ): 平均每分钟应添加到仿真中的车辆数。
  • 车辆配置列表 (L):车辆配置和生成概率的元组列表。

    L = [(p1,V1),(p2,V2),(p3,V3]),...

随机车辆发生器以概率pi生成车辆Vi

8、交通信号灯

红绿灯位于路段端点上,其特点是包含以下两个区域:

  • 减速区:减速距离减速系数为特征,是车辆使用减速系数减速的区域。
  • 停车区:停车距离为特征,是车辆停车的区域。这是通过此动态方程使用阻尼力实现的:

9、仿真

我们将采用面向对象的方法。每辆车和道路将被定义为一个类。

我们将反复使用以下的__init__函数。它通过一个函数set_default_config设置当前类的默认配置,并将一个字典中的属性设置为当前类实例的属性。这样,我们不必担心更新不同类别的__init__函数或将来的变化。

def __init__(self, config={}):
  # Set default configuration
  self.set_default_config()

  # Update configuration
  for attr, val in config.items():
      setattr(self, attr, val)

我们将为路段创建一个Road类:

from scipy.spatial import distance

class Road:
    def __init__(self, start, end):
        self.start = start
        self.end = end

        self.init_porperties()

    def init_properties(self):
        self.length = distance.euclidean(self.start, self.end)
        self.angle_sin = (self.end[1]-self.start[1]) / self.length
        self.angle_cos = (self.end[0]-self.start[0]) / self.length

当在屏幕上绘制道路及其角度的正弦和余弦时,我们需要道路的长度length

还有一个Simulation类,让我们添加了一些方法来将道路添加到仿真里。

from .road import Road

class Simulation:
    def __init__(self, config={}):
        # Set default configuration
        self.set_default_config()

        # Update configuration
        for attr, val in config.items():
            setattr(self, attr, val)

    def set_default_config(self):
        self.t = 0.0            # Time keeping
        self.frame_count = 0    # Frame count keeping
        self.dt = 1/60          # Simulation time step
        self.roads = []         # Array to store roads

    def create_road(self, start, end):
        road = Road(start, end)
        self.roads.append(road)
        return road

    def create_roads(self, road_list):
        for road in road_list:
            self.create_road(*road)

我们必须在屏幕上实时显示我们的仿真。为此,我们将使用pygame。我将创建一个Window类,以类Simulation作为参数。

我定义了多个绘图函数,有助于绘制基本形状。

loop方法创建一个pygame窗口,并在每一帧调用draw方法和loop参数。当我们的仿真需要逐帧更新时,这将变得有用。

import pygame
from pygame import gfxdraw
import numpy as np

class Window:
    def __init__(self, sim, config={}):
        # Simulation to draw
        self.sim = sim

        # Set default configurations
        self.set_default_config()

        # Update configurations
        for attr, val in config.items():
            setattr(self, attr, val)
        
    def set_default_config(self):
        """Set default configuration"""
        self.width = 1400
        self.height = 1000
        self.bg_color = (250, 250, 250)

        self.fps = 60
        self.zoom = 5
        self.offset = (0, 0)

        self.mouse_last = (0, 0)
        self.mouse_down = False


    def loop(self, loop=None):
        """Shows a window visualizing the simulation and runs the loop function."""
        # Create a pygame window
        self.screen = pygame.display.set_mode((self.width, self.height))
        pygame.display.flip()

        # Fixed fps
        clock = pygame.time.Clock()

        # To draw text
        pygame.font.init()
        self.text_font = pygame.font.SysFont('Lucida Console', 16)

        # Draw loop
        running = True
        while not self.sim.stop_condition(self.sim) and running:
            # Update simulation
            if loop: loop(self.sim)

            # Draw simulation
            self.draw()

            # Update window
            pygame.display.update()
            clock.tick(self.fps)

            # Handle all events
            for event in pygame.event.get():
                # Handle mouse drag and wheel events
                ...


    def convert(self, x, y=None):
        """Converts simulation coordinates to screen coordinates"""
        ...

    def inverse_convert(self, x, y=None):
        """Converts screen coordinates to simulation coordinates"""
        ...


    def background(self, r, g, b):
        """Fills screen with one color."""
        ...
        
    def line(self, start_pos, end_pos, color):
        """Draws a line."""
        ...

    def rect(self, pos, size, color):
        """Draws a rectangle."""
        ...

    def box(self, pos, size, color):
        """Draws a rectangle."""
        ...

    def circle(self, pos, radius, color, filled=True):
        """Draws a circle"""
        ...

    def polygon(self, vertices, color, filled=True):
        """Draws a polygon"""

    def rotated_box(self, pos, size, angle=None, cos=None, sin=None, centered=True, color=(0, 0, 255), filled=True):
        """Draws a filled rectangle centered at *pos* with size *size* rotated anti-clockwise by *angle*."""

    def rotated_rect(self, pos, size, angle=None, cos=None, sin=None, centered=True, color=(0, 0, 255)):
        """Draws a rectangle centered at *pos* with size *size* rotated anti-clockwise by *angle*."""


    def draw_axes(self, color=(100, 100, 100)):
        """Draw x and y axis"""

    def draw_grid(self, unit=50, color=(150,150,150)):
        """Draws a grid"""

    def draw_roads(self):
        """Draws every road"""

    def draw_status(self):
        """Draws status text"""


    def draw(self):
        # Fill background
        self.background(*self.bg_color)

        # Major and minor grid and axes
        self.draw_grid(10, (220,220,220))
        self.draw_grid(100, (200,200,200))
        self.draw_axes()

        # Draw roads
        self.draw_roads()

        # Draw status info
        self.draw_status()

我将trafficSimulator文件夹中的每一个文件使用__init__.py组合在一起。

from .road import *
from .simulation import *
from .window import *

每当定义新类时,应在此文件中导入该类。

trafficSimulator文件夹放置在我们的项目文件夹中将以便使用仿真模块。

from trafficSimulator import *

# Create simulation
sim = Simulation()

# Add one road
sim.create_road((300, 98), (0, 98))

# Add multiple roads
sim.create_roads([
    ((300, 98), (0, 98)),
    ((0, 102), (300, 102)),
    ((180, 60), (0, 60)),
    ((220, 55), (180, 60)),
    ((300, 30), (220, 55)),
    ((180, 60), (160, 98)),
    ((158, 130), (300, 130)),
    ((0, 178), (300, 178)),
    ((300, 182), (0, 182)),
    ((160, 102), (155, 180))
    
])

# Start simulation
win = Window(sim)
win.loop()

10、车辆

现在,我们必须增加车辆。

我们将使用Taylor级数来近似求解本文建模部分所讨论的动态方程。

一个微分方程f的泰勒级数展开是:

使用▲x替换a,使用x+▲x替换x, 我们得到:

使用位置x替换f

作为一个近似,对位置我们将在2阶截止,因为加速度是最高阶导数。我们得到方程(2)

对于速度,我们将用v代替x

我们将在1阶 停止,因为速度是1阶导数。方程(2)

在每个迭代(或帧)中,使用 IDM 公式计算加速度后,我们将使用以下两个方程更新位置和速度:

在代码中看起来像这样:

self.a = ...      # IDM formula 
self.v += self.a*dt
self.x += self.v*dt + self.a*dt*dt/2

由于这只是一个近似值,速度有时可能会变为负值(但模型不允许这样)。当速度为负时,会产生不稳定性,位置和速度会分化为负无穷大。

为了克服这个问题,每当我们预测负速度时,我们就会将其设定为零,并从那里找出方法:

在代码中,此实现如下:

if self.v + self.a*dt < 0:
  self.x -= 1/2*self.v*self.v/self.a
  self.v = 0
else:
  self.v += self.a*dt
  self.x += self.v*dt + self.a*dt*dt/2

要计算 IDM 加速度,我们将引导车辆表示为lead,并当lead不是None时,计算一个交互项(用alpha表示)。

alpha = 0
if lead:
  delta_x = lead.x - self.x - lead.l
  delta_v = self.v - lead.v
  alpha = (self.s0 + max(0, self.T*self.v + delta_v*self.v/self.sqrt_ab)) / delta_x
self.a = self.a_max * (1-(self.v/self.v_max)**4 - alpha**2)

如果车辆停止(例如在红绿灯处),我们将使用阻尼方程:

if self.stopped: 
  self.a = -self.b_max*self.v/self.v_max

然后,我们将所有内容整合在Vehicle类的update方法中:

import numpy as np

class Vehicle:
    def __init__(self, config={}):
        # Set default configuration
        self.set_default_config()

        # Update configuration
        for attr, val in config.items():
            setattr(self, attr, val)

        # Calculate properties
        self.init_properties()

    def set_default_config(self):    
        self.l = 4
        self.s0 = 4
        self.T = 1
        self.v_max = 16.6
        self.a_max = 1.44
        self.b_max = 4.61

        self.path = []
        self.current_road_index = 0

        self.x = 0
        self.v = self.v_max
        self.a = 0
        self.stopped = False

    def init_properties(self):
        self.sqrt_ab = 2*np.sqrt(self.a_max*self.b_max)
        self._v_max = self.v_max

    def update(self, lead, dt):
        # Update position and velocity
        if self.v + self.a*dt < 0:
            self.x -= 1/2*self.v*self.v/self.a
            self.v = 0
        else:
            self.v += self.a*dt
            self.x += self.v*dt + self.a*dt*dt/2
        
        # Update acceleration
        alpha = 0
        if lead:
            delta_x = lead.x - self.x - lead.l
            delta_v = self.v - lead.v

            alpha = (self.s0 + max(0, self.T*self.v + delta_v*self.v/self.sqrt_ab)) / delta_x

        self.a = self.a_max * (1-(self.v/self.v_max)**4 - alpha**2)

        if self.stopped: 
            self.a = -self.b_max*self.v/self.v_max
        
    def stop(self):
        self.stopped = True

    def unstop(self):
        self.stopped = False

    def slow(self, v):
        self.v_max = v

    def unslow(self):
        self.v_max = self._v_max

Road类中,我们将添加一个deque(双端队列)来跟踪车辆。队列是存储车辆的较好的数据结构,因为队列中的第一辆车是路上最远的车辆,它是第一个可以从队列中删除的车辆。要从deque 中删除第一个车辆,我们可以使用self.vehicles.popleft()

我们将在Road类中添加一个update方法:

def update(self, dt):
  n = len(self.vehicles)

  if n > 0:
    # Update first vehicle
    self.vehicles[0].update(None, dt)
    # Update other vehicles
    for i in range(1, n):
      lead = self.vehicles[i-1]
      self.vehicles[i].update(lead, dt

Simulation类中添加一个update方法:

def update(self):
        # Update every road
        for road in self.roads:
            road.update(self.dt)

        # Check roads for out of bounds vehicle
        for road in self.roads:
            # If road has no vehicles, continue
            if len(road.vehicles) == 0: continue
            # If not
            vehicle = road.vehicles[0]
            # If first vehicle is out of road bounds
            if vehicle.x >= road.length:
                # If vehicle has a next road
                if vehicle.current_road_index + 1 < len(vehicle.path):
                    # Update current road to next road
                    vehicle.current_road_index += 1
                    # Create a copy and reset some vehicle properties
                    new_vehicle = deepcopy(vehicle)
                    new_vehicle.x = 0
                    # Add it to the next road
                    next_road_index = vehicle.path[vehicle.current_road_index]
                    self.roads[next_road_index].vehicles.append(new_vehicle)
                # In all cases, remove it from its road
                road.vehicles.popleft() 

回到Window类,添加了一个run方法来实时更新仿真:

def run(self, steps_per_update=1):
  """Runs the simulation by updating in every loop."""
  def loop(sim):
    sim.run(steps_per_update)
  self.loop(loop)

现在我们将手动添加车辆:

sim.roads[4].vehicles.append(
  Vehicle({
    "path": [4, 3, 2]
  })
)

sim.roads[0].vehicles.append(Vehicle())
sim.roads[1].vehicles.append(Vehicle())
sim.roads[6].vehicles.append(Vehicle())
sim.roads[7].vehicles.append(Vehicle())

车辆生成器代码如下:

from .vehicle import Vehicle
from numpy.random import randint

class VehicleGenerator:
    def __init__(self, sim, config={}):
        ...

    def set_default_config(self):
        self.vehicle_rate = 20
        self.vehicles = [
            (1, {})
        ]

    def init_properties(self):
        self.upcoming_vehicle = self.generate_vehicle()

    def generate_vehicle(self):
        """Returns a random vehicle from self.vehicles with random proportions"""
        ...

    def update(self):
        """Add vehicles"""
        ...

VehicleGenerator包含(odds, vehicle) 元组的列表。

元组第一个元素是在同一元组中生成车辆的权重(不是概率)。我使用权重, 因为它们更容易工作, 因为我们可以只使用整数。

例如,如果我们有3辆车权重分别为132,这相当于生成概率分别为1/6 3/6 2/66

为了实现这一点,我们使用以下算法

  • 生成1到权重和之间的数字r。
  • r是非负数:循环所有可能的车辆,并在每次迭代时减去其权重。
  • 返回最后使用的车辆。

如果我们有权重:W1,W2,W3。 此算法将在1W 3之间的数字分配到第一辆车,将W₁W₁+W₂ 之间的数字分配给第二辆车,将W₁+W₂+W₃结束的数字分配给第三辆车。

def generate_vehicle(self):
  """Returns a random vehicle from self.vehicles with random proportions"""
  total = sum(pair[0] for pair in self.vehicles)
  r = randint(1, total+1)
  for (weight, config) in self.vehicles:
    r -= weight
    if r <= 0:
      return Vehicle(config)

每次生成器添加车辆时,last_added_time属性都会更新到当前时间。当当前时间和last_added_time之差值大于车辆生成周期时,添加车辆。

添加车辆的周期是60/vehicle_rate,因为vehicle_rate 的单位是车辆/每分钟60表示1分钟或60秒。

我们还必须检查道路是否还有空间来添加即将行驶的车辆。我们通过检查道路上最后一辆车之间的距离和即将行驶的车辆的长度和安全距离的总和来做到这一点。

def update(self):
    """Add vehicles"""
    if self.sim.t - self.last_added_time >= 60 / self.vehicle_rate:
        # If time elasped after last added vehicle is
        # greater than vehicle_period; generate a vehicle
        road = self.sim.roads[self.upcoming_vehicle.path[0]]      
        if len(road.vehicles) == 0\
           or road.vehicles[-1].x > self.upcoming_vehicle.s0 + self.upcoming_vehicle.l:
            # If there is space for the generated vehicle; add it
            self.upcoming_vehicle.time_added = self.sim.t
            road.vehicles.append(self.upcoming_vehicle)
            # Reset last_added_time and upcoming_vehicle
            self.last_added_time = self.sim.t
        self.upcoming_vehicle = self.generate_vehicle()

最后,我们应该通过调用Simulationupdate方法来更新车辆生成。

sim.create_gen({
    'vehicle_rate': 60,
    'vehicles': [
        [1, {"path": [4, 3, 2]}],
        [1, {"path": [0]}],
        [1, {"path": [1]}],
        [1, {"path": [6]}],
        [1, {"path": [7]}]
    ]
})

11、红绿灯

交通信号灯的默认属性是:

class TrafficSignal:
    def __init__(self, roads, config={}):
        # Initialize roads
        self.roads = roads
        # Set default configuration
        self.set_default_config()
        # Update configuration
        for attr, val in config.items():
            setattr(self, attr, val)
        # Calculate properties
        self.init_properties()

    def set_default_config(self):
        self.cycle = [(False, True), (True, False)]
        self.slow_distance = 40
        self.slow_factor = 10
        self.stop_distance = 15

        self.current_cycle_index = 0
        self.last_t = 0

self.cycleself.roads 的一个数组,每个元组包含道路的状态(True绿色,False红色)。

在默认配置中,(False, True)表示第一组道路为红色,第二组道路为绿色。 (True, False) 则恰恰相反。

所以使用此方法之,是因为它易于扩展。我们创建红绿灯,包括超过 2 条道路、带有左右转弯单独信号的红绿灯,甚至用于多个交叉路口的同步交通信号灯。

交通信号灯的update函数应该是可定制的。其默认行为是对称的固定时间循环。

def init_properties(self):
    for i in range(len(self.roads)):
        for road in self.roads[i]:
            road.set_traffic_signal(self, i)

@property
def current_cycle(self):
    return self.cycle[self.current_cycle_index]

def update(self, sim):
    # Goes through all cycles every cycle_length and repeats
    cycle_length = 30
	k = (sim.t // cycle_length) % 2
	self.current_cycle_index = int(k)

我们需要在Road类添加以下方法:

def set_traffic_signal(self, signal, group):
    self.traffic_signal = signal
    self.traffic_signal_group = group
    self.has_traffic_signal = True

@property
def traffic_signal_state(self):
    if self.has_traffic_signal:
        i = self.traffic_signal_group
        return self.traffic_signal.current_cycle[i]
    return True

而这个,在Roadupdate方法。

# Check for traffic signal
if self.traffic_signal_state:
    # If traffic signal is green or doesn't exist
    # Then let vehicles pass
    self.vehicles[0].unstop()
    for vehicle in self.vehicles:
        vehicle.unslow()
else:
    # If traffic signal is red
    if self.vehicles[0].x >= self.length - self.traffic_signal.slow_distance:
        # Slow vehicles in slowing zone
        self.vehicles[0].slow(self.traffic_signal.slow_speed)
    if self.vehicles[0].x >= self.length - self.traffic_signal.stop_distance and\
       self.vehicles[0].x <= self.length - self.traffic_signal.stop_distance / 2:
        # Stop vehicles in the stop zone
        self.vehicles[0].stop()

Simulationupdate 方法中检查交通灯状态:

for signal in self.traffic_signals:
  signal.update(self)

12、曲线

在现实世界中,道路有曲线。虽然从技术上讲,我们可以通过手写很多道路的坐标来接近曲线来创建此模拟中的曲线,但我们可以在程序上实现同样的事情。

我们将使用贝赛尔曲线来达到这个效果。

我创建了一个curve.py文件,其中包含有助于创建曲线并按其道路序号引用曲线的功能。

def curve_points(start, end, control, resolution=5):
	# If curve is a straight line
	if (start[0] - end[0])*(start[1] - end[1]) == 0:
		return [start, end]

	# If not return a curve
	path = []

	for i in range(resolution+1):
		t = i/resolution
		x = (1-t)**2 * start[0] + 2*(1-t)*t * control[0] + t**2 *end[0]
		y = (1-t)**2 * start[1] + 2*(1-t)*t * control[1] + t**2 *end[1]
		path.append((x, y))

	return path

def curve_road(start, end, turn_direction, resolution=15):
	points = curve_points(start, end, turn_direction, resolution)
	return [(points[i-1], points[i]) for i in range(1, len(points))]

测试:

from trafficSimulator import *

# Create simulation
sim = Simulation()

# Add multiple roads
sim.create_roads([
    ((0, 100), (140, 100)),
    ((150, 110), (150, 200)),

    *curve_road((140, 100), (150, 110), (150, 100))
])

sim.create_gen({
    'vehicle_rate': 20,
    'vehicles': [
        [1, {"path": [0, *range(2, 17), 1]}]
    ]
})


# Start simulation
win = Window(sim)
win.run(steps_per_update=5)

13、示例

这些示例的代码可在 Github 中找到。

公路匝道入口

双向交叉路口

环形交叉路口

钻石型分流交叉路口

14、局限性

虽然我们可以修改Simulation类来存储有关我们以后可以使用的模拟数据,但如果数据收集过程更加简化,则更好。

这种模拟仍然缺乏很多。曲线的实现是糟糕和低效的,并导致车辆和交通信号之间的相互作用的问题。

虽然有些人可能认为智能驱动模型有点过头了,但重要的是要有一个模型,可以复制现实世界的现象,如交通波(又名幽灵交通蛇)和司机的反应时间的影响。因此,我选择使用智能驱动模型。但对于精度和极端现实主义不重要的仿真,就像在视频游戏中一样,IDM可以被一个更简单的基于逻辑的模型所取代。

完全依赖基于仿真的数据会增加过度拟合的风险。你的 ML 模型可以优化用于仅存在于仿真中的处理,并且在现实世界中不存在。

15、结论

仿真是数据科学和机器学习的重要组成部分。有时,从现实世界中收集数据是不可能的,或者成本很高。生成数据有助于以更好的价格构建巨大的数据集。仿真还有助于填补真实数据中的空白。在某些情况下,现实世界数据集缺少对开发模型至关重要的边缘案例。

这个仿真是我参与的本科学校项目的一部分。目的是优化城市交叉路口的交通信号。我制作了此仿真来测试和验证我的优化方法。

我从来没有想过发表这篇文章,直到我看到特斯拉的AI展示日,其中他们谈到他们如何使用仿真来生成数据的边缘样本。


原文链接:Simulating Traffic Flow in Python

BimAnt翻译整理,转载请表明出处