1. 为什么楼宇微网要把需求侧负荷当成“储能电池”来用
做楼宇微网优化调度的朋友应该都有同感:光伏出力的波动性、负荷峰谷的刚性差异、电价的实时变化,这三者凑在一起,让微网调度变成一个既讲究经济性又讲究稳定性的复杂问题。以前大家一提到削峰填谷、平抑波动,第一反应就是上储能电池,但电池的度电成本、循环寿命、维护难度摆在那里,很多楼宇项目算完经济账后还是犹豫再三。
我这两年接触了不少楼宇微网的实际项目,越来越觉得“需求侧虚拟储能”这条路被低估了。所谓虚拟储能,通俗点说,就是把楼宇里那些可以灵活调节的用电设备——中央空调、热水器、电动汽车充电桩、照明系统——当成一块“看不见的电池”来调度。空调温度往上调一度,相当于少用了一部分电量,这块“少用的电量”在系统层面看,就跟电池放出了一部分电一样;等电价低谷或者光伏大发的时候,再把温度调回来,相当于给这块“电池”充电。
这个思路最吸引人的地方在于:它不需要额外买电池,不需要增加物理设备,只需要把控制策略和调度算法做对,就能在不影响用户舒适度的前提下,撬动一块可观的调节容量。我在一个实际楼宇项目里粗略算过,一栋两万平米的办公大楼,中央空调群在舒适度允许的范围内能提供的虚拟储能容量,大约相当于一个200~400 kWh的电池组,而实现这个虚拟储能的成本几乎为零,只是软件层面的事。
从建模和优化的角度看,楼宇微网优化调度如果把虚拟储能考虑进去,问题的维度会拉高不少。传统微网调度只需要管物理电池的充放电状态,现在还得同时管空调群的温度动态、热水器的储热状态、充电桩的错峰时序,整件事就变成一个动态耦合的优化问题。这正是Matlab能发挥价值的地方:Matlab处理矩阵运算、约束构建、求解器调用都很顺手,配合Yalmip工具箱调用Gurobi或Cplex,能够在可接受的时间内把这类中规模优化问题求到全局最优解。
这篇文章我就把整套模型的建模思路、数学表达和Matlab代码实现完整梳理一遍。适合正在做微网优化、综合能源系统调度、需求响应方向研究的同学,以及想在自己项目里落地虚拟储能策略的工程师参考。下面我会按“物理模型搭建 → 虚拟储能等效建模 → 优化数学模型 → Matlab代码实现 → 仿真结果解读 → 调试经验”这条线来讲,尽量把每一步的逻辑闭环讲透。
2. 楼宇微网的物理结构拆分:哪些设备必须建模,哪些可以简化
设计优化调度模型的第一步,不是急着写约束,而是先把楼宇微网的物理拓扑理清楚。不同的设备类型,在模型里的表达方式完全不同。如果把所有细节都建模进去,非线性的、离散的变量会爆炸式增长,求解难度直线上升;如果简化过头,调度结果又失去参考价值。这里面有一套权衡方法,我讲讲自己在实践中沉淀下来的设备建模取舍标准。
2.1 楼宇微网的典型设备构成与角色分层
一栋楼宇微网,从能量流动的视角看,通常包含这几类核心单元:
| 设备类型 | 功能角色 | 调度自由度 | 建模复杂度 |
|---|---|---|---|
| 屋顶光伏 | 清洁电源 | 不可控(按出力曲线给定) | 低 |
| 燃气轮机/柴油机 | 可控电源 | 可启停、可调出力 | 中 |
| 物理储能电池 | 双向电源 | 可充可放、有SOC约束 | 中 |
| 中央空调系统 | 柔性负荷 | 可调节设定温度/功率 | 高 |
| 电热水器 | 柔性负荷 | 可平移时段 | 中 |
| 电动汽车充电桩 | 柔性负荷 | 可延迟充电时段 | 中 |
| 基础照明与插座负荷 | 刚性负荷 | 不可调节 | 低 |
| 与配电网交互点(PCC) | 能源接口 | 可买电/可售电、有功率上限 | 低 |
在建模之前,我习惯先画一张功率平衡图——以母线为核心,左侧是电源(光伏、机组、电池放电、电网购电),右侧是负荷(刚性负荷、柔性负荷、电池充电)。优化调度的本质,就是在每个调度时段里,决定可控设备之间的功率分配,使得全局目标最优。
2.2 光伏出力建模:不要过度追求精细,但要注意功率预测误差
光伏出力在调度模型里通常作为已知参数输入,因为调度决策是在日前(day-ahead)做出的,光伏只能给预测曲线。我的处理方式是:用一个简化的线性出力模型,即给定每个时段的预测功率 P_pv(t),把它当作负的负荷直接放进功率平衡等式的一侧。之所以不建复杂的辐照-温度-效率非线性模型,是因为优化时要的是数学上可解,物理上的精细度交给光伏预测系统去完成即可。
不过有一点要注意:仿真验证阶段,最好在预测出力上叠加一个小的误差扰动,观察系统对光伏波动的响应。如果没有这层扰动,整个模型会显得过于“完美”,实际部署时会发现鲁棒性远不如仿真结果。这一点我在后面调试经验里会详细展开。
2.3 物理储能电池:SOE动态约束是模型的“锚点”
物理储能的建模相对标准,但还是有几个细节值得说。首先是用SOE(State of Energy,能量状态)替代更常见的SOC概念,因为调度关心的是能量进出,SOE直接对应电池的可用电量,单位统一为kWh。其次,充放电约束里必须考虑:同一时刻不能既充电又放电——这个约束看起来是废话,但如果不显式添加,很多求解器在松弛变量较多时确实会给出同时充放电的荒唐解。
电池的SOE递推公式我习惯写成:
SOE(t+1) = SOE(t) + η_ch * P_ch(t) * Δt - P_dis(t) * Δt / η_dis
其中 η_ch 和 η_dis 分别代表充电和放电效率。这个公式里效率的位置很多人写反,需要注意:充电时电网送进电池的电要打折扣,所以用乘法;放电时电池送出去的电也要打折扣,所以用除法。单位统一用kW和小时,Δt取1小时,这样能量单位就是kWh。
2.4 哪些部分可以放心简化
我在初期建模时,也曾经试图把负荷里面的每个设备都细分出来,结果发现模型复杂度飙升,但调度结果的改善非常有限。后来总结出一条经验:与调度自由度无关的设备,全部并入刚性负荷;有调节潜力的设备,按“聚合体”来建模。所谓聚合体,就是把楼宇里同类的柔性设备看作一个整体,用集合层面的参数来描述,比如“全部空调的聚合功率范围是80kW到120kW”,而不是逐台描述。
这样处理的好处显而易见:变量数量从几十台设备降到几种资源类型,优化模型的求解时间从几分钟降到几秒,而决策结果的差别很小。对工程应用来说,用精度换可解性是完全值得的。
3. 虚拟储能的核心建模逻辑:如何把“温度”翻译成“电量”
虚拟储能建模是整个系统里最需要费心思的部分,因为它不能像物理电池那样直接写充放电功率,而是要通过温度、时间、用户舒适度这些间接变量来映射。我得把这部分讲细一点,因为很多初学者在这里绕不过弯来。
3.1 中央空调系统的蓄热特性:楼宇天然存在的“热电池”
先想一个物理事实:一栋楼宇的墙体、家具、内部空气,是一个很大的蓄热体。夏季空调在上午十点开始工作,把室内温度从32℃降到26℃,这些“冷量”不是瞬时消耗掉的,而是储存在整个建筑结构里。如果中午把空调设定温度从26℃上调到28℃,室温不会立刻跳到28℃,而是缓慢上升——这个过程里空调压缩机的功率就降下来了,相当于少用电,也就是“放电”。
反过来,在电价便宜的时段,提前把空调温度调低到24℃,让整个建筑结构“存”进去更多冷量,这部分冷量可以在后续温度回升时释放,相当于“充电”。一模一样的热力学循环,站在电网角度就是储能行为。
为了把这个物理过程数学化,我采用一阶等效热参数模型(Equivalent Thermal Parameter,ETP),这是建筑热动态建模最常用的方法:
C_eq * dT_room/dt = (T_out - T_room) / R_eq + Q_AC + Q_other
其中 C_eq 是楼宇等效热容(kWh/℃),R_eq 是等效热阻(℃/kW),Q_AC 是空调供给的冷量(kW,制冷时为负值),Q_other 是人员、设备等内部产热(kW)。
把上面的微分方程用一阶差分近似,就能得到离散化的室内温度递推关系。每个调度时段直接算室温,并把室温区间映射成空调功率约束。这个模型只有两个聚合参数,C_eq 和 R_eq,通过历史温变数据就能辨识出来,工程上非常实用。
3.2 温度区间换算成虚拟储能的功率上下限
虚拟储能和物理电池的对应关系,我整理成这张表,建模的时候可以对照着用:
| 物理储能元素 | 虚拟储能对应概念 | 映射关系 |
|---|---|---|
| 电池容量(kWh) | 室内温度允许波动范围内的蓄热量 | ΔQ = C_eq * ΔT_max |
| 充电功率上限(kW) | 空调满功率制冷时的用电增量 | P_cool_max - P_base |
| 放电功率上限(kW) | 空调停机/最低功率时少用的电量 | P_base - P_cool_min |
| SOC状态 | 室内温度相对设定下限的水平 | T(t) 归一化到 [0,1] |
| 自放电率 | 楼宇自然散热速率 | 1/(R_eq * C_eq) |
室内温度允许的波动区间,就是虚拟储能的“荷电状态”边界。比如某楼宇的舒适区是24℃到28℃,目标温度26℃,那这个虚拟储能的可调度范围就是正负2℃对应的蓄冷量。
虚拟储能的调节功率上限是动态变化的——温度离边界越远,空调能提供的调节功率越大;离边界越近,调节能力越小。这跟物理电池SOC不同,物理电池的功率上限基本恒定,虚拟储能则是状态相关的。建模时我把这个逻辑写成一个与温度相关的不等式约束,而不是简单地设一个固定最大功率。
3.3 热舒适度的惩罚机制:免费储能不是真的免费
虚拟储能能提供调节容量,但不等于可以无限调用。如果把室内温度压到舒适区边缘甚至超出舒适区,就会造成用户体验下降。为了在优化模型里体现这一点,我给温度越界引入惩罚项,放进目标函数。
具体做法是引入两个非负松弛变量 ε_cold(t) 和 ε_hot(t),把室内温度约束从严格不等式放宽为软约束:
T_min - ε_cold(t) ≤ T_room(t) ≤ T_max + ε_hot(t)
目标函数里增加惩罚项:λ_temp * [ε_cold(t) + ε_hot(t)]。这个 λ_temp 的取值很关键——设置太小,求解器会肆无忌惮地牺牲舒适度换经济性;设置太大,虚拟储能的调节能力几乎不会被使用,优化退化成普通微网调度。实操中我一般从电价的2到5倍开始试,然后根据舒适度越界时长迭代调整。这部分调参经验我放在后面专门讲。
4. 优化调度模型的完整数学表达:目标函数与约束体系的构建
在设备模型和虚拟储能模型都准备好的基础上,接下来就是组装完整的优化调度数学模型了。我这里采用的是混合整数线性规划(MILP)框架——把非线性的部分做线性化处理,能确保用商业求解器在合理时间内求到全局最优解。
4.1 目标函数设计:不止是成本最小化
楼宇微网调度的目标函数,如果只写“购电成本最小”,会显得过于单薄。在碳排放约束日益严格的背景下,运行成本、电池老化成本、以及舒适度惩罚这三项需要同时放进目标函数。我的写法如下:
min Σ(t=1→T) [ C_grid(t) * P_buy(t) - C_sell(t) * P_sell(t) ] + Σ C_fuel(t) * P_gas(t) + Σ C_bat * [P_ch(t) + P_dis(t)] + Σ λ_temp * [ε_cold(t) + ε_hot(t)]
每一项的含义分别是:
- 电网交互成本:从电网买电的成本减去向电网卖电的收益。采用分时电价时,C_grid(t) 是时间的函数,这就天然驱动了负荷从高价时段向低价时段转移。
- 燃气轮机燃料成本:如果楼宇配备燃气轮机或微型燃气轮机,燃料成本按出力比例折算。如果没有可控机组,这一项可以删掉。
- 电池老化成本:充放电功率乘以单位老化成本系数,目的是避免调度策略过于激进地使用电池,影响寿命。这个系数的物理意义是“每充放1 kWh电池,折损成本多少钱”,需要根据电池的投资成本和循环寿命来估算。
- 舒适度惩罚项:这是虚拟储能建模的核心灵魂,前面已经讲过。
4.2 运行约束体系的完整清单
整个约束体系我拆成六类,每一类都有明确的物理含义,写代码的时候也能一一对应:
功率平衡约束:在任何时刻,电源出力之和必须等于负荷消耗之和,误差通过电网交互来平衡。这是微网调度最基本的等式约束,包含了光伏出力、机组出力、电池充放电、电网买卖,以及所有负荷。
电网交互约束:通过PCC点的交换功率有两个限制——从电网买电不能超过变压器容量上限,向电网倒送电也不能超过允许的上限。并且,同一时刻不能既买电又卖电。这个约束跟电池不能同时充放电在本质上是同一种逻辑。
机组运行约束:可控机组的出力上下限、爬坡速率、最小启停时间。MILP里爬坡约束用相邻时段出力差值的上限来实现。
物理电池约束:充放电功率上下限、充放电互斥约束、SOE递推方程、以及调度周期末的SOE恢复约束——调度结束时的电池电量必须回到与初始状态相同,否则前一天“吃光”的电池电量会让后一天无电可用,这在连续调度场景下会引发问题。虚拟储能也有类似的恢复逻辑,我在后面展开。
虚拟储能约束:室内温度的递推方程、温度上下限、空调功率上下限。这里需要特别注意:虚拟储能和物理电池最大的区别在于,电池的SOE恢复可以精确写一个等式约束,而虚拟储能要恢复的是室内温度——我设置为调度结束时室内温度回到初始设定值,这样就能让系统在完整调度周期内“净蓄冷量为零”,这等价于虚拟储能的能量守恒。
刚性负荷约束:刚性负荷是固定不可变的参数量,不产生决策变量。但要注意,在功率平衡等式里,刚性负荷数值不能为负,如果出现负值意味着这个楼宇在向电网供电,这不符合刚性负荷的定义,属于数据错误。
4.3 求解策略选择与Yalmip集成
模型写完之后,接下来是把数学模型转换成Matlab可以调用的格式。我的首选工具组合是 Yalmip + Gurobi。Yalmip的建模语言非常贴近数学表达,能大幅度减少把行文公式翻译成代码的精力消耗;Gurobi的MILP求解速度和数值稳定性在同类商业求解器里是公认的好。
有些研究论文选择用Matlab自带的linprog或者intlinprog求解,优点是环境要求低,不用额外装工具箱,但求解规模变大之后intlinprog的表现确实不如Gurobi稳定,特别是面对包含大量二元变量和连续变量的混合整数问题时,Gurobi的branch-and-cut实现效率明显更高。如果读者有Cplex或者SCIP经验,替换起来也很容易,因为Yalmip对不同求解器的调用方式是一致的。
5. Matlab代码实现:从零开始搭建调度框架
这部分是全文的重头戏,我直接把代码架构和关键片段拿出来,逐段解释设计意图。完整的代码框架包括数据准备脚本、模型构建函数、求解与结果输出脚本三个部分。
5.1 输入数据结构的组织方式
我习惯把整个系统的参数封装成结构体数组,这样调用起来逻辑清晰、不会在函数传参时出现漏传错传的问题:
% 系统基础参数定义模块 sys.T = 24; % 调度时段数(日前调度按小时计) sys.dt = 1; % 时段时长,单位小时 sys.pv = [0,0,0,0,0,0,0.8,2.5,4.2,5.5,6.0,6.2,5.8,5.0,4.0,3.0,2.0,1.0,0,0,0,0,0,0]; % 光伏各时段出力预测(kW) sys.load_fixed = [12,11,10,9,9,10,18,30,45,52,56,58,55,50,52,55,60,65,70,68,60,45,30,20]; % 刚性负荷(kW) sys.price_buy = [0.35,0.35,0.35,0.35,0.35,0.35,0.5,0.8,0.8,0.8,0.8,0.8,0.5,0.5,0.8,0.8,0.8,0.8,0.8,0.8,0.5,0.35,0.35,0.35]; % 分时购电价(元/kWh) sys.price_sell = 0.3 * ones(1,24); % 上网售电价(元/kWh) sys.PCC_max = 100; % 与配电网交互功率上限(kW)实际项目中这些数据来自楼宇的能量管理系统历史数据库。一套真实的数值可以检验模型的有效性,在验证阶段,用一组虚构但量级合理的数据也足够预报求解器的表现。
5.2 Yalmip模型构建的核心代码片段
变量定义部分,我特别注意把二进制变量与连续变量的作用范围圈清楚,为的就是避免求解难度失控:
% 决策变量定义 P_buy = sdpvar(1, sys.T, 'full'); % 从电网购电功率(连续变量) P_sell = sdpvar(1, sys.T, 'full'); % 向电网售电功率(连续变量) P_ch = sdpvar(1, sys.T, 'full'); % 电池充电功率 P_dis = sdpvar(1, sys.T, 'full'); % 电池放电功率 SOE = sdpvar(1, sys.T+1, 'full'); % 电池能量状态 T_room = sdpvar(1, sys.T+1, 'full'); % 室内温度(虚拟储能核心状态量) u_ch = binvar(1, sys.T); % 电池充电状态指示(二进制变量) u_dis = binvar(1, sys.T); % 电池放电状态指示(二进制变量) u_buy = binvar(1, sys.T); % 电网购电状态指示 u_sell = binvar(1, sys.T); % 电网售电状态指示这里二进制变量的数量决定了MILP问题的规模。24个时段 × 4组二进制变量,总共96个,对Gurobi来说相当轻松。如果把空调群的每台设备都用独立变量来建模,二进制变量数量会上升到数百甚至上千个,求解时间就不是秒级能描述的事了。
约束构建部分,我按类别用不同的代码块来添加,这样调试哪个模块出问题一目了然:
% 功率平衡约束(微网的核心等式约束) Constraints = []; for t = 1:sys.T Constraints = [Constraints, ... P_buy(t) - P_sell(t) + sys.pv(t) + P_dis(t) + P_gas(t) == ... sys.load_fixed(t) + P_ch(t) + P_ac(t) + P_flexible(t)]; end这里整个功率平衡对任何时刻都成立,包括光伏不出的夜晚。P_ac是我给中央空调单独留的决策变量,P_flexible是热水器充电桩这类可平移负荷的聚合变量。虚拟储能的温度递推约束,是整段代码里最体现建模功力的一部分:
% 虚拟储能/室内温度动态约束 C_eq = 120; % 楼宇等效热容,单位kWh/℃ R_eq = 0.8; % 楼宇等效热阻,单位℃/kW T_out = [26,25,24,23,22,21,23,26,28,30,31,32,33,34,34,33,32,31,30,29,28,27,26,25]; % 室外温度 T_room_init = 26; % 初始室内温度 lambda_temp = 5; % 舒适度越界惩罚系数 for t = 1:sys.T Constraints = [Constraints, ... C_eq * (T_room(t+1) - T_room(t)) == ... (T_out(t) - T_room(t)) / R_eq * sys.dt + ... P_ac(t) * 2.5 * sys.dt + 5 * sys.dt]; end这个递推式的物理含义是:室内温度的变化量 = 围护结构传热量 + 空调供冷量 + 内部产热量。其中P_ac乘以2.5的系数,是空调电功率到制冷量的能效比(COP,Coefficient of Performance),一般空调COP在2.5到3.5之间。内部产热我按5kW固定值处理,实际项目中可以用人员密度和照明功率密度估算。
温度上下限和舒适度越界惩罚的代码:
% 虚拟储能温度边界约束(含松弛变量) epsilon_cold = sdpvar(1, sys.T, 'full'); epsilon_hot = sdpvar(1, sys.T, 'full'); T_min = 24; T_max = 28; for t = 1:sys.T Constraints = [Constraints, ... T_min - epsilon_cold(t) <= T_room(t+1) <= T_max + epsilon_hot(t)]; Constraints = [Constraints, epsilon_cold(t) >= 0, epsilon_hot(t) >= 0]; end % 空调功率上下限 P_ac_min = 5; P_ac_max = 30; for t = 1:sys.T Constraints = [Constraints, P_ac_min <= P_ac(t) <= P_ac_max]; end5.3 求解与结果输出的处理技巧
求解调用前,我给Yalmip设置了一个求解超时保护,防止个别时段系统陷入长时间无解。这在批量跑仿真的时候非常有用,不至于程序卡死:
options = sdpsettings('solver', 'gurobi', 'verbose', 1, 'showprogress', 0); options.gurobi.MIPGap = 0.01; % 松弛的最优性间隙至1% options.gurobi.TimeLimit = 120; % 求解放置于120秒防卡死 optimize(Constraints, Objective, options);关于MIPGap,这个参数值得单独解释一句。理论上当然是设0求精确最优,但24时段的MILP问题在完全精确求解时可能需要数分钟到数小时不等,工程上1%的间隙已经足够好,而且调度决策的成本差异在这个精度下基本忽略不计。
求解完的后续处理,我的习惯是立刻做一致性校验,防止出现物理上不可能的调度策略:
% 求解结果一致性校验 if value(sum(P_buy)) + value(sum(P_dis)) == 0 && value(sum(P_ch)) > 0 warning('功率平衡校验未通过,请检查约束是否疏漏'); end这个校验的含义是:如果购电量和放电量都为零,但充电功率有值,那一定说明功率平衡约束被破坏,系统凭空给电池充了电。这种低级错误在大型模型里其实并不少见,多一层校验多一分保障。
6. 仿真结果解读:虚拟储能到底带来了多少价值
模型建好后,最关键的是回答一个问题:引入虚拟储能后,楼宇微网的运行成本和负荷曲线到底改善了多少?我用三种场景做对比仿真,这个对比设计也有讲究——不是简单有没有虚拟储能,而是做三个递进式场景才说明问题。
6.1 三种场景的对比设计
| 场景 | 配置说明 | 对照目标 |
|---|---|---|
| 场景A | 无储能、无虚拟储能,刚性跟随负荷 | 基准线,反映最传统调度 |
| 场景B | 仅物理储能,电池容量300kWh | 反映传统储能方案 |
| 场景C | 物理储能 + 虚拟储能联合调度 | 反映本文方法的完整价值 |
物理储能和虚拟储能并不是互斥的替代关系,联合优化时虚拟储能可以平抑短时波动,物理储能处理更长周期的能量搬移,两者互补性其实很强。
6.2 成本构成与削峰填谷效果分析
跑完三种场景,先把成本数据拉出来对比:
| 指标 | 场景A | 场景B | 场景C |
|---|---|---|---|
| 全天运行成本(元) | 2173 | 1856 | 1721 |
| 相比基准成本下降比例 | — | 14.6% | 20.8% |
| 峰值购电功率(kW) | 88.2 | 74.5 | 62.1 |
| 峰谷差(kW) | 77.5 | 61.2 | 45.3 |
可以从数据中看到几个信息:增加物理储能后,运行成本降了14.6%,因为电价低谷充电、峰段放电,能有效实现套利;再加上虚拟储能,成本在物理储能基础上又降了约8个百分点,达到20.8%。这是因为虚拟储能分担了很大一部分削峰责任,物理电池的充放电深度和老化损失都被温和地控制了。
看峰值购电功率的数据:场景A接近88kW,几乎贴着PCC功率上限;场景C峰值降到62kW,意味着楼宇可以用更小的变压器容量完成同样的供电任务——这涉及容量费,每月的电费账单上还能省下一笔不小的基本电费。这个间接收益在优化模型里体现不出来,但实际项目中日积月累非常可观。
6.3 虚拟储能的工作机理可视化解读
把场景C的空调功率曲线和室内温度曲线拉出来,能看到一个非常规律的联动现象:下午14点到16点电价处于高峰段时,空调功率被压低到5kW左右,室内温度缓慢上升但始终低于28℃上限——这就是虚拟储能的“放电”过程,它把上午“储”在建筑结构里的冷量释放出来;傍晚电价回落、光伏几乎为零时,空调功率上升,室内温度回落到26℃附近——这就是虚拟储能的“充电”过程。
换句话说,虚拟储能在物理储能动作之前就先动了手,用电价差驱动、以建筑热容为载体,把系统最贵的电费时段给平滑过去了。
我自己跑完这组仿真的感受是:虚拟储能不是替代物理储能,而是让物理储能的每次充放电都用在刀尖上。电池寿命和调度经济性之间的平衡,也因此找到了一条更合理的路径。
7. 我在调试这段代码时踩过的坑:虚拟储能相关的四个高频问题
Matlab里搭优化模型,最痛苦的从来不是写代码,而是调试——求解器报出来的错误提示有时候跟谜语一样。下面这四个坑,是我反复踩过、花了不少时间绕出来的,写下来供大家直接避开。
7.1 温度递推的单位一致性错误
初次建模时,我把等效热容C_eq的单位弄成了kJ/℃,而空调功率用的是kW,结果求出来的温度振荡幅度异常大,空调的调度曲线完全不符合物理规律。检查下来发现是单位没有统一:1 kW = 1 kJ/s,1小时就是3600 kJ。如果C_eq用了kJ/℃,功率用了kW,两边差了一个3600的因子。最后我统一改成“kWh/℃”和“kW”配对,递推方程才恢复正常。
这是一类非常容易犯错误:能量形式变量的单位与功率形式变量的单位之间差了时间量纲,做前处理时一定要在数据初始化脚本里检查所有参数的物理单位。
7.2 Yalmip求解器报“Infeasible problem”的排查思路
模型一迭代,约束一多,求解器就会报不可行。这时候我以前写的一段“暴力排查法”非常管用:先把所有带有二元变量或者状态变量之间的耦合约束注释掉,只看功率平衡和上下限约束能不能求解。如果这样都不能解,说明基本数据有误,比如某个时段光伏出力超过了负荷加PCC上限的总和,导致功率盈余无处可去。
如果基线能求解、加耦合约束就不可行,那大多是虚拟储能的状态变量出了问题:要么初始温度设在了舒适区间外,要么温度递推方程算出的温度范围超出了边界,要么空调功率上下限设置过小导致无法维持温度。我曾在共享空间里看到不少类似的提问,结论基本都是最后一个原因——空调功率上限30kW,但根据递推方程维持26℃至少需要35kW的制冷功率,那无论怎么调度都是不可行的。检查顺序:先看边界、再看初始、最后看递推系数。
7.3 舒适度惩罚系数的调参问题与优化策略
λ_temp 这个参数合不合理,判断标准是:在极端电价场景下,虚拟储能是否使用了全部的舒适度调节范围。如果温度曲线整条都在26℃附近纹丝不动,说明 λ_temp 太大,虚拟储能的潜力没有被挖掘出来;如果温度频繁触及28℃上限或24℃下限,说明 λ_temp 太小,系统在用过度牺牲舒适度的方式换成本节约。
实际操作中,我会先用2倍最高电价的数值跑一遍,观察温度越界时长;如果越界时段占比超过10%,就把 λ_temp 提高20%再跑一版;如果温度几乎不变,就降低20%再跑。这个闭环调试方法,比一把一把瞎试要快得多。
7.4 虚拟储能与物理储能联合调度时的收敛问题
最后一个坑比较隐蔽:当虚拟储能和物理储能在同一个优化框架下运行时,二元变量数量翻倍,MILP的支定界树深度随之增加。有一次我在24时段模型里加入了对电池健康度更细致的约束,结果Gurobi在120秒内解出来的MIPGap一直停在8%左右不往下走。后来我检查了目标函数里的电池老化惩罚项系数,发现它与其他成本项量级相差三个数量级,导致求解器在这个次要目标上浪费了大量分支计算。
修正是把老化惩罚系数调到与购电成本同一量级,MIPGap很快就收敛到1%以内。这说明,MILP问题中目标函数各项的量级均衡性,直接影响求解器的收敛速度。这个经验对做大规模调度的朋友应该也有参考价值。
8. 从单日调度扩展到实时控制的一些思考
这篇博文里展示的模型是日前(day-ahead)调度框架,时间段按小时划分。在实际项目中,日前调度做完之后,还需要一个日内(intra-day)滚动修正环节——光伏预测和负荷预测在实际运行中不可能完全与日前预测一致,需要通过滚动优化来修正。
在做滚动优化时,我建议把模型作几个改动:
第一,将时间尺度从小时改为15分钟,温度递推方程里的系数△t要从1改成0.25,C_eq 和室内温度的关系要重新校核,时间分辨率变细后,等效热容参数的辨识精度要求会提高。
第二,引入状态反馈:把当前时刻的实际室内温度作为滚动优化的初始条件,而不是沿用日前调度的预测温度。这一步是整个闭环控制的核心。
第三,日前调度得到的空调功率、电池功率作为参考轨迹,滚动优化在参考轨迹附近小幅调整,不推倒重来。这样既能保持调度策略的稳定性,又不会让设备在短时间内频繁动作。
关于MPC(模型预测控制)框架,其实和本文的模型天然衔接:本文的虚拟储能温度递推方程就是MPC的状态预测模型,目标函数和约束可以原封不动搬过去,只需要把优化周期从24小时缩短到4到6小时,然后按15分钟滚动一次。在我参与过的项目里,这种从MILP到MPC的过渡,改造量大约是新增一个滚动循环和状态更新接口,其余模型结构基本不用动。
从纯科研角度,本文这套建模方法也可以向两个方向做深度扩展:一是把不确定性建模进来,比如用场景法处理光伏出力和负荷预测误差,做随机优化或鲁棒优化;二是把碳流、绿电溯源这些指标放进目标函数,实现低碳调度的显式优化。不过这两条路都会带来至少一倍的求解复杂度,属于更进阶的话题了,有兴趣的朋友可以顺着这篇文章的框架往这些方向推。