☰
微网优化调度模型实战:基于Yalmip的建模与求解全解析
2026/10/7 22:43:32 网站建设 项目流程

1. 微网优化调度的思路拆解与模型架构

做微网优化调度模型这件事,我在项目里已经反复折腾过好几轮了。最早用的是纯Matlab手写线性规划,后来换到Yalmip之后明显感觉建模效率上了一个档次,尤其当你面对的是蓄电池、风电、光伏、柴油机四个不同特性的发电单元时,约束关系非常容易绕晕,Yalmip这种声明式的建模方式能把代码逻辑和物理逻辑对齐,后面维护起来也轻松得多。

先说说这个项目到底要干什么。微网系统里面有风电和光伏这两个不可控电源,有柴油机这个可控主力,还有蓄电池这个快速响应单元。优化调度的目标就是在满足负荷需求、机组运行约束、储能充放电约束的前提下,以最低的运行成本决定未来24小时内每个时段各单元的出力值。说白了,就是把"什么时候该用柴油机发多少电、什么时候该充电池、什么时候该放电池"这个问题变成一个数学优化问题,然后求解器去解。

我选择Yalmip有几个实际因素。第一,Yalmip的语法非常接近数学表达式的自然写法,一个约束写出来什么样,代码里就是什么样,不容易把逻辑写错。第二,底层求解器可以随时切换,同一个模型换成Cplex或者Gurobi只是改一行代码的事情。第三,和Matlab自带的优化工具箱相比,Yalmip在线性规划、混合整数规划上的支持成熟很多。对于微网调度这种典型的混合整数线性规划问题——里面有二进制变量表示柴油机启停、蓄电池充放电状态——Yalmip处理起来非常顺手。

微网系统的运行逻辑需要从一个整体视角去看:

  • 风电和光伏:属于不可控电源,出力由天气决定。调度模型里把它们当作"负的负荷"处理,优先消纳,如果预测出力过剩,可以弃风弃光。
  • 柴油机:可控电源,是系统里的主力调节手段。响应速度中等,启动需要时间,有爬坡约束,燃料成本是最主要的经济指标。
  • 蓄电池:快速响应单元,既能充电又能放电,是系统里的"缓冲池",用来削峰填谷、平抑风光波动。但它的约束也比较多——容量限制、SOC上下限、充放电功率限制、还有充放电不能同时进行的逻辑约束。

调度的本质就是把这几种特性完全不同的单元组合在一起,在24小时时间尺度上做协同优化。

关于模型架构,我当时设计的是典型的两层逻辑:边界条件层和优化决策层。边界条件层负责处理负荷预测、风光出力预测、柴油机参数、蓄电池参数这些输入;优化决策层基于Yalmip构建目标函数和约束集,调用求解器求最优解。数据流向是预测数据进模型,约束逻辑在模型里形成可行域,目标函数指引寻优方向,最后解出结果再做校验和后处理。整个过程说起来简单,实际操作中每个环节都有不少值得注意的坑,后面我会详细展开。

2. 四大发电单元的建模细节与约束处理

这一章是核心中的核心,建模的质量直接决定了求解结果是不是合理。我在这个项目里把四个发电单元分开建模,然后通过功率平衡约束把它们耦合起来。当初这样做最大的好处就是每个单元的内部逻辑相对独立,改一个单元的约束不会影响到其他单元,调试起来效率高很多。

2.1 蓄电池储能单元建模的要点

蓄电池是模型中最容易出问题的地方,因为涉及到状态变量、二进制变量还有递推关系。它的核心物理逻辑是SOC(荷电状态)随时间步进递推:

SOC(t+1) = SOC(t) - P_ch(t) * η_ch * Δt / Cap + P_dis(t) * Δt / (η_dis * Cap)

公式里P_ch是充电功率(正数),P_dis是放电功率(正数),Cap是电池容量,Δt是调度步长。充放电效率一般取0.9到0.95之间,如果做规划类模型也可以都取1简化处理,但实际项目里最好保留效率系数,不然算出来的结果会偏乐观。

