创建环境#

IR-SIM 仿真由 YAML 场景文件通过 irsim.make() 创建,并由一段简短的 Python 循环(env.step()env.render()env.done())驱动。本页介绍如何构建环境、核心循环、状态控制以及动态对象管理。若需同时运行多个场景,请参阅多环境

Python 脚本与 YAML 配置文件#

要开始仿真,需要先创建一个环境。环境是仿真中所有对象的容器,并负责在每个时间步更新仿真状态。

可以使用简单的 Python 脚本创建环境:

import irsim

env = irsim.make("basic_world.yaml")

make 函数会根据配置文件创建环境,支持的参数包括:

  • world_name(str,可选):世界 YAML 配置文件路径

  • projection(str,可选):投影类型(“3d” 表示 3D 环境,None 表示 2D)

  • step_mode"internal""external",可选):覆盖当前环境的 world.step_mode;若省略,则使用 YAML 中的值。

  • display(bool):是否显示环境可视化(默认 True)

  • save_ani(bool):是否将仿真保存为动图(默认 False)

  • log_level(str):环境日志级别(默认 "INFO")

  • seed(int,可选):IR-SIM 随机数引擎的种子。提供后,IR-SIM 生成的随机内容即可复现;若省略或为 None,则使用未设种子的生成器(不可复现)。若自定义扩展仍使用 np.random 或 Python random,需改用 IR-SIM RNG 或另行设定种子。

更多信息见 EnvBase 类文档。

在 YAML 文件(basic_world.yaml)中定义一个最小可运行世界:

world:
  height: 10  # the height of the world (meters)
  width: 10   # the width of the world (meters)
  step_time: 0.1  # simulation time step (seconds) - 10Hz
  sample_time: 0.1  # rendering frequency (seconds) - 10Hz
  offset: [0, 0] # the offset of the world origin [x, y]
  step_mode: 'internal' # state advancement: 'internal' or 'external'
  control_mode: 'auto' # control mode: 'auto', 'keyboard'
  collision_mode: 'stop' # collision behavior: 'stop', 'unobstructed', 'unobstructed_obstacles'
  obstacle_map: null # path to obstacle map file (optional)

robot:
  kinematics: {name: diff}
  shape: {name: circle, radius: 0.2}
  state: [1, 1, 0]
  goal: [9, 9, 0]
  behavior: {name: dash}
  color: g
  plot:
    show_trajectory: true
    show_goal: true

配置文件定义世界和机器人,主循环将机器人从起点推进到目标。

重要参数说明#

世界配置#

  • world:定义仿真环境属性的主段

  • heightwidth:以米为单位指定世界尺寸

  • step_time:控制仿真精度与速度(越小越精确但更慢)

  • sample_time:控制渲染频率(越大仿真越快,可视化越不平滑)

  • offset:以米为单位偏移世界坐标系原点 [x, y]

  • step_mode:决定由谁推进对象状态

    • 'internal':IR-SIM 根据动作或已配置的行为积分更新状态

    • 'external':外部代码提供状态,IR-SIM 同步派生数据

  • control_mode:决定仿真控制方式

    • 'auto':自动执行仿真

    • 'keyboard':手动键盘控制

  • collision_mode:定义碰撞检测行为

    • 'stop':发生碰撞时停止仿真(默认)

    • 'unobstructed':忽略所有碰撞

    • 'unobstructed_obstacles':仅忽略障碍物碰撞

  • obstacle_map:可选。障碍物地图图像的路径,或生成器配置(例如 { name: perlin, ... })。参见 配置栅格地图

性能考量#

  • 更小的 step_time:物理更精确但仿真更慢

  • 更大的 sample_time:仿真更快但可视化不够流畅

  • 世界尺寸:更大的世界需要更多算力

小技巧

可以通过 sample_time 控制渲染频率、提升仿真速度。默认情况下 sample_timestep_time 相同。

更详细的参数说明参见 YAML 配置

小技巧

