两阶段鲁棒优化解决微网电源容量配置的完整建模与求解 简介面向微电网规划研究人员与电气工程专业学生这份资源针对风、光、储、燃气轮机联合容量配置问题构建了考虑不确定性的两阶段鲁棒优化模型一阶段决策储能与风光装机容量二阶段优化典型日下实际出力并采用列与约束生成CCG算法迭代求解。包内共19个文件约3.76MB以MATLAB脚本m文件为核心包含主程序、MP/SP子问题及CCG实现6份docx文档详解两阶段鲁棒构建与模型求解过程便于对照阅读另有四个典型日数据表格、矩阵推导图片、参考论文及caj文献辅助理解算法细节。目前已有1328人学习适合需要复现微网两阶段鲁棒规划代码、撰写论文或完成课程设计的读者。1. 两阶段鲁棒优化算法解决微网电源容量配置问题的完整思路做微网规划的人迟早会在两种失败的方案之间来回挣扎按最保守的负荷与风光出力去配置容量结果是设备闲置率居高不下项目经济性被初始投资压死按典型日平均出力去配置一到连续阴天或极端低温系统又得靠外购电和切负荷来保底。电源容量配置本质上是“先决定买哪些设备、再在不确定出力下决定怎么运行”的顺序决策问题两阶段鲁棒优化算法恰好把这种顺序决策结构化第一阶段只做容量决策第二阶段在所有可能的风光出力序列下做最省钱的运行调度优化目标是在最坏情况下的年度总成本最小。这个思路在微网规划场景中既符合工程直觉又能被分解算法稳定求解是目前园区微网、海岛微网和企业微网容量配置里最常见的建模方式之一。接下来按建模、求解、参数整定、结果验证的路径展开每一部分都会给出可直接复现的公式、代码或排查思路。2. 微网电源容量配置的两阶段鲁棒优化建模2.1 容量配置模型中的决策变量与不确定性来源微网电源容量配置要确定的决策变量通常是光伏阵列的装机容量kWp、风电的装机容量kW、储能系统的额定能量与额定功率kWh/kW有时候还会加入柴油发电机台数或燃气轮机的单机容量。这些变量一旦确定在项目生命周期内基本不可调整属于典型的第一阶段决策。而第二阶段决策则是未来每个调度时段内的出力分配储能何时充电、何时放电、联络线交换功率多大、是否需要切负荷。风光出力和负荷需求都是不确定参数它们的时序序列直接影响到第二阶段调度的可行性和经济性。如果用确定性优化做容量配置常见做法是把全年 8760 小时压缩成几个典型日再乘以对应天数。这种做法在平稳负荷场景下可以接受但会丢掉小时级序列的连续性无法表达连续两天阴雨导致光伏出力持续偏低、午峰与晚峰叠加等场景也没有办法量化储能 SOC 在极端序列下的累积效应。两阶段鲁棒优化的价值正在这里它用一个数学意义上严格的 max-min 结构在给定的不确定集里寻找“最难受”的一组时序场景并保证容量配置在该场景下仍然可运行且运行成本不失控。两阶段鲁棒容量配置模型的标准形式是 min-max-min 三层结构。第一步决策变量是容量配置向量 $x$第二阶段是在给定 $x$ 和不确定参数 $u$ 下的运行调度 $y$。用紧凑形式表达为$$\min_{x \in X} ; c(x) \max_{u \in U} \min_{y \in F(x,u)} ; b(x,u,y)$$其中 $c(x)$ 是投资成本的年化值$b$ 是运行成本与惩罚成本之和。$U$ 是不确定集$F(x,u)$ 是固定 $x$ 和 $u$ 后第二阶段运行问题的可行域。这里需要特别强调 $u$ 是一个时序向量而不是若干个独立标量$\max_{u \in U}$ 寻找的不只是某一时刻的极端出力而是整段调度周期里让系统调节能力最受挤压的一组出力曲线。第二阶段运行约束包含每个时段的功率平衡约束、储能容量与充放电功率上下限、储能 SOC 跨时段递推、机组爬坡约束、联络线交换功率限制、弃风弃光量非负约束等。由于 SOC 递推是跨时段的线性方程第二阶段问题即使在其他部分都是单时段约束整体上仍然是一个带时间耦合结构的线性规划。这也是容量配置鲁棒优化区别于“仅做单时刻最优潮流”的鲁棒扩展的关键点。后面对偶变换若漏掉 SOC 递推对应的对偶变量求出来的最坏场景会天然偏乐观。2.2 不确定集设计盒式、预算约束与多面体扩展不确定集 $U$ 的构造直接决定鲁棒模型的保守程度和求解难度。盒式不确定集是最原始的形式把每个时段的光伏出力或负荷限制在区间 $[\bar{u}_t - \hat{u}_t, \bar{u}_t \hat{u}_t]$ 内。优点是代码简单、物理含义直观缺点是假设所有时段同时达到最坏情况这在真实气象过程中几乎不可能发生所得容量配置往往过度投资。工程上更常用的是带预算约束的盒式集合记 $u_t \bar{u}_t \delta_t \hat{u}_t$并要求偏差量满足$$U \left{ \delta_t \in \mathbb{R}, ; |\delta_t| \le 1, ; \sum_{t1}^T |\delta_t| \le \Gamma \right}$$参数 $\Gamma$ 是不确定预算$\Gamma0$ 对应确定性模型$\GammaT$ 退化为全盒式。它用一条线性不等式就实现了对总体偏离规模的限制且不破坏线性模型的对偶结构因此成为目前微网容量配置中使用频率最高的不确定集。$\Gamma$ 的具体取值没有放之四海而皆准的公式一般先按 $T/4$ 到 $T/3$ 设定然后通过蒙特卡洛模拟或历史数据的分位数来校正。比如一个 24 时段调度模型$\Gamma$ 通常取 6~8如果历史数据里连续阴雨的最大时长更大就要相应调高。如果风光之间、负荷之间存在时间相关性还可以采用多面体扩展形式比如加入相邻时段偏移量的差分约束或限制持续阴雨的最大天数。这类不确定集的刻画更精细但第二阶段对偶模型的系数矩阵会更稠密求解耗时明显上升。实际工程判断时先跑一遍预算约束模型若发现最坏场景几乎总集中在某几个连续时段说明时序相关不可忽略再升级到带差分约束的多面体集。不确定集类型保守程度对偶模型复杂度适用场景全盒式最高低保供要求极高的离网场景盒式预算约束中等低工商业并网微网、园区微网多面体时序差分较低中等高光伏渗透、多日连续阴雨地区2.3 用 Pyomo 定义带预算约束的不确定集在具体代码实现中我习惯用 Pyomo 作为建模语言求解器根据授权选择 Gurobi 或 CBC。先定义不确定参数 $u$ 与偏差变量 $\delta$再施加预算约束。以下代码把光伏出力的不确定集建模成预算约束形式import pyomo.environ as pyo def build_uncertainty_set(model, T, p_mean, p_dev, Gamma): # p_mean: 预测出力序列, p_dev: 允许偏差幅度, Gamma: 不确定预算 model.delta pyo.Var(range(T), bounds(-1, 1)) model.u_pv pyo.Var(range(T)) # 预算约束所有时段绝对偏差之和不超过 Gamma def budget_rule(m): return sum(abs(m.delta[t]) for t in range(T)) Gamma model.budget_constr pyo.Constraint(rulebudget_rule) # 实际出力 预测均值 偏差比例 * 偏差幅度 def link_rule(m, t): return m.u_pv[t] p_mean[t] m.delta[t] * p_dev[t] model.u_link_constr pyo.Constraint(range(T), rulelink_rule)Pyomo 会把abs(m.delta[t])自动线性化不需要手动引入辅助变量和额外约束。p_dev一般取预测均值的 0.15~0.3具体数值来自历史预测误差的分位数。这里有一点容易混淆$\Gamma$ 的单位是“时段偏移量的总计数”而不是“最多允许多少个时段发生偏移”的整数阈值因为 $\delta_t$ 可以是连续的。这样模型保持了线性规划性质子问题仍然可以用对偶方式求解。2.4 完整两阶段鲁棒容量配置模型的目标与约束将决策变量、不确定集和运行约束组合到一起容量配置问题可以写成如下完整形式。第一阶段目标函数为投资成本第二阶段目标函数包括运行维护成本、向上级电网购电成本和弃风弃光惩罚$$\min_{x} ; C_{inv} \max_{u \in U} \min_{y} ; \sum_{t1}^T \left( c_{om} y_t c_{buy} p^{buy}t c{curt} p^{curt}_t \right)$$约束方面第一阶段包括各电源容量的上下限、总投资预算上限第二阶段包括功率平衡、储能 SOC 递推、发电与购电上下限等。容量配置与纯调度问题的核心区别在于第二阶段的储能容量上限 $E_{t} \le E_{rated}$ 中$E_{rated}$ 是第一阶段的决策变量而这个变量在子问题求解时已经被固定为常数。也就是说子问题不会自动让储能容量更大容量是否够用完全看主问题给出的配置。这也解释了为什么 CCG 需要在主问题中反复加入极端场景下的第二阶段部分——只有这些极端场景被加进主问题后主问题才会意识到储能或光伏容量不足从而增加配置。3. 列与约束生成算法求解两阶段鲁棒容量配置问题3.1 为什么 min-max-min 不能一步求解两阶段鲁棒容量配置问题从数学结构上是 min-max-min 三层嵌套无法直接交给 Gurobi、Cplex 这类求解器因为它们要求表达式是标准约束规划或 min-max 或 min-min而不是这种混合嵌套。常规思路是切分成主问题和子问题交替求解。主问题只留第一阶段投资决策和一个场景池场景池里每加入一个极端场景第二阶段变量与约束就复制一套主问题变成有限规模的混合整数或线性规划。子问题则固定第一阶段变量在不确定集范围内搜索最坏场景及其最小运行成本。这种分解有两种常见形式Benders 分解和列与约束生成算法Column-and-Constraint GenerationCCG。Benders 分解产生的割平面基于第二阶段对偶变量的极点迭代中需要不断往主问题添加“关于投资变量”的线性不等式当第二阶段约束数量庞大时收敛较慢。CCG 不同它通过往主问题显式添加新的第二段变量和约束来逼近原问题本质上是一种“外逼近枚举场景”的混合方法。对于第二阶段是连续线性规划的微网容量配置问题CCG 的收敛性通常优于 Benders且实现难度并不高。3.2 CCG 主问题与子问题的数学描述设第 $k$ 次迭代时主问题包含已经发现的最坏场景集合 $U_k {u^{(1)}, u^{(2)}, ..., u^{(k)}}$则主问题可以写成$$\min_{x, y^{(1)}, ..., y^{(k)}} ; c(x) \max_{u\in U_k} F(x,u)$$上式的第二项是不可直接计算的需要把每个场景对应的第二阶段目标拆开写成对所有场景取最大。实际写出来其实是$$\begin{aligned} \min_{x, y^{(i)}} \quad c(x) \tau \ \text{s.t.} \quad \tau \ge b^T y^{(i)}, \quad \forall i1..k \ y^{(i)} \in F(x, u^{(i)}), \quad \forall i1..k \end{aligned}$$用变量 $\tau$ 替代 max再配合每个场景各自独立的第二阶段变量 $y^{(i)}$。请注意$y^{(i)}$ 是每个场景各一套不能共用否则场景会互相耦合变回一个巨大的单一确定性模型。CCG 算法的关键就是不断把新的 $u^{(k1)}$ 和对应的 $y^{(k1)}$ 维变量加入主问题。子问题在给定 $xx^*$ 时求解如下问题$$SP(x^): \quad \max_{u \in U} \min_{y} ; b^T y \quad \text{s.t.} \quad y \in F(x^, u)$$把内层取极小值的问题写成 LP然后取其对偶。因为内层是连续线性规划强对偶成立子问题等价于一个只含最大化的问题$$SP(x^): \quad \max_{u \in U, \lambda \in \Lambda} ; b^T y \lambda^T (g - A(x^) - B u)$$其中 $\lambda$ 是内层约束的对偶变量$A(x^*)$ 是第一阶段参数固定后形成的常数矩阵项。这个变换把三层问题压成了两层外层仍然搜索 $u$内层对偶变量随 $u$ 一起作为优化变量。储能 SOC 递推是等式约束对应的对偶变量没有符号约束在代码里体现为lbNone, ubNone这部分很容易写错。3.3 CCG 的完整 Python 代码框架下面给出一个基于 Pyomo Gurobi 的 CCG 实现骨架。这个骨架刻意省略了具体的约束展开重点展示主问题与子问题的交互逻辑def c_and_cg(data, tol1e-3, max_iter30): scenarios [data[nominal]] # 预置名义场景 LB -1e9 UB 1e9 for it in range(max_iter): # ---- 主问题 ---- mp build_master_problem(scenarios) res solve(mp) LB res[obj] # 主问题给出下界 x_star res[x] # 当前投资方案 # ---- 子问题 ---- sp build_subproblem(x_star, data) res_sp solve(sp) worst_cost res_sp[obj] u_worst res_sp[u] UB min(UB, investment_cost(x_star) worst_cost) gap (UB - LB) / abs(UB) print(fiter{it}, LB{LB:.4f}, UB{UB:.4f}, gap{gap:.4f}) if gap tol: break # 把最坏场景加入场景池 scenarios.append(u_worst) return x_star, LB, UB主问题的目标函数包含 $c(x)\tau$而 $\tau$ 的约束来自每个场景的运行成本因此每轮迭代主问题的规模都会增加。在使用 Gurobi 求解主问题时可以考虑传入上一轮求解的热启动文件节约前几个阶段反复求解的时间尤其是遇到 8760 时段或 8760 缩减成的 20 个典型日时。3.4 子问题求解时最容易踩的数值与对偶坑子问题求解不收敛或对偶不稳定的情况下结果会以 UB 不下降或 gap 震荡的形式出现。常见原因有两个。第一个是储能 SOC 的等式约束造成对偶矩阵奇异。解决办法包括统一能量单位到 MWh以及避免让 SOC 初始值、能量上限等参数的相对尺度差异超过 10 的 5 次方。第二个原因是子问题中约束两边存在数量级差距比如购电价格用元/MWh 而惩罚项用元/kWh会让最优解里的松弛变量异常活跃。解决方法是把价格基准统一惩罚系数显式写出而不是用 1e6 这类魔法数字同时在求解结束后检查子问题最优解是否真的满足所有约束而不是只读取目标函数值。下面的代码展示了如何固定 $x$ 并构造子问题def build_subproblem(x_star, data): m pyo.ConcreteModel() # ---- 变量确定场景下的第二阶段变量 / 偏差变量 ---- m.delta pyo.Var(range(T), bounds(-1, 1)) m.p_charge pyo.Var(range(T), bounds(0, P_max)) m.p_discharge pyo.Var(range(T), bounds(0, P_max)) m.p_buy pyo.Var(range(T), bounds(0, P_line_max)) m.p_curtail_pv pyo.Var(range(T), bounds(0, x_star[pv])) # ---- 不确定集预算约束 ---- m.budget pyo.Constraint(exprsum(abs(m.delta[t]) for t in range(T)) Gamma) # ---- 储能SOC递推 ---- m.soc_next pyo.Constraint(range(T-1), rulelambda m, t: m.soc[t1] m.soc[t] m.p_charge[t] * eta_c - m.p_discharge[t] / eta_d) return m真实项目中建议用数组表达式一次性构造m.soc的所有约束避免循环推导中把时间索引搞错。子问题求解时如果遇到不可行再额外检查功率平衡约束是否允许切负荷以及系统初始 SOC 是否设置在安全区间。4. 两阶段鲁棒优化参数整定与微网容量配置算例4.1 算例数据设置以一个并网型园区微网为例调度周期 T24单位时段 1 小时。光伏预测出力按夏季晴天曲线折算峰值容量系数 0.85负荷侧典型日峰谷差约 40%风电出力按三类风区经验曲线取均值。成本参数如下表参数数值单位光伏单位容量投资3500元/kW风电单位容量投资6800元/kW储能单位容量投资1200元/kWh储能单位功率投资850元/kW购电电价峰值/谷值1.05/0.38元/kWh年运行维护费率2%1/年实际工程里的成本参数应当从可研报告取值而不是论文里的典型值。特别是储能单位容量投资必须包含电池、BMS、PCS 和集装箱的整套成本否则第二阶段调度会把储能容量放大到不合理的程度。另一点是购电电价不要只用峰值谷值简单平均而应该按分时电价表构造 24 时段序列这样第二阶段模型才会合理地引导储能进行峰谷套利。4.2 不确定预算 $\Gamma$ 的敏感性分析使用上面的参数集将 $\Gamma$ 分别设置为 0、4、8、12、16、24运行完整的 CCG 得到归一化配置结果。表头中“相对值”以 $\Gamma0$ 时的配置结果为基准。Γ光伏容量风电容量储能容量总成本相对值01.001.001.001.0041.061.031.101.0481.141.071.221.08121.221.111.351.12161.291.161.441.15241.371.221.521.18这张表的典型规律是总成本的前半段增长斜率明显大于后半段。实际规划中把 $\Gamma$ 定在斜率由陡变缓的位置即可继续增加只会让投资额上升而可靠性提升不明显。这种“边际成本拐点”是两阶段鲁棒容量配置方案比选的核心指标也是汇报材料里最有说服力的图表之一。4.3 收敛曲线与异常回退排查运行 CCG 时我会同时记录每一轮的主问题目标、子问题最坏场景成本和投资成本。一个正常的收敛曲线通常是第一轮主问题只含名义场景获得的投资容量偏小UB 非常大第二轮加入第一个最坏场景后 LB 大幅上升UB 下降此后 gap 单调收敛。如果出现 gap 在某一轮反而增大多半是数值精度或子问题求解异常。排查时先打印本轮次发现的最坏场景的出力曲线和 SOC 轨迹。若 SOC 轨迹出现明显跳变比如从 0.2 瞬间到 0.85那说明 SOC 的递推约束在子问题中被错误松弛了。另一个高发问题是在主问题里复制场景时忘了把储能 SOC 初值也一起复制导致不同场景共享一套 SOC 变量产生物理上不可能同时存在的交叉调度。提示把每轮子问题的目标函数值保存成列表如果两轮之间波动超过 10%优先检查 Gurobi 的FeasibilityTol设置而不是直接加大迭代次数。5. 微网容量配置结果后验与工程加速技巧5.1 用蒙特卡洛模拟验证鲁棒配置的真实效果CCG 收敛后得到的容量配置其“最坏情况成本”是在不确定集 $U$ 内搜索的结果并不等于真实世界中的 95% 分位数成本。因此在交付前我会固定容量配置改用蒙特卡洛生成 5000 组符合实际概率分布的风光和负荷序列逐一求解确定性调度统计年运行费用的期望值和 p95 值。这个过程验证的是“不确定集设计得是否准确”。如果 p95 成本明显高于鲁棒模型给出的最坏场景成本说明 $\Gamma$ 偏小如果 p95 成本远低于最坏场景成本说明 $\Gamma$ 偏保守还有压缩投资的空间。def validate(x_star, weather_samples, price_curve): cost_list [] loss_list [] for w in weather_samples: sp build_deterministic_dispatch(x_star, w, price_curve) res solve(sp) cost_list.append(res[total_cost]) loss_list.append(res[loss_prob]) return ( np.mean(cost_list), np.percentile(cost_list, 95), np.max(loss_list) )其中weather_samples建议用历史气象年数据重采样而不是用简单的正态分布随机生成。光伏和风电的出力具有自相关性简单的独立随机序列会把最坏情况人为放大导致后验结论失真。5.2 主问题热启动与场景池初始化一段值得写的加速技巧CCG 从空场景池开始时第一轮主问题没有任何实际运行约束投资容量会系统性偏小首个子问题要花更多轮次把容量补回来。我的常规做法是初始场景池中加入名义场景和 1~2 个历史最差场景例如“连续三天阴雨晚峰荷载”这样的复合场景。这样第一轮主问题得到的是一个基本合理的容量配置后续迭代只需微调。另一个加速点是主问题求解完成后保留上一轮基解作为热启动。Gurobi 的warmstart对线性规划有效但对含整数变量的主问题效果有限因此通常只对第二阶段全为连续变量的问题生效。如果主问题引入了表示“是否投建某台机组”的 0-1 变量热启动收益就不明显。5.3 多资源不确定预算的拆分思路当模型中同时存在光伏、风电和负荷三类不确定变量时不要把三者的偏差加进同一个 $\Gamma$因为不同资源的最坏情况很少同时出现。工程上常见的写法是各自独立预算再增加一个总预算约束把三者绑定$$\Gamma_{pv} \Gamma_{wt} \le \Gamma_{total}$$这样做的好处是可以量化光伏与风电的互补性。如果某地区夜间风电大、白天光伏大两者在时间上天然错峰联合约束的收紧空间就大总体方案更经济。不过要注意这会让子问题的不确定变量数量膨胀Gurobi 求解时间可能翻倍建议先分别做单资源敏感性分析再决定是否用联合预算。本文还有配套的精品资源点击获取