☰
数据中心微网两阶段鲁棒规划:从EI论文到Matlab代码实现
2026/10/10 12:54:17 网站建设 项目流程

拿到EI论文去做复现,最头疼的往往不是公式看不懂,而是论文里一句"通过两阶段鲁棒优化求解"带过的内容,落到Matlab里却要搭出一套能跑的完整框架。这篇项目对应的正是这个场景——数据中心微网规划,考虑灵活性约束,用两阶段鲁棒方法处理风光不确定性,最终输出Matlab代码。本篇就把这个复现项目的完整思路、数学模型、代码架构和调试经验从头到尾捋一遍,给同样在做EI复现、研究生课题或工程项目选型的读者做个参照。

先说清楚这个项目到底在做什么。规划问题的本质是"花钱买能力"——在建设阶段决定储能配多大、燃气轮机选什么型号、光伏装多少容量。但因为未来有大量不确定性,拍脑袋定的建设方案可能在某个极端场景下根本撑不住运行。两阶段鲁棒规划就是解决这个问题的标准思路:第一阶段做投资决策(现在就要拍板),第二阶段等不确定场景暴露后再做运行决策(日后再调整)。数据中心微网的特殊性在于负荷不是普通居民负荷,IT设备7×24小时运行,UPS系统本身带储能属性,制冷和IT负荷强耦合,这些都会直接影响容量配置的结果。


1. 问题拆解:数据中心微网的规划到底难在哪

1.1 数据中心负荷:不能当普通负荷处理的三个原因

数据中心微网和常规园区微网最大的区别在负荷侧。普通工业园区负荷曲线再复杂,本质上还是一个"被动用电"的刚性负荷。但数据中心不一样,我实际建模时发现至少有三个方面必须特殊处理。

第一是IT负荷的强时变性和高基荷特性。服务器负载跟随业务流量波动,昼夜差异虽然存在,但最低负载率也不会太低,这和居民负荷"夜间接近零"的特性完全不同。如果按常规微网规划的思路,把负荷曲线取个典型日就完事,配置出来的储能容量一定偏大或偏小。

第二是制冷系统的耦合关系。数据中心PUE通常要求在1.2到1.5之间,制冷功耗与IT负载直接相关,不能孤立建模。规划阶段如果只考虑IT负荷而忽略制冷耦合,微网的峰值负荷会被严重低估。

第三是UPS系统的储能属性。数据中心为了保证供电可靠性必备UPS,这些电池在正常运行状态下处于浮充状态,但从微网角度看,它本身就是一套天然的储能资源,可以参与削峰填谷和应急供电。规划代码里如果不把UPS的充放电能力计入灵活性资源,就等于白白浪费了一部分可调节能力。

1.2 "灵活性"在投资决策层面的量化方式

规划问题里讲的灵活性,和运行调度里讲的灵活性不是一个维度。运行层面的灵活性是"当前系统还有多少上调/下调空间",规划层面的灵活性则是"以后系统能提供多少上调/下调空间"。

在这套代码里,我主要用三个指标来衡量规划方案提供的灵活性:

  • 向上备用能力:光伏出力骤降或负荷突增时,燃气轮机和储能能在规定时间内增加的出力上限
  • 向下备用能力:光伏大发而负荷低谷时,系统能消纳的多余电量和储能吸收能力
  • 保障时长:储能以额定功率放电能坚持的时间,UPS应急供电类似

这些指标最终都转化为规划模型的约束条件和目标函数里的成本项。也就是说,灵活性不是算完之后才看的指标,而是全程参与投资决策的约束条件。

1.3 为什么要用两阶段鲁棒而不是随机规划或场景法

很多人一上来就想用随机规划处理不确定性,但数据中心微网的一个现实问题是:我们很难拿到可信的风光出力概率分布。历史数据不够长、气候变化导致分布漂移、极端天气概率被低估——这些都会让"假设分布已知"的随机规划结果变得不可靠。

两阶段鲁棒优化的思路完全不同。它不需要假设概率分布,只需要给出不确定集合,比如"光伏出力在预测值的±30%范围内波动,同时波动的预测节点总数不超过某个上限"。然后模型自动寻找最恶劣的场景,保证即便在这个最恶劣场景下系统依然安全运行。

两阶段的结构和微网规划天然匹配:投资决策是当下必须做的选择(第一阶段),运行决策可以等不确定性暴露后再调整(第二阶段)。这种时序结构用数学语言表达就是:

  • 第一阶段:决定储能容量、燃气轮机选型、光伏容量
  • 第二阶段:在不同不确定性场景下决定出力、存储、购售电等运行计划

