
1. 这次调度问题背后要解决的是什么微电网做日前经济调度很多人第一反应就是“找一个最低成本计划”把负荷、风电、光伏、储能和柴油机摆在一起算24个小时的出力就完了。真上手做“基于风光储能和需求响应的微电网日前经济调度”时你会发现事情远没有这么简单风电和光伏的出力是波动的储能SOC会跨时段累积需求响应又牵扯到用户侧的真实响应意愿这些变量之间互相耦合任何一个环节建模不当Matlab代码跑出来的结果都会“看似合理、实则不可用”。我自己是在一个偏远地区园区微电网项目里做优化调度方案时切入这个课题的。那个园区原来用的是柴油发电机白天负荷小晚上负荷反而大光伏和风电装好之后情况更混乱——天气好的时候中午光伏顶着负荷柴油机白转晚上风机发电多却没人要只好弃风。我们接手后发现单纯加储能不能解决问题必须把“可削减负荷”和“可转移负荷”这些需求响应资源一起放进调度模型通过日前计划把明天的经济运行方式提前卡好。这里说的“日前调度”本质上是在已知明天24小时负荷预测、风电光伏出力预测和分时电价的基础上求解一组“明天每台机组何时启停、发多少出力、储能何时充放、哪些用户负荷要调节”的决策变量让全天总运行成本最低同时保证供电可靠。相比实时调度日前调度有更充足的计算时间可以反复比较方案也可以把比较重的MILP问题算到比较优的解但它的难点在于预测误差会在执行过程中暴露所以模型里必须给储能和需求响应留出足够调节空间。对于初学者我建议先把物理对象拆开看微电网里有哪些可控设备、哪些不可控电源、哪些调节手段。可控设备包括柴油机、微型燃气轮机、储能不可控电源包括风电、光伏调节手段除了机组出力之外就是需求响应和储能的充放电。日前经济调度的核心任务就是在这三组对象之间做协调。把这句话想清楚后面建模、写代码、调参才不会跑偏。1.1 从一次实际“柴油机兜底”说起我印象最深的一次是项目方案评审前夜我们用最基础的Matlab linprog跑了一个不考虑需求响应的版本得到的计划是柴油机保持全天最低出力运行、储能不充电也不放电所有波动都用柴油机踩油门来扛。结果实际一验证白天光伏出力跳动十几分钟一次柴油机调速根本跟不上频率和电压都拉不住储能已经没电可放用户负荷只能靠切负载来保重要设备。这个教训让我明白了一个关键点日前的“经济调度”预测值必须和“实时平衡”的调节能力配套模型里如果不给储能和需求响应设计明确的参与方式最终只能得到一份理论上省钱、实际上断电的计划。后来我们把需求响应分成了两类一类是可削减负荷比如路灯、温控负荷、非关键生产设备削掉一部分不影响基本运行另一类是可转移负荷比如抽水蓄能水泵、充电桩、热水器可以在时间上平移但总用电量不能少。这样做的效果是白天光伏大发时鼓励用户把可转移负荷挪到中午直接替代柴油机出力晚上风电出力大而负荷低谷时储能先充电可削减负荷适当减掉一部分柴油机可以安排停机而不是低负荷空烧。这个思路最终让柴油机每天少运行约5个小时测算的日运行成本降了大概两成。1.2 日前调度为什么比实时调度更麻烦很多人会觉得“既然有实时数据直接做实时调度不是更准吗”。但实际操作中储能充放电切换是有损耗和寿命代价的机组启停也有最小运行时间和最小停机时间约束这些设备状态变化如果等到实时阶段才决定往往已经来不及。日前调度的意义在于把储能的充放电深度、机组组合、需求响应时段提前安排好给实时控制留出一个可以跟踪的参考轨迹。麻烦的地方在于日前模型要考虑24个时间段之间的耦合关系。比如储能在0点充的电可能要到早晨负荷高峰才放如果把每个时段当成独立的线性规划来解SOC就会出现跳变得到的方案根本不可执行。需求响应也一样用户答应“中午转移200kW负荷”这个承诺是跨时段的不能只在一个时段内消解。所以日前调度必须用多时段联合优化的方法这就要把约束写成时间索引形式然后在Matlab里构建大规模矩阵或直接使用优化工具箱的约束表达式。另一个麻烦是预测误差的传递。日前预测的风电出力是1500kW实际可能只有600kW。模型里如果不考虑这种偏差储能可能一整天都被“安排得明明白白”根本守不住平衡。我的处理方式是先把风光预测值作为基准场景参与优化再用需求响应和储能备用容量来吸收偏差这样既保证了日前计划的求解效率也保留了实时调整的冗余。2. 数学模型目标函数和约束的取舍搭建日前经济调度模型第一件事不是写代码而是确定目标函数。我见过一些论文目标函数里又是运行成本、又是碳排放、又是用户舒适度还加了八九个惩罚项结果算出来的方案在现实中完全没法用。做工程仿真和做理论论文不一样我的原则是“目标能少就少约束能紧就紧”先把核心目标做扎实再逐步增加评价维度。2.1 目标函数运行成本最小还是碳排放最小我最终采用的是以运行成本最小为目标碳排放作为结果统计而不是优化目标。原因很简单实际项目里柴油机的启停、燃料消耗、储能充放电损耗、需求响应补偿都需要用钱来衡量成本函数可以和园区结算账单直接对应便于后续验证模型有效果。如果目标函数里混入碳中和权重就需要给碳价一个合理估值很容易变成调参游戏。目标函数的典型构成如下柴油机/微型燃气轮机的燃料成本常用二次函数或分段线性函数这是成本的主体机组启动成本用于避免频繁启停带来的机械损耗储能充放电损耗折算成本按充放电功率和一个效率系数折算需求响应补偿成本按可削减负荷和可转移负荷的响应价格乘以实际响应量计算弃风弃光惩罚成本用于约束模型不要为了省柴油而过度弃风弃光。在实际代码里我把燃料成本做了分段线性化处理把一台柴油机的出力区间切成3到5段每一段对应不同的边际成本然后用线性规划或混合整数线性规划直接求解。这里有个关键经验如果不用分段线性化而是用二次函数配一个非线性求解器计算时间会成倍增加而且非常容易陷入局部最优。在日前调度的场景里分段线性化完全够用误差控制在两个百分点以内线性化是“性价比”最高的选择。2.2 风光出力处理场景法还是区间法风光出力的预测值怎么进模型是决定模型复杂度的一个大分叉。最简单的做法是直接把预测曲线当作确定值代入模型求解这适合做方法验证和教学但结果偏乐观。稍微复杂一点的做法是生成多个风光出力场景每个场景带一个概率目标函数改成所有场景的期望成本——这就是随机规划的思路。再往上是鲁棒优化用一个不确定区间覆盖所有可能出力目标是“最坏情况下的最优解”。我做项目时三步都试过。第一个阶段用确定值模型跑得很快Matlab在几秒内就能出结果但实际运行日里出现了两次比较大的偏差一次因为乌云导致光伏骤降一次因为夜间风速低于预测都让我们的计划显得很被动。第二个阶段改成了多场景采样每个时段生成10个场景用蒙特卡洛抽样模型规模变大但在Matlab里用YALMIP或CVX配合合适的求解器还是能几分钟内算完。第三个阶段只针对风电做了区间鲁棒因为风电预测偏差在园区这类场景里往往比光伏更大区间模型会让调度结果偏保守但对供电可靠性提升非常明显。如果你想快速跑通一个版本我建议先用确定值模型然后把风光出力的“期望偏差”作为一个附加的负载备用约束加进模型比如要求系统在最大风光偏差下不切负荷。这样做的效果接近于区间法但代码改动量极小。在YALMIP里只需多定义几个约束把平衡式改成一个带偏差项的不等式组就能完成这个改造。2.3 储能模型SOC连续化多少算合理储能建模最容易出问题的就是SOC更新公式。理论上的表达式是SOC(t1) SOC(t) (充电功率 × 充电效率 - 放电功率 / 放电效率) × 时间步长 / 额定容量这个公式直接写进Matlab的优化模型没有问题只要把充电、放电分别设成非负决策变量并加上“不能同时充放电”的逻辑约束就行。很多人会在这里犯错为了让模型线性直接把SOC写成等于上一时段减去放电功率除以额定容量忽略了充放电效率。这样做的结果是储能的高峰套利行为被严重高估算出来的收益比实际高很多。对于“不能同时充放电”连续模型里可以用一个二元变量加两个大M约束来实现但更常用的手段是把充放电功率合成为一个变量允许取正负值再分别计算损耗。实际经验是在日前调度的时间粒度下通常1小时或15分钟充放电效率取常数已经够用。我通常在代码里设定充电效率0.95、放电效率0.95循环效率大约0.9这样和真实锂电池系统的往返效率非常接近。如果时间粒度缩短到5分钟效率还会随倍率有小幅变化需要再用非线性效率函数不过大部分微电网日前调度用不到这么细。SOC上下限一般设10%到90%避免深度充放导致寿命缩短。很多教程把SOC下限设成0这在数学上没有问题但工程上电池很难做到100%放空长期下来容量衰减会非常快。我到后期做方案时会把SOC下限提高到20%用弃风或柴油机低负载来保障末端电量实际效果比“榨干电池”的方案更稳定。3. 需求响应怎么建模才不会做出纸上谈兵的结果需求响应是我这次建模范文里投入时间最多的一块。原因很直接负荷调度不像机组调度那样完全受你控制用户说“我今天要削减负荷”可到了执行阶段很可能因为生产需要、天气变化临时取消。所以需求响应模型必须兼顾“物理可行性”和“用户实际响应能力”否则仿真数字再漂亮现场也是白搭。3.1 可削减负荷与可转移负荷的区分处理我把需求响应分成两类资源来建模。可削减负荷ZY(t)是直接减少某个时段的用电量代价是用户获得补偿约束包括上限和削减次数限制。可转移负荷TR(t)则允许把某个固定时段的用电量平移到其他时段核心约束是“全天移入电量 全天移出电量”也就是总用电量守恒。可削减负荷的建模相对简单0 ≤ ZY(t) ≤ ZY_max(t)即每个时段的削减量不能超过合同约定的上限全天削减总次数不超过一定值避免用户频繁响应导致反感削减后的负荷不能低于用户基本生产需求。可转移负荷则要麻烦得多。我的做法是把负荷拆成“基础负荷”和“可转移负荷块”可转移负荷块的时间窗可以平移但必须在一个连续时段内完成用电。这个约束写进Matlab时我用了二元变量来表示“开始转移的时刻”对应一组时间窗约束。如果只做学术演示可以简化成任意时段平移而不要求连续性但工程项目中空调、水泵、生产线转移都是需要连续过程的不能把负荷割裂成碎片。3.2 需求响应补偿价格怎么定价格参数是整个模型中敏感度最高的部分。我刚开始参考论文一般把可削减负荷补偿价格定为1.2元/kWh可转移负荷补偿价格为0.8元/kWh结果模型几乎把每一天的所有可削减容量都用满了因为价格比柴油机发电成本还低。但实际园区用户根本不买账离线负荷削减影响生产1.2元的补偿根本不值当。后来我改成阶梯式补偿价格基础电量削减部分给0.8元/kWh超出10%的部分给1.5元/kWh超出20%可以到2.2元/kWh。这样模型在优选中会自动权衡“是叫用户多削减还是让柴油机多发一点电”。这个设计模拟了真实市场里的边际成本递增逻辑算出来的需求响应量也保守很多。我认为在代码中把需求响应补偿做成分段线性函数是让模型结果“可落地”的关键一步比任何精确的风电预测都重要。3.3 响应容量上限和舒适度约束很多人做需求响应只设置了一个“削负荷不超过总负荷的20%”之类的总上限认为这样覆盖了所有问题。实际上不同类型负荷的响应特性差别很大。空调类负荷持续削减1小时后房间温度可能就超出允许范围了充电桩负荷可以灵活转移但不能让人第二天早上没有车可用生产线负荷要么整线转移要么完全不动半吊子转移没有意义。我在项目中引入了“连续可调幅度”和“最长时间窗”两个约束。具体来说空调类可削减负荷单次持续时间不超过2小时全天不超过4小时可转移负荷必须在指定时间段内完成转移比如充电桩只能在22点到次日8点之间充电每个节点电压约束在细化模型中用以确保需求响应不会导致电压越限。这些约束在Matlab实现中会显著增加决策变量数量但换来的是方案的真实可行性。我建议做微电网调度的新手从一开始就把舒适度和用户约束放进去不要等模型算完了再人工排查哪条响应不合理——那会非常痛苦。4. Matlab实现数据结构、线性化与求解器选择有了数学模型之后如何组织Matlab代码是另一个大问题。我一开始用的是“矩阵堆叠”法把所有的系数矩阵手动拼起来然后调用linprog。这种方法适合教学但一旦加入机组组合二元变量、储能充放电二元变量、需求响应分段约束矩阵维度会迅速膨胀手写索引根本维护不动。4.1 数据准备预测曲线应该修到什么程度构建Matlab代码时我把输入数据定义成三个结构体load_forecast、renewable_forecast、grid_tariff。分别存24点的有功负荷预测值、风电光伏有功出力预测值、分时电价。初学者容易忽略的是数据清洗风电预测曲线里如果有负值或空值直接用的话会在求解阶段爆NaN。我的习惯是先做一个简单的预处理脚本把平谷时段的风电预测做平滑滤波把超过装机容量的点强制压回上限避免不合理的约束造成无解。另外我强烈建议把时间步长统一成“小时”或“15分钟”并保持一致。我见过有人用1小时建模但电价却是30分钟一个数据最后时间索引对不上排查了整整一下午。用结构体统一存储并写几个校验函数可以避免这类问题。4.2 决策变量的组织方式在YALMIP下我一般定义:Pg sdpvar(ngen, 24, full); % 机组出力 On binvar(ngen, 24); % 机组启停状态 Pch sdpvar(1, 24, full); % 储能充电功率 Pdis sdpvar(1, 24, full); % 储能放电功率 SOC sdpvar(1, 24, full); % 荷电状态 Zcut sdpvar(1, 24, full); % 可削减负荷量 Ztr sdpvar(1, 24, full); % 可转移负荷量正负表示转入转出用YALMIP的好处是不用手动拼矩阵约束可以按数学表达式直接写比如C []; C [C, SOC(:, t1) SOC(:, t) (Pch(:, t)*eta_ch - Pdis(:, t)/eta_dis)/E_cap];这样的代码可读性极强而且YALMIP会自动把问题转化为标准形式并交给求解器。我用这个方法把原来600多行的手拼矩阵代码压缩到两百行左右而且后期加一条约束容易得多。4.3 用Gurobi/CPLEX还是用linprog求解器的选择直接决定你能不能在可以接受的时间内得到一个可用的解。如果模型完全是连续线性规划比如不考虑机组启停直接用Matlab自带的linprog就行求解速度极快。但本文涉及机组组合、储能防同时充放电、需求响应时间窗这些都需要二元变量所以问题升级为混合整数线性规划MILP。Matlab自带的intlinprog也能解MILP但对于大规模问题——比如超过100个二元变量、上千条约束——速度会明显变慢。我的经验是如果只是案例规模1台柴发、1台储能、24时段intlinprog完全够用而且省去安装第三方求解器的麻烦。但如果你要做6台机组、96个时段、多场景风光那就要换成Gurobi或CPLEX。我用Gurobi做过一个含900多个二元变量的算例求解时间在1分钟之内intlinprog则需要跑超过15分钟。需要说明商业求解器需要授权学术用途一般可以申请免费license。在Matlab代码里切换求解器非常方便YALMIP下只需把solvesdp改成optimize并指定相关参数比如ops sdpsettings(solver, gurobi, verbose, 2); optimize(C, Objective, ops);4.4 为什么我最终选了MILP这个路线我最终没有选用粒子群、遗传算法等启发式算法原因有三点一是启发式算法每次求解结果不一致给领导汇报时很难解释“为什么同样数据两次算出的成本不同”二是MILP可以求出全局最优解便于做敏感性分析和方案对比三是MILP在Matlab生态里工具链最成熟代码易维护也容易扩展到滚动优化。当然MILP的短板在于建模时需要把非线性环节线性化冷启动费用、分段成本、需求响应阶梯价格都要做近似。但如前所述这些近似在日前调度的时间粒度下误差可控。我的建议是如果课题中不强制要求智能算法对比就不要为了“看起来高级”去选遗传算法MILP在工程上的价值和说服力要高得多。5. 跑通模型之后结果验证和敏感性分析求解器返回“求解成功”不代表模型就是对的。我见过不少代码目标函数收敛了但把电量和SOC画出来一看完全不符合物理规律。所以跑通模型之后我建议从三个维度验证结果电量平衡、储能状态趋势、需求响应执行情况。5.1 第一关检查每一个时段电量是否闭合微电网中的功率平衡方程是P_diesel(t) P_pv(t) P_wind(t) P_dis(t) P_grid_buy(t) P_load(t) P_ch(t) P_grid_sell(t) - Zcut(t) ± Ztr(t) P_curtail(t)其中P_curtail是弃风弃光量也必须当作决策变量写进约束。很多模型的隐藏错误是弃风弃光变量约束写反了导致结果里出现“既弃风又发电”的怪异现象。我的验证方法很简单求解完成后把每个时段的所有功率画在一张堆叠图上然后让程序自动计算每个时段的残差如果残差绝对值超过0.1kW就说明约束写错了。有一次我就是在这一步发现储能效率系数乘错了位置导致全天电量不守恒修正后成本又降了3%。5.2 第二关看储能SOC曲线是否符合预期储能SOC曲线应该是一条平滑变化的曲线充满到90%后不应出现凭空掉到40%的情况。如果SOC出现锯齿状跳变多半是SOC更新公式里缺少效率系数或是充放电功率和SOC的时索引错位。我之前就犯过一个经典错误在Matlab循环里把SOC更新放在约束之外导致每个时段求解时SOC没有继承上一时段的状态变成一个自由变量最后优化器“聪明”地把SOC初始时刻设在90%然后一路放电到底方案看似成本最低实际完全不可行。加入跨时段约束后这个漏洞就堵住了。5.3 第三关对电价和负荷系数做敏感性分析模型求出一个方案后我通常会再做个敏感性分析。改电价表中的峰值电价看需求响应和储能放电是否相应变化改风电出力比例看弃风和柴发出力如何联动。这一步能直观看出模型是否符合经济学直觉。我实测的结果如下表所示场景峰值电价变化储能放电荷电可削减负荷占比柴油机出力占比基准0%62%18%45%高电价30%81%26%33%低电价-20%41%9%55%这张表清晰显示电价越高储能越愿意在高峰放电需求响应削减比例也加大柴油机出力相应减少。如果得到的结果对电价完全不敏感那模型里很可能存在冗余约束需要重新检查参数的量级关系。我还做了一次极端测试把风光出力全设为零看系统能不能依靠储能和柴发维持负荷。如果这都能平衡说明模型的基础约束是完备的。这个测试在项目汇报中非常有用它向业主证明“即便明天没风没光系统也有备用手段”。6. 我踩过的坑和后续扩展建议最后这部分我写几个个人觉得最有价值的坑以及如果读者希望继续深入的话可以做哪些扩展。每个坑都是我实际在Matlab里调试时一次次撞出来的不是教科书上的理论。6.1 储能寿命损耗建模的简化处理第一个坑是储能寿命损耗。我最初的模型里没有寿命损耗项只看充放电效率结果模型倾向于让储能频繁、大倍率地充放电以赚取峰谷价差。但实际上每多一次充放循环电池容量都会微降深度的充放循环对寿命影响尤其大。经济调度如果完全忽略这个成本等于在提前透支电池寿命换取账面成本降低。我的折中方案是把每次充放电折算成一个固定的“循环成本常数”放在目标函数里加一个惩罚项。充电或放电1kWh就收取几分钱的寿命损耗费用。这个常数根据电池更换价格和全生命周期的等效循环次数估算出来。虽然模型没有把电池衰退机制完整纳入但优化器会自动倾向于“少做无意义的浅充浅放”效果显著改善了。6.2 需求响应的“假响应率”问题第二个坑涉及需求响应的执行率。很多模型把需求响应容量当成刚性约束认为用户答应了100kW就会削减100kW。实际执行中由于用户操作延迟、天气预报变化、临时生产任务需求响应执行率往往只有50%到80%。如果日前计划把需求响应当成了完全的“必然资源”实时运行时会出现功率缺口。我在日前模型中引入了一个“响应调整系数”把可削减负荷上限打了个八折。这个系数可以通过历史响应记录不断更新比如第一个月执行率是70%第二个月就把系数改成0.7。这样做虽然会让日前调度的成本略高一点但换来的是实时运行可靠性的显著提升。对工业园区的微电网来说防止一次非计划停电的价值远超这一点点运行成本的增加。6.3 扩展方向滚动优化与鲁棒优化读者如果不满足于单日的静态调度可以尝试两个自然的扩展方向。第一个是滚动优化每运行一个时段就用最新的实测数据重新求解未来24小时的计划只执行下一个时段的结果。这个方案能有效吸收风光预测误差但计算负荷比单次日前调度高很多。在Matlab中只需把主程序包进一个循环让时间窗整体滑动实际改动量不大。第二个方向是条件风险价值CVaR约束下的鲁棒调度。如果把风光出力不确定性用一个概率分布来描述可以在目标函数里加入CVaR惩罚项专门约束“尾部的坏场景”。这个扩展我在后续版本里做过它比单纯的最坏情况鲁棒更经济又比随机规划的期望成本更稳健属于那种兼顾理论价值和工程意义的进阶玩法。不过建议读者先把基础MILP版本彻底吃透再一步步往这个方向走否则很容易被建模复杂度淹没。以上基本就是我这次用Matlab做风光储与需求响应协调的日前经济调度时的主要经验。每个模型参数、每段Matlab约束背后都对应实际运行中的一次取舍和权衡。希望大家照着这个思路调自己的算例时能少走几步弯路多省几个晚上的调试时间。