ARTICLE DETAIL

资讯详情

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

多微网电能互补与需求响应双层优化:Matlab建模到KKT求解实践

多微网电能互补与需求响应双层优化:Matlab建模到KKT求解实践 先说一句大实话做微电网优化这个方向很多人第一步不是倒在算法推导上而是倒在一套能跑通的结果上。多微网电能互补、需求响应、双层优化这几个词拆开看每个都有大量论文但真正把三者放进同一个模型还要用Matlab把代码撸出来能稳定复现结果的资料其实不多。这篇就是想把这套东西从建模到求解的完整路径讲清楚。这套模型解决的核心痛点很直接单个微网里光伏、风机出力看天吃饭本地负荷又时高时低靠自己的储能和燃气轮机硬扛成本高、新能源利用率低。而多个微网之间如果能通过联络线互相支援再加上用户侧的需求响应来削峰填谷整体运行成本能明显降下来。但难点在于多微网的运营商和各微网内部的用户/分布式电源之间存在一层“上级制定电价、下级跟随响应”的主从关系普通单层模型表达不了这种博弈。所以要用双层优化模型来描述上层做联合调度决策下层做用户侧响应。下面按我的实际经验把这套模型的建模、代码实现、参数设计、调试避坑完整拆开讲。1. 从“各自为政”到“多微网互助”这个模型到底在解决什么问题1.1 多微网电能互补省钱的本质是“错峰余缺互济”先想一个最简单的场景两个微网并排挨着微网A屋顶光伏装得多中午发电量大到自己负荷用不完储能又已经充满这时候多余的电要么浪费掉要么低价往回卖微网B正好是商业负荷为主午间用电高峰买市电价格高企。如果能拉一条联络线让A把多余的电卖给B两边都划算。这就是多微网电能互补最朴素的价值。用专业一点的话说各微网的分布式电源容量、负荷曲线、储能配置各不相同净负荷负荷减去可再生能源出力在时间分布上有很强的互补性。通过微网之间的功率交换把“弃光弃风时段”的发电量转移给“缺电时段”的微网整体对主网的依赖就降下来了购电成本和大网潮流压力同时减小。我在实际建模时会遇到一个很容易被忽略的点微网间的交互功率是有方向性的而且交换功率会产生线路损耗或交易成本。很多初版模型直接只设一个交互功率变量就完事结果求解出来微网A“一边买高价电一边低价卖给B”出现这种不合理结果通常是缺少了交互功率的定价机制或方向限制。这个后面建模章节细讲。1.2 需求响应让用户侧不再是“局外人”再往下一层每个微网内部还有一类资源长期被低估用户负荷本身的调节能力。传统调度把负荷当成硬性边界条件用户用电多少是给定的微网只能被动去追负荷曲线。需求响应Demand Response, DR的思路就是把用户侧变成可调节资源。常用的建模方式有两种价格型DR和激励型DR。价格型DR的核心是需求价格弹性电价涨了用户就少用/转移用电电价跌了用户就多用电一般用弹性系数矩阵来描述负荷对电价的敏感程度。激励型DR则是微网和用户签协议允许在特定时段切除或转移一部分负荷微网给用户补偿费用补偿费用通常是非线性递增的切得越多单位补偿价越高。国内微网研究里用得比较多的还是价格型DR因为参数好设置、结果直观。实际操作中要设两个关键参数自弹性系数同一个时段负荷对电价的响应和交叉弹性系数其他时段电价对这个时段负荷的影响。自弹性一般是负值比如-0.2就表示电价上涨10%该时段用电量下降2%。交叉弹性一般是正值说明用户会把高电价时段的用电转移到低电价时段。需求响应放进双层模型里最大的好处是它天然属于“下层响应”的一部分。上层微网运营商给出分时电价下层用户根据电价调整用电行为再把调整后的用电负荷反馈给上层这就是典型的Stackelberg博弈。1.3 双层优化单层模型表达不了“博弈关系”现在关键问题来了为什么要用双层优化模型能不能把所有目标捏成一个单层模型能但失真。如果直接写单层目标函数就是“总成本最小”然后微网间交互功率、售购电价格、用户负荷全部一起优化。这时候求解器可以把售电价定得很高同时让用户负荷调得极低来降低成本但现实中用户根本不会接受因为没有考虑“用户也有自己的利益诉求”。双层模型对应的是 Stackelberg 博弈结构。上层是领导者多微网系统运营商或配网调度中心做决策的先手先给出微网间交互电价/零售电价、燃气轮机出力和储能充放电计划下层是跟随者各微网或用户在给定上层电价后以自己用电成本最小为目标优化自身的购电量和可转移负荷。上层做决策时必须考虑到下层会理性响应反过来下层又受上层决策的约束。这种“你中有我、我中有你”的递阶决策关系单层模型是刻画不出来的。做了这些年优化模型我的体会是如果你只是在做仿真实验且算例规模不大双层模型确实比单层麻烦不少但只要你的研究内容涉及“上级定价格、下级做响应”就踏踏实实双层建模否则审稿人一眼就能看出模型缺陷。2. 双层优化的数学模型公式拆开其实没想象中难2.1 上层模型多微网系统总运行成本最小先明确双层模型里各层的决策变量上层决策变量主要有各微网购/售电功率和主网的交互、微网间联络线交换功率、微型燃气轮机出力、储能充放电功率、微网向用户售电的分时电价。上层目标函数是系统总运行成本最小我习惯写成min F 主网购电成本 - 向主网售电收益 燃气轮机燃料成本 储能运行维护成本 需求响应补偿成本 微网间交易成本其中主网购电成本等于从上级电网购电功率乘以购电价这里购电价一般是分时电价或者实时市场出清价是已知外部参数。燃气轮机燃料成本通常用二次函数 C(Pmt) aPmt² bPmt c二次项表示高出力时效率下降带来的附加成本这个系数一般通过实测数据拟合。上层约束包括每个微网的功率平衡约束分布式电源出力 储能放电 购电功率 微网间受入功率 本地负荷 储能充电 售电功率 微网间送出功率联络线传输容量约束任何时候交换功率不能超过线路限值储能SOC递推公式SOC(t1) SOC(t) (充电功率*充电效率 - 放电功率/放电效率) * Δt / 储能容量储能SOC上下限约束、充放电功率约束、一个调度周期始末SOC保持一致这是很多新手容易漏的约束不加上容易出现“把储能电量用光”的不可行方案微型燃气轮机出力上下限和爬坡约束上层还会给需求响应设定边界比如可转移负荷占总负荷的比例上限以及调整前后总用电量不变移峰填谷不是削减总量而是改变用电时段。2.2 下层模型用户侧用电成本最小下层是用户或者单个微网的用电优化问题在给定上层电价后用户决定各时段的购电量和可转移负荷。下层目标函数一般是用电成本最小min f Σ 各时段零售电价 * 该时段实际用电量下层的约束主要是负荷调整的边界实际用电量 原始负荷 转移进来的负荷 - 转移出去的负荷同时可转移负荷在一定范围内一个周期内的总转移量守恒。值得注意的是下层问题通常是一个线性规划这为后面的单层化转化提供了非常关键的性质——线性规划的强对偶成立。2.3 双层模型的单层化KKT条件 大M法线性化双层模型没法直接用常规优化求解器求解所以要把它转成单层。业内最常用的方法是把下层问题替换成它的KKT最优性条件这样下层优化问题就变成了一层约束整个问题变成一个带互补约束的数学规划MPEC再用大M法把互补约束线性化成混合整数线性规划。具体操作分三步走。第一步写下层问题的拉格朗日函数求偏导得到平稳性条件、原始可行性条件、对偶可行性条件以及互补松弛条件。第二步处理互补松弛条件。互补条件的形式是“对偶变量 ≥ 0约束残差 ≥ 0两者乘积 0”。这是个非线性约束直接用会让求解器崩溃。工程上最稳的做法是用大M法离散化引入0-1整数变量把“乘积为0”转成二选一的逻辑约束。第三步处理上层目标函数里的双线性项。这是这个模型里最容易踩坑的地方。上层目标里微网间交易成本 交互价格 * 交互功率如果交互价格也是决策变量那目标函数里就出现了“变量乘变量”的非线性项。解决办法有两个一是如果交互价格是固定已知参数那就不存在这个问题二是如果交互价格也要优化需要利用下层问题的强对偶条件对双线性项进行等价替换把非线性项转成线性表达式或者用辅助变量引入McCormick包络做线性松弛。不少论文在这个地方含糊带过实际写代码时你会在这里卡很久具体细节我在后面代码部分展开。另外还要说明KKT单层化要求下层问题是凸的。如果你的下层模型里有非线性约束和有整数变量这个方法就不直接适用。所以设计下层模型时尽量保持它是线性规划这是能用商业求解器大规模求解的前提。3. Matlab代码实现的关键环节求解器和代码框架选型3.1 求解器与工具箱的合理搭配Matlab代码实现里最推荐的组合是Matlab Yalmip CPLEX或Gurobi。Yalmip是一个建模工具它的核心价值是让你能用接近数学公式的语法直接写优化模型不用手搓标准矩阵形式极大地减少建模出错概率同时可以无缝切换多种求解器。我在实际使用中的版本建议Matlab R2020a及以上Yalmip的最新Release版本CPLEX 12.10或Gurobi 9.5都可以。如果你的实验室只有CPLEX学术版够用了如果追求求解速度Gurobi在混合整数线性规划上通常略快但差距对这个规模的算例来说不太大。需要提醒一句CPLEX/Gurobi的安装路径不要带中文否则Matlab调用时经常莫名报“license not found”的错。另外运行前先在命令行输入yalmiptest测试Yalmip能否正确识别求解器这一步能排除80%的环境问题。3.2 代码模块结构与核心片段写这种优化模型最忌讳的是把所有内容糊在主脚本里后期改一个参数要翻半天。我通常按下面的模块拆分main.m 主程序参数初始化、建模型、求解、保存结果 define_params.m 全局参数定义 build_upper.m 上层模型约束构建 build_lower.m 下层模型约束构建 build_kkt.m 下层KKT条件构建 大M线性化 solve_model.m 求解器配置与调用 plot_results.m 结果可视化下面是主程序核心骨架的伪代码还原了建模层的核心逻辑%% 初始化 define_params; % 填入微网数量、时段时间、负荷曲线等 %% 上层变量 Pbuy sdpvar(N_mg, T); % 各微网向主网购电功率 Psell sdpvar(N_mg, T); % 各微网向主网售电功率 Pmt sdpvar(N_mg, T); % 燃气轮机出力 Pch sdpvar(N_mg, T); % 储能充电功率 Pdis sdpvar(N_mg, T); % 储能放电功率 SOC sdpvar(N_mg, T1); % 储能荷电状态 Pex sdpvar(N_mg, N_mg, T); % 微网间交互功率带方向 price sdpvar(N_mg, T); % 上层制定的零售电价决策变量 %% 下层变量用户需求响应后负荷 L_shift_in sdpvar(N_mg, T); % 转移进入的负荷 L_shift_out sdpvar(N_mg, T); % 转移出去的负荷 %% 下层KKT条件转上层约束 % 以用户成本最小为目标的下层线性规划写出KKT条件后 % 通过大M法线性化加入上层约束集合 Constraints [Constraints, KKT_constraints]; %% 目标函数已通过强对偶消除双线性项 Objective sum(sum(price_buy_grid .* Pbuy)) ... sum(sum(a.*Pmt.^2 b.*Pmt c)) ... ... % 各项成本 sum(sum(price .* L_shift_in)) - sum(sum(price .* L_shift_out)) ... big_M_linear_terms; % 对偶变量与参数组成的线性项 %% 求解 ops sdpsettings(solver, cplex, verbose, 2); optimize(Constraints, Objective, ops);这里有个编程细节要特别提醒在Yalmip里变量和参数之间要用矩阵点乘. *而不是*。尤其是矩阵索引时一个不注意就把矩阵乘法算成了维数错误报错信息还不直观。写代码时统一用点乘来处理逐元素运算能省很多调试时间。还有个很实用的技巧把所有约束分成几个cell数组比如Cons_power {}、Cons_storage {}、Cons_kkt {}最后统一Constraints [Cons_power{:}, Cons_storage{:}, ...]。这样当求解器报错时能快速定位是哪一类约束出了问题。3.3 参数标幺化与数量级控制双层优化模型里变量众多参数量级跨度非常大。微网功率可能是几千千瓦电价是几毛钱一千瓦时SOC是0到1的小数目标函数里各项数值差了好几个数量级。如果不做处理求解器的数值容差会出问题表现出来就是明明模型没问题求解结果却歪得离谱或者干脆报“numerical issues”。我的经验是把功率统一用kW、电量用kWh、电价用元/kWhSOC用标幺值0-1时间步长取1小时。这样目标函数的量级基本在10^3到10^4之间不会有极端量级差。还有一个做法是对所有变量做归一化但归一化会导致约束含义不直观调试不便对于这个规模的优化问题其实没必要。不过有一个时间单位的小细节要注意如果Δt不是1小时而是15分钟那么储能递推式里的充放电功率必须乘以0.25小时的因子否则储能SOC更新会出错。看起来是个小问题实际跑出来曲线会完全不对。另外大M的取值也要讲究不要图省事取个10^6或10^8。大M太大时整数规划求解器在处理互补约束时会出现数值病态问题。通用做法是取“该约束可能达到的最大量级”再稍微放大一点。比如功率平衡残差约束的最大值不会超过总负荷规模5000kW那M取50000就足够了过大反而有害。4. 算例设计与结果分析怎么做出一套论文级图表4.1 三微网典型日算例设计我用的案例是三个微网互联模拟一个工业园区场景。微网1是居民区为主早高峰和晚高峰负荷明显微网2是商业区白天负荷高微网3是小型工业区负荷平稳但是总量大。每个微网都配置光伏、风机、微型燃气轮机和储能其中微网1的光伏装机最大中午出力峰值很高弃光风险大。这里我把每个微网的光伏、风机、负荷的24小时曲线说明一下。因为微网电能互补的核心价值就是利用出力曲线的错峰特性所以案例设计有意识地让三个微网的净负荷峰谷时段错开。比如微网1午间净负荷最低光伏大发微网2午间净负荷最高商业空调负荷这样午间时段微网1向微网2送电才能明显体现互补收益。算例时段取24小时步长1小时这是一个最经典的调度周期长度。储能SOC的初值设为0.2末值约束到0.2保证每天是一个完整调度周期。4.2 四种方案对比证明每个模块“确实有用”很多读者问过我双层模型都建好、代码也跑通了怎么证明模型有效我的做法是设置四个对照方案方案一不考虑电能互补不考虑需求响应各微网独立运行基准方案方案二只考虑多微网电能互补不开需求响应方案三只考虑需求响应各微网不互联方案四电能互补和需求响应同时开启完整模型运行下来完整模型相对基准方案的总运行成本降幅通常在8%到15%之间。更重要的是要看降本的路径是什么互补带来的降本主要体现在减少向主网购电、减少弃光需求响应带来的降本主要体现在把高价时段用电转移到低价时段降低峰值购电电价。比重成本更有说服力的是曲线对比。把方案一和方案四的联络线总功率画在一张图里能清晰看到完整模型在午间时段让光伏富余的微网向负荷高峰微网送电。再画出用户负荷调整前后的曲线能看到晚高峰被削掉一小块、低谷时段被填上峰谷差明显收窄。4.3 结果可视化曲线怎么出才有论文感Matlab画图这块建议大家用统一的风格图幅比例3:4较协调、线宽2.0、字号11-12、坐标网格用浅色虚线。我常用的输出有四张图各微网电功率平衡堆叠图电源在上、负荷在下直观展示功率平衡关系储能SOC曲线确认SOC在规定范围内且收尾一致微网间交互功率曲线正值方向表示送出负值方向表示受入用阶梯图加不同颜色区分需求响应前后负荷曲线对比图原负荷、响应后负荷、电价曲线放同一坐标系双坐标显示我建议所有曲线最终都通过exportgraphics或print导出为矢量图PDF或EPS格式投论文时不会有像素糊的问题。这个问题在投稿阶段极其常见图导出成PNG然后被期刊要求重做不如一开始就养成输出矢量图的习惯。5. 常见问题排查与避坑实录下面这些问题是我从零复现这个模型时实际踩过、也帮学生排查过的坑每个都有代表性。5.1 求解器报“infeasible problem”怎么办模型无解是最常见又最让人抓狂的问题。无解时的排查顺序我建议严格按照下面的步骤走第一步先查储能约束。把SOC的初始值和末值约束注释掉跑一次如果求解成功问题就在储能SOC约束上。大概率是SOC上限设置不合理比如总容量1000kWh初始SOC 0.9又在负荷低谷时段强制大功率充电一个时段就直接超过上限。第二步查功率平衡的“符号”。这类模型变量方向很混乱购电是正、售电是负充电是正、放电是负。功平衡方程写错正负号在数学上也能“凑出一个目标值”但物理上完全不对表现在结果上就是储能边充电边放电、微网同时从主网购电又向主网售电。解决方法是所有关于功率的流进流出画一张方向图对着图检查约束公式别只在脑内心算。第三步查大M约束。如果互补松弛线性化后模型无解检查整数变量是否需要互斥同一个互补对里 z 和 1-z 方向是否写反了。这种错误十分隐蔽因为单纯看每一行约束都像是“对的”。5.2 求解慢优化方向比改求解器参数更重要24时段、3个微网、下层KKT全展开后模型里的整数变量数量会很可观每个互补约束配对引入一个0-1变量混合整数线性规划的规模可能达到数千个变量。CPLEX/Gurobi在默认配置下会尝试开启多个求解线程但瓶颈往往在于模型本身的Big-M松弛太松导致分支定界树的节点爆炸。我试过几个有效操作缩小大M的取值M越小分支定界的上界越紧。但也不能太小太小会割掉可行解。尽量减少互补约束的数目。对于只需要一层KKT转化的问题检查下层变量的维度如果某个变量在最优解里不可能同时出现在两个互补约束里就可以通过变量替换减少约束条数。给求解器设置一个合理的MIPGap比如1%或0.5%。工程应用里没必要追求gap0尤其当数据本身精度有限时1%的gap完全够用。5.3 双线性项处理出错结果出现“倒挂”在处理双层模型时如果上层的零售电价是决策变量下层用户购电量也是决策变量那么目标函数里会出现“电价 × 电量”的双线性项这是整个模型里最麻烦的一个点。处理的方法是利用KKT单层化后的强对偶关系。具体来说下层问题是线性规划最优解处强对偶成立原问题最优值对偶问题最优值这样可以把目标函数里的双线性项替换成下层对偶变量与常参数的线性组合。我建议在代码里分步验证这个替换是否写对先用一组固定电价手动计算用户最优负荷再用对偶形式计算对比两个数值是否一致。数值如果对不上说明公式推导有误不要急着跑全模型。5.4 参数灵敏度论文化输出不能只有一组曲线最后说一个特别实用的技巧只跑一组典型日曲线论文是不够丰满的。评审一定会问如果光伏渗透率翻倍呢如果储能容量减半呢所以在代码里把参数设置部分做好元结构随时能改光伏容量系数、储能容量、DR弹性系数跑批量实验。我的做法是在define_params.m里定义一组基础参数然后在外层写一个for k 1:cases循环每个case修改某个参数并调用主程序求解最后把多组结果汇总到一个综合指标表里。这种可扩展的算例结构能让你在项目后期省下大量重复劳动。综合指标建议包含总运行成本、主网购电量、弃光率、峰谷差、DR响应量。用表格展示不同方案和参数变动下的指标变化比单纯贴一堆曲线更有说服力。最后再分享一个个人习惯代码里的所有变量命名尽量和论文公式的符号保持一致Pbuy、Psell、Pmt、SOC、Pex不要让名字带上中文拼音缩写。模型和代码混着调试的时候公式和变量名一一对应能节省大量脑力。这个项目做到后面你会发现比起堆公式把模型转换成清晰可维护的代码才是真正花时间的地方而前期每一分建模规范都会在后期十倍回报回来。
返回列表