对比常规场景法,两阶段鲁棒最大的优势是保守度可控——通过调节不确定集合的边界,可以从"完全不考虑不确定"平滑过渡到"准备应对极端情况",这也是EI论文里最常见的处理方式。


2. 数学模型的建立:从确定性模型到两阶段鲁棒

2.1 先从确定性模型出发,把变量和约束理清楚

复现论文时最忌讳一上来就写鲁棒模型,那样连基本的变量流转都容易搞混。我的建议是先把确定性模型搭出来,跑通了再往上加不确定性层。这套代码里确定性模型的核心变量和约束如下表所示。

变量/约束具体内容说明
投资变量x储能容量、燃气轮机额定功率、光伏装机容量0-1变量控制是否建设某个候选设备
运行变量y储能充放电功率、燃气轮机出力、光伏出力、购售电功率每个典型时段一组
功率平衡约束光伏+燃气轮机+储能放电+购电=负荷+储能充电+售电数据中心负荷包含IT和制冷
储能约束SOC递推、充放电功率上限、容量上下限注意充放电不能同时发生的约束处理
燃气轮机约束出力上下限、爬坡速率燃气轮机是灵活性主力
互联电网约束购电容量上限数据中心允许从电网购电,但容量受限

目标函数是总成本最小化,包括投资成本(等年值折算)和运行成本(燃料费、购电费、运维费)。确定性模型可以用这样的Matlab骨架来描述:

% 确定性模型骨架 x = binvar(ngen, 1); % 投资决策变量 y = sdpvar(nt, nbus, nvar); % 运行决策变量 Constraints = []; Constraints = [Constraints, investment_capacity(x)]; Constraints = [Constraints, power_balance(y)]; Constraints = [Constraints, storage_dynamics(y)]; Constraints = [Constraints, grid_import_limit(y)]; Objective = investment_cost(x) + operation_cost(y); optimize(Constraints, Objective);

这个骨架代码虽然简单,但复现时大部分错误其实都发生在这一步:约束漏了、变量维度对不上、某个时段变量写串了。

2.2 引入不确定性集合:盒式和预算约束的选择

跑通确定性模型之后,开始加入不确定性层。这里我采用的是带预算约束的盒式不确定集,这是鲁棒优化里最常见的构造方式。

盒式集合就是每个不确定参数给一个波动范围:

  • 光伏出力不确定量在[-ΔP, +ΔP]范围内,ΔP通常取预测值的20%~30%
  • 负荷不确定量在[-ΔL, +ΔL]范围内

但纯粹盒式集合有一个问题:所有参数同时取最坏值,结果过于保守。实际运行中不可能所有光伏电站同时在最坏偏差,所以引入预算约束(Budget Constraint)来控制同时取极值的不确定参数个数。数学上写成:

  • |z|∞ ≤ 1(每个参数的偏离幅度上限)
  • ‖z‖1 ≤ Γ(所有参数偏离的总量上限)

Γ就是预算参数,取值越大越保守,Γ=0时退化为确定性模型。实际代码中可以根据投资者风险偏好调节Γ,这也是复现测试里最常用的敏感度分析对象。

2.3 两阶段鲁棒模型的整体结构

现在把不确定集嵌入到模型里,两阶段鲁棒模型写成:

  • min (x) {投资成本 + max (u) min (y) {运行成本}}
  • 约束条件:第一阶段约束 + 第二阶段可行性约束(对所有u∈U)

用自然语言描述就是:投资者先做设备选型决策x,然后大自然"恶意地"给出最坏的不确定场景u,运行调度人员针对这个场景作最优运行决策y。规划方案必须保证:无论自然怎么恶意,系统都能找到一个可行的运行方案。

这种min-max-min嵌套结构的难点在于,它不是标准优化问题,没法直接丢给Cplex/Gurobi/求解器。目前主流的处理方法是把外层min和内层max-min交替求解,这就是CCG算法的用武之地。

2.4 为什么用CCG:和Benders分解的对比

复现这类论文时,绕不开的一个选择是用Benders分解还是用C&CG(Column and Constraint Generation,列与约束生成)。

经典的Benders分解思路是把max-min问题对偶,向主问题添加Benders割。每一步迭代只添加一个约束(割平面),收敛慢,而且对偶问题不可行时处理很麻烦。

