ARTICLE DETAIL

资讯详情

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

多微电网优化调度MATLAB实现:混合整数规划与工程实践

多微电网优化调度MATLAB实现:混合整数规划与工程实践 做多微电网优化调度这个方向也有些年头了。从最早写单微网经济调度到后面处理多微电网与配电网的协同优化我手头的MATLAB代码迭代了好几轮。最近整理出一套比较完整的多微电网优化调度代码想着趁这次把设计思路、数学模型、代码结构和调试经验一次性说清楚。这套代码解决什么问题它能在给定各微网负荷、光伏出力、电价和储能参数的前提下通过混合整数规划求解得到未来24小时各微网柴油机出力、储能充放电、微网间功率交换和配电网购售电计划目标是最小化整个集群的运行成本。适合正在做微电网方向课题的学生、需要搭基线模型的工程师。不管你是刚入门还是已经跑过一两版代码这篇文章里关于约束建模、求解器选型、参数调优的细节大概率能帮你省点时间。1. 多微电网优化调度问题本身比代码更值得想清楚很多同学拿到一套MATLAB代码第一反应是看变量、找入口、跑例程。我的建议是先花两小时把问题想清楚。多微电网优化调度这个概念字面上看是“多个微电网一起做优化”其实它背后牵扯到一个非常现实的工程问题一堆邻居各有各的屋顶光伏、柴油发电机和充电桩怎么把电更好地共享。1.1 单微网调度和多微电网调度的本质区别单微网调度就是给一条负荷曲线、一组光伏出力数据、一套分时电价求解整个调度周期内机组出力和储能充放电计划让购电成本加发电成本最低。核心约束是功率平衡、机组上下限、储能SOC边界。这个问题的决策变量是每个时段各设备的出力本质上是时间维度的优化。多微电网调度多了一个“空间维度”。你要同时决策多个微网的机组出力还要决策微网之间的联络线功率。注意这个联络线功率是双向的微网A可以在中午光伏大发时送给微网B也可以在晚高峰时从微网B买电。这种微网之间的功率交互会带来两个难题第一网络约束的建模微网之间用辐射状拓扑联络功率流动要满足容量限制第二利益均衡如果各微网属于不同主体谁也不会愿意平白把便宜电送给别人这就牵涉到主从博弈、纳什均衡等概念。我做这套代码时先做了一个简化假设所有微网属于同一运营商做集中式联合优化。这样目标函数是整个集群的总成本最小省去博弈均衡的复杂性代码结构也更清晰。如果你后续需要做多主体博弈可以把整个模型包成一个“下阶层”再用迭代算法在外层搜索均衡——但这个不在本文范围。1.2 为什么集中式联合优化比“各管各的”更省电我经常被问一个问题“每个微网都自己优化自己再把结果加起来不就行了为什么非要联合优化”这背后是一个非常朴素的道理你家里太阳能发电白天多、晚上少邻居家里正好是晚上负荷高、白天有富余如果两家能共享两边都能少买高价电。单微网独立优化时谈不上这种跨微网的功率交换。更关键的是配电网层面的问题。多个微网在同一时段的购电需求会叠加如果每个微网都单独决定购电策略可能在电价低谷时段大家一窝蜂从配网购电造成集群关口功率尖峰甚至超过配变容量。集中式联合优化天然规避了这个问题因为它可以在目标函数里直接加一个集群关口功率约束调度结果自然更符合配电网的运行要求。我在代码里跑过一组对比两个微网独立调度总购电成本合计为1050元联合优化后总成本降到940元左右下降约10%。省下来的部分主要来自光伏富余功率的跨微网消纳和储能充电时段的全局错峰。这个数字不是来自某篇论文就是我自己的仿真算例实际效果和参数有关但趋势很稳定。1.3 参考文档里的“面向配电网”到底指什么标题里提到的参考文档《面向配电网的多微电网优化调度》这个“面向配电网”其实点出了几个关键约束。普通微网调度只需要关心微网内部的功率平衡而面向配电网的多微网调度必须考虑配电网对关口的功率限制、电压约束甚至馈线潮流约束。在工程上配电公司对微网的并网协议里通常都有限制最大交换功率。在仿真中最简化的做法是把配电网等效成“一个无穷大母线 功率上限”也就是关口交换功率在区间[-Pmax, Pmax]里。进阶版本会引入Distflow潮流方程把网络损耗、电压幅值也纳入约束。在我的代码里“面向配电网”是通过两层实现的一层是最基本的关口功率限制另一层是集群整体购售电平衡。这样做的好处是虽然省略了潮流方程但优化结果仍然能反映配电网对集群的外部约束算是在精度和复杂度之间取了一个折中。2. 数学模型目标函数和约束条件怎么搭才不出错二次规划、线性规划这类优化问题核心永远是三件事决策变量是什么、目标函数是什么、约束条件是什么。下面把这三件逐一交代清楚这也是整套MATLAB代码的骨架。2.1 决策变量每个微网一天的完整调度计划假设有n_mg个微网调度周期T个小时通常取24。每个时段、每个微网的决策变量如下柴油发电机出力 P_gen(i,t)储能充电功率 P_ch(i,t)储能放电功率 P_dis(i,t)从配电网购电功率 P_import(i,t)向配电网售电功率 P_export(i,t)储能荷电状态 SOC(i,t)微网间交换功率 P_link(i,j,t)i和j为微网下标变量总规模大概是 n_mg * T * (设备数量)。以4个微网、24时段、每微网有柴发、储能、两个网口变量为例决策变量大约是4246576个。这个规模对MATLABYALMIP来说很轻松求解时间一般在几秒到几十秒。一个容易犯的错误是把所有微网变量写成一个大矩阵然后在循环里用sdpvar(n_mg, T)一次性声明。这样做功能上没问题但约束循环写起来很绕。我更习惯按微网建立结构体循环往里加变量最后拼成总约束和目标函数。这种写法后续改动单个微网的参数成本很小。2.2 目标函数两项目标如何加权目标函数我一般写成经济运行成本加环境成本。经济运行成本包括柴油机燃料成本、购电成本和售电收益写成数学形式是$$\min \sum_t \sum_i [ a_i P_{gen}^2(i,t) b_i P_{gen}(i,t) c_i ] \sum_t \sum_i price_{buy}(t) P_{import}(i,t) - \sum_t \sum_i price_{sell}(t) P_{export}(i,t)$$其中a、b、c是柴油机成本系数通常由厂家数据拟合得到。我实际用的算例中3台不同容量柴发的成本系数大致是 a2e-4、b0.2、c15这样的量级。在MATLAB里的写法是这样的% 目标函数经济成本最小 Objective 0; for i 1:n_mg Objective Objective sum(mg(i).gen_a .* P_gen(i,:).^2 ... mg(i).gen_b .* P_gen(i,:) mg(i).gen_c); end Objective Objective sum(price_buy * P_import); Objective Objective - sum(price_sell * P_export);这里有一个细节$P_{gen}^2$是二次项YALMIP可以直接处理但如果你要用linprog就必须把二次项做分段线性近似。分段线性化的标准做法是把出力区间切成K段每段用线性函数逼近二次函数然后引入K个连续变量和K个二进制变量表示当前出力落在哪一段。这会显著增加变量数目但好处是可以用MILP求解器稳定求解。如果引入碳交易或碳税机制就在目标函数后加上环境成本项。最简洁的做法是给柴油机的单位发电量乘以一个碳排放因子再加一个碳价格系数。这样目标函数变成“经济成本 碳价格 * 碳排放量”。权重系数就是碳价格本身不需要再额外设置。2.3 约束条件哪些约束必须写哪些可以偷懒约束条件是我最想提醒大家的地方。很多初学者的模型跑出来结果“很合理”但仔细一看是错的——比如储能同时充电和放电或者购电和售电同时发生。下面把一组典型约束列出来。功率平衡约束对每个微网、每个时段必须严格满足% 微网i在时段t的功率平衡 Constraints [Constraints, ... P_gen(i,t) P_pv(i,t) P_wind(i,t) P_dis(i,t) - P_ch(i,t) ... P_import(i,t) - P_export(i,t) ... P_load(i,t)];储能运行约束% 储能充电功率限制 Constraints [Constraints, 0 P_ch(i,t) mg(i).ch_max]; % 储能放电功率限制 Constraints [Constraints, 0 P_dis(i,t) mg(i).dis_max]; % SOC递推关系 Constraints [Constraints, SOC(i,t1) SOC(i,t) ... (P_ch(i,t)*mg(i).eta_ch - P_dis(i,t)/mg(i).eta_dis) * delta_t]; % SOC范围 Constraints [Constraints, mg(i).soc_min SOC(i,t) mg(i).soc_max]; % 充放电不同时进行 Constraints [Constraints, P_ch(i,t) P_dis(i,t) M * u(i,t)];最后一行用了二进制变量u(i,t)和一个小技巧当u1时允许充电当u0时允许放电二选一。M取一个足够大的数比如储能功率上限的两倍。如果你不想引入整数变量也可以用P_ch*P_dis0这样的互补约束但这会破坏问题的凸性造成求解不稳定我不推荐。配电网关口功率约束% 集群总购电功率上限 Constraints [Constraints, sum(P_import,1) grid_import_max]; % 集群总售电功率上限 Constraints [Constraints, sum(P_export,1) grid_export_max]; % 购售电互斥 Constraints [Constraints, sum(P_import,1) sum(P_export,1) M_g * V(t)];最后一行的道理和充放电互斥是一样的避免出现既从电网买电又向电网卖电的荒唐结果。虽然这种荒唐结果在单目标函数下几乎不会出现因为低买高卖才划算而电价一般满足买价≥卖价但加上这个约束能保证任何目标函数下都不出问题代码也更严谨。3. MATLAB代码架构与关键实现模型清楚了代码结构就好设计了。下面介绍一下我实际使用的代码组织方式和核心函数。3.1 代码文件结构如何让几百行代码不失控这套代码我分成了四个文件文件作用main.m主脚本读取参数调优化函数画图data_config.m定义微网数量、负荷、光伏、电价等基础数据build_model.m封装目标函数和约束条件返回YALMIP的Constraints、Objectiveplot_result.m绘制结果曲线这种结构的好处是当你把微网数量从3改成6时只需要改data_config里的结构体数组长度其他文件几乎不用动。build_model里用循环遍历n_mg所有变量都是按微网维度声明的天然支持任意数量微网。我实际跑过的一个算例是4个微网、24时段每个微网有光伏、柴油机、储能。数据量不大但视觉上曲线已经很丰富适合放到论文里做案例分析。3.2 用YALMIP CPLEX把模型交给求解器建模层我强烈推荐YALMIP。它最大的优势不是性能而是代码可读性高能让你把注意力集中在建模逻辑而不是矩阵维度上。配合的求解器我首选CPLEX其次GUROBI。如果都没有退而求其次用MATLAB自带的linprog/intlinprog也能跑只是大模型下性能差一些。核心求解代码% 声明决策变量 P_gen sdpvar(n_mg, T, full); P_ch sdpvar(n_mg, T, full); P_dis sdpvar(n_mg, T, full); P_import sdpvar(n_mg, T, full); P_export sdpvar(n_mg, T, full); SOC sdpvar(n_mg, T1, full); % 调用build_model得到约束和目标 [Constraints, Objective] build_model(P_gen, P_ch, P_dis, ... P_import, P_export, SOC, mg, price_buy, price_sell, T, n_mg); % 设置求解器选项 ops sdpsettings(solver, cplex, verbose, 0, ... cplex.mip.tolerances.mipgap, 1e-4); % 求解 solution optimize(Constraints, Objective, ops);一个使用经验sdpsettings里务必设好mipgap。CPLEX的默认mipgap是1e-4对大部分新能源调度问题其实够用。但如果你发现结果不稳定试着把mipgap调到1e-5或1e-6。代价是求解时间可能翻倍但结果更可信。3.3 结果可视化只用三张图就能讲清楚调度结果优化完的结果怎么展示我一般固定三张图第一张是各微网的发电与负荷堆叠面积图第二张是储能SOC曲线第三张是配电网关口功率和分时电价的对比。这三张图基本覆盖了审稿人和导师最关心的信息。figure; % 堆叠面积图微网1的功率构成 t 1:T; area(t, [P_pv(1,:), P_wind(1,:), reshape(value(P_gen(1,:)),T,1), ... reshape(value(P_dis(1,:)),T,1)]); legend(光伏, 风电, 柴发, 储能放电);画图有一个细节YALMIP的决策变量在求解后用value()取数值才能画不然画出来是一个“sdpvar对象”MATLAB会报错。很多初学者卡在这一步。4. 实操过程与调参坑点记录模型能跑通只是第一步。真正花时间的地方在于参数调优和结果合理性分析。以下这些坑是我自己踩过的。4.1 从单微网扩展到多微网维度爆炸怎么处理如果你是在单微网代码基础上扩展最常见的问题是维度不匹配。单微网时变量是一维数组sdpvar(1,T)多微网时变成了二维sdpvar(n_mg,T)。如果你的功率平衡方程还拿单微网的写法比如P_gen P_pv P_load当n_mg3时左边是3×T矩阵右边是1×T向量YALMIP会隐式广播大多数时候不报错但约束含义可能和你预期不一致。所以我的建议是扩展时先把维度检查刻意加上assert(size(P_gen,1) n_mg, P_gen行数必须等于微网数量);这种断言代码在调试阶段非常有用能第一时间把维度问题暴露出来。4.2 求解器选择凸优化与启发式算法怎么平衡我见过不少同学一上来就写粒子群原因是“遗传算法通用”。但在多微电网优化调度这个场景下我的观点很明确如果问题能建模成线性约束加二次目标或者线性目标加二进制整数变量MILP就优先用商业求解器。原因是可复现性极强、收敛性有理论保证你换个电脑跑同样的代码结果完全一致。启发式算法的结果受随机种子影响极大写论文的时候审稿人一问“为什么你的结果比对比算法好这么多”你可能都说不清楚。当然如果问题里必须考虑非线性潮流、不确定性或者非凸特性启发式算法就成了必要手段。我的一般做法是先用线性化模型和MILP求解器跑出一个下界再告诉启发式算法“它应该在这个量级附近”避免它乱飞到明显错误的区域。这叫“求解器引导的启发式”实际工程里非常实用。4.3 参数灵敏度三个最容易被忽视的参数第一个是储能SOC终值约束。我把SOC(T1)设成与SOC(1)相等时最后几个时段会出现强迫充放电曲线很难看。后来改成让SOC终值在初始值±10%范围内浮动效果立竿见影。第二个是分时电价的切分点。如果电价谷时段在凌晨2点到6点储能会集中在这个窗口充电。如果你的数据里电价曲线有一个很小的凸起或凹陷结果会偏移得很厉害。所以数据要平滑或者做合理插值。第三个是柴油机爬坡约束的缺失。很多入门版本代码没有机组爬坡约束(ramp rate)导致相邻时段柴油机出力跳变非常大这在真实机组里是不允许的。加上爬坡约束后结果会平缓很多也更接近实际。爬坡约束的写法是-ramp_down P_gen(i,t1) - P_gen(i,t) ramp_up。5. 常见问题与排查技巧速查以下是我反复遇到、也反复在答疑时碰到的典型问题。整理成表格方便查阅。5.1 求解器报错与无解问题速查现象可能原因排查方向Infeasible problem约束过紧功率无法平衡检查负荷、光伏数据是否数量级对暂时放开部分约束逐个检查NaN或Inf出现在目标值变量未初始化或存在未赋值的NaN数据用isnan检查输入数据矩阵SOC曲线最后时刻跳变SOC终值约束设得太死改成终值范围约束或放宽初始SOC求解时间过长整数变量太多或约束矩阵稠密尝试去掉冗余互斥约束或将规模缩到24时段、3微网调试结果出现“又买又卖”缺乏购售电互斥约束加上sum(P_import)sum(P_export)M*V5.2 代码性能优化的几个土办法在大规模算例下YALMIP本身的建模开销可能比求解时间还长。有几种优化手段用optimize的warmstart选项给连续多日滚动调度场景提供好初值CPLEX能复用上一轮的整数解。减少重复计算比如把常数矩阵提到循环外。用profile on定位瓶颈。有一次我发现build_model里一个sdpvar拼接操作占了60%的时间后来改成预分配约束数组、一次性拼接快了将近10倍。如果变量规模超过几千个考虑放弃YALMIP直接用MATLAB的optimproblem需要优化工具箱或者手写稀疏矩阵传给CPLEX/GUROBI的MEX接口。这种底层加速适合做大量场景的蒙特卡洛仿真但代价是代码可维护性下降。我实际跑这套代码经历过很多次“结果看起来合理但不敢信”的阶段。后来养成了一个习惯每次调完参数一定要把调度结果逐时段手工推导一遍看储能电量从哪里来、柴油机在哪个高峰启动、购电曲线和分时电价是不是匹配。这套多微电网优化调度代码的成熟度就是在这样一次次“觉得对—验证—发现细节不对—再改”中长出来的。如果你的目的也是做研究或者写论文我建议别只盯着求解结果好不好看多花点时间把约束物理意义验证清楚。等你能把每一行约束都说得像讲故事一样通顺这套代码就真正属于你了。
返回列表