蓄电池建模的三个关键约束:

  • SOC上下限约束:一般设置在0.1到0.9之间,避免过度充放损伤电池寿命。那些直接写0到1的做法在学术文章里能看到,做实际项目不建议这么干。
  • 充放电功率限幅:P_ch和P_dis都要小于最大充放功率,这是由电池本身的特性决定的。
  • 充放电互斥约束:这里需要一个二进制变量,让充电和放电不能同时发生。写成表达式就是P_ch ≤ M * u和P_dis ≤ M * (1-u),u是二进制变量,M是足够大的常数。

有一个细节我刚做的时候踩过坑:如果时间步长不是1小时,Δt必须写进递推公式里。我做过一个步长15分钟的调度模型,忘记把Δt换算成0.25小时,结果SOC曲线全乱了,充电量对不上,电池储能越算越多。这个问题排查了一个多小时,最后才发现是单位的问题。

2.2 风光机组模型怎么处理才合理

风电和光伏在微网模型里通常作为不可控电源处理。它们对调度模型来说就是一个预测出力的输入序列,不需要也不应该去"调度"它们,因为风光的出力取决于天气条件,调度所能做的决定只有"消纳多少"或者"弃掉多少"。

光伏出力受光照强度影响,曲线形状是一个典型的"钟形"——夜间为0,中午峰值最高,我在项目里直接采用预测数据;风机则看风速,预测数据是时间的函数。这两种电源的基本处理方式是一样的:

P_wind_use(t) ≤ P_wind_forecast(t) P_pv_use(t) ≤ P_pv_forecast(t)

最优解里通常会把风光预测出力全部消纳掉,因为风电光伏的边际成本接近0。但有一种情况会出现弃风弃光:比如中午光伏出力最大时,负荷很低或者蓄电池已经充满了,柴油机又因为技术出力下限不能压得很低。这时候模型为了满足功率平衡,只能舍弃部分光伏出力。这种约束写法允许模型"选择"不用多少可再生能源,在最优解里它自然有判断。

我在模型里给风光加了一个"弃用惩罚成本",让模型在一堆可行方案中优先选择尽量多消纳风光的方案。惩罚成本设成零的话,经常会出现求解器随意弃用风光的情况,因为对目标函数没有任何影响,导致结果看起来不太符合物理直觉。加了一个微小的正系数之后,模型的行为就正常多了。

2.3 柴油机建模要留意成本函数线性化

柴油机在微网系统里充当的是"保底电源"的角色,风电、光伏不足的时候它就是最可靠的出力来源。它的一个特点是出力有下限——柴油机技术上的最小稳定出力大概在额定功率的30%左右,低于这个值机组运行不稳定甚至熄火。所以模型中必须加入这个约束,这也是很多微网模型后半夜风光不足时没法完全依赖柴油机"少量发电"的原因。

柴油机的成本由三部分组成:

F_diesel(t) = a * P_diesel(t)^2 + b * P_diesel(t) + c

其中a是二次项系数——燃料消耗的边际递增特性决定了这部分的存在,b是一次项系数(代表每度电的燃料成本),c是固定启停成本。由于标准线性规划不能直接处理二次项,Yalmip中最简单的做法是把成本函数做分段线性化近似:

  • 二次项用分段线性函数去拟合,段数取3-5段。段数太多会额外增加变量和约束,求解速度下降;段数太少拟合精度不够,解的出力值会有偏差。我在这类项目中一般取3段。
  • 固定启停成本用二进制变量处理:如果柴油机在时段t启动,则在此后的几个时段内,无论出力多少都要支付一笔固定费用。这个逻辑在调度结果中表现为——柴油机尽量保持持续运行状态,频繁启停会被成本约束自动排除掉。

爬坡约束也是柴油机建模中不能忽略的一环。柴油机出力变化的斜率有限,不可能从一个时段到下一个时段瞬间从2MW跳到10MW。爬坡约束写成:

