
简介该资源面向电力系统、新能源与运筹优化方向的毕业设计学生及科研人员围绕《基于两阶段鲁棒优化算法的微网多电源容量配置》展开解决微电网中可再生能源与负荷不确定性下的容量配置与调度问题。资源包共423个文件以276个xls数据表、110个mat数据文件为主辅以m脚本、csv、docx说明文档及caj文献等压缩包约89.47MB覆盖算例数据、仿真结果与代码实现。已有148人学习下载。模型采用min-max-min结构考虑储能、需求侧负荷及可控分布式电源的运行约束与协调控制并引入不确定性调节参数灵活调整保守性基于列约束生成算法与强对偶理论将原问题分解为主子问题交替求解最终通过仿真验证有效性并给出分时电价下储能调度边界条件可为投资商规划储能及配电网运营商设计激励机制提供参考。1. 两阶段鲁棒优化做微网容量配置为什么确定性方案在真实场景里会翻车微网多电源容量配置这件事很多人第一次做的时候都会走一条看起来最顺的路把光伏、风电、储能的典型日曲线拉出来用确定性优化跑一遍得到一个最优的风光储配比。结果项目评审会上被问一句如果连续三天阴雨加无风你的储能够不够整个方案就站不住了。问题不在于优化算法写得对不对而在于输入本身就不确定——光照、风速、负荷这三样东西没有一个是你能提前锁死的。两阶段鲁棒优化算法就是冲着这个痛点来的。它的核心思路是第一阶段先定容量投多少光伏、多少风机、多少储能这个决策必须在不确定性揭晓之前做出第二阶段是在最坏场景下做运行调度用运行可行性反过来约束第一阶段的容量决策。换句话说它不追求典型日最优而是追求最坏情况下也能扛住。这套方法特别适合微网规划、综合能源系统、配电网扩容这类投资周期长、一旦建成很难改的场合。如果你正在做微网容量配置的论文或者工程项目并且被审稿人或者甲方追问过不确定性怎么处理那这篇内容就是给你写的。2. 两阶段鲁棒优化的数学骨架min-max-min 结构到底怎么拆2.1 为什么是两阶段而不是单阶段鲁棒单阶段鲁棒优化的写法是 min_x max_u f(x,u)意思是你在选容量的时候直接假设不确定性会取到对你最不利的值然后在这个最坏值下优化容量。这种做法的问题很明显太保守了。因为它不允许你在运行阶段做任何调整——容量定死之后运行策略也定死了但实际上储能充放电、可控电源出力这些都是可以随实际风光出力动态调整的。两阶段的结构是 min_x max_u min_y f(x,y,u)。外层 min_x 选容量中间 max_u 找最坏的不确定性场景内层 min_y 在这个场景下做最优运行调度。关键区别在于内层的 min_y 给了你随机应变的空间。容量决策是这里建多少运行决策是实际跑起来怎么调两者分开保守度就降下来了。这个结构在数学上叫自适应鲁棒优化Adaptive Robust Optimization求解难度比单阶段大不少但工程上更合理。2.2 不确定性集怎么构造才不算拍脑袋不确定性集是鲁棒优化的灵魂。你定义了一个不确定性集就等于告诉模型我认为真实值不会超出这个范围。定义得太窄方案不够鲁棒定义得太宽方案过于保守投资成本飙升。常见的不确定性集有三种类型数学形式适用场景保守度盒式不确定集|u_t - u_t^0| ≤ Δu_t波动范围明确的场景高椭球不确定集(u-u^0)^T Σ^{-1} (u-u^0) ≤ Γ²有相关性结构的场景中预算不确定集Σ|u_t - u_t^0|/Δu_t ≤ Γ需要控制保守度的场景可调我一般推荐用预算不确定集Budget Uncertainty Set因为它多了一个参数 Γ 可以调。Γ0 退化成确定性优化Γ 等于时段数就退化成盒式不确定集中间值可以连续调节保守程度。这个参数在论文里也特别好做灵敏度分析——审稿人问你的鲁棒性怎么验证你就把 Γ 从 0 扫到 T画一条总成本 vs 鲁棒性水平的曲线故事就完整了。具体构造方式设光伏出力的预测值为 \bar{P}{pv,t}最大偏差为 \hat{P}{pv,t}引入辅助变量 z_t^ 和 z_t^- 表示正负偏差约束为P_pv,t \bar{P}_pv,t (z_t^ - z_t^-) * \hat{P}_pv,t z_t^ z_t^- ≤ 1 Σ(z_t^ z_t^-) ≤ Γ z_t^, z_t^- ∈ {0,1}风电和负荷同理。Γ 的取值一般从 1 到 T 之间扫看成本曲线的拐点在哪里。2.3 对偶变换把 max-min 内层问题变成可求解的形式两阶段鲁棒优化不能直接丢给求解器因为 max_u min_y 是一个双层问题。标准做法是用对偶理论把内层 min_y 转成 max 形式然后两个 max 合并整个问题变成一个单层的 min 问题。具体步骤内层 min_y 是一个线性规划给定 x 和 u 之后写出它的对偶问题对偶变量记为 λ。对偶之后内层变成 max_λ g(x,u,λ)。然后外层 max_u 和对偶后的 max_λ 合并成 max_{u,λ}整个问题变成 min_x max_{u,λ}。这时候 max 里面出现了 u 和 λ 的乘积项双线性项不能直接用线性求解器。处理方法有两种一种是 Big-M 法引入辅助 0-1 变量线性化另一种是 CCGColumn-and-Constraint Generation算法迭代求解主问题和子问题。我一般用 CCG因为它的收敛性有理论保证而且迭代次数通常不多。下面是一个简化的 CCG 主问题框架# CCG 主问题给定已识别的最坏场景集合优化容量 # 变量x_cap (容量), y_oper (运行), eta (最坏场景成本上界) import gurobipy as gp from gurobipy import GRB def build_master_problem(scenarios, params): scenarios: 已识别的最坏场景列表每个场景是一组 (P_pv, P_wt, P_load) 时序 params: 系统参数成本系数、设备参数、负荷等 m gp.Model(master) # 第一阶段变量容量决策 x_pv m.addVar(lb0, ubparams[pv_max], namex_pv) x_wt m.addVar(lb0, ubparams[wt_max], namex_wt) x_es m.addVar(lb0, ubparams[es_max], namex_es) # 最坏场景下的运行成本上界 eta m.addVar(lb-GRB.INFINITY, nameeta) # 目标函数投资成本 最坏场景运行成本 m.setObjective( params[c_pv] * x_pv params[c_wt] * x_wt params[c_es] * x_es eta, GRB.MINIMIZE ) # 对每个已识别场景添加运行约束 for k, scenario in enumerate(scenarios): # 第二阶段变量该场景下的运行决策 p_pv m.addVars(params[T], lb0, namefp_pv_{k}) p_wt m.addVars(params[T], lb0, namefp_wt_{k}) p_ch m.addVars(params[T], lb0, namefp_ch_{k}) p_dis m.addVars(params[T], lb0, namefp_dis_{k}) soc m.addVars(params[T], lb0, ubparams[es_cap_max], namefsoc_{k}) for t in range(params[T]): # 功率平衡 m.addConstr( p_pv[t] p_wt[t] p_dis[t] - p_ch[t] scenario[load][t], namefbalance_{k}_{t} ) # 出力不超过容量 m.addConstr(p_pv[t] x_pv * scenario[pv_ratio][t]) m.addConstr(p_wt[t] x_wt * scenario[wt_ratio][t]) # 储能 SOC 递推 if t 0: m.addConstr(soc[t] params[soc0] p_ch[t]*params[eta_ch] - p_dis[t]/params[eta_dis]) else: m.addConstr(soc[t] soc[t-1] p_ch[t]*params[eta_ch] - p_dis[t]/params[eta_dis]) # 充放电功率限制 m.addConstr(p_ch[t] params[p_es_max]) m.addConstr(p_dis[t] params[p_es_max]) # 该场景的运行成本 oper_cost gp.quicksum( params[c_oper] * (p_pv[t] p_wt[t]) params[c_es_oper] * (p_ch[t] p_dis[t]) for t in range(params[T]) ) m.addConstr(eta oper_cost, namefeta_constr_{k}) return m, x_pv, x_wt, x_es, eta这段代码的关键点eta是所有已识别场景运行成本的上界主问题在最小化投资加这个上界。每迭代一次子问题找到一个新的最坏场景就往主问题里加一组约束。scenario[pv_ratio][t]表示该场景下光伏归一化出力乘以容量就是实际出力。参数说明c_pv、c_wt、c_es是单位容量投资成本元/kWc_oper是运行维护成本元/kWheta_ch和eta_dis是储能充放电效率soc0是初始荷电状态。这些参数需要根据实际项目或论文设定来填。2.4 子问题求解怎么找到当前容量下最坏的那个场景子问题是在给定容量 x* 的条件下找使运行成本最大的不确定性场景。由于内层 min_y 已经通过对偶合并进来了子问题实际上是一个 max 问题约束包括对偶可行域和不确定性集。def solve_subproblem(x_pv_val, x_wt_val, x_es_val, params): 给定容量求解子问题找最坏场景 返回最坏场景下的运行成本 和 对应的不确定性取值 m gp.Model(subproblem) # 不确定性变量 z_pv m.addVars(params[T], vtypeGRB.CONTINUOUS, lb-1, ub1, namez_pv) z_wt m.addVars(params[T], vtypeGRB.CONTINUOUS, lb-1, ub1, namez_wt) z_load m.addVars(params[T], vtypeGRB.CONTINUOUS, lb-1, ub1, namez_load) # 预算约束 m.addConstr(gp.quicksum(abs(z_pv[t]) for t in range(params[T])) params[Gamma_pv]) m.addConstr(gp.quicksum(abs(z_wt[t]) for t in range(params[T])) params[Gamma_wt]) # 这里需要根据对偶后的形式构建目标函数 # 实际实现中对偶变量和不确定性变量的乘积需要线性化 # 简化示意目标为最大化运行成本 # ...对偶约束和目标函数构建 m.setObjective(/* 对偶目标函数 */, GRB.MAXIMIZE) m.optimize() if m.status GRB.OPTIMAL: worst_cost m.objVal worst_scenario { pv: [params[pv_forecast][t] z_pv[t].X * params[pv_dev][t] for t in range(params[T])], wt: [params[wt_forecast][t] z_wt[t].X * params[wt_dev][t] for t in range(params[T])], load: [params[load_forecast][t] z_load[t].X * params[load_dev][t] for t in range(params[T])] } return worst_cost, worst_scenario else: return None, None子问题里abs(z_pv[t])需要引入辅助变量线性化实际写的时候要拆成 z_plus 和 z_minus。Gamma_pv和Gamma_wt就是前面说的预算参数控制光伏和风电各自的不确定性预算。CCG 的迭代流程是初始化场景集合通常放一个预测场景求解主问题得到容量带入子问题找最坏场景如果子问题目标值大于主问题里的 eta 加上容差就把新场景加入主问题重新求解直到收敛。一般迭代 5 到 15 次就能收敛取决于 Γ 的大小和系统复杂度。3. 微网多电源容量配置的完整建模从设备模型到求解流程3.1 光伏、风电、储能、可控电源的约束怎么写微网里常见的电源类型包括光伏、风电、储能电池、柴油发电机或燃气轮机。每种设备的模型不一样但都要在优化框架里统一表达。光伏和风电的核心约束是出力上限实际出力不能超过装机容量乘以归一化出力系数。归一化出力系数来自历史数据或典型日曲线是一个 0 到 1 之间的时序序列。在鲁棒优化里这个系数本身是不确定的用前面说的不确定性集来描述。储能的约束稍微复杂一些主要包括SOC 递推关系、SOC 上下限、充放电功率上限、充放电互斥不能同时充和放。如果考虑寿命还要加循环次数约束或者吞吐量约束。我一般会在模型里加一个日循环约束保证一天结束时的 SOC 等于起始 SOC避免模型偷电。可控电源的约束包括出力上下限和爬坡率限制。如果考虑燃料成本目标函数里要加燃料费用项。爬坡率约束在微网这种小系统里有时候可以忽略但如果可控电源占比大最好还是加上。# 储能日循环约束和 SOC 约束的补充 for t in range(params[T]): # SOC 上下限 m.addConstr(soc[t] params[soc_min] * x_es) m.addConstr(soc[t] params[soc_max] * x_es) # 充放电互斥用 Big-M 或者二进制变量 # 如果不想引入整数变量可以省略互斥约束因为同时充放在最优解中不会出现 # 因为同时充放会增加成本但不增加收益 # 日循环约束 m.addConstr(soc[params[T]-1] params[soc0])这里有个细节SOC 的上限是soc_max * x_es也就是说储能容量是决策变量SOC 不能超过容量乘以最大荷电系数。这个约束把容量决策和运行决策耦合起来了是两阶段结构里第一阶段和第二阶段的关键连接点。3.2 目标函数投资成本、运行成本、弃风弃光惩罚怎么权衡目标函数一般是最小化总成本包括三块设备投资年化成本、运行维护成本、弃风弃光惩罚。投资年化成本 单位容量投资 × 容量 × 资本回收系数。资本回收系数 r(1r)^n / ((1r)^n - 1)其中 r 是折现率n 是设备寿命。这个系数把一次性投资摊到每年。运行成本包括可控电源的燃料成本、储能的充放电损耗成本、从主网购电的成本如果并网运行。弃风弃光惩罚是给弃电加一个惩罚系数避免模型大量弃电。惩罚系数一般取上网电价的 1 到 2 倍太低不起作用太高会让模型过度投资储能。在鲁棒优化框架下目标函数是 min_x [投资成本 max_u min_y 运行成本]。投资成本只跟 x 有关运行成本跟 x、y、u 都有关。CCG 主问题里的 eta 就是 max_u min_y 运行成本的上界。3.3 用 CCG 算法串起整个求解流程完整的求解流程可以写成下面这样def ccg_algorithm(params, max_iter50, tol1e-4): CCG 主循环 # 初始化用预测场景作为第一个场景 initial_scenario { pv: params[pv_forecast], wt: params[wt_forecast], load: params[load_forecast], pv_ratio: [params[pv_forecast][t] / params[pv_max] for t in range(params[T])], wt_ratio: [params[wt_forecast][t] / params[wt_max] for t in range(params[T])] } scenarios [initial_scenario] lb -float(inf) # 下界 ub float(inf) # 上界 for iteration in range(max_iter): # 步骤1求解主问题 master, x_pv, x_wt, x_es, eta build_master_problem(scenarios, params) master.optimize() if master.status ! GRB.OPTIMAL: print(f主问题不收敛迭代 {iteration}) break lb master.objVal x_pv_val x_pv.X x_wt_val x_wt.X x_es_val x_es.X # 步骤2求解子问题 worst_cost, worst_scenario solve_subproblem(x_pv_val, x_wt_val, x_es_val, params) if worst_cost is None: print(f子问题不收敛迭代 {iteration}) break # 计算上界 invest_cost (params[c_pv] * x_pv_val params[c_wt] * x_wt_val params[c_es] * x_es_val) ub invest_cost worst_cost # 步骤3检查收敛 gap abs(ub - lb) / abs(ub) if abs(ub) 1e-6 else abs(ub - lb) print(f迭代 {iteration}: LB{lb:.2f}, UB{ub:.2f}, Gap{gap:.6f}) if gap tol: print(f收敛于第 {iteration} 次迭代) break # 步骤4添加新场景到主问题 worst_scenario[pv_ratio] [worst_scenario[pv][t] / params[pv_max] for t in range(params[T])] worst_scenario[wt_ratio] [worst_scenario[wt][t] / params[wt_max] for t in range(params[T])] scenarios.append(worst_scenario) return { x_pv: x_pv_val, x_wt: x_wt_val, x_es: x_es_val, lb: lb, ub: ub, iterations: iteration 1 }这个流程里lb是主问题给出的下界因为主问题只考虑了部分场景放松了约束ub是实际最坏场景下的总成本上界。两者差距小于容差就认为收敛。实际跑的时候一般 10 次以内就能收敛到 1e-4 的精度。参数max_iter设 50 是保险起见正常不会用到。tol设 1e-4 对于工程精度够了如果做论文需要更精确可以设 1e-6但迭代次数会增加。4. 避坑与排查两阶段鲁棒优化落地时最容易翻车的五个地方4.1 现象CCG 迭代不收敛上下界来回震荡原因最常见的原因是子问题没有求到全局最优。如果子问题里用了启发式或者局部求解器找到的最坏场景可能不是真正最坏的导致上界估计偏低主问题加入的场景不够狠下界上不去。另一个原因是 Big-M 取值不当线性化松弛太松主问题的最优解在实际中不可行。解决子问题必须用精确求解器Gurobi、CPLEX 都行确保 max 问题求到全局最优。Big-M 要根据物理量量级合理设置比如功率平衡约束的 Big-M 取最大负荷的 2 到 3 倍就够了不要随手写 1e6。检查对偶变换的符号有没有搞错这是血泪经验——符号错一个整个迭代方向就反了。4.2 现象优化结果里储能容量为零但明明设置了弃风弃光惩罚原因弃风弃光惩罚系数太低或者储能投资成本太高模型算下来觉得弃电比建储能划算。还有一种可能是 SOC 约束写错了比如 SOC 上限没有乘以容量导致储能可以无限充放模型就随便给个很小的容量意思一下。解决先检查 SOC 约束是不是soc[t] soc_max * x_es这个乘以容量的操作很容易漏。然后调惩罚系数一般设成上网电价的 1.5 倍左右。如果还是零看看储能投资成本是不是设高了——实际项目中储能单位成本差异很大从 1000 元/kWh 到 3000 元/kWh 都有要按实际报价来。4.3 现象Γ 从 1 调到 T总成本几乎不变原因不确定性集的预算参数没有真正起作用。可能是约束写成了Σ|z_t| ≤ Γ但 z_t 的上下界设成了 0 到 1 而不是 -1 到 1导致预算约束永远不紧。也可能是负荷不确定性没加进去只考虑了风光而风光在总成本中占比小。解决检查 z 变量的上下界必须是 -1 到 1。检查预算约束是不是加在了绝对值上如果求解器不支持 abs要拆成 z_plus z_minus 且 z_plus, z_minus ≥ 0。把负荷不确定性也加进去负荷波动对运行成本的影响往往比风光更大。如果这些都对那可能是系统本身鲁棒性很好Γ 增大确实不影响成本——这时候可以适当减小设备容量上限逼模型做取舍。4.4 现象求解时间过长跑一晚上还没出结果原因CCG 迭代次数太多或者主问题规模太大。每加一个场景主问题就多 T 组变量和约束场景多了之后主问题变得很臃肿。另一个原因是用了二进制变量做充放电互斥整数规划本身求解就慢。解决首先充放电互斥约束可以去掉——在最优解中同时充放电不会出现因为那只会增加成本。去掉之后主问题变成纯线性规划求解速度快很多。其次可以限制场景集合的大小比如只保留最近 5 个最坏场景旧的场景如果对当前解没有约束力就删掉。最后如果还慢考虑用 Benders 分解替代 CCGBenders 的对偶子问题通常比 CCG 的原始子问题好解。4.5 现象论文里的结果和代码跑出来的对不上原因十有八九是参数单位不统一。投资成本用万元而运行成本用元或者功率用 kW 而容量用 MW量级差 1000 倍结果肯定对不上。另一个常见原因是典型日曲线的归一化方式不同——有人用装机容量归一化有人用最大值归一化出来的 ratio 不一样。解决在代码开头统一单位建议全部用元、kW、kWh、小时。典型日曲线归一化方式在论文里要写清楚代码里加注释。如果论文里给了参数表逐项核对特别是折现率和设备寿命这两个参数不同取值对年化成本影响很大。我一般会在代码里加一个参数检查函数把所有输入参数打印出来跑之前先看一眼量级对不对。5. 从能跑到好用灵敏度分析、场景验证和参数标定的几个实战技巧模型能跑出结果只是第一步真正让这套方法在论文或项目中站住脚还得做三件事灵敏度分析、样本外验证、参数标定。灵敏度分析最核心的是扫 Γ。把 Γ 从 0 扫到 T每个值跑一次 CCG记录总成本、各设备容量、弃电率。画出来一般是这样的规律Γ 从 0 增加到某个值的时候总成本上升较快因为模型开始为不确定性留裕度超过某个阈值之后成本上升变缓因为系统已经足够鲁棒了。这个拐点就是性价比最高的鲁棒水平。论文里放这张图审稿人一看就知道你认真做了分析。样本外验证是另一个加分项。用历史数据里没参与建模的 N 天数据把优化出来的容量固定然后每天做一次确定性运行优化或者用规则调度看有没有切负荷、弃电率多少。如果所有测试日都能满足负荷且弃电率在可接受范围说明方案确实鲁棒。我一般会留出 20% 的数据做测试测试结果用表格呈现测试日、切负荷量、弃电量、储能循环次数。参数标定主要是标不确定性集的偏差范围。光伏出力的预测偏差跟预测时间尺度有关日前预测偏差一般在 15% 到 25% 之间超短期预测可以到 5% 以内。风电的偏差更大日前预测偏差 20% 到 35% 都算正常。负荷预测偏差相对小3% 到 8%。这些数字不要拍脑袋去查你所在地区的实际预测精度报告或者用历史预测值和实际值算一下 MAPE用 MAPE 的 1.5 倍作为偏差上界。还有一个技巧是给不确定性集加时间相关性。实际的光伏出力在相邻时段是相关的不会这一小时满发下一小时为零。可以在不确定性集里加一个约束限制相邻时段偏差的变化率比如|z_t - z_{t-1}| ≤ 0.3。这样找到的最坏场景更符合物理实际不会出现剧烈波动这种现实中不太可能的情况。最后说一个我自己的习惯每次跑完 CCG我都会把最坏场景的曲线画出来看一眼。如果最坏场景是光伏全天为零、风电全天为零、负荷全天最大那说明不确定性集太宽了模型在防一个几乎不可能发生的情况。这时候要么收紧偏差范围要么加时间相关性约束。最坏场景应该是光伏在傍晚出力骤降、风电夜间偏小、负荷晚高峰偏高这种有物理意义的场景而不是极端到离谱的组合。这个检查花不了几分钟但能避免很多审稿人的质疑。希望帮到你。本文还有配套的精品资源点击获取