ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

微电网多时间尺度经济调度:MATLAB/Simulink建模与求解器应用实践

微电网多时间尺度经济调度:MATLAB/Simulink建模与求解器应用实践 最近在做一个微电网的经济运行优化项目整套模型是在MATLAB/Simulink环境里搭的用Optimization Toolbox做求解。项目做完之后想把这个过程完整写出来因为断断续续有同行和朋友问多时间尺度调度到底怎么分层日前和日内模型该怎么衔接Optimization Toolbox里那么多求解器实际工程里到底选哪个这些问题靠看help文档很难快速找到答案踩坑之后我觉得还是值得整理一篇实操向的文章把思路、建模细节、代码骨架和调试经验都讲透。这篇文章不会太多讲理论推演重心放在“怎么在MATLAB/Simulink里把多时间尺度的微电网经济调度模型搭起来并跑通”。如果你是做电力系统、新能源调度方向的研究生或者正在做微电网项目的工程师又或者想用MATLAB做能量管理算法的人这篇内容应该能省下你不少摸索时间。我尽量把建模中非常容易踩坑的地方比如变量索引设计、约束矩阵组装、电池SOC回退、Simulink里调用优化函数的方式都用实际经验和代码片段讲清楚。1. 多时间尺度微电网优化到底在优化什么1.1 单层调度为什么不够用很多人第一版做微电网经济运行都是直接做一个24小时、15分钟或者1小时间隔的单层优化给定光伏、负荷、电价的预测曲线一次性求出一整天的柴油机出力、储能充放电功率、联络线购售电功率让总运行成本最小。这种单层开环方案在理想条件下没有问题但放到真实场景里就“绷不住”了。核心原因在于预测误差。光伏和负荷的预测结果尤其是提前24小时做出的预测误差通常能达到15%~30%。一个多云天气里光伏出力可能一小时前还是满发一小时后直接掉到20%这种情况下你按昨天做好的日前计划去执行要么储能该放的时候没电放要么柴油机来不及爬起来要么购电功率超过联络线限值整个调度计划流于形式。所以工程上需要把调度问题拆成不同时间尺度让每个决策都建立在尽可能新的信息之上。我最终采用的方案是两层结构日前调度层负责未来24小时的机组启停、与大电网交互电量的大方向分辨率取1小时日内滚动调整层负责未来4小时之内的精细化功率分配分辨率取15分钟每15分钟滚动一次只执行第一步。这个思路和模型预测控制MPC是同一个逻辑只不过目标从“跟踪参考值”换成了“优化运行成本”。1.2 两层框架如何分工两层优化各自管的事不一样约束和目标函数的侧重点也不同。日前调度层的职责是确定那些“变化慢、切换代价大”的决策量。比如柴油发电机组的启停组合一个机组今天启动几次、启动成本多少这种决策一旦定下来日内基本不会改动。储能虽然可以每天充放多次但日前也要先给出一条SOC参考轨迹让日内优化有个大致边界。日前层的目标函数里除了正常购电、燃料成本还必须考虑机组启动成本否则求解器会频繁启停机组来赚取“灵活性”实际运行中根本不可行。日内滚动调整层的职责是修正日前决策中因为预测误差导致的不合理部分。它拿到最新的光伏超短期预测、负荷超短期预测在保持机组启停状态不变的约束下重新优化储能、联络线和机组出力。这个优化域短、变量少求解速度快才能支撑15分钟一次的滚动更新。实时控制层也就是秒级到分钟级的一次调频、二次调频、下垂控制等不在本文讨论范围内但需要在Simulink仿真模型里做接口预留。Simulink在这个层面的价值就体现出来了把日内优化算出来的指令作为设定值接到电池、柴油机的动态模型上看实际响应能不能跟得上。1.3 为什么用MATLAB/Simulink这一套工具链做微电网优化可选的技术栈其实挺多Python里可以用Gurobi、PuLP、Pyomo做研究完全没问题。但我自己还是倾向于用MATLAB/Simulink Optimization Toolbox搭建尤其在项目偏工程落地的时候理由有几点。数据流顺畅。预测数据、负荷曲线、电价表从Excel或CSV导入MATLAB后用timetable和时间序列工具预处理非常顺手不需要额外的数据工程代码。算完结果后画Gantt图、功率堆叠图、SOC曲线也是几行代码的事。Simulink作为仿真验证环境可以和MATLAB脚本无缝互调省去Python和MATLAB之间倒数据的麻烦。Optimization Toolbox的求解器覆盖了微电网优化几乎全部需要的场景线性规划用linprog混合整数线性规划用intlinprog非线性甚至非凸问题用fmincon。虽然商用求解器Gurobi在混合整数规划上的大规模性能更好但微电网日内优化问题的规模一般远没到需要Gurobi的程度。以24个时段的日前调度为例变量数量通常在200~400之间intlinprog几秒钟就能稳定求解性能完全够用。还有一点很重要Simulink生态里做控制系统、电力电子的人很多后续如果要在RT-LAB或dSPACE上做硬件在环模型可以导出C代码或者FMU整个链路是通的。这一点对“论文结束就要交付项目”的工程师来说非常关键。2. 日前调度层建模方法与求解器选型2.1 目标函数怎么搭日前调度层的建模我建议从决策变量定义开始不要把变量直接在目标函数里铺开写后面约束矩阵会编到怀疑人生。假设日前调度周期有T个时段本文中T24每个时段1小时。决策变量包括柴油机出力P_dg(t)、储能充电功率P_ch(t)、储能放电功率P_dis(t)、联络线购电功率P_grid_buy(t)、联络线售电功率P_grid_sell(t)、弃光功率P_curtail(t)以及表示柴油机开机动作的0-1整数变量y_start(t)。如果你系统里有k台柴油机可以把P_dg展开成多个机组变量本文为了方便描述先用一台柴油机代表。目标函数可以拆成四块燃料成本。柴油机燃料成本通常可以用二次函数或者分段线性函数描述。如果直接用二次函数目标函数变成二次规划整个模型求解会慢一些工程上我更推荐把燃料成本做分段线性近似这样整个模型保持线性能用intlinprog求解。最小二乘拟合一条二次曲线后在运行区间内取若干断点做线性插值即可。购电成本减去售电收益。分时电价场景下在电价低谷买电、在电价高峰卖电这是微电网主要的经济来源之一。储能退化成本。很多文章里这个问题被忽略了但我实际算下来影响很大。电池每多吞吐1 kWh循环寿命就有损耗折算下来成本大概是0.3~0.7元/kWh这个量级在典型日成本里占比不低。计算公式很简单电池全生命周期可用吞吐量 额定容量 × 循环寿命 × 放电深度。比如一个1 MWh电池循环寿命6000次DOD按80%算可用吞吐量就是1400 MWh如果电池按照替换成本算每kWh约1000元单位吞吐量成本就是1000 × 装机容量 / 可用吞吐量折算到每kWh约0.71元。这个值加进目标函数后储能就不会出现高频次浅充浅放的“无意义动作”。弃光和弃负荷惩罚。这是对系统安全性和新能源消纳的政策性约束惩罚系数要设置得足够高让求解器在没有极端情况下不会主动选择弃光或切负荷。最终目标函数写成min Σ [ c_fuel(P_dg(t)) price_buy(t)×P_grid_buy(t) - price_sell(t)×P_grid_sell(t) c_degrad×(P_ch(t)P_dis(t)) c_curt×P_curtail(t) c_start×y_start(t) ]光看公式可能比较抽象后面第2.4节我会放一个实际算例的数值结果方便对照。2.2 约束条件的数学表达约束条件我按功能分几类每类单独说明。功率平衡约束是硬约束任何时刻系统内所有电源出力加上购电功率必须等于负荷加上储能充电和售电功率即P_dg(t) P_pv(t) P_wt(t) P_dis(t) P_grid_buy(t) P_load(t) P_ch(t) P_grid_sell(t) P_curtail(t)这里P_pv和P_wt是预测值之所以把P_curtail放在等式左边是因为弃光本质上是“应发未发”它不算电源出力只在允许的范围内调节。储能SOC动态约束SOC(t1) SOC(t) [ η_ch×P_ch(t) - P_dis(t)/η_dis ] × Δt / C_bat其中η_ch和η_dis分别是充放电效率C_bat是电池容量。这个约束是跨时段耦合的也是形成约束矩阵时最容易写错的地方。储能运行约束包括SOC上下限、充放电功率上限以及SOC初末值约束。SOC初值取实际值末值如果直接固定可能限制优化空间更科学的做法是把“SOC回到目标值”改成软约束通过目标函数加惩罚项来引导。这个细节会在第5章展开。柴油机约束包括出力上下限、爬坡约束、启停逻辑约束。启停逻辑必须引入整数变量y_start(t)表示“t时段是否发生启动动作”如果机组在t-1和t时刻都是停机那y_start(t)必为0如果从停机变为运行y_start(t)必为1。这类约束用大M法建模由y_start的0-1特性决定。联络线约束是购电功率和售电功率的上下限同时保证购电和售电不会同时为正。这里有两种做法加一个0-1变量或者用分时电价天然抑制同售同购。在实际代码里加0-1变量会显著增加求解时间我一般更倾向于不加整数变量而是通过把购售电功率拆成两个非负变量加上目标函数里购价高于售价的默认条件求解器自然会把其中一个压到零省掉整数变量后求解速度能快不少。但要注意如果出现购电电价反而低于售电价格的特殊场景这个方法就失效了。绝大多数实际运营中购电价永远高于售电价所以这种简化是合理的。2.3 linprog、intlinprog、fmincon怎么选Optimization Toolbox里三个求解器经常被混用实际选型逻辑其实很清晰。求解器适用问题类型优点缺点微电网里的典型用途linprog线性目标 线性约束求解速度极快几毫秒到几百毫秒不支持整数变量和非线性固定机组启停后的纯功率分配intlinprog混合整数线性目标 线性约束可表达机组启停、储能充放电互斥等逻辑变量规模增大时求解时间增长明显日前调度层主求解器fmincon非线性目标/约束可处理潮流方程、损耗非线性项非凸问题局部最优需要初值好配电网潮流耦合优化、损耗计算quadprog二次目标 线性约束比fmincon更稳定适合凸二次约束形态受限燃料成本二次函数直接优化实际项目里我的分工是日前调度层用intlinprog处理机组启停日内滚动调整层如果沿用日前给定的启停状态就把整数变量固定直接用linprog求解速度快一个量级如果遇到网络损耗、电压约束这些非线性问题才考虑fmincon。有个调包建议Optimization Toolbox的linprog在较新版本里默认算法已经是dual-simplex数值稳定性比老版本好很多我在求解大规模日内模型时几乎没有遇到过无解或数值异常的情况。但intlinprog在遇到整数变量比较多时可以限制求解时间比如设置MaxTime120秒让它在可接受的时间内返回次优解。对于日内调度来说一个次优但能在30秒内得到的结果往往比一个全局最优但要跑10分钟的结果更有工程价值。2.4 一个数值算例3MW光伏微电网的日前成本结构为了让模型更具体我直接放一个简化算例。假设某工业微电网系统参数如下光伏装机3MW储能1.5MW/3MWh柴油机2台单机容量1MW和0.6MW上级电网联络线限值2MW日最大负荷2.8MW。分时电价设置为峰时1.2元/kWh、平时0.75元/kWh、谷时0.38元/kWh。使用日前调度模型求解后典型晴天结果如下凌晨低谷时段电价便宜储能从SOC 30%开始充电同时微电网从电网购电满足负荷上午光伏大发柴油机基本停机储能开始充电消纳光伏避免向电网倒送功率下午光伏出力下降储能转为放电支撑晚高峰负荷晚间电价高峰柴油机启动一台加上储能放电压制购电功率在较低水平。优化结果中购电成本占比最大约60%柴油机燃料成本占约30%储能退化成本占约8%弃光和启停成本占2%左右。这组结果提醒我们一个容易忽略的事实很多文章把储能退化成本当成次要项忽略但从成本结构看它并不算小尤其是电池配置容量不大、循环频繁的场景忽略退化成本会导致储能被过度使用实际运营收益会显著低于模型预测。3. 日内滚动调整层预测误差下的实时修正3.1 滚动优化的基本逻辑日内层和日前层最大的区别在于两点预测域短、更新频率高。我把日内优化设定为每15分钟触发一次每次对未来4小时的16个时段优化决策结果只执行当前这15分钟到下一个触发时刻重新优化。这个滚动优化的“滚动”逻辑本质上就是模型预测控制。你可以把它理解成开车看导航导航会提前规划整段路线相当于日前计划但实际驾驶中每过一个路口导航会根据实时路况重新规划接下来一段路怎么走相当于日内滚动。你永远只执行“下一个路口怎么走”的建议而不是把半小时前规划的整段路线当圣旨。在代码层面实现滚动优化的方式并不复杂。主循环里维护一个当前时间指针每次循环从数据源读取最新光伏、负荷预测序列调用优化函数求解得到第一步控制量更新系统状态再把时间指针后移15分钟重复直到一天结束。3.2 预测校正的实现方式日内滚动能不能发挥作用很大程度上取决于预测数据质量。超短期光伏预测通常的做法是综合数值天气预报、辐照强度传感器实时数据和前几小时的历史出力数据用线性外推或者简单的机器学习模型给出未来4小时的出力曲线。我实际做的预测校正比较朴素就是用“比例缩减法”。光伏日前预测曲线给出的是全天每小时的预测值日内每15分钟滚动时根据最近15分钟的实际出力与日前预测的偏差比对接下来4小时的预测值做整体缩放。比如日光日给出的预测是100%满发但实际因为云层遮挡只有60%那就把未来4小时的光伏预测都乘以0.6。这个方法虽然粗糙但工程实现成本低效果也不错因为大气系统的惯性决定了未来1~4小时的光伏偏差比基本延续当前偏差。负荷预测校正同理只不过负荷变化相对平滑偏差比光伏小可以直接用最近几个15分钟的平均偏差做平移修正。3.3 与日前计划如何衔接日内滚动优化不是从零开始它必须继承日前层的结果。我在代码里做以下处理机组启停状态直接取自日前优化结果日内层不改变启停只优化“在线机组的出力”。SOC参考轨迹也来自日前结果但这不是硬约束只在目标函数里加一个SOC偏差惩罚项让滚动优化尽量靠近日前计划的储能轨迹但允许偏离。联络线功率的上下限、爬坡速率等参数同样沿用日前设定。这样设计的好处是两层模型之间形成“指导-修正”关系而不是相互对立。如果日内层完全自由它可能因为只看到未来4小时的情况而做出短视决策——比如在光伏预测偏高的时段多放电结果4小时后光伏预测下调储能电量又不够用了。加入参考轨迹惩罚能有效抑制这种短视行为。实际运行测试中我发现采用日内滚动修正之后相比直接用日前计划开环执行典型日的运行成本约节省了8%~12%。主要收益来自光伏预测误差的及时修正光伏被云遮挡时日内层提前降低储能放电功率、调整柴油机备用避免在高电价时段从电网买高价电。4. 在Simulink中落地从优化模型到仿真验证4.1 Simulink在优化工作中的定位很多人会问优化模型在MATLAB脚本里跑得好好的为什么还要搬到Simulink里这是个好问题。我的理解是Simulink做优化的角色不是“求解器”而是“被控对象仿真器和时序验证器”。优化算法算出的是功率指令而真实系统里电池不能瞬间响应柴油机有爬坡特性光伏逆变器有控制延迟。Simulink里把这些动态响应建出来可以验证优化指令在物理系统上执行的效果。如果只是做纯算法研究脚本模式足够不必上Simulink。但一旦项目涉及并网切换、储能控制策略、柴油发电机并机逻辑这些动态交互Simulink的价值就非常明显。我自己的项目里Simulink模型负责模拟整个微电网的动态过程优化算法被封装成一个函数在Simulink的定时触发子系统里周期调用。4.2 用MATLAB Function块封装优化逻辑在Simulink里调用Optimization Toolbox最顺手的做法是使用MATLAB Function块把优化函数封装成类似黑盒的模块。模型里的输入是当前SOC、最新光伏预测序列、负荷预测序列、电价序列输出是当前时段的控制指令。一个容易踩坑的细节是MATLAB Function块默认每个仿真步长都会执行一次。而我在仿真里设置的是定步长例如仿真步长10ms如果要每个控制周期15分钟才运行一次优化直接写在Function块里会卡到爆炸因为每10ms就跑一次intlinprog仿真根本走不动。解决方案是在Function块内部加一个“时间闸门”用Simulink的clock模块读仿真时间只有当当前仿真时间落入控制周期的整数倍时才调用优化函数否则直接输出上一周期的指令。具体实现时可以在MATLAB Function块里用persistent变量存储上一周期的控制指令和上次调用时间。4.3 和C代码生成、FMU导出的衔接Simulink对项目交付有一个非常有利的能力代码生成。如果控制算法要部署到实际控制器可以通过Simulink Coder直接把调度模型生成C代码烧录到嵌入式设备里。这个时候有一个坑要特别注意Optimization Toolbox的linprog和intlinprog是MATLAB解释环境下的函数代码生成支持并不完整。很多版本中linprog可以生成C代码但intlinprog不一定支持。所以如果项目目标是嵌入式部署更稳妥的路径是在开发阶段用Optimization Toolbox验证算法部署阶段把优化问题转换成嵌入式可执行的求解器例如用自带的quadprog生成代码或者集成第三方嵌入式QP求解器。如果项目只需要仿真验证可以导成FMU模型在Simulink、Dymola等不同仿真环境里联调这个流程和普通的Simulink FMU导出完全一致。4.4 一个典型的仿真验证流程我在项目中搭建的Simulink模型结构大致是数据源子系统从工作空间或CSV读取光伏、负荷、电价序列转成Simulink时间序列信号。优化调度子系统MATLAB Function块内部调用日前/日内优化函数输出控制指令。被控对象子系统封装电池一阶动态模型、柴油机惯性模型、电网接口模型。监视与记录子系统用Scope记录功率曲线、SOC曲线用To Workspace输出数据供MATLAB脚本后处理。整个仿真的运行逻辑是仿真启动后优化调度子系统在t0时刻先跑一次日前调度获得全天计划随后每15分钟触发一次日内滚动优化更新控制指令被控对象子系统根据指令动态响应仿真结束后从工作空间取出结果在MATLAB脚本里画图分析。这套流程跑通之后后续做“硬件在环”或者“策略对比”都非常灵活只需要替换被控对象子系统的模型精度或者把数据源从“历史数据”换成“实时采集数据”。5. 常见问题与调参经验实录5.1 约束矩阵维度错误这是初学时期最常遇到的问题报错往往是“A must have the same number of columns as f”或者“Index exceeds array bounds”。根因在于变量索引对不上。我的经验是所有矩阵构建都用命名索引不要在代码里写魔法常数。例如变量按块排列后定义T 24; nVar 6 * T; idx.dg (1:T); idx.ch (T1:2*T); idx.dis (2*T1:3*T); idx.gb (3*T1:4*T); idx.gs (4*T1:5*T); idx.curt (5*T1:6*T);写每一条约束时都通过idx来引用变量这样即使后面增加了新的决策变量块也不容易改了某一处忘改另一处。我再强调一句这个习惯在项目稍微复杂一点后节省的调试时间远远超过写哪一行“1:24”之类的直白代码。另外常见错误是SOC动态约束写出来是循环的但对应到线性约束矩阵时上下行索引算错。建议先把约束矩阵在纸上画出结构再写代码尤其是跨时段约束在矩阵里呈现为“对角线副对角线”的形态写紧凑格式时要特别小心。5.2 电池同时充放电的数值陷阱如果目标函数里储能退化成本是正数同时充电功率和放电功率又是两个独立的非负变量求解器会不会为了套利而让电池在同一个时段既充电又放电答案是在目标函数里只要充放电单价不同就有可能出现这种异常。举个极端例子某时刻电价波动剧烈求解器发现“先买电充入电池再立刻放出卖给电网”可以在账面获得价差收益而储能退化成本如果没设置到位它就会疯狂制造这种虚假套利。结果SOC曲线几乎不动充放电功率却很大完全不符合物理直觉。解决思路有两个一是在目标函数里给充放电功率加一个足够大的互斥惩罚让同时充放电变得无利可图二是直接引入0-1互斥变量但会拖慢求解速度。我的做法是优先用惩罚项只有当惩罚系数需要设得非常大、影响数值稳定性时才换用整数互斥。验算方法也简单把求解结果里每个时段的P_ch和P_dis画在一张图上如果存在同一个时段两根柱都为正说明惩罚项没起作用或者目标函数系数设计有问题。这一步检查我建议每次跑完模型都做一遍别省。5.3 SOC末端回退导致次日无解日前求解时如果强制要求SOC_末 SOC_初某些极端场景下会无解特别是储能容量小、晚高峰又长的时候SOC会被顶到下限根本回不到初始值。我把末值约束改成软约束在目标函数里加一个“SOC末端偏离惩罚”项penalty w × (SOC(T) - SOC_target)^2但加了平方项整个优化问题就从线性变成二次再用linprog就不行了。如果你的场景用linprog可以改成绝对值线性化引入一个非负变量soc_dev让SOC(T) - SOC_target soc_devSOC_target - SOC(T) soc_dev然后在目标函数里加w × soc_dev。这样既保持了线性约束又实现了对末端SOC偏离的惩罚。实际调w的时候要小心w太小SOC可能偏离目标很远影响下一日运行w太大又等同于硬约束。我一般会做几次扫参观察SOC末端和总成本之间的权衡曲线取一个拐点处的值。5.4 Simulink仿真速度太慢的排查思路Simulink里跑优化模型仿真速度慢通常是三种原因。一是前面提到的时间闸门没做好优化函数在每个仿真步长都被调用。解决方式是在MATLAB Function块里用persistent变量记录上次调用时间并设置阈值。二是被控对象模型步长太短。如果只是为了验证优化调度策略电池模型和柴油机用一阶惯性环节足够了没必要用非常精细的电力电子开关模型。把电力电子细节留给后续专项仿真调度层验证模型尽量轻量化。三是intlinprog在大模型中计算太慢。日内层优化通常变量少、无整数用linprog就好。如果日前层也嫌慢可以先固定机组启停试算一次linprog把结果作为intlinprog的初始可行解传入能明显缩短求解时间。5.5 数据导入与时间序列处理的常见问题项目中一定会遇到数据导入问题比如从CSV读光伏出力、负荷数据然后参与仿真。MATLAB里我推荐用readtable把数据读进来再用timetable统一管理时间序列这样在滚动优化里按时间窗口截取预测子序列会非常方便。如果类似“把CSV导入MATLAB之后做FFT”这样偏信号处理的需求其实和微电网调度是两回事。但注意一点任何数据进入优化模型之前都要先做时间轴对齐。光伏预测数据可能是整点、15分钟或者5分钟一个点负荷数据可能来自不同的数据源如果不做插值和重采样优化模型里按索引取数时很容易错位。我一般统一用retime把数据重采样到优化分辨率然后再参与运算。写在最后的调试心得这套模型从初版到稳定运行我迭代了三个版本。第一个版本把所有东西都写在一个大脚本里能跑但每次改约束都提心吊胆第二个版本把日前和日内拆开已经能出结果但Simulink联调卡了很久第三个版本才把变量索引、约束矩阵构建、滚动循环、Simulink时序控制全部规范化效率才真正起飞。我的体会是这种优化模型最难的不是数学公式而是代码结构和调试纪律。每次跑出异常结果先别急着怀疑求解器按顺序查三个地方约束矩阵有没有拼错、目标函数系数符号有没有搞反、输入数据有没有对齐。这三类问题的排查做完80%的坑都能避开。最后再分享一个辅助工具心得如果用AI编程助手或者优化求解器的新版本不要直接照搬生成的约束矩阵一定要理解每一条约束对应的物理含义画出矩阵结构做交叉验证。算出来的结果、尤其是低成本方案一定要回到物理场景里问一句“这合理吗”这个习惯能帮你挡掉很多潜在的数据和模型错误。
返回列表