CCG算法则在主问题中直接引入最坏场景对应的完整运行变量和约束,每次迭代添加一批变量和约束。变量数增加更快,但收敛速度远快于Benders,通常10次以内迭代就能收敛到1e-4的水平。实际测试中CCG在30节点以下微网模型上的表现非常稳定,这也是论文作者选择CCG作为求解框架的原因。

CCG的完整流程在代码实现部分详细展开。


3. Matlab代码架构:从数据准备到CCG迭代落地

3.1 环境准备:Yalmip + 商业求解器的正确搭配

先把环境配置说清楚。我的复现环境是Matlab R2022b、Yalmip最新版、默认求解器用Cplex,也可以用Gurobi。Yalmip封装了建模接口,负责把sdpvar、约束和目标转成求解器能识别的标准形式;求解器负责真正的数学求解。

安装步骤不复杂,网上教程很多,但有两个坑需要注意:

  • 安装完Yalmip后,务必运行yalmiptest,它会自动检测当前PATH下可用的所有求解器,确认Cplex是否被识别
  • 如果Cplex和Gurobi同时存在,Yalmip默认选的可能是Gurobi,想指定Cplex求解器时需要在每次optimize之前加一行assign('cplex', Cplex);或者用sdpsettings('solver','cplex')指定

验证是否安装成功的代码很简单:

sdpvar x; optimize([x >= 0, x <= 1], x);

如果能正常返回最优解且不报错,说明Yalmip建模链路已经没问题。

3.2 数据准备的整理方式

复现EI论文最耗时间的其实是数据整理。论文里通常只有一张网架图和一些典型参数,你要把它们组织成Matlab可用的结构体。我习惯按功能分成几个struct:

% 数据组织示例 data.gen.unit_cost = [1200, 800]; % 机组单位投资成本 data.gen.candidate_cap = [0.5, 1.0]; % 候选容量 data.storage.energy_cap = [1.0, 2.0]; % 储能候选容量 data.storage.power_ratio = 0.5; % 充放电功率与容量的比例 data.load.IT_curve = load('it_load.mat'); % IT负荷时序 data.load.PUE_max = 1.5; % PUE上限 data.solar.forecast = load('solar.mat'); % 光伏预测值 data.solar.deviation = 0.3; % 波动范围30% data.grid.import_max = 1.0; % 购电上限 data.delta_t = 1; % 调度时段,单位小时

负荷数据和光伏数据建议存成矩阵:每行是一个时段,每列代表一类负荷或一个光伏电站。规划模型常用的典型日数量在12到24个之间,每个典型日24个时段。如果论文给的典型日太多,可以先按K-means聚类缩减到可控数量。

3.3 主问题(MP)的Yalmip建模

CCG迭代框架里,主问题是这样的结构:给定一组已知的不确定场景u(第一轮可能只有名义场景),求投资决策和这些场景下的运行决策,使总成本最小。

function [x_opt, obj_mp, LB] = solve_MP(MP_data, scenarios) % 主问题求解 x = binvar(ngen, 1); y = sdpvar(nt, ns, nvar); % 对所有已知场景建运行变量 Constraints = MP_data.base_constraints(x); for s = 1:length(scenarios) % 对每个已知场景添加运行约束 Constraints = [Constraints, operation_constraints(y(:,:,s), scenarios(s))]; % 目标函数累加该场景的运行成本 obj = obj + operation_cost(y(:,:,s), scenarios(s)); end obj = investment_cost(x) + obj / length(scenarios); optimize(Constraints, obj, sdpsettings('solver','cplex')); LB = value(obj); x_opt = value(x); end

主问题求解得到的是一个下界(LB)。因为主问题只是在部分场景下求最优,真实最优不可能比这个更小,所以LB是全局最优值的下界。

3.4 子问题(SP):最恶劣场景的识别

子问题是CCG的核心。它的结构是在已知投资决策x*的前提下,找能让运行成本最大的不确定性场景u,以及在那个场景下的最优运行方案y。

%- 数学形式: max_{u in U} min_{y feasible(x*,u)} d^T y

这是一个max-min双层问题,不能直接求。解决办法是把它转成单层优化。由于内层问题是一个线性规划(LP),一定满足强对偶条件,可以对偶成max形式,于是整个子问题变成单层max问题:

function [u_opt, obj_sp, UB] = solve_SP(SP_data, x_opt) % 子问题:通过强对偶转化为单层max u = sdpvar(nu, 1); % 不确定量 dual_vars = sdpvar(ndual, 1); % 对偶变量 Constraints = SP_data.dual_constraints(u, dual_vars, x_opt); obj = SP_data.dual_objective(u, dual_vars, x_opt); % 目标取max,因为对偶后是max问题 optimize(Constraints, -obj, sdpsettings('solver','cplex')); % 注意用负号 u_opt = value(u); UB = value(obj); end