自动配置检测:默认 YAML 配置文件与 Python 脚本同名。例如脚本为 test.py,IR-SIM 会在同目录自动寻找 test.yaml

import irsim

# Automatically uses 'test.yaml' if this file is 'test.py'
env = irsim.make()

该特性避免手动指定配置文件,简化了开发流程。

基础仿真循环#

创建环境后,通常会运行如下仿真循环:

import irsim

env = irsim.make("config.yaml")

# Main simulation loop
for i in range(1000):
    env.step()  # Update simulation state
    env.render(0.05)  # Render with 0.05 second interval (20Hz)

    if env.done():  # Check if simulation should end
        break

env.end()  # Clean up resources

核心方法说明#

  • env.step():让仿真前进一步

  • env.render(interval):按设定的帧间隔刷新可视化

  • env.done():当满足结束条件时返回 True

  • env.reset(random=False):将对象恢复为初始状态。传入 random=True 时,会基于缓存的 YAML 解析结果重建世界,从而让随机元素(如 distribution: random、随机形状生成器)按当前 RNG 状态重新采样 —— 可配合 irsim.util.random.set_seed(seed) 获得可复现的全新场景。此操作不会重新读取磁盘上的 YAML 文件;如需加载磁盘上的修改,请改用 env.reload()

  • env.refresh():在不推进仿真的前提下,刷新由状态派生的属性(几何、传感器读数、碰撞树以及状态)。当你直接修改对象状态(例如 robot.set_state(...))后,若希望在下一次 env.step() 之前让传感器和碰撞信息同步到最新状态,使用此方法。

  • env.reload(world_name=None):重新解析 YAML 文件(可选指定其他文件)并重建世界/对象。

  • env.get_msg():捕获无需额外依赖的 ROS 风格世界快照。

  • env.receive_msg(...):将外部 ROS 风格里程计应用到 IR-SIM 对象,并刷新其派生状态。

  • env.end():正确关闭环境并释放资源

  • env.close()env.end() 的别名,提供 Gym 风格的 API 兼容性

小技巧

更新顺序

环境会先推进所有对象,再更新全部传感器。这种两阶段更新保证传感器读取到的都是最新世界状态。若手动推进对象,请在 ObjectBase.step(...) 传入 sensor_step=True,或在更新对象状态后调用 obj.sensor_step()

用于批量训练的无头模式#

传入 headless=True 即可在没有任何图形、窗口和键盘/鼠标控制的情况下运行。环境创建开销降低数倍,丢弃环境时也不会留下任何残留,这在训练循环需要创建大量环境时很重要;render()、各绘图辅助函数和 save_figure() 都会变成空操作,因此同一脚本无需修改即可运行:

env = irsim.make("config.yaml", headless=True)

disable_all_plot=True 作为别名保留。如果需要离屏渲染并仍然保存动画或图片,请改用 display=False

内部与外部步进模式#

默认的 internal 模式保持 IR-SIM 的常规循环。可以显式提供动作,也可以省略动作,由已配置的行为生成动作:

env = irsim.make("config.yaml")
env.step(action=[1.0, 0.0], action_id=0)

动作的传入方式遵循常见的单智能体 / 多智能体惯例:

  • env.step([1.0, 0.5]):把一个动作(列表、元组或 ndarray)施加给第一个机器人。

  • env.step(action, action_id=2):作用于 id 为 2 的对象(即 obj.id;机器人最先创建,因此 id 0..n-1 就是机器人)。也可以用名字,如 "robot_2"

  • env.step([a0, a1, a2]):按顺序给每个机器人一个动作,或从 action_id 开始依次施加。

  • env.step(actions, action_id=[0, 3]):动作与给定的 id 一一对应。

  • env.step({"robot_0": a0, "robot_3": a3}):以机器人名字为键的字典。

多余的动作会被丢弃并给出警告;不存在的 id 或名字会抛出 ValueError