P_diesel(t+1) - P_diesel(t) ≤ R_up * Δt P_diesel(t) - P_diesel(t+1) ≤ R_down * Δt

如果不加这个约束,模型可能会给出一种极端方案——比如某时段风光突然消失,柴油机出力瞬间拉满,实际运行中这根本做不到。爬坡约束其实就是模型里"机组物理特性"的体现。

2.4 功率平衡与旋转备用约束的写法

把四个单元衔接起来的最关键约束就是功率平衡方程,它也是整个模型的"硬性约束",意思是任何时刻都必须满足供需平衡:

P_pv_use(t) + P_wind_use(t) + P_diesel(t) + P_dis(t) - P_ch(t) = P_load(t)

这个式子的含义是:光伏和风电的消纳量加上柴油机出力加上蓄电池放电,减去充电功率,等于负荷需求。蓄电池充电算负的负荷,放电算正电源,从数学形式上来看非常干净。

实际模型里还需要加入旋转备用约束。简单来说就是系统要有能力应对负荷预测偏差或者风光出力突然下跌的情况,确保系统的安全性和稳定性。旋转备用约束的写法是系统可用总出力上限要大于等于负荷加备用需求:

P_load(t) + R_require(t) ≤ P_pv_max(t) + P_wind_max(t) + P_diesel_max(t) + P_dis_max(t) - P_ch_min(t)

这个约束在实际项目中容易被遗漏。但如果微网是孤岛运行的,没有大电网作支撑,备用需求必须覆盖最恶劣的场景,否则系统随时可能面临断电风险。

3. Yalmip建模实操与求解全流程

上一章讲的是建模层面的物理逻辑,这一章说说怎么把这些物理逻辑翻译成Yalmip代码,以及求解过程中我踩过的坑。Yalmip的最大优势在于"声明式建模"——你要做的事情就是定义变量、写约束、声明目标函数,剩下的求解细节交给求解器处理。

3.1 决策变量怎么定义才方便后续处理

Yalmip中定义变量的核心函数是sdpvar和binvar。针对24时段微网调度问题,我的做法是直接把决策变量定义成一个向量,这样后面写约束和结果处理都更方便。

% 24时段调度,时间步长1小时 T = 24; P_diesel = sdpvar(1, T); % 柴油机出力,连续变量 P_ch = sdpvar(1, T); % 蓄电池充电功率 P_dis = sdpvar(1, T); % 蓄电池放电功率 u_start = binvar(1, T); % 柴油机启动状态 u_run = binvar(1, T); % 柴油机运行状态 u_ch = binvar(1, T); % 蓄电池充电状态(1充电,0放电) SOC = sdpvar(1, T+1); % 荷电状态,多一个元素是为了表示0时刻初始值

注意SOC定义成了T+1个元素,因为需要SOC(1)对应初始时刻的荷电状态,SOC(t+1)对应第t个时段末的状态。这是我做这个项目时的一个重要习惯:宁可多一个变量把递推关系写清楚,也不要为了省一个变量让索引关系变得难维护。Yalmip既然允许向量定义,就用向量思维来处理,在循环里处理的时候效率也更稳定。

3.2 约束组装用循环还是向量化

Yalmip支持两种约束写法:一种是向量约束整体写入,另一种是for循环逐条添加。对于维数不高的24时段模型,我用for循环最直观,每行约束对应一个时段,逻辑清晰。

