ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微网容量配置如何应对不确定性?两阶段鲁棒优化与CCG算法解析

微网容量配置如何应对不确定性?两阶段鲁棒优化与CCG算法解析 简介面向电力系统、微网优化与鲁棒控制领域的研究者和工程师代码包提供了基于两阶段鲁棒优化算法的微网多电源容量配置MATLAB源代码。算法兼顾决策阶段与不确定调整阶段可有效应对可再生能源出力波动和负荷变化等不确定性帮助读者掌握微网容量配置的建模与求解思路。压缩包共426个文件、约91MB以xls/xlsx数据表、mat数据、csv历史负荷与气象数据、m源码、docx说明文档为主另含部分pdf和caj格式参考文献目录结构清晰便于按模块研读。已有150人学习下载。代码涵盖数据输入、不确定性建模、优化模型构建、两阶段鲁棒算法实现、结果可视化与仿真验证等模块配套数据与文档可支撑完整的复现学习过程从源码中还可学习线性/非线性规划建模与鲁棒优化的实际求解技巧对于研究微网与电力系统优化的学者和工程师极具参考价值。1. 微网容量配置为什么非要用两阶段鲁棒优化一个真实的上头瞬间做过微网规划的人大概都有过这种经历辛辛苦苦建了一个风光储柴的容量配置模型用确定性优化跑出来一套结果光伏装机、储能配比、柴油机台数都算得明明白白结果拿去做方案评审专家第一句话就是“负荷和光伏出力波动这么大你的配置在极端天气下还能不能撑住”这一问很多时候就把方案问死了。问题不在于你的优化模型不对而在于你用单一场景去刻画了一个天然不确定的世界。“微网综合能源源代码021基于两阶段鲁棒优化算法的微网多电源容量配置.zip”这个标题看着长但它精准指向了一个行业痛点微网里的光伏、风电出力看天吃饭负荷侧又有高峰低谷和突发波动传统的确定性优化和简单的场景枚举法在应对极端工况时要么过度保守、投资浪费要么过度乐观、可靠性欠账。两阶段鲁棒优化恰恰是冲着“最恶劣场景下仍然可行”这个目标去的它把“现在做决策”和“未来看结果”拆成两个阶段在第二阶段让大自然不确定性先出牌你再做最有利的调整。这篇文章不聊虚的直接从模型原理、代码结构到调试避坑给你捋出一条能落地的主线适合正在做微网规划、储能容量配置或者学术论文复现的工程师和研究生。2. 先把优化目标说清楚微网多电源容量配置到底在优化什么2.1 容量配置问题的数学本质投资决策与运行决策的耦合微网多电源容量配置本质上是一个双层决策问题。上层是投资决策决定光伏装多少千瓦、风电装多少台、储能配多少容量和功率、柴油发电机买几台下层是运行决策在给定的装机容量下安排每个时段各台机组的出力、储能的充放电策略、与主网的交互功率。这两个决策的时间尺度完全不同投资决策是以“年”为单位的运行决策是以“小时”甚至“分钟”为单位的但它们耦合在同一组等式和不等式约束里。用数学语言描述这就是一个典型的混合整数线性规划问题MILP。投资变量通常是0-1整数变量比如某台柴油机装不装和整数变量比如装几台运行变量是连续变量出力、功率等。目标函数是年化总成本最小包括投资等年值成本、运维成本、燃料成本、购电成本如果有碳排放约束还要加上碳成本。我在实际建模中见过不少翻车案例最常见的错误是把投资成本和运行成本直接相加忽略了设备寿命不同带来的等年值换算问题。光伏寿命25年、储能寿命10年、柴油机寿命15年如果不在同一时间尺度上做等年值折算优化结果一定会偏向寿命短的设备。常见做法是用资本回收系数把初始投资折算成等年值再和年运行成本相加。2.2 不确定性集合两阶段鲁棒优化的灵魂所在两阶段鲁棒优化和传统随机规划最大的区别在于对不确定性的建模方式。随机规划给不确定性变量赋予概率分布用期望值做目标鲁棒优化不给概率分布只给一个不确定集合目标是在集合内最恶劣的情况下仍然可行。这个“往坏了想”的思路决定了它的保守程度完全由你定义的不确定集合来控制。在微网容量配置里不确定性主要来自两块可再生能源出力和负荷需求。光伏出力和负荷的预测误差通常用箱式不确定集合来描述也就是每个时段的不确定变量在预测值附近的一个区间内波动并且所有时段的波动总量受到一个预算参数的限制。这个预算参数budget of uncertainty是控制保守度的核心旋钮取0时退化为确定性模型取最大值时变成最保守的逐时段极端场景。不确定集合的数学形式看着简单但里面有个容易踩坑的细节光伏出力和负荷的不确定性通常需要分别建集合不能混在一起。原因很简单光伏出力有天然的时序相关性早上低、中午高、晚上归零如果用一个无差别的大箱式集合去描述优化结果会产生很多实际上不可能出现的极端场景导致过度保守。我一般会按季节或者按典型日来分时段构建不确定集合这样保守度更可控。2.3 为什么不能把第二阶段直接折叠进第一阶段论CCG算法的必要性很多人第一次接触两阶段鲁棒优化时都会问同一个问题既然第二阶段是“见机行事”能不能把第二阶段的优化问题用KKT条件或者对偶理论直接折叠进第一阶段变成一个单层问题求解理论上可以但对于微网容量配置这种规模的问题直接折叠会让约束条件爆炸式增长而且引入的二进制变量和互补松弛条件会让问题变成非凸的求解器经常卡死。而行之有效的方案是CCG列与约束生成算法。它的思路很朴素第一阶段先把不确定变量固定为某个值第一次迭代通常是预测值求解得到一个投资方案和一组运行策略第二阶段在这个投资方案下让不确定变量在集合里寻优找到让运行成本最大也就是系统最难受的那个场景如果这个最恶劣场景导致第二阶段问题不可行或者目标值超出容忍范围就把这个场景对应的约束加回第一阶段重新求解。如此迭代直到上界和下界的间隙小于设定阈值。这套算法之所以在工程中被广泛采用是因为它每轮迭代只需要求解两个相对小规模的MILP问题收敛速度在绝大多数微网配置实例上都很快往往十几轮迭代就能达到1%以内的间隙。但要注意CCG的收敛性依赖第二阶段是线性的如果你的模型里加入了非线性项比如储能的充放电效率随SOC变化就需要先做线性化处理否则算法可能在迭代过程中振荡甚至发散。3. 代码结构拆解这套源码里大概率藏着什么3.1 从zip包到可运行工程先解决文件组织问题拿到“021基于两阶段鲁棒优化算法的微网多电源容量配置.zip”这个压缩包第一步自然是解压。在Windows上右键解压就行但在Linux服务器上就需要用命令行操作这是不少新手第一次翻车的地方——直接在图形界面双击zip结果中文文件名乱码。# 在Linux下解压含中文文件名的zip包避免乱码 unzip -O GBK 021基于两阶段鲁棒优化算法的微网多电源容量配置.zip -d weiguang_config # 如果unzip版本不支持-O参数先安装p7zip再解压 7z x 021基于两阶段鲁棒优化算法的微网多电源容量配置.zip -oweiguang_config参数说明-O GBK指定zip包内文件名的编码方式大多数Windows环境下压缩的zip默认使用GBK编码Linux默认UTF-8不指定就容易乱码-d指定解压目标目录。如果遇到-O参数不支持的情况用7-zip替代x命令表示解压并保留目录结构。解压后建议先看一眼目录结构。我处理过的类似源码包通常包含以下几个部分data/目录存放负荷曲线、光伏出力系数、设备参数等输入数据model/或src/目录存放主程序文件和算法实现result/目录存放优化结果输出。先确认输入数据的格式和单位这是后面调参的基础。如果发现数据文件是Excel格式需要确保环境里装了openpyxl或pandas依赖这是最常见的运行报错来源。3.2 主程序的三个核心模块参数、主问题、子问题两阶段鲁棒优化的代码实现无论用什么语言常见的是MATLABYalmip、PythonGurobi或者PythonPuLP主线都是三个模块参数初始化模块、主问题求解模块、子问题求解模块。下面是一个主问题求解的典型代码骨架以Python Gurobi为例# 主问题给定不确定性场景求解投资决策和运行策略 import gurobipy as gp from gurobipy import GRB def solve_master_problem(uncertainty_scenarios, master_params): 求解两阶段鲁棒优化的主问题 uncertainty_scenarios: 当前迭代累积的不确定性场景列表 master_params: 设备参数、负荷参数等 m gp.Model(Master_Problem) # 投资决策变量0-1变量表示是否安装 x_pv m.addVar(vtypeGRB.BINARY, namex_pv) # 光伏 x_wt m.addVar(vtypeGRB.BINARY, namex_wt) # 风电 x_ess m.addVar(vtypeGRB.BINARY, namex_ess) # 储能 x_dg m.addVar(vtypeGRB.INTEGER, namex_dg) # 柴油机台数 # 连续变量各场景下的运行决策 # 注意每个场景都需要独立的运行变量副本 p_pv {} # 光伏出力 p_dg {} # 柴油机出力 soc {} # 储能荷电状态 for idx, scenario in enumerate(uncertainty_scenarios): p_pv[idx] m.addVar(lb0, ubscenario[pv_capacity], namefp_pv_{idx}) p_dg[idx] m.addVar(lb0, namefp_dg_{idx}) soc[idx] m.addVar(lb0.2, ub0.9, namefsoc_{idx}) # 目标函数投资等年值成本 所有场景下的运行成本期望或最大 inv_cost master_params[c_pv] * x_pv master_params[c_wt] * x_wt inv_cost master_params[c_ess] * x_ess master_params[c_dg] * x_dg run_cost 0 for idx in range(len(uncertainty_scenarios)): run_cost master_params[c_fuel] * p_dg[idx] m.setObjective(inv_cost run_cost, GRB.MINIMIZE) # 约束功率平衡、设备出力上下限等 # 此处省略具体约束表达式实际使用时根据模型补全 m.optimize() # 返回投资决策和最优目标值 return {var.varName: var.x for var in m.getVars()}, m.objVal这段代码的逻辑侧重说明主问题在给定一组不确定性场景的条件下求解每个场景需要一套独立的运行变量副本这组副本之间通过投资变量耦合。投资决策变量的类型直接影响求解难度——光伏、风电、储能用BINARY表示装或不装柴油机用INTEGER表示台数。目标函数里投资成本只算一次但运行成本需要在每个场景下各算一次再汇总。参数设置上lb0.2, ub0.9表示储能SOC的允许范围这个区间要根据电池特性调整磷酸铁锂可以放宽到0.1到0.95铅酸电池建议收窄到0.3到0.8。泛化一点说任何参数的修改都需要和实际设备的物理特性对齐否则优化结果在工程上没有意义。3.3 子问题与最恶劣场景识别看懂max-min结构的拆解子问题才是两阶段鲁棒优化的核心难点。它的任务是在主问题给定的投资方案下找到让系统运行成本最大的不确定性场景。这个子问题本身是一个双层优化内层在给定不确定性值条件下最小化运行成本外层让不确定性变量在集合内寻优来最大化这个最小值。这就是标准的max-min结构。求解这个max-min问题的常见做法是使用强对偶理论把内层的最小化问题转换为对偶最大化问题和外层合并成一个单层的最大化问题。前提是内层问题是线性的如果模型里有整数变量就必须先处理掉。# 子问题识别最恶劣的不确定性场景 def solve_sub_problem(investment_decision, uncertainty_set, sub_params): 子问题在给定投资方案下找最恶劣场景 investment_decision: 主问题求解出的投资方案 uncertainty_set: 不确定集合的参数预测值、波动区间、预算 sp gp.Model(Sub_Problem) # 不确定性变量各时段的负荷和光伏出力波动 delta_load {} delta_pv {} for t in range(sub_params[T]): # 波动变量在[-1, 1]之间 delta_load[t] sp.addVar(lb-1, ub1, namefdelta_load_{t}) delta_pv[t] sp.addVar(lb-1, ub1, namefdelta_pv_{t}) # 对偶变量用于转化内层min问题 dual_var {} # 目标函数最大化内层问题的最优值通过强对偶转化 # 同时要满足对偶可行性约束和不稳定集合约束 sp.setObjective(..., GRB.MAXIMIZE) # 预算约束所有时段波动总和不能超过预算值 sp.addConstr(gp.quicksum(delta_load[t] for t in range(sub_params[T])) sub_params[budget]) sp.addConstr(gp.quicksum(delta_pv[t] for t in range(sub_params[T])) sub_params[budget]) sp.optimize() # 提取最恶劣场景 worst_scene {} for t in range(sub_params[T]): worst_scene[load_ str(t)] sub_params[load_pred][t] sub_params[load_dev][t] * delta_load[t].x worst_scene[pv_ str(t)] sub_params[pv_pred][t] sub_params[pv_dev][t] * delta_pv[t].x return sp.objVal, worst_scene这段代码里最关键的是对偶变量部分它直接来自于对偶理论。具体来说你需要先把内层的最小化问题写成标准形式再推导出对偶问题把对偶约束作为外层最大化问题的约束加进去。这一步推导如果错了整个算法结果都是错的而且这种错误很难排查因为迭代过程可能看起来在收敛但收敛到的是一个错误的解。预算约束的解释budget这个参数决定了不确定变量能在多大范围内同时偏离预测值。它本质上是用来控制保守度的。budget0意味着所有不确定变量都固定在预测值模型退化为确定性优化budget取最大值通常等于时段数意味着每个时段都可以取极端值模型极度保守。实际工程中这个值通常取总时段数的1/3到1/2比如24小时的优化周期budget取8到12。4. CCG迭代主循环从零开始把算法串起来4.1 主循环的收敛判据与实现细节主程序和子问题都写好了接下来就是用CCG算法把它们串起来迭代求解。这个主循环是整个代码的发动机每个环节的容错处理直接决定算法能不能在合理时间内收敛。# CCG主循环迭代求解两阶段鲁棒优化 def ccg_solver(data, params): 完整的列与约束生成算法实现 # 初始化下界设为-∞上界设为∞ LB -float(inf) UB float(inf) gap float(inf) # 初始化场景集先用预测场景不确定性为0 scenarios [{load: data[load_pred], pv: data[pv_pred]}] # 记录最优投资决策 best_investment None iteration 0 while gap params[gap_threshold] and iteration params[max_iter]: iteration 1 # 求解主问题得到下界 investment, master_obj solve_master_problem(scenarios, params) LB max(LB, master_obj) # 主问题目标值是最优值的下界 # 求解子问题得到上界 # 子问题目标值 投资成本 一个可行解的目标值上界 sub_obj, worst_scene solve_sub_problem(investment, params) UB min(UB, master_obj - investment_cost sub_obj) gap abs(UB - LB) / abs(UB) print(fIter {iteration}: LB{LB:.2f}, UB{UB:.2f}, gap{gap:.4f}) # 判断收敛 if gap params[gap_threshold]: best_investment investment break # 未收敛把最恶劣场景加入场景集 scenarios.append(worst_scene) return best_investment, LB, UB, iteration这个循环的逻辑需要仔细理解主问题的目标值为什么是下界因为主问题只考虑了有限的几个场景初始只有预测场景约束少、可行域大所以求出的最优值一定小于等于真实最优值。子问题求得的是在给定投资方案下的最恶劣运行成本把它和投资成本加起来对应的是一个实际可执行的方案先用当前投资方案扛下最极端的情况所以它是真实最优值的上界。代码里有个细节容易出错UB更新时用的是master_obj - investment_cost sub_obj因为master_obj里已经包含了投资成本不能在UB里重复计算。这个细节我在实际编码中至少踩过两次坑第一次是忘了减UB一直偏大第二次是减错了变量UB出现了振荡。处理这个问题的通用方式是始终用主问题目标函数中的投资成本部分做分离。4.2 收敛阈值和最大迭代次数的设置逻辑CCG算法的收敛阈值和最大迭代次数的设置直接关系到求解精度和耗时之间的平衡。阈值设置得太小比如0.01%算法可能要多跑几轮迭代设得太大解的质量又不够。我在实际项目中通常把间隙阈值设为1%到2%这是因为容量配置问题本身的数据精度负荷预测误差、设备成本估算误差远大于这个范围追求过小的间隙在工程上没有实际意义。最大迭代次数的保护作用很重要。虽然CCG算法在理论上有有限收敛性保证但实际运行时会遇到数值病态问题——比如某个场景导致子问题的对偶问题无解或者主问题因为数值原因求解失败。没有最大迭代次数的保护程序可能死循环跑上几小时日志刷屏最后你只能强杀进程。我一般把最大迭代次数设在20到50之间正常情况下CCG在15轮以内基本都能收敛到1%间隙跑超过30轮说明模型或者数据大概率有问题不如停下来检查。4.3 主循环日志的设计怎么判断算法在正常干活调试CCG算法时日志信息是你的第一信息来源。很多新手拿到代码后直接跑看到循环在迭代就觉得万事大吉直到最后发现结果完全不合理才回头查。我建议在主循环里至少输出以下信息每轮迭代的LB和UB值、间歇大小、当前最恶劣场景的编号比如是第几个时段波动最大、主问题求解耗时、子问题求解耗时。规律性的收敛曲线是判断算法是否正常的重要依据。正常情况下LB应该单调递增因为场景在变多约束在变多可行域在收缩UB应该单调递减因为投资方案在逐步变得更健壮。如果你看到LB或UB出现回退说明代码里有bug最常见的原因是子问题求出来的最恶劣场景没有真正被加入主问题的场景集或者主问题的场景变量索引错乱导致约束没加对。另一个常见现象是间隙在震荡U-B曲线不是平滑收窄而是锯齿状。这往往意味着子问题的max-min转化出了问题内层的线性化改造有缺陷。这种问题排查起来很费时间唯一的建议是退回到小规模测试把时段数减少到4个设备种类减少到2种光伏储能手工推演几步预期结果对比程序输出的中间量。这种玄学式排查虽然原始但确实是我用过的所有方法里最有效的。5. 参数敏感性分析与代码调试噪声、边界和收敛方向5.1 不确定性预算参数的敏感性测试方法不确定性预算参数budget of uncertainty是整个模型里最需要重点关注的参数。它直接决定了模型的保守程度进而影响投资方案的构成。通常的做法是跑一组预算参数扫描budget从0开始逐步增加每次增加一个步长记录对应的最优投资方案和总成本变化。# 不确定性预算的敏感性分析 def sensitivity_analysis_budget(data, params): 扫描不同程度的不确定性预算观察投资方案的变化 results [] # 时段数设为24最大预算就是24 for budget in range(0, 25, 4): params[budget] budget investment, LB, UB, iter_num ccg_solver(data, params) results.append({ budget: budget, pv_installed: investment[x_pv], wt_installed: investment[x_wt], ess_installed: investment[x_ess], dg_installed: investment[x_dg], total_cost: (LB UB) / 2, # 取上下界中值为参考 iterations: iter_num }) return results分析这个敏感性扫描结果时你通常会看到几种典型形态budget较小时光伏和风电占比高储能容量适中随着budget增大可再生能源比例下降柴油机台数增加储能容量先增后减。这个先增后减的现象很有趣适度增加不确定性时储能作为灵活性资源的价值凸显但当不确定性继续增大到一定程度储能也扛不住极端场景了只能靠柴油机来兜底。5.2 负荷和光伏预测偏差的建模细节不确定集合的定义方式决定了模型的保守度和计算复杂度之间的平衡。最常见的做法是对每个时段的预测误差做归一化处理用相对偏差的百分比作为波动区间的上下界。比如光伏预测偏差设为15%负荷预测偏差设为10%那么第t个时段的不确定变量可以表示为预测值乘以1±偏差比例再乘以波动系数。这里有一个实际工程中容易出问题的地方光伏出力的不确定性在一天内有明显的时段特征。白天光伏出力大预测误差的绝对值也大傍晚和夜里光伏出力为零不存在预测误差。如果不对这种情况做处理那些光伏为零的时段在不确定集合里还会被赋予波动范围导致子问题找出“光伏在夜里出力为80%预测值”这种物理上不可能的场景。解决的办法是引入时段相关的偏差系数光伏预测偏差在夜间直接设为零。负荷的不确定性建模也有讲究。微网负荷通常有工作日和休息日两种典型模式如果混在一起建一个不确定集合集合会过大。我一般会分别建模两个不确定集合在CCG迭代中交替使用确保最恶劣场景搜索能覆盖两种负荷形态。5.3 代码单步调试与数据回验最有效的验证手段拿到一个新的两阶段鲁棒优化代码直接跑完就提交结果是最危险的做法。建议至少做三类验证特殊场景验证、物理一致性验证、基准对比验证。特殊场景验证是把不确定集合缩小到只剩预测场景此时两阶段鲁棒优化应该退化为确定性优化。跑一次确定性优化不考虑不确定性作为基准对比两者的投资方案应该完全一致。如果结果不一致说明鲁棒模型里有没关联在一起的约束或者是目标函数在场景数为1时出现了重复计算。物理一致性验证是检查优化输出的各个变量是否满足物理限制。比如储能SOC曲线应该在一个连续范围内波动且首尾相接如果约束里要求日循环光伏出力不应该超过装机容量乘以光照效率柴油机出力不应该超过额定功率。这些检查用简单的print加人工判断就能完成非常值得做。基准对比验证是拿已发表的论文数据复现结果。如果代码的输入数据来自某个典型微网测试系统尝试调动文献里的参数看结果能否和文献对得上。对不上不要急着下结论先检查单位换算——很多文献里光伏成本按元/kW给有的按万元/MW给差了一个数量级结果对不上但模型本身没问题这种情况我见过太多次了。6. 避坑指南两阶段鲁棒优化代码调试的五个血泪经验6.1 对偶问题推导出错但表面看不出来现象CCG算法正常迭代间隙在下降但最终收敛结果明显不合理——光伏装机为零柴油机装了十几台总成本高得离谱。原因子问题中对偶问题的约束推导有误。具体来说我在早期版本里漏掉了某个对偶变量非负约束导致对偶可行域变大子问题求出来的“最恶劣场景”实际上不可行导致UB虚高主问题被迫不断加装柴发来“兜底”一个根本不存在的极端场景。根源其实是对偶理论中不等式约束对应的对偶变量符号没搞对。解决把子问题的内层最小化问题打印出来用一个小规模实例3个时段、2台设备手工推导一遍对偶对比代码里的表达式。别嫌麻烦这是唯一可靠的排查方式。另一种玄学一些的方法是换个求解器试试——如果你用的是Gurobi换CPLEX跑同样的问题如果结果不一致说明你的模型在数值上存在不稳定因素。6.2 储能SOC变量的边界与能量平衡约束冲突现象主问题的求解时间特别长而且最终储能容量配置结果经常是0——模型宁愿多装柴油机也不装储能。原因储能建模里有一个约束细节SOC的上下限和充放电功率之间必须满足能量平衡约束。有些实现里SOC上下限设置为0.2到0.9但充电效率只有0.9、放电效率0.95这意味着从20%充到90%需要的电量大于从90%放到20%能放出的电量。如果约束条件里没有处理这种效率不对称性储能的实际可用容量会被高估进而让优化模型觉得储能“不够用”。解决明确SOC的递推公式SOC(t1) SOC(t) eta_ch * P_ch(t) * dt - P_dis(t) / eta_dis * dt其中充电效率乘在充电功率上放电效率除在放电功率上。在写入循环约束时严格区分两个效率并且检查SOC首末状态是否需要相等日循环场景一般需要。另外检查一下储能的功率容量和能量容量是否需要分别乘以效率系数。6.3 子问题在特定场景下无解导致程序崩溃现象迭代到第5轮或第8轮时子问题求解返回infeasible状态程序直接抛异常。原因主问题跑出来的投资方案在子问题对应的极端场景下不能满足功率平衡约束。这其实不是bug而是CCG算法的一个特性主问题只考虑了有限场景所以中间迭代出的投资方案可能并不适用于尚未生效的极端场景。但如果代码没有对这个infeasible状态做处理程序就会崩溃。解决在子问题里可以引入松弛变量来处理不可行情况。给功率平衡约束加上非负松弛变量并在目标函数里给松弛变量一个足够大的惩罚系数通常是运行成本量级的100到1000倍。这样即使子问题遇到暂时不可行的场景也能通过对偶求解出一个有值的“近似最恶劣场景”把它加回主问题让主问题在下一轮迭代中修正投资方案。这种做法在工程中非常常见本质上是用一种软约束替代硬约束来保证迭代可继续。6.4 收敛间隙震荡不是源码bug而是数据数量级问题现象LB和UB虽然间隙在缩小但缩小的速度极慢而且有反复——LB升上去又降下来UB降下来又升上去。原因数据里不同变量的数量级差异过大。比如光伏投资成本是每千瓦6000元量级在10^3到10^4而燃料成本是每千瓦时0.6元量级在10^-1两者相差4到5个数量级。优化器在求解时数值敏感性极高会导致对偶变量在迭代中不稳定。解决对所有参数做无量纲化或归一化处理。常见的做法是把所有成本和功率参数折算到同一基准值上。我的习惯是以年负荷总量为基准把所有单位成本换算成元/千瓦时的等价成本或者用pu值标幺值体系——设定基准功率为1MW、基准成本为1万元其他所有参数除以对应基准值。这样处理后不仅迭代更稳定收敛速度还能提升不少。6.5 代码结果和论文结果对不上先查数据预处理再查算法现象按某篇论文的参数设置运行代码结果和论文里给出的最优配置相差很大。原因工程实践表明大部分结果不一致问题都出在数据预处理环节其次才是算法实现细节。我遇到过的问题包括负荷曲线单位是kW但光伏数据是MW没做统一某些时段的负荷数据缺失被粗暴地填了零影响了整体约束还有把月数据当成日数据用导致全年结果完全偏掉。解决建立一个数据核验清单——先检查单位是否统一、数据是否完整、时间轴是否对齐再检查相关参数比如光伏容量系数、储能充放电效率是否跟实际系统一致最后才是对比算法细节。如果前面都检查过还没有头绪把日志里的中间结果比如每一轮迭代的间歇和场景值打印出来和论文的收敛曲线比对看是否在相同的迭代轮次收敛到相同水平。论文和代码的差异一般都能通过这种方式找到根源。7. 把算法成果做成可复现的工程包数据、文档和扩展方向7.1 输入数据文件的设计规范让使用者少踩一半的坑好的代码包不只是算法本身更重要的是输入数据的组织方式。我在交付类似源码时通常会用一个统一的配置文件来管理所有参数而不是把参数散落在代码的不同角落。# config.yaml微网容量配置的统一参数入口 # 设备参数 pv: cost_per_kw: 4500 # 光伏单位投资成本元/kW lifetime: 25 # 寿命年 derate_factor: 0.85 # 综合折减系数考虑灰尘、温度损耗 max_area: 5000 # 最大可安装面积m2 ess: cost_per_kwh: 1200 # 储能能量成本元/kWh cost_per_kw: 1500 # 储能功率成本元/kW lifetime: 10 # 寿命年 eta_ch: 0.95 # 充电效率 eta_dis: 0.95 # 放电效率 soc_min: 0.2 # 最低SOC soc_max: 0.9 # 最高SOC dg: cost_per_unit: 80000 # 单台柴油机成本元/台 rated_power: 100 # 单台额定功率kW fuel_consumption: 0.25 # 油耗L/kWh fuel_price: 6.5 # 油价元/L lifetime: 15 # 寿命年 # 不确定性参数 uncertainty: load_deviation: 0.1 # 负荷预测偏差比例 pv_deviation: 0.15 # 光伏预测偏差比例 budget_ratio: 0.35 # 预算比例相对时段数 # 求解参数 solver: gap_threshold: 0.01 # 收敛间隙阈值 max_iter: 30 # 最大迭代次数 time_limit: 3600 # 求解时间上限秒用YAML或JSON配置文件管理参数好处是使用者不用在代码里找参数位置而且更容易做参数敏感性分析。我一般还会再加一个README.md里面写清楚每个参数的物理含义、取值范围和推荐初始值以及数据文件的格式说明——列名是什么、单位是什么、一个文件里包含几个典型日。7.2 从两阶段鲁棒到分布鲁棒升级方向扩展两阶段鲁棒优化并不是终点如果后续想把模型做得更精细可以考虑几个扩展方向一是从箱式不确定集合升级为分布鲁棒优化DRO用矩模糊集或Wasserstein距离来定义不确定性既能保留鲁棒性又能利用历史数据的概率信息在保守度上更可控二是在子问题中加入需求响应模型让负荷变成部分可调资源这需要把不确定性变量扩成“负荷基线可调范围”的结构三是考虑多微网互联的场景不确定性集合变成多维耦合CCG的框架仍然适用但子问题的对偶推导会复杂很多。7.3 一种实用的结果可视化验证技巧代码跑完别急着写报告做一个简单的可视化验证把最优投资方案下的各时段功率平衡图画出来包括光伏出力、风电出力、储能充放电、柴油机出力、购电功率和负荷曲线。把这张图和数据表放在一起看基本上能发现绝大多数隐藏的建模错误。具体来说好的容量配置结果应该满足以下几个特征功率平衡图在所有时段都闭合发电购电负荷储能充电储能SOC曲线在上下限之间平滑变化且在日循环场景下首尾相接柴油机出力只在光伏和储能不足以支撑负荷的时段出现而不是全天都在跑如果设置了并网购电购电功率曲线应该避开电价高峰期如果你在模型里加了分时电价的话。这套验证方法不涉及复杂的数学检验但一次可视化能节省你数小时的数据核验时间。我现在的习惯是每次跑完必出图不只是一张而是把收敛曲线、功率平衡图、SOC曲线、投资成本饼图一起输出到同一个文件夹里方便复盘。最后说一句我的习惯两阶段鲁棒优化代码的维护性并不好它比普通优化模型多了一层嵌套逻辑调试成本成倍增加。所以每轮迭代的日志、每次调参的配置、每个验证图都要按日期归档一个月之后回头看你会感谢当时记录下的这些细节。希望这篇内容能帮你在微网容量配置这个方向上少走一些弯路把精力花在真正需要烧脑的地方。本文还有配套的精品资源点击获取
返回列表