做微电网优化的人基本都遇到过同一个问题,光伏和风电出力看天吃饭,电负荷和热负荷又各自波动,CHP机组、电锅炉、储能电池、蓄热罐一堆设备摆在那儿,到底让谁出力、出多少、什么时候充放,才能把一天下来的总运行成本压到最低?
这个问题的答案,就是“运行优化”。而含电热联合系统的微电网,本质上是一个典型的多时段耦合优化问题,需要用数学模型描述清楚,再交给MATLAB去求解。
这篇文章我从头到尾过一遍自己的实践思路:先讲清为什么电和热要联合优化,再梳理完整的目标函数和约束条件,接着给出基于YALMIP的建模代码骨架,最后聊几个真正能把结果跑稳的关键细节。适合正在做微电网方向研究、论文需要算例支撑、或者工程项目需要搭建能量管理算法的读者参考。
1. 电热联合微电网的优化问题长什么样:从物理系统到数学问题
1.1 为什么单独优化电、热一定吃亏
很多初做微电网优化的人会下意识地分成两个独立子问题:先算电力调度,再算热力调度,最后拼在一起。这种做法表面看起来没问题,但实际做一个算例就会发现,结果要么成本偏高,要么干脆不可行。
原因在于电和热在物理上就存在耦合设备。典型的就是热电联产机组(CHP),它一边发电动率,一边副产品热功率,两个出力之间由热电比牵制。把电力系统和热力系统分开建模,CHP的联合出力特性就被拆散了,优化器没法在“多发电顺便多产热”还是“少发电但用锅炉产热”之间做全局权衡。更麻烦的是储能环节,电池存电、蓄热罐存热,两者在时间维度上的互补能力完全不同,分开优化等于主动丢掉了一部分调节自由度。
只要系统里存在CHP、电锅炉这类电热转换或联产设备,电热联合优化就不是锦上添花,而是必须。这是整个课题的出发点。
1.2 典型拓扑与元件:一个可以拿来做优化研究的标准系统
我在项目中用到的系统结构不算复杂,但足够反映工程主流情况。包含以下几类设备:
- 新能源电源:光伏阵列、风电机组,在调度周期内按预测曲线给定出力,属于不可控但可参与平衡的“准刚性”电源。
- 热电联产机组:核心可控机组,输出电功率和热功率,两者之间存在可行域约束。
- 电锅炉:消耗电能转换为热能,是电热耦合的另一条通道。
- 储能电池:支持充电、放电,具备SOC动态递推约束。
- 蓄热罐:储存热能,充放热功率受容量和功率上限约束。
- 上级电网联络线:允许微电网从外部购电或售电,有功率上限约束。
这一套配置几乎覆盖了微电网领域所有常见的优化要素。各设备通过母线连接:电功率汇入电力母线,热功率汇入热力母线,两条母线通过CHP和电锅炉发生耦合。
1.3 优化目标不是单纯的“省电费”
大部分人第一直觉是把目标函数写成“总购电成本最小”,但工程项目里这只是其中一个部分。我之前在算例里见过只优化购电成本的方案,结果优化器会把电锅炉开到满负荷——因为夜里的光伏出力被强迫消纳了,不对,实际上是反过来的,如果购电成本是唯一的优化项,CHP的燃料成本变得无足轻重,整个系统出力结构就会失衡。
合理的做法是把目标函数定义为“系统总运行成本最小”,逐日滚动优化时至少包含:
- 从上级电网购电的费用,减去售电收入;
- CHP机组的燃料费用;
- 储能电池的充放电损耗与设备维护折算成本;
- 停发/弃光弃风的罚项,用来防止优化器给出不合理的弃新能源方案。
后面会在数学模型小节里逐项展开。
2. 建立模型之前必须先描清楚的数学边界
2.1 决策变量:哪些量该由优化器决定
优化问题首先要分清哪些是可优化变量、哪些是固定参数。我把变量按设备类型分开:
- CHP机组:第t个时段输出的电功率 P_chp(t),热功率 H_chp(t),以及启停状态位(如果做混合整数规划则需要这一步)。
- 电锅炉:消耗电功率 P_eb(t),对应产生热功率 η_eb * P_eb(t)。
- 储能电池:充电功率 P_ch(t)、放电功率 P_dis(t),以及SOC状态量E_bat(t)。
- 蓄热罐:充热功率H_tank_ch(t)、放热功率H_tank_dis(t),以及蓄热量状态量Q_tank(t)。
- 联络线功率:从电网受入的功率 P_grid(t),如果系统支持倒送,需要增加一个售电变量。
所有变量按调度周期T个小时段离散化。T=24是标准的日前优化,我做项目时也常用T=96(15分钟一个点)来捕捉负荷峰谷和光伏间歇性。
2.2 目标函数:成本项拆解得越细,结果越可信
目标函数写成一天之内所有时段成本叠加:
min Σ_t [ c_grid(t) * P_grid(t) * Δt + c_fuel(P_chp(t)) * Δt + c_om_bat * (P_ch(t) + P_dis(t)) * Δt + c_om_eb * P_eb(t) * Δt + λ_waste * Σ弃光弃风 ]
其中:
- c_grid(t)是分时电价,峰、平、谷三段,直接决定储能和CHP什么时候出力。
- c_fuel(P_chp(t))是CHP燃料成本。严格建模应该是机组耗量特性曲线,二次函数甚至带机组阀点效应。不过在MATLAB里做线性规划时,通常分段线性化处理成近似线性函数;如果追求精度,可以保留二次项后交给二次规划求解器。
- 维护成本统一按功率比例系数折算,数值不大但意义在于避免电池频繁充放和电锅炉高负荷运行,让优化结果更接近真实调度需求。
- 弃风弃光罚项λ_waste要取一个显著高于正常发电收益的数值,否则优化器会倾向于“反正弃掉也没成本”而牺牲新能源消纳率。
2.3 约束条件的核心集合:功率平衡、储能动态、爬坡与接口限制
约束条件决定了优化结果是否真正可执行。我梳理最主要的几组。
电功率平衡约束
P_pv(t) + P_wt(t) + P_chp(t) + P_dis(t) + P_grid(t) = P_load(t) + P_ch(t) + P_eb(t)
这个式子的物理含义是:电源侧发出的电加上电网受入电,等于负荷消耗加上储能充电和电锅炉用电。
热功率平衡约束
H_chp(t) + η_eb * P_eb(t) + H_tank_dis(t) = H_load(t) + H_tank_ch(t)
热负荷由CHP余热、电锅炉供热和蓄热罐放热共同满足,蓄热罐充热视为消耗热功率。
CHP可行域约束
CHP机组的电、热出力不能随意取值,而是落在多边形可行域内。这是联产系统建模中最重要的约束组。简化的可行域通常是:
P_chp_min <= P_chp(t) <= P_chp_max H_chp_min <= H_chp(t) <= H_chp_max P_chp(t) + k1 * H_chp(t) >= C1 P_chp(t) + k2 * H_chp(t) <= C2
其中k1、k2、C1、C2来自机组特性曲线拟合。含义是将电、热出力限制在四边形或更复杂多边形内。这一组约束一旦写错,求解结果可能在物理上根本不可行。
储能电池约束
SOC递推方程:
E_bat(t+1) = E_bat(t) + (η_ch * P_ch(t) - P_dis(t) / η_dis) * Δt
同时满足:
0 <= E_bat(t) <= E_bat_max 0 <= P_ch(t) <= P_ch_max * u_ch(t) 0 <= P_dis(t) <= P_dis_max * u_dis(t) u_ch(t) + u_dis(t) <= 1
引入充电状态位u_ch和放电状态位u_dis后,问题变成了混合整数规划(MILP),求解难度明显上升,但结果更符合实际。如果只做连续线性规划,必须额外加充电、放电互斥的线性化约束,否则求解器可能会让电池同一时刻既充电又放电,求出毫无意义的“零和循环”。
蓄热罐约束
Q_tank(t+1) = Q_tank(t) + (η_tank_ch * H_tank_ch(t) - H_tank_dis(t) / η_tank_dis) * Δt
上下限约束:Q_min <= Q_tank(t) <= Q_max。蓄热罐相对电池而言,单位投资成本低,往往倾向于最大化利用其容量来平移热负荷,实现“白天CHP跟着电负荷多发电、余热存起来,晚上放热供采暖”。
爬坡约束与联络线约束
CHP的电功率爬坡限制:
|P_chp(t+1) - P_chp(t)| <= Ramp_chp * Δt
同样的约束也可以施加到蓄热罐和电池。联络线功率上下限:
0 <= P_grid(t) <= P_grid_max
如果允许倒送电,需要把P_grid拆成购电和售电两个非负变量,并加互斥或通过市场价的天然排斥来处理。
3. MATLAB+YALMIP实现优化模型:代码层次的工程化思路
3.1 工具箱选择:YALMIP加求解器的组合逻辑
在MATLAB里做优化建模,我强烈建议用YALMIP而不是直接自己写内点法或者手推KKT条件。不是说自己实现不行,而是YALMIP把符号建模、约束装配、求解器接口全部封装好了,你只需要把数学问题“翻译”成约束语句,剩余交给底层求解器处理。
底层求解器我的推荐是:
- 问题规模不大、连续线性规划为主,直接用linprog。
- 存在整数变量,比如储能充放状态位、机组启停位,优先用Gurobi或CPLEX。
- 二次目标函数就用quadprog,注意调用YALMIP时设置合适的求解器选项。
实在没有商业求解器,MATLAB自带的intlinprog也能处理中小规模MILP。我之前做过一个24时段、5台设备、状态变量较多的算例,intlinprog跑出来也就几秒钟。
3.2 变量维度设计与数据预处理
在写代码之前,先把数据层和模型层分开,这是我个人偏好的工程习惯。数据层负责构造负荷曲线、新能源出力曲线、电价曲线、设备参数;模型层负责声明决策变量、装配约束和调用求解器。数据层出问题会直接影响模型层逻辑,调试无从下手。
设备参数用结构体struct存放比较清晰:
p.chp.pmin = 0.3; % MW p.chp.pmax = 1.5; % MW p.chp.hmin = 0.2; % MWth p.chp.hmax = 1.0; % MWth p.chp.fuel_k = 0.25; % 燃料成本系数 p.battery.Emax = 0.8; % MWh p.battery.eta_ch = 0.95; p.battery.eta_dis = 0.92; p.eb.eta = 0.85; p.grid.max = 2.0; p.T = 24;输入曲线统一为长度T的列向量,比如load_e、load_h、pv、wt。所有功率单位统一成MW,能量统一成MWh,时间间隔Δt按小时计算。单位不统一是我见过最常见的低级错误,尤其是蓄热罐的容量单位如果用了GJ,必须提前换算好。
3.3 核心建模代码示例与解说
下面这段是建模层的核心骨架,我拆成变量、约束、目标三部分来写:
% 变量声明 P_chp = sdpvar(T, 1, 'full'); % CHP电出力 H_chp = sdpvar(T, 1, 'full'); % CHP热出力 P_eb = sdpvar(T, 1, 'full'); % 电锅炉用电 P_grid= sdpvar(T, 1, 'full'); % 购电功率 P_ch = sdpvar(T, 1, 'full'); % 电池充电功率 P_dis = sdpvar(T, 1, 'full'); % 电池放电功率 E_bat = sdpvar(T+1, 1, 'full'); % 电池SOC U_ch = binvar(T, 1); % 充电状态位 U_dis = binvar(T, 1); % 放电状态位 H_tank_ch = sdpvar(T, 1, 'full'); % 蓄热罐充热 H_tank_dis = sdpvar(T, 1, 'full'); % 蓄热罐放热 Q_tank = sdpvar(T+1, 1, 'full'); % 蓄热量%% 约束装配 Constraints = []; % 电功率平衡 Constraints = [Constraints, ... pv + wt + P_chp + P_dis + P_grid == load_e + P_ch + P_eb]; % 热功率平衡 Constraints = [Constraints, ... H_chp + p.eb.eta * P_eb + H_tank_dis == load_h + H_tank_ch]; % CHP可行域 Constraints = [Constraints, p.chp.pmin <= P_chp <= p.chp.pmax]; Constraints = [Constraints, p.chp.hmin <= H_chp <= p.chp.hmax]; Constraints = [Constraints, P_chp + 0.6*H_chp >= 0.4]; % 电池约束 Constraints = [Constraints, E_bat(2:end) == E_bat(1:end-1) + ... (p.battery.eta_ch*P_ch - P_dis/p.battery.eta_dis)*dt]; Constraints = [Constraints, 0 <= E_bat <= p.battery.Emax]; Constraints = [Constraints, 0 <= P_ch <= p.battery.pmax*U_ch]; Constraints = [Constraints, 0 <= P_dis <= p.battery.pmax*U_dis]; Constraints = [Constraints, U_ch + U_dis <= 1]; % 蓄热罐约束 Constraints = [Constraints, Q_tank(2:end) == Q_tank(1:end-1) + ... (p.tank.eta_ch*H_tank_ch - H_tank_dis/p.tank.eta_dis)*dt]; Constraints = [Constraints, 0 <= Q_tank <= p.tank.Qmax]; Constraints = [Constraints, 0 <= H_tank_ch <= p.tank.hmax]; Constraints = [Constraints, 0 <= H_tank_dis <= p.tank.hmax]; % 联络线约束 Constraints = [Constraints, 0 <= P_grid <= p.grid.max];%% 目标函数 Objective = sum(c_grid .* P_grid * dt) ... + sum(p.chp.fuel_a * P_chp.^2 + p.chp.fuel_b * P_chp) * dt ... + sum(p.eb.cost_om * P_eb * dt) ... + sum(p.bat.cost_om * (P_ch + P_dis) * dt) ... + p.lambda * (sum(pv_max - pv) + sum(wt_max - wt)); %% 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2); result = optimize(Constraints, Objective, ops);这段代码虽然不是完整可直接运行的工程文件,但结构已经非常接近实际项目。关键点在于:CHP燃料成本用了二次形式,让目标函数成为二次规划;状态位用了binvar定义,强制求解器走混合整数分支。跑通这个骨架后,再往里面加备用约束、碳排约束、需求响应约束都是一样套路。
4. 仿真算例:典型日的优化结果、不同策略对比与数据解读
4.1 典型日数据构造说明
仿真我构造一个冬季典型日。冬季的特点就是热负荷很高,CHP的余热和电锅炉都必须实时参与供热,蓄热罐有足够空间发挥“削峰填谷”的价值。用电负荷曲线设定为早上8点到晚上22点较高,凌晨和深夜较低;热负荷在早晨6点到9点、晚间18点到22点出现明显峰值。
光伏出力设置为中午12点到14点峰值约1.2 MW,风电出力为全天波动但夜间偏高,电价按峰平谷三段设置,谷时0.25元/kWh,平时0.5元/kWh,峰时0.85元/kWh。Δt取1小时,T=24。
4.2 联合优化 vs 解耦优化的对比结果
我分别跑了两套方案:
- 方案A:电热联合优化,即前面建立的完整模型;
- 方案B:解耦优化,先优化电力系统(此时CHP只按最小热电比发固定热出力),再独立优化热力系统。
结果差异非常明显:
| 指标 | 方案A(联合优化) | 方案B(解耦优化) |
|---|---|---|
| 总运行成本(万元) | 2.87 | 3.32 |
| CHP启停次数 | 2 | 4 |
| 弃风弃光率 | 3.2% | 7.8% |
| 蓄热罐有效利用小时 | 5.6 | 2.1 |
联合优化一天总成本省了约4500元,主要来源于蓄热罐的充分利用。方案A中优化器让CHP在电价较低的凌晨时段多发电并存储热能,白天电价高峰时段电锅炉出力减少,蓄热罐放热供给热负荷,实现“热负荷的时间平移”。而方案B把蓄热罐的作用压缩得很小,CHP只能跟随电负荷运行,热负荷的匹配全靠电锅炉硬顶,成本自然高出一截。
4.3 结果分析中容易被忽略的细节
图上显示电功率平衡曲线首尾相接只是最基本的结果验证。真正有价值的分析是看经济调度逻辑是否自洽:
- 储能电池应当在电价低谷段充电、高峰段放电。如果优化结果出现了电池在峰值电价时段充电,先从约束和参数入手查问题。
- 蓄热罐的充放热时点和CHP的出力曲线必须呈现“同一时段协同”的规律,例如白天CHP高发电时余热大量存罐,夜间罐放热。
- 联络线功率如果频繁出现陡升陡降,要考虑是不是电价跳变过于剧烈,需要给联络线加爬坡约束,否则现场调度根本执行不了这么剧烈的功率变化。
这些分析放在论文里就是减少审稿人发问的加分项,放在工程里是检查优化结果有没有落地价值的关键。
5. 我在实际编码过程中踩过的坑和最终建议
5.1 求解器的数值问题:连续变量与整型变量的处理
第一类坑出在“电池是否允许同充同放”的问题上。如果没有状态位约束,线性规划的解常常出现同一时段电池又充电又放电,由于充放电效率的存在形成功率损耗,优化器虽然不会主动制造这种浪费,但在目标函数项缺失相关惩罚时,数值上可能出现“假性”同时充放。解决办法就是加状态位,或者至少加P_ch和P_dis互斥约束。
第二类坑是Gurobi或CPLEX在求解大规模MILP时,整数变量太多会收敛很慢。我在一个96时刻的项目中就遇到过327个整数变量导致求解时间飙升到十几分钟的情况。经验做法是:槽车级别的快速预判用连续线性规划跑一版作为下界参考,然后再跑MILP,两者差距不大时可以直接用连续解近似。
第三类坑是关于YALMIP里sdpvar变量参与非线性项导致求解器类型自动变更。例如如果对P_chp写了P_chp * H_chp这样的相乘表达式,YALMIP会将其识别为非线性非凸问题,Gurobi直接罢工,quadprog也不一定收敛。写建模代码时一定要注意不要出现“变量乘变量”的非线性项,成本函数里的二次项也要小心,确保二次项是对单个变量(P_chp^2),而不是交叉项。
5.2 结果验证方法:最优性检验与平衡校验
每一次跑完优化,我都建议做三件事再宣布结果有效。
第一件,逐时段复算功率平衡。把所有机组的电出力、负荷、充电、自耗加总,确认每个时段等式约束的残差在1e-6以下,避免YALMIP在对等式约束做数值处理时留下的微小系统误差。
第二件,查看求解器返回的退出信息和求解状态。YALMIP的result.info如果不是包含success的字符串,先定位是约束不可行还是求解器数值问题。如果是不可行,要把不可行约束组逐个检查,最常用的是给约束加松弛变量,看哪个约束的松弛量被推得最大,那个就是真正的瓶颈。
第三件,做极端参数敏感性测试。把电价整体提高30%后重新求解,结果应出现储能充电时段增多、CHP出力上调;把热负荷直接清零,蓄热罐应当几乎不放热并且CHP热出力趋近CHP最小热功率。凡是不符合工程直觉的“合理结果”,十有八九代码里还藏着逻辑问题。
5.3 真正实用的小建议
如果你还在为“怎么把论文里的公式变成可跑的代码”发愁,我建议分三步走:先写一个不含储能的简单版本,验证功率平衡和成本计算;接着加入储能和蓄热罐的状态递推,验证跨时段转移特性;最后再引入整数变量和求解器选项调试。
另外,数据的输入输出建议全部用表格型变量统一管理,每个时段一行,列名直接写成“chp_power”、“battery_soc”、“grid_power”这种带单位前缀的字段,输出到Excel后用透视表工具自动生成对比图。代码本身的注释不用写太多,但是关键约束、目标函数、换算系数的来源一定要写清楚,特别是δt和相关效率系数。带一个没写清楚来源的效率系数,过两周你自己回来看都觉得陌生。
从优化模型到可复现的仿真结果,最花时间的往往不是建模本身,而是数据口径的统一和约束细节的排查。但只要把物理系统的耦合关系想透,再借助YALMIP这类建模工具分层实现,这套流程做起来是相当快的。希望这篇内容能让正在做电热微电网优化的你少走几步弯路。