Constraints = []; % 柴油机出力上下限约束与运行状态绑定 for t = 1:T Constraints = [Constraints, P_diesel_min * u_run(t) <= P_diesel(t) <= P_diesel_max * u_run(t)]; end % 蓄电池充放电约束,包含互斥逻辑 for t = 1:T Constraints = [Constraints, 0 <= P_ch(t) <= P_ch_max * u_ch(t)]; Constraints = [Constraints, 0 <= P_dis(t) <= P_dis_max * (1 - u_ch(t))]; end % SOC递推关系 for t = 1:T Constraints = [Constraints, SOC(t+1) == SOC(t) - P_ch(t) * eta_ch / Cap + P_dis(t) / (eta_dis * Cap)]; end % 功率平衡约束 for t = 1:T Constraints = [Constraints, P_pv(t) + P_wind(t) + P_diesel(t) + P_dis(t) - P_ch(t) == P_load(t)]; end

这里有一个细节值得注意:蓄电池的充放电互斥约束我用了u_ch(t)这个二进制变量来实现,没有用两变量相乘的表达式,因为那样模型就变成非线性了。Yalmip对非线性问题的处理能力虽然也不错,但非线性求解器在效率和稳定性上远不如线性求解器,能用线性处理的地方我坚决不用非线性。

Yalmip约束拼接有一个比较隐蔽的性能坑。如果约束条数非常多(比如几百条以上),写成Constraints = [Constraints, new_constraint]这种追加方式在Matlab里会有复制的开销,求解器在启动阶段会比较慢。这个场景下把约束放进cell数组里,最后用一个循环Constraints = [Constraints{:}]拼接,速度会快很多。24时段调度约束也就几百条的量级,影响不明显,但如果以后扩展到96时段15分钟步长,这个优化就很有必要了。

3.3 目标函数与求解器配置的实操细节

目标函数代表的是整个系统的运行成本。这个项目里我把它定义为三部分之和——柴油机燃料成本、蓄电池维护成本、弃风弃光惩罚成本:

Objective = 0; % 柴油机燃料成本 % 这里假设成本函数已经线性化为三段,F1/F2/F3分别是各段的成本变量 for t = 1:T Objective = Objective + F_diesel(t) * P_diesel(t) * dt; end % 蓄电池维护成本(充放电都会产生电池损耗) for t = 1:T Objective = Objective + C_bat * (P_ch(t) + P_dis(t)) * dt; end % 弃风弃光惩罚成本 for t = 1:T Objective = Objective + C_curtail * ((P_pv_max(t) - P_pv_use(t)) + (P_wind_max(t) - P_wind_use(t))) * dt; end

在调度的24小时范围内,成本计算把每个时段的值乘以时间步长dt再累加求和。dt是小时数,如果步长是15分钟则dt=0.25。刚开始做的时候我总觉得用"元/小时"的功率乘1小时好像多此一举,但实际上功率乘以时间才是能量,乘以电价才是成本。举个例子,调度步长改成15分钟却忘记乘0.25,算出来的成本就比实际值放大了4倍,调度结果的倾向性也会出问题。

求解器调用是整个过程里最简单的一步:

ops = sdpsettings('solver', 'gurobi', 'verbose', 0, 'showprogress', 0); optimize(Constraints, Objective, ops);

solver参数可以设定为'gurobi'、'cplex'、'sedumi'等,取决于你机器上装了哪些求解器。如果没有商业求解器,'solver'也可以直接写'gurobi',大不了改成'cplex'或者'sedumi',Yalmip的可移植性就在这里。

3.4 求解结果怎么提出来并验证

求解完成后,最关键的步骤是使用value()函数提取变量数值:

P_diesel_opt = value(P_diesel); P_ch_opt = value(P_ch); P_dis_opt = value(P_dis); SOC_opt = value(SOC);

提取出来之后我一般会画两张图做验证:第一张是24小时的功率平衡堆叠图,柱状堆叠显示光伏、风电、柴油机、蓄电池放电各贡献多少,同时叠加负荷曲线;第二张是SOC变化曲线,观察蓄电池是否在合理范围内来回波动。如果堆叠图各段加起来不等于负荷曲线,说明某个约束写错了,回查约束逻辑是排查问题的第一步。

