2026/10/3 18:17:00

两阶段鲁棒优化微网容量配置:CCG算法与MATLAB代码实现详解

两阶段鲁棒优化微网容量配置:CCG算法与MATLAB代码实现详解 简介面向电力系统与微电网规划研究人员这份资源围绕风、光、储、燃多能互补微网给出两阶段鲁棒优化容量配置的完整Matlab实现。模型以储能、风电、光伏容量作为一阶段决策以风光燃储实际出力作为二阶段变量显式处理源荷不确定性适合开展鲁棒规划与经济调度联合研究。压缩包整体约3.76MB共19个文件包含5个m脚本CCG迭代、主问题MP与子问题SP、6个docx模型及构建过程说明、2篇pdf参考论文另有矩阵推导图片、典型日数据xlsx与caj文献从数学建模、矩阵推导到列与约束生成求解均有对应材料涵盖模型推导、算法实现与结果校验。readme与效果截图能帮助快速检验代码结果整体文件按模型构建、求解过程、参考文献分区组织便于逐模块学习。目前已有1331人学习下载适合具备一定优化基础、希望深入理解两阶段鲁棒方法在微网容量规划中实际落地的研究生与工程师。1. 两阶段鲁棒优化微网容量配置这套MATLAB代码为什么值得拆微网电源容量配置最怕的就是“规划很丰满运行很骨感”。确定性规划把风电、光伏出力当成固定值算出来的配置往往在极端天气下直接翻车而两阶段鲁棒优化把不确定性显式建模一阶段定容量、二阶段看最坏场景下的调度可行性算出来的配置虽然保守一点但真上场不会掉链子。这套资源的核心就是完整的微网两阶段鲁棒规划调度MATLAB代码配套有CCG算法文档、矩阵推导和四个典型日数据适合正在做微网容量规划、想从确定性优化过渡到鲁棒优化的研究生和工程师。你不用从零啃论文照着main.m、MP.m、SP.m这套链路就能跑通。2. 问题建模一阶段定容量、二阶段看运行先搞清决策变量归属两阶段鲁棒优化最让新手困惑的不是算法而是“哪个变量放在哪一阶段”。这个归属搞错了后面的CCG迭代全都是错的。2.1 微网结构与不确定性刻画这套代码里的微网包含风电、光伏、储能、燃气轮机四种电源。一阶段决策的是风电装机容量、光伏装机容量、储能额定容量和功率二阶段决策的是在每个不确定场景下这些设备的实际出力、储能充放电功率以及燃气轮机的出力。不确定性主要来自风电和光伏的出力波动。代码用盒式不确定集合来刻画也就是每个时段的出力都有一个波动区间再通过不确定性预算Gamma来控制“最坏情况下同时偏离的时段数”。这个Gamma后面我会专门讲怎么调因为它是保守与经济的平衡杆。2.2 目标函数与约束的两层结构目标函数是总成本最小包含两部分一阶段的投资成本按年折算和二阶段的运行成本燃料费、运维费、启停费。一阶段约束是装机容量上限、储能容量与功率的耦合关系二阶段约束就多了有功平衡、储能充放电约束、燃气轮机爬坡与出力区间、弃风弃光约束还有一个关键的东西——机组禁止运行区间代码里MP2.m就是专门处理这个的。两阶段耦合的地方在于一阶段把容量传给二阶段作为参数二阶段在最坏场景下的可行性与成本再通过割平面反馈给一阶段。这就是CCG算法的本质主问题定方案子问题找最坏场景。2.3 MP/SP/MP2三个文件的分工代码里MP.m和MP2.m都是主问题区别在于MP2.m加了机组禁止运行区间的处理SP.m是子问题它要找到让运行成本最大的不确定场景。实际跑的时候先跑MP求得当前最优容量方案再跑SP看看这个方案在最坏场景下能不能行得通不行就把SP给的割平面加到MP里继续迭代。提示别一上来就改MP.m先原样跑通再用MP2.m替换MP.m对比差异。3. CCG算法求解框架主问题、子问题、割平面怎么耦合CCGColumn and Constraint Generation是求解两阶段鲁棒优化的主流算法比Benders分解收敛快得多。这套代码里用的是经典的外逼近思路但细节上有几个地方值得展开。3.1 CCG迭代核心流程整个迭代过程可以用一句话概括主问题给出方案子问题验证方案并返回最坏场景主问题再带着新场景重算循环到上下界收敛。初始化的上界取一个大数下界取一个负大数或者用一阶段成本做初值。每一轮迭代里主问题求解得到的下界是当前最优成本把主问题决策的容量代入子问题子问题求解出来的最坏场景对应成本加上一阶段投资成本就是上界。当上下界差小于阈值迭代停止。代码里这个阈值参数一般设在1e-3到1e-4之间太松配置偏冒进太紧迭代次数暴涨。3.2 addC.m把子问题的“最坏场景”翻译成主问题约束addC.m是这套代码里最关键的文件。子问题求解后会得到一组新的不确定场景u_new和对应的连续变量y_newaddC就负责把这组场景对应的约束添加到主问题里。function mpc addC(mpc, u_new, y_new, params) % addC.m % 功能向主问题添加子问题返回的最坏场景约束割平面 % 输入mpc-当前主问题结构体u_new-新场景y_new-新场景下的出力params-参数结构体 % 输出mpc-更新后的主问题结构体添加了一组新约束 % 取出主问题当前的约束数量 num_c mpc.constraints.num; % 新约束的索引 new_idx num_c 1; % 将新场景下的出力变量约束添加到主问题中 % 这里以有功平衡约束为例 mpc.constraints.power_balance(new_idx) ... params.wind_forecast * u_new.wind ... % 风电实际出力 params.solar_forecast * u_new.solar ... % 光伏实际出力 y_new.gt y_new.storage ... % 燃气轮机储能出力 - mpc.decision.load; % 减去负荷 % 储能充放电功率约束 mpc.constraints.storage_power(new_idx) ... abs(y_new.storage_power) mpc.decision.storage_power_max; % 更新约束计数器 mpc.constraints.num new_idx; end这段代码的逻辑是主问题是一个逐渐长大的线性规划每轮迭代新增一行割平面。u_new是子问题找到的最坏场景y_new是应对该场景的可行调度方案。新增约束的意义在于让主问题的容量方案必须能应对这个场景否则就要调整容量。addC.m里你还需要关注一件事每次新增的不是一条约束而是那一整个场景下所有关联约束。代码里注释明确写了哪些是新增的对照你的目标函数去理解。3.3 收敛判据为什么要同时看上下界很多第一次跑CCG的人卡在“为什么迭代几十轮还不收敛”。常见原因是把子问题返回的客观值直接当上界用但忘了加一阶段投资成本。% 伪代码逻辑 lower_bound main_problem.obj; % 主问题目标值 upper_bound investment_cost sub_problem.obj; % 一阶段成本子问题运行成本 gap (upper_bound - lower_bound) / upper_bound; % 相对间隙 while gap tol % 继续迭代 end子问题SP.m求得的最坏场景运行成本必须加上一阶段投资成本才是真正的上界。代码里SP.m输出的objective直接是运行成本你在main.m里去拼接上界时一定要检查有没有加投资成本这一项。4. MATLAB代码实现从main.m到MP/SP的完整链路这一章我把代码的主干链路给你拆出来你拿着代码对照着看十分钟就能建立整体认知。4.1 main.m入口数据加载与迭代主循环main.m是整套代码的灵魂和一般仿真脚本不同它不是一个“读数据-调函数-出图”的线性脚本而是一个CCG迭代主循环。%% main.m 主循环节选 clear; clc; close all; % 加载四个典型日数据 load(四个典型日数据.xlsx); % 注意这里需要xlsread或readtable过渡 % 初始化主问题模型 mpc init_mp(params); % 初始化上下界 LB -1e6; UB 1e6; toll 1e-4; iter 0; while (UB - LB) / abs(UB) toll iter iter 1; % 求解主问题 [mpc, LB] solve_mp(mpc, params); % 求解子问题 [sp_obj, u_new, y_new] solve_sp(mpc.decision, params); % 计算上界 UB mpc.invest_cost sp_obj; % 添加割平面 if (UB - LB) / abs(UB) toll mpc addC(mpc, u_new, y_new, params); end fprintf(迭代次数: %d, LB%.4f, UB%.4f, gap%.4f\n, ... iter, LB, UB, (UB - LB) / abs(UB)); end核心逻辑很清楚主问题求解得到容量配置传给子问题子问题找最坏场景如果上下界没闭合就添加约束继续迭代。注意solve_mp返回的是主问题的目标值但主问题内部包含投资成本和运行成本的期望项别把它理解成纯投资成本。4.2 MP.m主问题Yalmip建模与求解器选择MP.m用Yalmip建模求解器可以选Gurobi或CPLEX。如果你本机没有Gurobi默认求解器会退回速度慢很多。%% MP.m 主问题建模节选 % 决策变量 wind_cap sdpvar(1,1); % 风电装机容量 pv_cap sdpvar(1,1); % 光伏装机容量 storage_cap sdpvar(1,1); % 储能额定容量 % 一阶段目标投资成本年值化 invest_cost 5000 * wind_cap 4000 * pv_cap 8000 * storage_cap; % 二阶段期望运行成本这里用变量表示后续割平面逐轮添加 oper_cost 0; for t 1:T % 运行成本变量燃料费、运维费 oper_cost oper_cost sum(c_gt * p_gt(t) c_om * (p_w(t) p_pv(t))); end % 总目标 Objective invest_cost oper_cost; % 约束储能容量与功率耦合 Constraints [storage_cap 2000, storage_power_max 0.25 * storage_cap]; % 求解 optimize(Constraints, Objective, sdpsettings(solver, gurobi));MP.m里有一个隐藏的技巧一阶段约束不能只给变量上限必须把储能功率与容量耦合关系写进去这是决定储能配置经济性的关键约束。代码里storage_power_max 0.25 * storage_cap是典型参数你可以根据实际储能倍率改。4.3 SP.m子问题max-min结构的对偶与大M法子问题之所以需要单独一个文件是因为它内部是一个max-min双层结构外层找最坏的u内层找对应u下成本最小的y。直接求解很难代码采用了对偶变换大M法线性化。%% SP.m 子问题求解节选 % 对偶变量 lambda sdpvar(T,1); % 有功平衡对偶乘子 mu sdpvar(T,1); % 储能状态对偶乘子 % 对偶目标最大化等价于最小化原问题对偶 dual_obj sum(lambda .* params.load_forecast) ... sum(mu .* params.storage_max); % 不确定性集合约束u在盒式区间内且偏离度受Gamma约束 Constraints_u [u_w u_w_forecast - delta_w, ... u_w u_w_forecast delta_w, ... sum(u_w - u_w_forecast) Gamma * T, ... % 预算约束 u_pv u_pv_forecast - delta_pv, ... u_pv u_pv_forecast delta_pv]; optimize(Constraints_u, -dual_obj, sdpsettings(solver,gurobi)); % 返回u_new最坏场景和对应y u_new.wind value(u_w); u_new.solar value(u_pv);这段代码最关键的是Gamma约束sum(u_w - u_w_forecast) Gamma * T它限定了最坏场景同时偏离预测值的总幅度而不是每个时段都取极端值。实际运行中Gamma取0.2~0.5经验值比较合适具体调法我在第6章展开。注意SP.m求解完需要返回u_new和y_new两个东西缺一不可。u_new是场景y_new是场景对应的调度方案addC要靠它们构造约束。4.4 文件清单速查文件功能备注main.m主循环迭代驱动、上下界更新从这里开始读MP.m主问题建模一阶段容量决策YalmipGurobiMP2.m主问题进阶版含机组禁运区间对比MP.m看差异SP.m子问题最坏场景求解对偶加大M线性化addC.m添加割平面约束整套代码的核心矩阵推导1-3.jpgCCG数学原理手推配模型文档看四个典型日数据.xlsx风电/光伏/负荷四季典型日曲线4×24时点数据两阶段鲁棒构建过程.docx建模全过程说明新手先看这个5. 避坑与常见问题我从这套平台里踩过的五个坑5.1 现象主问题迭代次数爆炸gap始终不降原因主问题没有把子问题返回的完整场景约束全部加入只加了目标相关约束丢了储能约束或功率平衡约束。很多时候addC.m里新增约束不完整导致主问题的容量方案永远不满足子问题场景。解决让子问题返回的不仅仅是不确定场景u_new还要返回该场景下的可行调度y_new。addC时把u_new和y_new对应的全部约束一起加入主问题并确认新增约束数量等于子问题中所有运行约束数量。% 检查addC后约束数量是否增加 fprintf(约束数量: %d - %d\n, num_old, mpc.constraints.num);5.2 现象一阶段投资成本为零原因目标函数中投资成本的年值化系数没有乘以容量变量或者投资成本项被放在二阶段运行成本内部。这类代码里最常见的就是把c_inv当成常数而不是和wind_cap相乘。解决检查MP.m里目标函数的第一项用5000 * wind_cap这种形式不能是5000。5.3 现象子问题返回的最坏场景永远是极端值原因不确定性集合里没有Gamma预算约束或者Gamma取太大。当Gamma≥时段数时每个时段都可以取到波动边界结果就是所有时段都取最坏值鲁棒解保守到没有实际意义。解决把Gamma设到总时段数的20%-50%。四个典型日各24小时Gamma取5-12之间。5.4 现象MP2.m运行报错提示机组禁运区间约束不可行原因燃气轮机的禁运区间和爬坡约束之间存在冲突。禁运区间把机组的可行域挖掉一块如果爬坡约束把出力限制在禁运区间内形成不可行。解决先跑MP.m确认模型框架没问题再逐步加入禁运区间约束。MP2.m里通常会用松弛变量处理禁运约束观察松弛变量取值判断哪个约束过于严苛。5.5 现象四个典型日的配置结果差异极大原因典型日数据没有归一化或者不同类型的风速、光照、负荷数据量纲不统一。比如风速是m/s光照是W/㎡直接用矩阵计算时数值量级差10倍以上导致目标函数被某一个量纲主导。解决数据导入后先做归一化处理或者将单位统一到同一基准。实际做法是在main.m的数据加载后面加一行归一化% 归一化处理 wind_data (wind_data - min(wind_data)) / (max(wind_data) - min(wind_data)); pv_data pv_data / 1000; % 按kW标准化6. 调参验证技巧让鲁棒配置从过分保守走向经济合理代码跑通后真正区分方案水平的是不确定性预算Gamma和投资成本系数的取值。我实测下来Gamma从0变化到T配置结果会发生三次质变。第一次质变发生在Gamma很小的时候比如1-3此时鲁棒解和确定性解几乎一样储能配置基本为零全靠燃气轮机扛最坏场景。第二次质变在GammaT/4附近储能开始大量配置因为风光出力波动的“深度”开始体现储能的经济性超过燃气轮机的燃料成本。第三次质变在Gamma接近T时系统开始为几乎不可能的“所有时段同时极端偏离”买单储能容量直接飙到上限经济性明显恶化。我的建议是用四个典型日分别跑三组Gamma值比如3、6、12画出容量配置变化曲线找出“容量变化不再剧烈”的拐点。那个点的Gamma就是鲁棒性和经济性的平衡点。验证方法上把优化得到的容量配置代入SP.m改掉不确定集合里的Gamma参数改成历史真实波动数据回放一遍看日运行成本是否突破配置线。我更习惯的做法是把历史数据按季度切片每个季度抽一个“最差日”把那天数据喂给SP.m看系统的运行成本是否超过设计上界。这套验证逻辑其实就是把鲁棒优化的“最坏场景”从数学世界拉回物理世界用真实天气数据检验。从那以后我每次拿到两阶段鲁棒代码都会先跑通原始例程再替换成自己的典型日数据最后手动改回Gamma验证参数敏感性确认没有单点失效之后才敢把模型拿去做方案比较。CCG这条链路迭代轮次越多越不能急每一轮割平面的质量直接决定最终容量配置能不能在工程上落地。希望帮到你。本文还有配套的精品资源点击获取