external 模式下,状态由另一个仿真器或外部系统更新。请先提供新的状态和速度,再调用不带动作的 env.step()

env = irsim.make("config.yaml", step_mode="external")
robot = env.robot

while not env.done():
    state, velocity = external_system.read_robot()
    robot.set_state(state)
    robot.set_velocity(velocity)
    env.step()

外部步进不会执行 IR-SIM 的运动学或行为逻辑。它基于同一状态快照刷新所有对象的几何信息、重建碰撞索引、更新传感器和状态、记录轨迹,并推进世界时钟。在此模式下向 env.step() 传入 action 会引发 ValueError,以防意外混用内部和外部状态推进方式。

交换仿真消息#

当控制器、日志记录器、桥接程序或学习流程需要一致的仿真器快照时,可使用 env.get_msg()。返回的 WorldState 按照常见 ROS 话题名称组织每个对象的数据:robot.odomOdometry 消息,robot.scan 是其主 LaserScan 消息。

msg = env.get_msg()

print(msg.header.seq)  # simulation step count
print(msg.header.stamp)  # simulation time in seconds
robot = msg.robots[0]

print(robot.odom.pose.pose.position.x)
print(robot.odom.twist.twist.linear.x)

if robot.scan is not None:
    print(robot.scan.ranges)

payload = msg.to_dict()  # JSON-compatible lists and scalar values

消息类型提供稳定的逻辑 ros_type 提示,例如 robot.odom.ros_type == "nav_msgs/Odometry"。其中的斜杠形式仅为兼容性保留,并不表示选择 ROS 1。桥接程序负责选择原生 ROS 1 或 ROS 2 消息类,并将 IR-SIM 的浮点时间戳和序号转换为相应的 Header 结构。scans 列表包含全部 LiDAR 读数,scan 是第一项主读数的别名。

LaserScan 仅包含共享的 sensor_msgs/LaserScan 数据字段;没有强度数据时,intensities 为空数组。其角度元数据能够精确重建仿真的光束方向。由于 IR-SIM 从同一个几何快照计算全部光束,time_increment 为零;scan_time 是配置的扫描间隔。笛卡尔目标速度、FMCW 径向速度和有效性等 IR-SIM 专用测量仍可通过 sensor.get_scan()env.get_lidar_scan() 获取。

里程计和扫描消息使用常见的 worldbase_link 和传感器坐标系名称。ROS 桥接程序仍负责发布相应的 /tf/tf_static/clock 消息。

消息是特定时刻的副本:之后调用 env.step() 或对象设置方法,不会修改已经捕获的消息。这些消息类无需额外依赖,也不要求安装 ROS;ROS 桥接程序可在边界处将它们映射为原生 ROS 消息。

使用 env.receive_msg(...) 可由其他仿真器或 ROS 桥接程序驱动 IR-SIM。该方法接受完整的 WorldState、单个 ObjectState,或 IR-SIM/原生 ROS 风格的 Odometry。独立的里程计消息默认更新主机器人;也可以通过稳定的对象名称或 IR-SIM 对象 ID 选择其他本地对象:

external_odom = source_env.get_msg().robots[0].odom
updated = env.receive_msg(external_odom, object_name="message_robot")
assert updated == 1

对于 WorldState,传入对象会先按名称、再按 ID 与本地对象匹配。IR-SIM 应用二维位姿和机体坐标系 twist,而接收环境保留自己的配置、目标、扫描数据和仿真时钟。默认情况下,每次调用还会刷新几何、本地仿真的传感器、碰撞和到达状态。仅在批量更新时传入 refresh=False,随后统一调用一次 env.refresh()。任何对象被修改之前,所有传入更新都会先完成验证。

桥接程序也可以单独复用这一转换:from_msg() 将原生 ROS 里程计消息转换为经过校验的 IR-SIM 消息,to_state_velocity() 则返回该消息对某个对象所对应的 (state, velocity) 数组,并且不会修改该对象。二者都是 from_object() 的逆运算,因此位姿和 twist 在“捕获—应用”的往返过程中保持一致。