另外还有一个验证技巧值得分享:把优化结果代回目标函数,手动重新计算一次总成本,看是不是和求解器的目标函数值一致。如果不一致,大概率是某个公式里dt的系数有问题或者某个成本项的变量带错了。这个小检查只需要几行代码,能避免非常多下游分析阶段的数据错误。

4. 常见问题排查与调参实战

模型跑通了那是一回事,结果合理才是另一回事。这一章专门讲讲我在这个项目里实际遇到的几个典型问题,哪些经验是可以复用的、哪些坑是必须避开的。

4.1 模型不可行怎么定位问题

Yalmip模型报infeasible(不可行)是我最常遇到的问题。整段模型几百条约束,任何一条冲突都会导致整体无解。解决不可行问题的一个高效做法是把模型拆开来看——先把蓄电池约束全部注释掉,只保留柴油机和功率平衡,看模型是否可解。如果可以求解,说明问题出在蓄电池约束内部;如果还是不可行,再看是不是功率平衡和预测数据之间存在矛盾。

另外有一个很重要的思路:负荷平衡约束的本质要求是"所有可调出力之和要能覆盖负荷与风光预测的差额"。如果某个时段负荷特别高而风光特别低,柴油机最大出力加上电池放电最大出力仍然小于净负荷,那模型一定会不可行。项目中出现这个情况时不应该去调约束参数,而是要考虑是否应该增加柴油机容量,或者削减高峰时段的部分负荷。模型不可行往往是在提醒你:系统物理配置本身存在缺口,而不是代码写错了。

提示:Yalmip提供了一个叫diagnostic的功能,通过查optimize的输出信息能发现一些线索,但真正高效的调试方式仍然是逐步注释法,不要指望一次定位到某一条具体约束。就我的经验来说,70%以上的不可行问题出在"功率平衡约束"和"蓄电池SOC递推约束",排查时优先看这两个。

4.2 求解速度慢怎么优化

这个问题在引入大量二进制变量之后会变得突出。微网调度本质上是混合整数线性规划,整数变量越多,求解时间越长,这是数学上不可避免的特性。优化求解速度有几个方向:

第一个方向是减少整数变量的数目。比如蓄电池充放电状态变量,很多情况下可以通过合理设置目标函数来避免使用二进制变量——如果充电和放电的成本系数都是正数,目标函数最小化就会天然避免同时充放电这种荒谬行为。但要注意,如果没有互斥约束,某些特殊场景下求解器仍可能给出同时充放电的次优解,还是要加上约束更稳妥。

第二个方向是给求解器设置合理的容差。Gurobi和Cplex都有MIPGap参数(MIP问题的相对最优间隙),默认一般比较严格,可以适当放宽:

ops = sdpsettings('solver', 'gurobi', 'mipgap', 0.01);

对于微网调度这种场景,1%的间隙意味着目标函数与最优解的偏差最多为1%,在实际工程中足够用了。但代价是求解时间可能减少60%到80%。我之前有一个96时段的模型,默认参数求解要60秒以上,把MIPGap调整到1%后只需要10秒左右,运行结果差异肉眼根本看不出来。

第三个方向是从数学建模层面优化。把某些双线性约束改写成线性等价形式,或者用Big-M法替代逻辑判断。这些改写对求解器的友好度提升非常明显。

4.3 运行结果不合理的检查方向

有一次,我跑出来的结果中,蓄电池在某个时段又充电又放电,但数值恰好都在很小的量级,目标函数没有产生明显损失。仔细排查后发现问题出在我给电池维护成本设置的系数太小——蓄电池在两个方向上的成本变化对目标函数的影响微乎其微,求解器选择了"充一点也放一点"的不合理方案。解决方法是直接加上互斥约束(那组二进制变量),或者把电池维护成本调大一些。

还有一次,我注意到模型里柴油机出力一直顶在上限,即使风光都非常充足。原因是我在写爬坡约束的时候把R_up写成了R_down,导致柴油机的减载能力被模型限制住了。这个错误在代码层面非常隐蔽,因为完全符合格式,逻辑上看着没有任何问题。

