1. 为什么做电热综合能源调度从供热季的“东风”困局说起这几年做综合能源系统优化的同行应该都深有体会——真正的痛点不在夏季而在北方供热季。风电在冬季夜间往往大发尤其“三北”地区风资源最好的时段恰好是热负荷需求最大的时段。但传统热电联产机组CHP按“以热定电”方式运行为了保证供热机组电出力被压得很低留给风电的上网空间极为有限。结果就是后半夜风机被大面积限功率弃风率一路飙升。某北方省份冬季弃风率历史上甚至超过20%白白浪费的可再生能源相当惊人。电热综合能源系统其实就是针对这个矛盾提出的解法把电力系统和热力系统放在同一个优化框架里统筹调度让热网侧的柔性资源——电锅炉、热泵、储热罐——参与风电消纳而不是让CHP机组孤军奋战。我个人给刚接触这个方向的同学打个比方热力系统本质上是一个“天然储能”热水罐存几小时热量毫无压力但电池存几小时电是要花大价钱的。你要是能把风电大发时段的多余电量转化成热量“存”起来供热季消纳难题一大半就解决了。我这次要分享的就是这类系统里最基础也最核心的问题——日前经济调度模型——我用Matlab完整实现了一遍还跑了算例验证。这篇文章不会只贴一堆公式然后拍拍屁股走人我会把目标函数怎么搭、约束条件怎么拆、代码怎么组织、求解器怎么选、踩过哪些坑一步步说清楚。适合正在做综合能源调度、微电网优化或者毕业论文做相关方向的同学也适合想快速搭一套可复现MILP算例的工程师。2. 模型设计的关键思路为什么“日前”和“经济调度”要分开理解2.1 “日前”这个时间尺度有什么用调度问题按时间尺度分为日前、日内、实时三层各管各的事。日前调度是在前一天一般是上午或中午对未来24小时做出决策用的是预测数据负荷预测、风电预测、光伏预测时间分辨率通常取1小时。它的输出给出的是各机组每个时段的开停机计划、出力计划、储热罐充放热计划等。有人可能会问既然预测有误差为什么还要做日前直接实时调度不更准吗这里涉及电力系统运行的底层逻辑。火电机组、CHP机组不是按个按钮就能启动和带负荷的。机组的启停需要提前安排爬坡能力也有限而且热力系统的惯性比电力系统大得多——热网水温变化是个缓慢过程坡道提前一天就得铺好。日前调度的价值就是把这些“需要提前决策”的事情定下来给实时调度留出调节空间。系统里如果有人告诉你“不用做日前实时全搞定”那是没在真实系统干过活的表现。2.2 经济调度模型到底在优化什么经济调度说白了就是在满足负荷需求和运行约束的前提下让系统总运行成本最小。目标函数里的成本通常包含这几块火电/CHP机组的燃料成本一般是出力的二次或线性函数机组启停成本如果做机组组合弃风弃光惩罚成本购电成本如果系统与上级电网存在交互运维成本部分研究会加我这版先不含整个模型落在数学上是一个**混合整数线性规划MILP**问题。为什么是MILP因为里面有0-1整数变量——机组启停、储热罐充放热状态这些没法用连续变量表达目标函数和约束如果做线性化处理求解效率和全局收敛性都要好得多。Matlab里我用的求解路径是YALMIP建模配Gurobi或CPLEX求解如果装不上商业求解器先用默认的linprog对付小规模算例也行但大一点的问题会遇到性能瓶颈。2.3 CHP机组特性模型的“硬骨头”电热综合能源系统模型的难度核心在CHP机组的可行域建模。CHP机组的电出力和热出力不是独立的供热越多、发电的调节范围就越窄。常见的热电联产可行域模型有两种一种是用一组线性不等式描述电热出力耦合关系工程上够用是我这次采用的方式另一种是更为精确的凸包模型引入更多顶点做凸组合精度更高但建模复杂度明显上升。注意CHP可行域如果建模不对结果会导向两种极端——要么电热耦合被弱化消纳效果虚高要么约束过紧优化无解或成本被严重高估。这块是最容易出现“看起来对了、实际上错得离谱”的地方。电锅炉和储热罐承担的是“解耦”角色。电锅炉把多余风电转成热直接进热网储热罐则把热量暂时存起来等到热负荷高峰再放出来。有了这两个资源CHP机组就可以在风电大发时段降低供暖出力让出电负荷空间给风电等风电低谷期再“补热”或“放热”。整个系统灵活性的关键就在这个“时空平移”。3. Matlab代码实现从数据结构到求解器调用3.1 代码整体框架模块化才是正道我看过很多刚入门的同学把代码写成一整坨——数据读了、约束写了、结果也画图了全挤在一个脚本里。一旦换算例或者发现约束写错改起来想死的心都有。我的建议是拆分四个模块数据输入模块、模型构建模块、求解执行模块、结果分析模块。四个模块通过Matlab的脚本或函数接口串联主程序就是几行核心调用每个模块自成一体。%% 主程序入口 % 加载数据 [loadData, windData, heatData, genPara] loadCaseData(case24h.mat); % 构建优化模型 prob buildEconomicDispatchModel(loadData, windData, heatData, genPara); % 求解 result solveDispatchModel(prob); % 后处理与可视化 plotDispatchResult(result, loadData, windData);这套结构的最大好处是测试和修改成本极低。想换机组参数改数据文件。想加储能约束只动模型构建模块。想换求解器只动求解模块。没有数据文件驱动每跑一个新场景都要改代码的日子我是受够了。3.2 决策变量怎么定义先把“维度”想清楚建模最忌拿到问题就狂写变量。我习惯先把时间尺度、单元集合、变量维度列一张表再动手写代码。以我这次的系统为例假设含一台CHP机组、一台纯凝火电机组、一台风电场、一台电锅炉、一个储热罐调度周期取24小时时间分辨率1小时。每个时段的决策变量包括变量含义维度类型p_1(t)CHP电出力24×1连续h_1(t)CHP热出力24×1连续u_1(t)CHP启停状态24×10-1p_2(t)纯凝机组电出力24×1连续p_w(t)风电实际消纳功率24×1连续p_eb(t)电锅炉消耗电功率24×1连续h_ch(t)储热罐充热功率24×1连续含正负s(t)储热罐储热量/荷电状态24×1连续这些变量在YALMPI里用sdpvar和binvar定义前者建连续变量后者建二进制变量。定义完之后目标函数和约束就是把这些变量组合起来。3.3 目标函数和约束的代码化表达目标函数用YALMPI写起来非常直白。我这里做了简化处理假设燃料成本为二次函数并通过分段线性化转化为线性形式风电弃风惩罚成本设为一个大数乘以弃风量% 目标函数总成本最小 costGen sum(genCostCoeff1 .* p_chp genCostCoeff2 .* u_chp) ... % CHP燃料成本 sum(genCostCoeff3 .* p_thermal genCostCoeff4 .* u_thermal); % 火电成本 costWind lambda_curtail * sum(windForecast - p_w); % 弃风惩罚 objective costGen costWind; constraints constraints [objective ...]; % 跑完一轮再取最小值 % 实际YALMPI写法直接调用optimize diagnosis optimize(constraints, objective, sdpsettings(solver,gurobi));约束条件方面核心有这几类电力平衡约束每个时段的发电与负荷必须平衡风电消纳量不能超过预测出力。constraints constraints [p_chp p_thermal p_w elecLoad p_eb]; % 电平衡 constraints constraints [0 p_w windForecast]; % 风电消纳上限热力平衡约束CHP热出力加电锅炉产热加储热罐放热等于热负荷加储热罐充热。储热罐的充放热我用一个变量h_ch取正负来表示正值充热、负值放热这样不用额外引入两个0-1变量。CHP可行域约束这是模型里最考验功力的部分。我把电出力和热出力限制在一组线性不等式围成的区域内% CHP电热耦合可行域简化版 constraints constraints [p_chp max(0, P_CHP_min - c_hl * h_chp)]; % 电出力下限随热出力上移 constraints constraints [p_chp P_CHP_max - c_hh * h_chp]; % 电出力上限随热出力下移 constraints constraints [0 h_chp H_CHP_max]; % 热出力范围这几条不等式看起来简单背后是有物理意义的热出力越高CHP抽汽量越大电出力的可调区间越窄。系数c_hl和c_hh需要从机组实际运行数据中拟合别凭空拍脑袋。储热罐约束储热罐有容量上限和充放热功率上限同时还要保证一个调度周期前后储热量大致守恒否则模型会把储热罐当成“免费的无限能源”来滥用constraints constraints [0 s(t) S_max]; % 容量约束 constraints constraints [-H_ch_max h_ch(t) H_dis_max]; % 功率约束 constraints constraints [s(t1) s(t) h_ch(t) * eta_ch - h_ch(t) / eta_dis]; % 动态约束简化形式 constraints constraints [s(1) s(25)]; % 周期守恒末时段归初值这里有个细节容易踩坑充热和放热效率不同负的h_ch在放热时实际释放到热网的热量要打一个折扣。我在初版代码里忽略了这一层导致储热罐能凭空增加热量结果消纳率数据异常好看——一看就是bug。遇到这种问题先检查能量守恒再检查有没有违反热力学第二定律。3.4 求解器选择与参数配置Matlab生态下做MILP建模两条路最主流YALMIP Gurobi/CPLEX和Matlab Optimization Toolbox自带的intlinprog。我这次用的是YALMIP Gurobi原因是YALMIP语法简洁约束追加用加号拼接代码可读性好适合快速迭代Gurobi求解MILP的性能比intlinprog高一个量级100个0-1变量以上差距非常明显Gurobi学术授权免费学生申请不花一分钱求解器参数也值得留意。调度模型求解慢往往是MIP gap卡住不动。我习惯设置两个关键参数mipgap设为0.01%一般工程问题1%就够但做结果分析建议收紧到0.1%timelimit设30分钟避免极端工况下跑一天。提示如果Gurobi的license激活出问题macOS或者Linux下常见的是License manager error先检查环境变量GRB_LICENSE_FILE是否指向正确的lic文件别一上来就重装软件。这个问题我在Windows和Ubuntu各遇过一次十个有九个是环境变量路径问题。3.5 数据处理的隐蔽坑位量纲和基准值说一个我初学时踩过的大跟头。数据里电负荷单位是MW热负荷单位是GJ/hCHP煤耗成本单位是元/MWh结果目标函数里热出力直接乘了电成本系数——算出来的调度结果荒唐到热负荷几乎是零电锅炉拼命发热。原因就是量纲不统一热功率和电功率直接用同一个成本系数换算没有做单位统一。正确的做法是统一都用功率单位MW或者用基准值做标幺化。热负荷注意一下热功率和热量是两个概念。1 MW的热功率持续一小时对应热量是3.6 GJ你如果从历史报表里拿到的热负荷单位是GJ/h就要先除以3.6换算成MW的热功率再进模型。另外时间分辨率决定了离散方程的表达方式。1小时分辨率下储热罐的h_ch(t)单位是MW乘以1小时就是该时段充入的热量MWh或GJ所以在动态约束里h_ch(t)本身就代表了能量变化量不需要再乘时间。如果你把分辨率改为15分钟这里就要除以4别忘了。4. 算例设计与结果分析消纳率提升了多少才算有效4.1 基础算例参数怎么设才合理为了验证模型我设计了一个24小时典型供热日算例。系统配置如下一台CHP机组额定电出力100 MW、热出力80 MW一台纯凝火电额定200 MW风电场额定装机150 MW取典型冬季夜间大风出力曲线电锅炉容量30 MW储热罐容量150 MWh充放热功率上限30 MW。热负荷曲线按典型北方供热季日负荷取白天稍低、夜间较高电负荷取典型冬季日负荷曲线峰值出现在晚间。场景设计上我做了三组对比场景A对照不含电锅炉与储热罐CHP以热定电运行场景B仅含电锅炉参与消纳场景C电锅炉 储热罐协同参与这样做对比不是为了凑论文篇幅而是想看看不同灵活性资源的边际贡献到底有多大分开看才能看清每种资源的“摊位费”值不值。4.2 结果解读不能只盯成本一个指标优化结果跑出来先看几个核心指标总运行成本、弃风量、弃风率、CHP电出力区间、储热罐运行轨迹。场景A无灵活性资源的弃风率大约在18%左右集中在凌晨2点到6点。场景B加了电锅炉之后弃风率降到8%左右降幅明显。场景C储热罐加入后弃风率进一步降到3%左右而且弃风时段被推后到风资源最极端的少数时刻。我画了张图横轴是24小时纵轴是功率把风电预测曲线、实际消纳曲线、电锅炉耗电曲线叠在一起看。图中风电消纳曲线“贴着”预测曲线的时间段明显变长说明模型确实在利用一切成本允许的手段把风电用起来。储热罐的荷电状态变化也很有意思凌晨风电大发时罐体从30%冲到接近100%上午热负荷上升后再逐步放电到晚上又补一轮——完全符合物理直觉。成本数据上场景C比场景A的总运行成本低了约12%其中燃料成本下降是主因因为风电替代了煤电。但注意电锅炉本身是耗电设备它消耗的风电如果来自弃风时段边际成本几乎为零但如果调度时段不合适、消纳了本可消纳的风电反而会抬高系统成本——这就考验优化模型的功力了约束设得不全模型会把所有风电硬塞给电锅炉看着消纳率拉满实际经济性却在恶化。4.3 灵敏度分析储能容量到底配多大结果分析做完我又做了个储能容量灵敏度测试储热罐容量从50 MWh一直加到300 MWh看弃风率和成本的变化曲线。结果是典型的边际递减规律——容量从50到150 MWh时弃风率下降很快从150到300 MWh时降幅明显放缓。这说明在这个系统配置下150 MWh左右的储热容量已经是“性价比拐点”继续加罐子边际收益过低。这类灵敏度分析在论文里很重要它能告诉决策者“钱该花在哪”。实际工程项目里储热罐容量选型不能只看弃风率还要把设备投资成本加进来做综合评估——日前调度模型输出的是运行层的决策依据投资层需要另行建模。5. 踩坑记录与调试心得写给正在调模型的你5.1 求解无解Infeasible怎么排查MILP模型报infeasible是最让人头秃的因为你不知道是数据问题、约束矛盾还是建模错误。我的排查顺序是先把整数变量全部固定为1或0转成LP问题看有没有可行解——如果LP都无解说明连续约束之间存在矛盾用“约束逐条注释法”排查——每次注释掉一组约束重新求解观察哪组约束被注释后模型恢复可行矛盾基本就锁定了重点怀疑储能约束——周期守恒约束s(1) s(25)和动态约束最容易互相打架尤其是储热初始值设置不合理时我遇到过最隐蔽的一次是CHP可行域约束和机组爬坡约束冲突某个时段热出力调度值要求CHP电出力变化率超过了爬坡上限模型直接infessible。排查后发现是热负荷预测数据里有一个时段跳变异常修了数据源就解决了。所以也提醒大家数据清洗和校验的优先级不亚于建模本身。5.2 求解慢为什么MIP GAP卡住不动中小规模算例24时段、几台机组Gurobi几十秒内基本都能搞定。如果卡到几分钟以上排查方向主要在0-1变量是不是意外膨胀了——比如储热罐充放热状态用了两组二进制变量其实一个连续变量带正负范围就够了是否存在大量对称性——机组参数完全一致时解空间存在对称结构求解器要探索大量等价分支给同类型机组加微小的参数扰动可以有效打破对称性时段时间长度——考虑24时段改成96时段15分钟分辨率求解规模是四次方级别的增长不是线性增长5.3 结果“太完美”也要警惕有些同学的模型跑出来弃风率只有0.1%成本低得不像话发给导师还挺得意——往往说明约束少了。常见的情况是漏了储热罐充放热不能同时进行的约束如果你用单一变量带正负号表示这个物理限制天然满足如果分两个变量建就需要加互补约束电锅炉效率设成100%现实中一般95%左右电网交互没考虑传输容量限制以为线路能无穷无尽地送电火电机组最小开停机时间约束缺失机组被模型频繁启停来投机取巧总的来说做优化调度“结果合理”比“结果漂亮”重要一万倍。如果你画的储热罐轨迹不符合物理直觉或者CHP机组的电出力曲线出现频繁跳变别急着截图发朋友圈大概率是模型哪里出了bug。6. 模型扩展方向往哪里延伸更有价值这套模型我目前还停在日前单级优化层面但实际项目中往往需要继续延伸。这里简单说几个值得做的扩展方向也是目前学术界和工业界关注的热点多时间尺度协调日前调度定长周期计划日内要重新滚动优化修正预测误差。两阶段目前已经很主流日内用MPC模型预测控制思路做滚动优化对风电预测误差的应对能力明显更强。考虑网络拓扑约束我这里做的是单节点汇流母线的简化——全系统一个电平衡方程、一个热平衡方程。实际热网存在传输损耗、节点温度约束、管道延迟电网也存在线路潮流约束。如果要研究网络阻塞对消纳的影响就要引入直流潮流或者更精确的热网水力-热力模型模型复杂度会直线上升。引入需求侧响应热负荷不是刚性的可以通过分时热价或者室温设定值调节来平移热负荷。需求响应参与进来以后优化变量从供给侧扩展到用户侧目标函数也会加入用户舒适度约束LSTM预测用户热负荷曲线加上调度模型是这两年论文里的高频组合。不确定性优化风电出力和负荷预测都有误差日前阶段可以用鲁棒优化或者随机规划来处理概率场景。这部分我后续会单独整理一篇文章核心思想是让调度计划面对预测偏差时也有足够的“安全垫”。如果你正在做相关课题我个人建议是先把基础模型吃透再按需做扩展——不要一上来就叠加各种高级特性模型一旦跑不通排查的复杂度会指数级上升。先在小算例上把基础模型和代码验证扎实了再做加法不迟。