Lesson 04 · 控制
用 Python 操控它:pyenergyplus 与 EMS
强化学习的命脉:能实时"读传感器、写执行器"。这一课讲清它怎么做到的。
前三课的 EnergyPlus 还是个"被动演员"——你给它 IDF 和 EPW,它演完一年吐个报表。但我们的智能体要边演边干预:每个时间步,读当前室温,决定冷机怎么开,再把这个决定塞回去。这一课讲的就是这件事的机制——EMS 和它上面的 pyenergyplus。这是整个 RL 接入的关键,也是 Sinergym(第 5 课)的地基。
核心概念:EMS(能源管理系统)
EnergyPlus 内部早就有一套机制让"外部逻辑"干预仿真,叫 EMS(Energy Management System,能源管理系统)。它的模型很干净,三个角色:
| EMS 角色 | 类比 | 对应我们什么 |
|---|---|---|
| Sensor(传感器) | 读量 | RL 的观测(室温、水温、CO₂…) |
| Actuator(执行器) | 写量 | RL 的动作(冷机功率、水泵频率…) |
| -meter(计量表) | 读能耗 | RL 的奖励(耗电量、COP) |
EMS 把"仿真内部"和"你的控制逻辑"解耦了
EnergyPlus 物理引擎照常算楼的热响应;EMS 在每个时间步给你一个"插口"——你通过它读到内部变量(Sensor),再把控制值写回去(Actuator)。引擎收到你的值后继续往下算。这套机制让任何外部控制器(PID、规则、RL)都能接同一个仿真。
必须在 IDF 里声明要用哪些 Sensor/Actuator,给它们起名字(方案 [§2.2.3] 里提到的 EnergyManagementSystem:Actuator 就是干这个)。没声明的量,外部读不到也写不了。这是为什么方案强调"IDF 建模需暖通工程师参与"——RL 要碰的执行器,得在建模型时就埋好。
从 Erl 到 Python:pyenergyplus 的由来
历史上,EMS 的控制逻辑用一种叫 Erl(EnergyPlus Runtime Language)的脚本写,写在 IDF 里。Erl 很笨重。从 EnergyPlus 9.3(2020 年 3 月)起,官方内置了 Python 解释器,你可以用 Python 写 EMS 逻辑——这就是 Python EMS / Python Plugin。再往后,官方又把这套能力打包成更通用的 Python API(pyenergyplus),不局限于"写 EMS 脚本",而是把整个仿真进程驱动起来。
pyenergyplus 的三块能力
入口是一个类 EnergyPlusAPI,它分三类能力(官方 API 文档口径):
from pyenergyplus.api import EnergyPlusAPI api = EnergyPlusAPI() # 1) Functional —— 一次性查询(不在仿真循环里) functional = api.functional # 2) Runtime —— 注册"在仿真的某个时刻"回调(核心!) runtime = api.runtime runtime.callback_begin_zone_timestep_before_set_current_heat_balance(my_handler) # 3) Exchange(数据传递)—— 在回调里读 Sensor / 写 Actuator exchange = api.exchange exchange.get_variable_value(...) # 读 exchange.set_actuator_value(...) # 写 # 然后用 api.run_energyplus(...) 启动整个仿真
| 能力 | 用途 | RL 场景 |
|---|---|---|
| Functional | 仿真外的工具查询 | 查询状态、版本 |
| Runtime | 注册回调(在某个仿真钩子点被调用) | 每个时间步触发"读→决策→写" |
| Exchange | 在回调内读写变量/执行器 | 取观测、回写动作 |
RL 的一步,落到 EMS 上是什么样
把方案 [§2.1.4] 里 HvacEnv.step() 的逻辑翻译成 EMS 语言:
RL 智能体调 env.step(action)
│
▼
┌─ Sinergym / 自建封装层把 action 存进队列 ─────────┐
│ │
│ EnergyPlus 走到一个时间步 │
│ │ │
│ ▼ │
│ Runtime 回调触发(begin_zone_timestep) │
│ │ │
│ ├─▶ exchange.get_variable_value(...) × N │ ← 读传感器 = 观测
│ │ (室温/水温/CO₂/室外温...) │
│ │ │
│ ├─▶ exchange.set_actuator_value(...) × M │ ← 写执行器 = 动作
│ │ (冷机功率/水泵频率/AHU风量...) │
│ │ │
│ └─▶ 让仿真继续走这一步 │
│ │
│ 仿真算完 → 把新观测 + 能耗推回队列 → 算 reward ────┘
│
▼
返回给 RL 智能体:obs, reward, done
EnergyPlus 用 EMS 把物理引擎和外部控制解耦:Sensor 读观测、Actuator 写动作、meter 给奖励。从 9.3 版起,控制逻辑可用 Python 写(Python EMS / pyenergyplus),核心是 Runtime 回调(每个时间步触发)+ Exchange(在回调里读写)。RL 的 step() 就落在这一套机制上,而 Sinergym 替我们封好了它。
RL 智能体要调冷机功率,前提是?
卡住了就问我:"回调 + 队列"通信如果让你头晕,正常——它是异步线程模型。可以问我"为什么不能同步地读一个变量",我会解释 EnergyPlus 是 C++ 主循环、Python 是被它回调的,所以天然是异步的。