从那次开始,我养成了一个习惯:每次修改模型后都要跑一个"极端场景测试"。怎么测?把负荷设得全低、风光设得全高,看系统是否自动减柴油机出力、给电池充电;把负荷设得全高、风光设得全0,看系统是否柴油机满发、电池放电。如果这两种极端的响应行为符合物理直觉,模型的可靠性就基本有保障了。

4.4 参数敏感性分析与结果稳健性

微网优化调度模型有一个绕不开的现实问题:风光预测数据不可能完全准确,负荷预测同样会有偏差。这就导致模型算出来的"最优调度方案"在实际执行时不一定最优,甚至可能不可行。为了解决这个问题,我在项目里会做一个简单的敏感性分析——将风电、光伏的预测值按-20%到+20%的比例扰动,重新运行模型,检查柴油机出力和蓄电池SOC的变化幅度。

敏感性分析结果通常会呈现这样的规律:风光预测误差对系统的影响主要取决于柴油机的调节能力和蓄电池的容量。如果柴油机爬坡快、容量裕度大,那风光预测误差的影响就被消化掉了;如果系统本身调节能力很弱,一个小的预测偏差可能就导致切负荷。做敏感性分析的真正的价值不在于精确预测,而在于给系统找出"短板资源",告诉工程团队需要在哪里加裕量。

从成本和环保的角度来看,这个模型本质上是在做经济学上的折中:柴油机多发电,成本高、碳排放高,但系统稳定性好;蓄电池多充放,调度灵活度提升,但电池寿命损耗不可避免;风光满发,边际成本最低,但可能出现消纳不了的情况。目标函数里的各项成本系数其实就决定了模型在这些目标之间如何取舍。把燃料成本系数调高,结果会自动偏向少用柴油机;把电池损耗系数调高,蓄电池的充放电次数会明显减少。调参的过程本身就是在做决策策略的选择。

5. 经验总结与一些实际建议

这个项目完整跑下来,我最大的感受是:微网优化调度模型真正的门槛不在数学建模也不在编程实现,而在于你对物理系统的理解是否足够深。Yalmip只是工具,它把从物理逻辑到数学表达的翻译过程变得顺畅,但物理逻辑本身必须由你来把握——什么约束必须加、什么约束可以简化、哪些参数对结果影响大、哪些参数不过是细枝末节,这些判断决定了模型的成品质量。

几个实际建议可以分享给要上手类似项目的人:

第一,先把单机模型跑通,再做联合调度。先建一个只有"柴油机+负荷"的模型,确认它能正确求解;然后加入蓄电池,确认SOC递推和充放电逻辑正确;最后再加入风光。一步到位往往导致出问题时根本不知道问题出在哪一层。

第二,在做调度之前先把数据校准做完。我在项目中有一次光伏预测数据和实际数据之间存在系统性偏差,结果算出来的调度方案里蓄电池总是在午后提前充满,等到傍晚负荷高峰时反而没有容量释放了。这个问题在模型里无解,只能靠修正预测数据解决。

第三,时刻把结果可视化放在身边。Yalmip模型跑的是一次性优化,不等于你就能理解结果。功率平衡图、SOC曲线、各电源的日出力曲线,这三张图能覆盖大部分结果合理性判断。

最后分享一个我在迭代中发现的细节:这个模型后续可以扩展的方向很多——加入储能寿命衰减模型、需求侧响应、碳排放约束、多场景随机规划等。但所有扩展都建议在主流程跑通之后再逐项增加,否则复杂度一上来,调试的成本会远超预期。微网调度这个领域,核心在于对系统平衡的理解和建模基本功的扎实,工具终究只是辅助。

从实践来看,这个模型在运行中表现稳定,求解速度快,结果也符合预期。把一个优化调度模型从零搭到可以交付,其实并没有太多神秘之处。把握住物理规律、整理好数据、选择顺手的建模工具、耐心调试,最终的结果会足够可靠。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询