子问题求得的上界(UB)才是当前最恶劣场景下的总成本,这才是真实可行成本的上界。

3.5 UB与LB的更新逻辑和收敛判定

CCG迭代过程中,用主问题求LB,把x传给子问题求最坏场景u和UB,然后再把u*传回主问题添加新场景。每一次循环都让LB上升、UB下降,当两者差距小于设定阈值时收敛。

% CCG主循环框架 LB = -inf; UB = inf; k = 0; scenarios_list = {nominal_scenario}; % 第一轮只有名义场景 while (UB - LB) / UB > epsilon && k < max_iter k = k + 1; % 1. 求解主问题 [x_k, ~, LB] = solve_MP(data, scenarios_list); % 2. 求解子问题,找最恶劣场景 [u_k, obj_sp, UB] = solve_SP(data, x_k); % 3. 将新场景加入主问题的候选场景列表 scenarios_list{end+1} = u_k; fprintf('Iter %d: LB=%.4f, UB=%.4f, Gap=%.4f\n', ... k, LB, UB, (UB - LB)/UB); end

这里有个新手容易混淆的点:第一轮主问题只有一个名义场景,求出的解往往很乐观,LB可能非常小;子问题计算出的UB却可能因为某个极端场景而非常大。所以第一轮Gap通常很大,这很正常,不要慌张。随着迭代继续,Gap会快速收窄。实测下来,5到10轮基本能收到1%以下。


4. 结果验证与灵敏度分析:跑通代码后必须做的几件事

4.1 检查最优解的投资结构是否符合直觉

跑出最优投资方案后,第一步不是急着分析数值,而是检查结果是否符合工程直觉。

我在复现过程中遇到过一种情况:鲁棒优化结果完全放弃了燃气轮机,全靠储能+购电撑极端场景。表面看成本也能算过去,但仔细检查发现是因为燃气轮机的爬坡约束写得过紧,导致它在这种模型里根本起不到作用,鲁棒场景下爬坡跟不上,投资回报变成负的。

检查方法很简单:把最优投资结果打印出来,结合典型日运行图看每个设备在高峰和低谷时段是否真的出力合理。比如储能容量配了2MWh,但运行结果显示每天只充放0.5MWh,那说明约束条件或目标函数里有问题,大概率是容量约束写错了。

4.2 用确定性方案做对比:鲁棒优化的"价格"到底值不值

这是复现EI论文必备的对比实验,也是审稿人最常要求的内容。跑一组对照:一组用确定性模型(不考虑不确定),一组用两阶段鲁棒模型(考虑不确定),对比投资成本、运行成本和最坏场景下的安全表现。

我的实测经验是,鲁棒方案相比确定性方案,储能容量通常会增加20%~40%,燃气轮机装机可能不变或略增,总投资成本上升5%~15%,但最坏场景下的运行成本显著更平稳。差异越大,说明不确定性对系统的影响越明显,论文里的"考虑灵活性"也就越有价值。

如果对比结果差异很小,往往说明不确定性集合取得太小,或者数据中心负荷本身就是高度稳定的,需要适当加大ΔP和预算参数Γ再测。

4.3 预算参数Γ的灵敏度扫描

Γ是两阶段鲁棒模型中最核心的参数,控制模型的保守程度。我在代码里做了一个专门的扫描实验:Γ从0取到最大可能值,每隔一个步长求解一次完整CCG问题,记录每种情况下的最优投资方案和总成本。

画出来的曲线非常有参考价值:

  • Γ=0时,方案就是确定性方案
  • 随着Γ增大,光伏容量大概率会缩减,储能容量大概率会增加
  • 总成本呈阶梯上升趋势,但在某一点后趋于平缓

这个拐点说明系统的灵活性已经能够承受大部分不确定性,再增加保守度只会浪费投资。论文里经常提到的"鲁棒代价"(Price of Robustness)就是这条曲线的关键信息。

4.4 一个容易被忽略的细节:数据中心UPS资源的建模

很多复现代码会把UPS当成普通储能来处理,但数据中心UPS有自己特殊的特性——它首要任务是保障IT设备供电,只有剩余容量才能参与微网调度。我在代码里给UPS增加了一个最低电量约束(比如平时必须保持在90%以上),只在电网失电或极端场景下允许短时低于该值。

