
做电力系统优化调度的人应该都有共识风光柴储微网的多目标优化调度最难的不是模型本身而是“怎么把模型写进代码、跑出结果、再让结果经得起推敲”。我之前被这个题目折腾了挺久一开始拿网上的通用框架跑总是要么成本降不下来要么储能SOC直接越界后来干脆自己照着原理从零写了一套MATLAB代码用多目标优化算法NSGA-II去求解24小时机组出力计划才算把经济性和环保性这两个矛盾目标理顺。这篇文章就是我当时的那套思路和代码结构从数学模型搭建、算法选型到MATLAB实现细节、仿真结果解读再到踩过的坑一次性讲完。所涉及的问题适合正在做微网调度、综合能源系统优化或者正在写多目标优化方向论文的同学参考。我会尽量把每个“为什么这么写”都说清楚因为光贴代码没意义能照着改、照着调才是这套东西的价值。1. 问题建模风光柴储微网调度的数学模型怎么搭1.1 系统结构与目标函数拆解先明确研究对象。所谓“风光柴储微网”系统内部最核心的单元就是四类风力发电、光伏发电、柴油发电机、储能电池。微网与大电网之间还留了一个联络线可以买电也可以卖电。调度周期取典型日24小时时间步长1小时决策结果就是未来24小时内各个可控单元的出力计划。在这个系统里风电和光伏属于不可控电源它们的出力上限由预测曲线决定调度模型不能要求它们超出预测值出力但可以选择弃风、弃光也就是实际出力可以小于预测出力。柴油机和储能是可控单元柴油机可以调节出力储能可以充放电。上级电网交换功率也可以作为调节手段但通常受到联络线容量约束。目标函数一开始我列了三个运行经济性、污染物排放量、以及电能质量相关的指标。但为了不让问题过于复杂也为了能画出一张清晰的帕累托前沿图我最终保留了前两个目标第三个目标放在约束条件里间接考虑。第一个目标是综合运行成本公式上一般拆成四块柴油机燃料成本采用二次耗量特性曲线即 a_i b_i * P_i(t) c_i * P_i(t)^2这是经典做法设备运行维护成本风电、光伏、柴油机、储能各自乘以单位运维系数柴油机启停成本因为模型里用连续出力变量启停成本可以用一个简化表达式近似处理比如每次状态从0到非0时增加一个固定费用与电网的购售电费用购电价高于售电价所以目标函数鼓励自发自用。第二个目标是污染物排放量主要是柴油机燃烧产生的CO₂、SO₂、NOₓ统一折算成等效排放量然后乘以排放系数。风电、光伏、储能在运行阶段基本零排放所以排放目标实际上只跟柴油机和电网购电的间接排放有关。这样做虽然简化了但已经能反映出“多目标”的本质成本最小化通常会让柴油机多出力而排放最小化则希望压低柴油机出力、多用新能源和储能两个目标方向相反这就构成了非劣解集。1.2 约束条件功率平衡、机组出力、储能SOC都不能踩线约束条件才是调度模型真正花时间的地方。我归纳了一下至少包含以下五类功率平衡约束。任意时刻 t光伏出力加风电出力加柴油机出力加储能放电功率加购电功率必须等于负荷加储能充电功率加售电功率。这是等式约束也是整个模型最核心的一条线。实际编程时我基本不会把这条等式的所有项都作为独立决策变量那样等式约束很难严格满足后面会说残差变量的处理技巧。柴油机出力上下限约束。单台柴油机有最小稳定技术出力通常取额定功率的30%也有取20%的具体看机型。如果调度周期内包含停机状态还要考虑连续启停时间约束。简化处理时我会直接要求各时段出力要么为0要么在上下限区间内。爬坡约束。柴油机从一个时段到下一个时段的出力变化量不能超过爬坡速率。这个约束容易在代码里漏掉跑出来的调度曲线经常出现相邻时段从10kW直接跳到50kW的极端情况现场肯定不允许。储能SOC约束与充放电功率约束。储能电池的SOC按时间递推SOC(t) SOC(t-1) (η_ch * P_ch(t) - P_dis(t)/η_dis) * Δt / E_cap。SOC有上下限通常取0.1到0.9避免过充过放。充放电功率也有最大值限制并且同一时段不能同时充电和放电。这个约束如果不用逻辑变量处理起来有点麻烦我倾向于在个体修复环节强制写入当天充放电状态由符号判定只允许其中一个方向。联络线功率约束。与电网的交换功率不能超过联络线允许容量一般取微网总负荷峰值的30%到50%具体看上级配电网给的接入方案。这些约束里功率平衡是等式约束其他都是不等式约束。处理方式有罚函数法、修复法、以及将部分变量作为残差量的方法我后面会重点讲残差变量的思路因为这是让NSGA-II快速收敛的关键。2. 求解思路与算法选型多目标优化算法怎么选2.1 为什么选NSGA-II而不是加权求和一开始很多人会想把成本和排放各自乘个权重加起来变成一个单目标然后就能用现成的线性规划或遗传算法去求解不是更简单吗确实简单但有个本质问题权重系数怎么定成本和排放量纲不同权重本质上代表决策者对两个目标的偏好这个偏好换一个人可能就完全不一样。更麻烦的是权重不均匀扫描时很容易漏掉非凸帕累托前沿上的部分解。所以我直接上多目标进化算法用的是NSGA-II。选择它的理由很朴素第一MATLAB生态里NSGA-II的资料最多出图方便改造成本低第二它基于非支配排序和拥挤度距离既保证收敛性又保证多样性一次运行就能得到一组帕累托前沿解第三不需要提前处理量纲差异哪怕成本是几万元、排放是几百公斤直接扔给算法就行。NSGA-II的核心流程是初始化种群然后进入快速非支配排序把种群分成多个前沿等级同一前沿内部再用拥挤度距离排序保证解的分散程度然后通过锦标赛选择、模拟二进制交叉、多项式变异生成子代父子合并后再排序截断。循环迭代直到满足终止条件。我也可以提一下其他算法作为备选MOPSO多目标粒子群收敛速度快但解的分布有时候不如NSGA-II均匀SPEA2在多样性维护上很有特色但实现代码稍复杂。如果你用的是新版MATLABGlobal Optimization Toolbox里其实自带多目标遗传算法solve函数与gamultiobj不过它封裝程度高对想深入理解机理的人来说还是自己写一遍收获更大。2.2 决策变量编码与残差变量思路决策变量怎么选直接决定优化的搜索难度。最容易想到的做法是把每个时段的柴油机出力、储能充放电功率、电网交换功率都作为变量。比如24小时、4类决策变量那就是96维搜索空间。这么做也不是不行但约束处理会很痛苦功率平衡等式几乎不可能精确满足罚函数惩罚系数还特别难调。我采用的思路是“残差变量法”。核心思想是功率平衡等式里的某一项不参与随机编码而是在目标函数计算时用其他变量反推出来。具体来说我把决策变量设计成两组24小时的柴油机出力序列24小时的储能充放电功率序列。然后电网交换功率 P_grid(t) 由功率平衡公式直接计算得出不再作为独立变量P_grid(t) P_load(t) - (P_pv(t) P_wind(t) P_discharge(t) - P_charge(t) P_dg(t))这样一来只要P_grid(t)落在联络线约束范围内功率平衡等式就自动满足不需要额外的罚函数项。如果P_grid(t)越界那么再对该个体的出力做小幅修正或者直接把这个越界量作为惩罚加进去。这个“残差变量”的做法在微网调度代码里非常实用强烈推荐。储能同时充放电的问题我也在修复阶段处理如果某个时段P_ch和P_dis都大于0就按净功率折算成单一方向并对SOC递推公式重新计算。这样做虽然牺牲了一部分理论精确性但换来的是约束的严格满足对进化算法来说更友好。3. MATLAB代码实现从零搭一套可跑的调度程序3.1 数据初始化与主流程框架MATLAB代码我拆成了几个文件职责分开这样调试时不用在一个几千行的脚本里上下翻main_optimize.m主程序负责参数初始化、调用优化、输出结果obj_fun.m目标函数输入一个个体输出成本和排放两个目标值constraint_check.m约束校验返回越限量nsga2_operator.mNSGA-II核心算子非支配排序、拥挤度计算、选择、交叉、变异。先看数据初始化部分。所有基础参数我都用结构体para统一管理避免到处散落全局变量% 基础参数 para.dt 1; % 时间步长 1小时 para.T 24; % 调度周期 para.Ndg 2; % 柴油机台数 para.pv [%; ...]; % 24小时光伏预测出力 (kW) para.wind [%; ...]; % 24小时风电预测出力 (kW) para.load [%; ...]; % 24小时负荷预测 (kW) para.Ecap 100; % 储能容量 kWh para.SOC0 0.5; % 初始SOC para.SOC_min 0.1; para.SOC_max 0.9; para.Pch_max 25; % 最大充电功率 kW para.Pdis_max 25; % 最大放电功率 kW para.Pgrid_max 80; % 联络线最大交换功率 kW para.DG_min [15; 15]; % 各柴油机最小出力 para.DG_max [50; 50]; % 各柴油机最大出力 para.ramp 20; % 爬坡约束 kW/小时 para.eta_ch 0.9; % 充电效率 para.eta_dis 0.9; % 放电效率光伏、风电、负荷这组数据如果是做真实项目应该来自SCADA系统或气象预测服务如果是复现论文可以直接取典型日的公开数据或者自己按日曲线形状造一组。我在案例里用的是某微网示范工程的夏季典型日数据光伏白天明显有驼峰状风电夜间出力大负荷早晚各有一个峰值。主程序框架相对固定numIndividual 100; % 种群规模 numGen 200; % 迭代次数 numVar 48; % 决策变量维度2台柴机24小时 储能24小时 72示例 % 这里实际按动态变量数目设计见后文说明我上面只写了48维但实际代码里决策变量数量需要跟柴油机台数和调度周期严格对应。比如2台柴机24小时出力序列占48维储能充放电又占48维总维数是96维。初代种群用随机数在上下限之间均匀生成然后用“残差变量法”和约束修复逻辑得到合法个体再进行进化迭代。3.2 目标函数与约束处理的关键代码目标函数代码是整套程序的心脏。我简化后的版本长这样function [cost, emission] obj_fun(x, para) % 解码决策变量 numDg para.Ndg; P_dg x(1 : numDg*para.T); P_dg reshape(P_dg, numDg, para.T); P_bat x(numDg*para.T1 : end); P_ch max(0, P_bat); % 充电功率 P_dis max(0, -P_bat); % 放电功率负数表示充能 % 功率平衡计算电网交换功率 P_grid para.load - (sum(P_dg,1) para.pv para.wind P_dis - P_ch); % 成本目标燃料 运维 购电 启停 % 燃料成本按二次耗量曲线 cost_fuel 0; for i 1 : numDg cost_fuel cost_fuel sum(para.a(i) para.b(i).*P_dg(i,:) para.c(i).*P_dg(i,:).^2); end % 运维成本 cost_om sum(para.k_pv.*para.pv para.k_w.*para.wind ...); % 购电/售电成本 cost_grid sum(max(P_grid,0).*para.price_g min(P_grid,0).*para.price_s); % 启停成本 cost_start para.cost_start .* sum(diff([zeros(1,numDg); P_dg]~0, 1) 1); cost cost_fuel cost_om cost_grid cost_start; % 排放目标简化等效排放系数 emission sum(sum(para.emis_para * P_dg)) sum(max(P_grid,0).*para.emis_grid); end注意代码里我用了reshape把个体向量还原成矩阵这是MATLAB里处理多维决策变量最常见的方式。奥妙在于把“柴油机出力”和“充放电功率”分开编码但充放电状态用的是带符号的单一净功率值符号正负代表放电或充电。这样做的好处是规避了同时充放电的约束。约束校验我单独写了个函数检查SOC递推是否越限、爬坡是否越限、P_grid是否越限。对越限的部分我不是简单罚完就完事而是优先做局部修复比如把SOC越限的功率按比例缩回边界。修复后仍然不满意的再在目标函数上叠加一个比较大的惩罚项。分层处理下来种群里可行率提高非常多。帕累托前沿提取和最优折中解选择则在后处理阶段完成。具体实现是用非支配排序把所有个体分层第一层即帕累托前沿然后用模糊隶属度函数在所有非劣解里选折中解也就是对每个解分别计算它在两个目标上的归一化满意度再求和取总满意度最高的那个作为最终的推荐调度方案。这个方法在实际论文和报告里接受度很高因为避免了拍脑袋定权重。4. 仿真结果分析与场景验证4.1 帕累托前沿结果解读经济与环保之间的取舍跑完NSGA-II后把所有非支配解画在二维坐标上横轴是运行成本纵轴是等效排放。我实际得到的帕累托前沿是一条单调递减的曲线从左上到右下。左端点对应最低排放方案成本最高右端点对应最低成本方案排放最高。中间解则分布在成本与排放的不同权衡上。这个曲线的形状非常直观。成本最低的调度方案倾向于让柴油机在负荷高峰时段高负载运行因为本地柴油机的综合发电成本可能低于峰时段从电网购电的价格所以哪怕排放高经济上也划算。排放最低的方案则反着来尽可能压低柴油机出力把光伏和风电尽量全部消纳同时让储能在光伏大发时段充电、晚间放电缺电时宁可高价购电也不让柴油机启动。我选的折中解位于前沿中段单位成本下降带来的排放上升幅度开始变大的那个拐点附近。这个位置对应的方案兼顾了两边柴油机不频繁启停储能有较大充放电深度弃风弃光率控制在一个合理的范围内。4.2 典型调度方案输出分析把折中解对应的24小时出力曲线打出来看典型场景是整个白天光伏出力旺盛柴油机基本维持下限运行储能持续充电傍晚负荷开始爬升光伏出力衰减柴油机出力逐渐抬升晚间负荷达到晚高峰储能开始放电配合柴油机一起顶峰夜间风电出力较高柴油机回落。可以看出储能的作用主要体现在“削峰填谷”白天存光伏和风电的余量晚间高峰释放减少从电网买电的量。而柴油机则是兜底手段既受爬坡约束限制又在成本目标推动下尽可能优化使用。多台柴机之间还有一个负荷分配逻辑我代码里采用了等微增率的思想做初始化偏好不过因为NSGA-II的进化机制它自己也能在种群中找到近似最优的分配方式。此外我还加了两个验证场景一个场景是柴油机和储能都正常运行另一个场景是设置储能故障退出运行。结果很明显储能退出后系统更依赖柴油机和电网购电运行成本上升排放也上升且晚间高峰时段的联络线功率经常触顶。这从侧面验证了模型约束的有效性。5. 常见问题与排查技巧实录5.1 高频问题速查表我把实际编码过程中遇到的高频问题整理成一张表方便大家直接对照排查现象可能原因解决方案帕累托前沿只剩几个点多样性差种群规模太小或变异率过低增大种群规模到100以上变异概率提高到1/决策变量数附近SOC曲线长期贴上下边界约束只惩罚不修复个体很难回到可行域对SOC越限做功率缩放的修复处理或在初始化时就强制边界功率平衡误差大结果明显不合理功率平衡等式被粗暴罚函数处理改用残差变量法让电网交换功率由等式反推相邻时段柴油机出力突变爬坡约束不满足爬坡约束只写入文件忘了挂在目标函数里在约束校验中显式循环相邻时段违反时累加罚项成本曲线出现不合理跳变电价、耗量系数或单位不统一检查元/kWh、kW、kWh量纲尤其是购电价和售电价是否分开每次运行结果不一致无法复现随机数种子未固定主程序开头写rng(2023)或任意固定种子算法收敛慢种群可行率低罚函数惩罚系数设置不合理采用动态罚函数进化早期惩罚系数小后期逐渐加大5.2 参数整定与避坑经验NSGA-II的参数看着不多但整定起来还是有不少讲究。我常用的初值是种群规模100迭代200代交叉概率0.9变异概率取1/决策变量数模拟二进制交叉的分布指数取20多项式变异的分布指数取20。跑完看帕累托前沿的分布情况如果前沿不完整优先增加种群规模而不是迭代次数因为迭代次数上去了前沿可能还是稀疏但种群规模越大每个迭代代数上分布的个体也越多。还有几个我在代码结构上的心得常规博客里很少提到。第一目标函数里尽量少用全局变量。所有参数都塞进para结构体传进去虽然会多打几个字但调试时不会出现“某个变量在别的脚本里被改了”的诡异问题。第二决策变量边界别卡太死。比如柴油机最小出力取15kW初始化时直接在15~50kW均匀采样但爬坡约束只能靠下面几代的自然选择慢慢过滤所以初代种群可行率不会太高。不用慌把修复函数做扎实几代之后可行率就上来了。第三MATLAB的数组运算比循环快得多能用矩阵化写法就不要用几百次的for。尤其是计算两天内所有时段的燃料成本、SOC递推用矩阵整体运算可以快一个数量级。SOC递推虽然看起来有自然的时序关系实际上也可以写成向量化递推只要写成SOC SOC0 cumsum(...)即可。第四如果想做进一步扩展比如把确定性调度升级成考虑风光预测误差的随机优化代码的扩展点主要集中在数据生成和目标函数两层。可以用蒙特卡洛生成多个风光场景然后在目标函数里做期望值计算算法部分基本不用动。我在实际跑这个案例时印象最深的是“残差变量法”带来的改变。一开始我是把所有变量都放进决策向量结果跑了50代有30代都在想办法平衡功率帕累托前沿稀稀拉拉。改成电网功率作为残差反推后同样的计算量前沿质量和收敛速度都好了很多。这种“把等式约束藏进变量设计”的思路其实在很多优化领域都通用只是微网调度里的体现特别明显。另外想提醒一句网上MATLAB代码零零散散很多但如果不能回答“为什么这里要这样解码”“为什么这个约束要用罚函数”拿过来大概率也是跑不动的。真正花时间把模型和代码打通之后你会觉得多目标优化调度其实没那么玄它就是一个带约束的搜索问题耐心调参总能得到一张清晰漂亮的帕累托前沿。后续如果还有机会我再写一版把风光预测不确定性和低碳运行约束加进去的扩展版本。