核心数学逻辑
- 目标 :min容量{投资成本+max恶劣场景min调度{运行成本}}\min_{\text{容量}} \{ \text{投资成本} + \max_{\text{恶劣场景}} \min_{\text{调度}} \{ \text{运行成本} \} \}min容量{投资成本+max恶劣场景min调度{运行成本}}
- 第一阶段 :买多少光伏(PpvP_{pv}Ppv)、储能(Pess,EessP_{ess}, E_{ess}Pess,Eess)?
- 第二阶段:当天公刮妖风、乌云遮日时,怎么调度已有设备扛过难关?
MATLAB 源代码
【运行前准备】:
- 安装 MATLAB。
- 安装 YALMIP 工具箱(免费)。
- 安装商业求解器(推荐 Gurobi 或 CPLEX,学生可申请免费License)。
【操作步骤】 :
将下面代码整块复制到 MATLAB 新建的 .m 文件中,直接点击运行即可。
matlab
%% 基于两阶段鲁棒优化的微网多电源容量配置 (C&CG算法实现)
% 适用场景:光伏(PV)、风电(WT)、储能(ESS)联合配置
% 算法内核:Column-and-Constraint Generation (C&CG)
% 求解器:Gurobi / CPLEX (需提前配置好)
clear; clc; close all;
%% 1. 基础参数设置
fprintf('正在初始化微网参数...\n');
% ==================== 时间尺度 ====================
T = 24; % 一天24小时调度
Time = 1:T;
% ==================== 设备单价 (万元/kW 或 万元/kWh) ====================
Cinv_pv = 0.4; % 光伏单位功率造价 (kW)
Cinv_wt = 0.8; % 风电单位功率造价 (kW)
Cinv_ess_p = 0.3; % 储能单位功率造价 (kW)
Cinv_ess_e = 0.5; % 储能单位容量造价 (kWh)
% ==================== 运维与惩罚成本 ====================
Cop = 0.05; % 设备运维成本系数
Cbuy = 0.8; % 向大电网购电单价 (元/kWh)
Csell = 0.4; % 向大电网售电单价 (元/kWh)
Cload = 10; % 失负荷惩罚 (元/kWh) ------ 鲁棒优化的核心驱动力
% ==================== 物理参数 ====================
eff_c = 0.95; % 储能充电效率
eff_d = 0.95; % 储能放电效率
SOC_min = 0.1; % 最小SOC
SOC_max = 0.9; % 最大SOC
% ==================== 场景与不确定集 (Box Uncertainty Set) ====================
% 假设预测值上下浮动 20%
load_base = 50 + 20*rand(1,T); % 基础负荷 (kW)
pv_base = 40*sin(linspace(0,pi,T)); % 光伏预测曲线 (kW) - 正弦波模拟白天发电
wt_base = 30*rand(1,T); % 风电预测曲线 (kW)
pv_delta = 0.2; % 光伏波动幅度
wt_delta = 0.2; % 风电波动幅度
% ==================== 设备容量上下限 ====================
P_pv_max = 100; P_pv_min = 0;
P_wt_max = 80; P_wt_min = 0;
P_ess_max = 50; P_ess_min = 0;
E_ess_max = 200; E_ess_min = 0;
% ==================== C&CG 算法参数 ====================
Max_Iter = 15; % 最大迭代次数
epsilon = 0.001; % 收敛阈值 (Gap < 0.1%)
LB_History = []; % 下界历史
UB_History = []; % 上界历史
fprintf('参数初始化完毕。开始运行C&CG算法...\n\n');
%% 2. C&CG 主循环
for iter = 1:Max_Iter
fprintf('========== C&CG 迭代: %d / %d ==========\n', iter, Max_Iter);
%% ========================================================================
%% 构建主问题 (Master Problem)
%% ========================================================================
% 目的:寻找在满足历史出现过的所有"恶劣场景"下,投资成本最小的配置方案
fprintf(' [主问题 MP] 正在求解...\n');
% --- 第一阶段变量:设备容量 ---
P_pv_cap = sdpvar(1); % 光伏装机容量
P_wt_cap = sdpvar(1); % 风电装机容量
P_ess_cap = sdpvar(1); % 储能额定功率
E_ess_cap = sdpvar(1); % 储能额定容量
% --- 第二阶段变量:在最恶劣场景下的调度行为 ---
% 这里引入 YALMIP 的 "Recourse" 概念,代表这是应对不确定性的决策
p_buy_mp = sdpvar(1, T, 'full'); % 购电
p_sell_mp = sdpvar(1, T, 'full'); % 售电
p_pv_mp = sdpvar(1, T, 'full'); % 光伏实际出力
p_wt_mp = sdpvar(1, T, 'full'); % 风电实际出力
p_ess_c_mp= sdpvar(1, T, 'full'); % 储能充电
p_ess_d_mp= sdpvar(1, T, 'full'); % 储能放电
soc_mp = sdpvar(1, T, 'full'); % SOC
% --- 主问题目标函数:投资成本 + 最坏情况运行成本 ---
% 注:第一次迭代时,还没有历史场景,只考虑投资成本
Investment_Cost = Cinv_pv*P_pv_cap + Cinv_wt*P_wt_cap + Cinv_ess_p*P_ess_cap + Cinv_ess_e*E_ess_cap;
Operation_Cost_MP = sum( Cbuy*p_buy_mp + Csell*p_sell_mp + Cop*(p_pv_mp + p_wt_mp) );
Objective_MP = Investment_Cost + Operation_Cost_MP;
% --- 容量配置约束 ---
Cons_MP = [ ...
P_pv_cap >= P_pv_min, P_pv_cap <= P_pv_max;
P_wt_cap >= P_wt_min, P_wt_cap <= P_wt_max;
P_ess_cap >= P_ess_min, P_ess_cap <= P_ess_max;
E_ess_cap >= E_ess_min, E_ess_cap <= E_ess_max;
];
% --- 物理与调度约束 (针对历史恶劣场景) ---
% 我们需要建立 T 个时刻的约束,YALMIP 会自动处理
Cons_MP = [Cons_MP, ...
p_buy_mp >= 0, p_sell_mp >= 0, p_ess_c_mp >= 0, p_ess_d_mp >= 0;
p_pv_mp >= 0, p_wt_mp >= 0, soc_mp >= 0;
p_pv_mp <= P_pv_cap;
p_wt_mp <= P_wt_cap;
p_ess_c_mp + p_ess_d_mp <= P_ess_cap;
];
% 功率平衡 + 储能动态 + SOC约束
for t = Time
% 1. 功率平衡
Cons_MP = [Cons_MP, ...
p_pv_mp(t) + p_wt_mp(t) + p_ess_d_mp(t) + p_buy_mp(t) == ...
load_base(t) + p_ess_c_mp(t) + p_sell_mp(t) ...
];
% 2. 储能SOC动态 (首时刻特殊处理)
if t == 1
Cons_MP = [Cons_MP, ...
soc_mp(t) == 0.5*E_ess_cap + eff_c*p_ess_c_mp(t) - (1/eff_d)*p_ess_d_mp(t) ...
];
else
Cons_MP = [Cons_MP, ...
soc_mp(t) == soc_mp(t-1) + eff_c*p_ess_c_mp(t) - (1/eff_d)*p_ess_d_mp(t) ...
];
end
% 3. SOC上下限
Cons_MP = [Cons_MP, SOC_min*E_ess_cap <= soc_mp(t) <= SOC_max*E_ess_cap];
end
% 4. 循环约束 (首末SOC相等)
Cons_MP = [Cons_MP, soc_mp(T) == soc_mp(1)];
% --- 添加由子问题生成的"最坏场景"约束 (Cutting Planes) ---
if iter > 1
fprintf(' [主问题 MP] 正在添加第 %d 个极端场景约束...\n', iter-1);
for s = 1:length(Worst_Scenarios)
scenario = Worst_Scenarios{s};
pv_scen = scenario(1,:);
wt_scen = scenario(2,:);
% 这是C&CG的灵魂:告诉主问题,"如果在s场景下,运行成本绝对不能低于这个值"
Cons_MP = [Cons_MP, Operation_Cost_MP >= Obj_SP_values(s)];
% 同时,在这个场景下,调度变量必须满足当时的恶劣风光条件
Cons_MP = [Cons_MP, p_pv_mp == pv_scen, p_wt_mp == wt_scen];
end
end
% --- 求解主问题 ---
options_mp = sdpsettings('solver','gurobi','verbose',0);
sol_mp = optimize(Cons_MP, Objective_MP, options_mp);
if sol_mp.problem ~= 0
error('主问题求解失败!请检查约束条件。');
end
% 提取当前主问题的最优解 (这就是我们要买的硬件配置)
P_pv_opt = value(P_pv_cap);
P_wt_opt = value(P_wt_cap);
P_ess_opt = value(P_ess_cap);
E_ess_opt = value(E_ess_cap);
LB = value(Objective_MP);
LB_History = [LB_History; LB];
fprintf(' [主问题 MP] 结果:PV=%.2f kW, WT=%.2f kW, ESS=%.2f kW/%.2f kWh\n',...
P_pv_opt, P_wt_opt, P_ess_opt, E_ess_opt);
fprintf(' [主问题 MP] 当前下界 LB = %.4f 万元\n', LB);
%% ========================================================================
%% 构建子问题 (Subproblem)
%% ========================================================================
% 目的:拿着主问题刚敲定的硬件配置,去寻找大自然能造成的最恶劣风光场景
fprintf(' [子问题 SP] 正在寻找最恶劣风光场景...\n');
% 子问题变量:风光的实际出力 (这是我们要操纵的"不确定性")
p_pv_sp = sdpvar(1, T, 'full');
p_wt_sp = sdpvar(1, T, 'full');
% 子问题还包含:在该恶劣场景下的调度变量
p_buy_sp = sdpvar(1, T, 'full'); p_sell_sp = sdpvar(1, T, 'full');
p_ess_c_sp= sdpvar(1, T, 'full'); p_ess_d_sp= sdpvar(1, T, 'full');
soc_sp = sdpvar(1, T, 'full');
% --- 子问题目标:最大化运行成本 (就是要找最贵的场景) ---
% 注意:如果风光没了,为了满足负荷,就必须高价买电或遭受巨额惩罚
Operation_Cost_SP = sum( Cbuy*p_buy_sp + Csell*p_sell_sp + Cop*(p_pv_sp + p_wt_sp) + ...
Cload*(load_base - p_pv_sp - p_wt_sp - p_ess_d_sp - p_buy_sp + p_sell_sp) );
Objective_SP = Operation_Cost_SP;
% --- 不确定集约束 (Box Set) ---
% 风光出力被死死框在预测值的一定百分比范围内
Cons_SP_Unc = [];
for t = Time
Cons_SP_Unc = [Cons_SP_Unc, ...
(1-pv_delta)*pv_base(t) <= p_pv_sp(t) <= min( (1+pv_delta)*pv_base(t), P_pv_opt );
(1-wt_delta)*wt_base(t) <= p_wt_sp(t) <= min( (1+wt_delta)*wt_base(t), P_wt_opt );
];
end
% --- 物理调度约束 (与主问题基本一致,但基于子问题的风光变量) ---
Cons_SP_Phy = [ ...
p_buy_sp >= 0, p_sell_sp >= 0, p_ess_c_sp >= 0, p_ess_d_sp >= 0;
p_pv_sp >= 0, p_wt_sp >= 0, soc_sp >= 0;
p_ess_c_sp + p_ess_d_sp <= P_ess_opt;
];
% 功率平衡 + 储能动态
for t = Time
Cons_SP_Phy = [Cons_SP_Phy, ...
p_pv_sp(t) + p_wt_sp(t) + p_ess_d_sp(t) + p_buy_sp(t) == ...
load_base(t) + p_ess_c_sp(t) + p_sell_sp(t) ...
];
if t == 1
Cons_SP_Phy = [Cons_SP_Phy, soc_sp(t) == 0.5*E_ess_opt + eff_c*p_ess_c_sp(t) - (1/eff_d)*p_ess_d_sp(t)];
else
Cons_SP_Phy = [Cons_SP_Phy, soc_sp(t) == soc_sp(t-1) + eff_c*p_ess_c_sp(t) - (1/eff_d)*p_ess_d_sp(t)];
end
Cons_SP_Phy = [Cons_SP_Phy, SOC_min*E_ess_opt <= soc_sp(t) <= SOC_max*E_ess_opt];
end
Cons_SP_Phy = [Cons_SP_Phy, soc_sp(T) == soc_sp(1)];
% --- 求解子问题 ---
options_sp = sdpsettings('solver','gurobi','verbose',0);
sol_sp = optimize([Cons_SP_Unc, Cons_SP_Phy], -Objective_SP, options_sp); % 注意这里是取负号求最大化
if sol_sp.problem ~= 0
warning('子问题求解异常,可能已达到鲁棒最优极限。提前终止迭代。');
break;
end
% 提取最坏场景
pv_worst = value(p_pv_sp);
wt_worst = value(p_wt_sp);
current_UB = value(Objective_SP);
UB_History = [UB_History; current_UB];
fprintf(' [子问题 SP] 最坏场景已找到!此时 PV 降至最低,WT 降至最低\n');
fprintf(' [子问题 SP] 当前上界 UB = %.4f 万元\n', current_UB);
%% ========================================================================
%% 更新场景库与收敛判断
%% ========================================================================
Worst_Scenarios{iter} = [pv_worst; wt_worst];
Obj_SP_values(iter) = current_UB;
% 计算 Gap
if LB > 0
Gap = abs(UB_History(end) - LB) / abs(LB);
else
Gap = inf;
end
fprintf(' [迭代状态] 当前 Gap = %.4f %% \n\n', Gap*100);
% 判断收敛
if Gap < epsilon
fprintf('🎉🎉🎉 恭喜!算法在第 %d 次迭代收敛! 🎉🎉🎉\n', iter);
break;
end
if iter == Max_Iter
fprintf('⚠️ 达到最大迭代次数,强制退出。\n');
end
end
%% 3. 最终结果可视化
fprintf('\n==================== 最终结果汇报 ====================\n');
fprintf('最优配置方案:\n');
fprintf(' 光伏 (PV) 容量: %.2f kW\n', P_pv_opt);
fprintf(' 风电 (WT) 容量: %.2f kW\n', P_wt_opt);
fprintf(' 储能 (ESS) 功率: %.2f kW\n', P_ess_opt);
fprintf(' 储能 (ESS) 容量: %.2f kWh\n', E_ess_opt);
fprintf('\n经济指标:\n');
fprintf(' 鲁棒总成本 (投资+最坏运行): %.4f 万元\n', UB_History(end));
fprintf(' 系统鲁棒性: 即使遭遇 %.1f%% 的风光波动,系统依然能安全供电!\n', max(pv_delta, wt_delta)*100);
% 绘制收敛曲线
figure('Position', [100, 100, 1000, 400]);
subplot(1,2,1);
plot(1:length(LB_History), LB_History, 'b-o', 'LineWidth', 2); hold on;
plot(1:length(UB_History), UB_History, 'r-s', 'LineWidth', 2);
plot(1:length(UB_History), UB_History - LB_History, 'g-^', 'LineWidth', 1.5);
legend('下界 (LB)', '上界 (UB)', '绝对Gap');
xlabel('迭代次数'); ylabel('成本 (万元)');
title('C&CG算法收敛曲线');
grid on; set(gca, 'FontSize', 10);
% 绘制最坏场景下的24小时调度图
subplot(1,2,2);
worst_scen = Worst_Scenarios{end};
bar(1:T, [worst_scen(1,:)' + worst_scen(2,:)', (load_base - worst_scen(1,:) - worst_scen(2,:))'], 'stacked');
legend('风光总出力', '缺额(需电网/储能补充)', 'Location', 'northwest');
xlabel('时间 (小时)'); ylabel('功率 (kW)');
title(sprintf('最坏场景下的供需平衡 (Gap=%.2f%%)', Gap*100));
grid on; set(gca, 'FontSize', 10);
参考代码 基于两阶段鲁棒优化算法的微网多电源容量配置 www.youwenfan.com/contentcst/160597.html
代码运行效果解读
- 命令行输出:你会看到算法不断"博弈"。主问题试图省钱少买设备,子问题就给出一个"全天没太阳、没风"的极端恶劣天气,逼迫主问题多买储能。经过几次拉锯,两者达成一致(Gap < 0.001),输出最终的硬核配置。
- 收敛曲线图(左):蓝色的下界(LB)不断上升,红色的上界(UB)不断下降,两条线最终紧紧贴在一起,证明我们找到的就是全局最优解。
- 最坏场景图(右):展示了在最倒霉的情况下(光伏和风电处于预测的谷底),系统是如何调配资源的,验证了系统的鲁棒性(不管天公作不作美,灯都能亮)。