这个约束看似增加了复杂度,实际效果非常明显:它能让规划方案更真实地和UPS自身的可靠性功能解耦,避免把应急资源当成普通储能白白用掉。


5. 踩坑记录:复现EI论文最容易翻车的五个细节

5.1 "功率平衡约束导致无法求解"的真正原因

这是我复现过程中遇到的第一类大坑。问题表现在子问题求解时Cplex返回infeasible,实际原因是功率平衡约束里包含了不确定变量,且不确定变量的维度与时段变量对不齐。

排查过程:把子问题里的不确定变量位置单独打印出来,发现光伏波动量在某个时段取到了0,但约束里对应的乘子项没有归零,导致等式约束永远无法满足。解决办法是统一矩阵维度索引,或者把不确定量改写为独立变量后再引用。

5.2 对偶转换时强对偶条件失效

子问题要转对偶,前提是内层问题满足LP对偶定理的约束条件。如果内层问题因为储能SOC递推约束的存在出现数值病态,Cplex返回dual infeasible,对偶就会出问题。

排查过程:先单独求解内层问题,观察是否是可行但有界问题。如果发现是数值问题,优先检查储能SOC递推约束的系数数量级,很常见的问题是电量基准值在MW和MWh之间单位不一致,导致约束矩阵的数值差异过大。解决办法是把所有单位统一到同一基准,比如统一用pu或用MWh而不混用MW。

5.3 需要添加可行性割的情形判断

CCG主问题不断添加场景后,可能出现某个新场景下原投资方案完全不可行的极端情况。此时主问题会返回infeasible。

正确做法不是调大求解器容差,而是给主问题加一类新的约束——可行性割。可行性割的本质是要求主问题决策x的时候,强制保证在每一个已发现的不确定场景下,至少能达到某个可行的运行方式,即使以高成本为代价。

判断标准很简单:如果你发现Cplex返回"infeasible"或者"unbounded"的次数超过两三次,就应该把CCG升级为带可行性割的版本。不少EI复现论文的代码其实都隐式包含了这一步,不写出来单纯是因为篇幅放不下。

5.4 求解器选择的性能对比:Cplex 和 Gurobi 在 CCG 中的差异

实际测试下来,Cplex和Gurobi都能完成CCG的求解,但耗时差异明显。Gurobi在处理大整数变量的主问题时通常更快,而Cplex在子问题对偶后的大规模线性规划上稳定性略好。我最终采用的做法是:

  • 主问题用Gurobi(因为投资变量是整数,MIP求解更快)
  • 子问题用Cplex(纯LP,Cplex的单纯形法在稠密矩阵上更稳)

在Yalmip里切换求解器很简单,只需要在optimize的sdpsettings里改solver字段。

环节CplexGurobi
主问题MIP求解一般较快
子问题LP对偶稳定尚可
大规模约束建模好好
数值稳定性良好良好

5.5 收敛阈值设置不当造成的误判

CCG循环的终止条件常用相对Gap = (UB-LB)/UB。很多人默认设1e-4,但实际电力规划问题没必要这么紧。实测发现,Gap从1%进一步收到0.1%,迭代轮次可能从6轮增加到10轮以上,但投资方案几乎不变。

我建议:

  • 初步验证阶段用1%
  • 最终结果分析阶段收紧到0.1%或0.5%
  • 如果只是粗看一下趋势,5%都足够

设置不当的另一个坑是初始LB和UB赋值。如果初始化UB为0而LB为1e6,第一轮循环相对Gap就是负数,容易误判为已收敛。初始化时务必让LB=-inf或1e10,UB=inf或-1e10,方向别弄反。


最后再分享一个实际调试中的小技巧:CCG迭代里如果发现Gap迟迟不降,先别急着调算法参数,试着把第一轮最优x打印出来,和第二轮、第三轮的x对比。很多时候是子问题返回的最坏场景过于极端,导致主问题投资方案大幅波动。这时可以在子问题目标函数里加一个小的正则项(比如对不确定量的偏离程度加0.001的惩罚),让最坏场景不要太跳变。这个操作在论文里很少写,但实际运行基本必用。

整个复现过程下来,最深的感受是:EI论文里的"简单模型"和"可直接复现的代码"之间,永远隔着大量工程细节。但只要把确定性模型、不确定性集合、CCG迭代主体这三个模块拆清楚,每个模块单独验证,整体的可靠性自然就立住了。希望这篇拆解能帮正在啃类似论文的同行少走点弯路。

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

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

立即咨询