完整的可运行示例位于 usage/25msg_world/

环境控制与状态#

状态管理#

import irsim

env = irsim.make("config.yaml")

# Check current status
print(f"Current status: {env.status}")
print(f"Current time: {env.time}")

# Control simulation state
env.pause()  # Pause the simulation
env.resume()  # Resume the simulation

# Simulation loop with status checking
for i in range(50):
    env.step()
    env.render(0.05)

    if i < 10:
        env.pause()
    elif i > 20 and i < 30:
        env.resume()

    if env.status == "Pause":
        print("Environment is paused")

    if env.done():
        print("Simulation completed successfully")

env.end()

运行时状态值#

IR-SIM 会在仿真运行时更新 env.status。YAML 中的 world.status 设置仅提供初始显示标签;它不会暂停、恢复或停止执行。

  • "Running":环境在自动控制模式运行

  • "Running (keyboard)":环境在键盘控制模式运行

  • "Pause":仿真已暂停

  • "Arrived":所有机器人都到达目标

  • "Collision":检测到碰撞

  • "Pause (Debugging)":调试模式(按下 F5 时)

  • "Reset":环境已复位

  • "Reload":环境已重新加载

  • "Save Figure":已保存图像

  • "Quit":环境已退出

配置环境标题#

默认情况下,环境标题会显示仿真时间与状态。可通过设置 show_title 并调用 env.set_title() 来自定义该行为。

import irsim

env = irsim.make("config.yaml")

# Set custom title
env.set_title("Multi-Robot Navigation Simulation")

# Update title dynamically
for i in range(100):
    env.step()

    # Update title every 10 steps
    if i % 10 == 0:
        env.set_title(f"Simulation Step: {i}")

    env.render(0.05)

env.end()
world:
  height: 20
  width: 20
  control_mode: 'auto'
  plot:
    show_title: true    # Show title with time and status
    show_axis: true     # Optional: show axis labels

动态对象管理#

除了在 YAML 配置文件中定义对象外,还可以在运行时通过代码动态创建和添加对象。这适用于在仿真过程中动态生成对象,例如随机生成障碍物或动态添加机器人。

通过代码创建对象#

使用 env.create_robot()env.create_obstacle() 通过关键字参数创建对象:

import irsim

env = irsim.make("empty_world.yaml")

# Create a differential-drive robot
robot = env.create_robot(
    kinematics={"name": "diff"},
    shape={"name": "circle", "radius": 0.2},
    state=[1, 1, 0],
    goal=[8, 8, 0],
    name="robot_0",
)

# Create a static circular obstacle
obstacle = env.create_obstacle(
    shape={"name": "circle", "radius": 0.5},
    state=[5, 5, 0],
    name="obs_0",
)

常用关键字参数包括:

  • kinematics(dict):运动学模型,如 {"name": "diff"}{"name": "omni"}{"name": "acker"}。省略或使用 {"name": "static"} 表示静态对象。

  • shape(dict):形状定义,如 {"name": "circle", "radius": 0.5}{"name": "polygon", "vertices": [[0,0],[1,0],[0,1]]}

  • state(list):初始状态 [x, y, theta, ...]

  • goal(list):目标状态 [x, y, theta, ...]

  • color(str):对象颜色(默认值因运动学类型而异)。

  • name(str):唯一对象名称,不能与已有对象重名。

  • goal_threshold(float):到达检测的距离阈值(默认 0.1)。

完整参数列表参见 ObjectBase 类文档。

向环境添加对象#

创建对象后,使用 env.add_object()env.add_objects() 将其添加到环境中:

# Add a single object
env.add_object(robot)

# Add multiple objects at once
env.add_objects([obstacle])

每个对象必须有唯一名称。添加重名对象会引发 ValueError

另请参阅#

  • 多环境 —— 以相互隔离的状态同时运行多个场景,适用于对比研究或强化学习训练。