C&CG(列与约束生成)算法,来解决“风光随机性”下的微网鲁棒配置问题

核心数学逻辑

  • 目标 :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 源代码

【运行前准备】

  1. 安装 MATLAB。
  2. 安装 YALMIP 工具箱(免费)。
  3. 安装商业求解器(推荐 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

代码运行效果解读

  1. 命令行输出:你会看到算法不断"博弈"。主问题试图省钱少买设备,子问题就给出一个"全天没太阳、没风"的极端恶劣天气,逼迫主问题多买储能。经过几次拉锯,两者达成一致(Gap < 0.001),输出最终的硬核配置。
  2. 收敛曲线图(左):蓝色的下界(LB)不断上升,红色的上界(UB)不断下降,两条线最终紧紧贴在一起,证明我们找到的就是全局最优解。
  3. 最坏场景图(右):展示了在最倒霉的情况下(光伏和风电处于预测的谷底),系统是如何调配资源的,验证了系统的鲁棒性(不管天公作不作美,灯都能亮)。
相关推荐
谭欣辰1 小时前
C++ 版Dijkstra 算法详解
c++·算法·图论
李白的天不白1 小时前
读到数据为undefind是的几种情况
开发语言·javascript·ecmascript
wayz112 小时前
Day 11 编程实战:XGBoost金融预测与调参
算法·机器学习·金融·集成学习·boosting
念越2 小时前
算法每日一题 Day07|双指针求解和为S的两个数
算法·力扣
LeocenaY2 小时前
C语言面试题总结
c语言·开发语言·数据结构
qeen872 小时前
【算法笔记】双指针及其经典例题解析
c++·笔记·算法·双指针
黎阳之光2 小时前
黎阳之光:以视频孪生+全域感知,助力低空经济破局突围
大数据·人工智能·算法·安全·数字孪生
爱吃芹菜炒肉2 小时前
Chapter 16: Power Management
服务器·c语言·网络·tcp/ip·pcie
CM莫问2 小时前
详解机器学习中的马尔可夫链
人工智能·算法·机器学习·概率论·马尔可夫·马尔科夫