
做园区微电网规划的朋友应该都有过这种经历——风电场、光伏板、储能电池各有各的调度逻辑运维团队各管各的看起来每个环节都在努力降本合在一起算总账却发现园区整体电费不降反升。问题出在哪大部分人第一反应是设备效率不行但根据我接触过的几个园区项目真正的问题往往是“各管各的”这五个字。这篇博文分享我最近重新梳理的一个园区微电网风光储协同规划实战用Matlab做建模、Gurobi做求解把原本独立决策的风、光、储三个环节改造成一套联合优化模型核心目标只有一个——让园区整体用能成本最低。全文包括模型设计思路、核心代码实现、求解器调试经验以及几个绕过的坑适合正在做微电网容量规划、储能调度策略或者准备参加数学建模竞赛的同学参考。1. 园区微电网协同规划的底层逻辑为什么独立运营不是最优解1.1 园区微电网的基本架构与能量流先说我理解中的“园区微电网”。它不是简单地把风机、光伏板和电池堆在一起而是一个在园区配电网层面能自治管理的小型电力系统。典型结构是在10kV或0.4kV母线下面挂接风力发电机组、光伏阵列、储能PCS和换流设备再加上园区的各类负荷通过一个并网开关和上级大电网相连。能量流大致有三条路径第一风电和光伏发出的直流/交流电经变流器上到母线直接供给负荷第二富余电量给储能充电或者反向卖给电网第三发电不足时储能放电和从电网购电共同补足缺口。这个架构听起来很简单但真正跑起来能量流的时序匹配问题就暴露了——光伏在午间出力最大但园区负荷可能还没到高峰风机夜间出力强但那时候电价便宜储能如果不配合发出来的电根本不值钱。我见过不少园区风光装机容量都不小但实际运行下来弃风弃光率常年维持在8%到15%储能利用率却不到三成。问题在于每一个环节都是“各自为政”风电场追求满发、光伏系统追求满发、储能按固定时间充放没有人站在整个园区的角度去协调这些设备。1.2 独立运营模式的局限性与痛点独立运营模式听起来也合理每个环节各自优化只要每个设备都高效整体自然高效。但实际情况恰恰相反。风电和光伏的出力是随机波动的无法“按需生产”。如果只追求最大出力午间光伏大发的时候园区负荷根本用不完多余的电量只能以较低的上网电价卖给电网或者干脆弃掉傍晚负荷高峰的时候光伏出力又归零了只能高价从电网买电。储能在独立运营模式下通常用的是固定策略比如“谷充峰放”。晚上22点到次日6点充电白天10点到14点放电。这个策略在电价曲线平滑时还好用但碰上园区负荷高峰出现在傍晚19点到21点时储能早就放完了完全没有起到削峰填谷的作用。更离谱的是有时候午间光伏大发、电价低谷储能却在放电白白浪费了便宜的绿电。我在一个实际项目里统计过一组典型日数据独立运营模式下一天购电成本大约是8530元弃风弃光率8.2%储能每天只完整循环了1.2次等效利用率不到35%。说白了储能基本是个摆设风光发出来的电也没有真正“用到刀刃上”。1.3 联合优化的收益来源从“各管各”到“一盘棋”联合优化的思路是把风、光、储和电网交互决策放进同一个优化问题里用一个统一的目标函数去求解。这个目标通常是最小化园区一天的运行总成本。这么说可能有点抽象我拿家庭理财做个类比独立运营相当于工资、理财、信用卡各自安排工资什么时候花、理财什么时候买、信用卡什么时候刷全凭感觉联合优化则是根据每个月的收支预期统一规划现金流的进出让闲置资金产生收益让信用卡账单尽量晚到期。微电网的联合优化也一样光伏大发的时候不急着卖电先让储能充电等到傍晚电价高、光伏出力为零时储能再放电供给负荷把原本要高价购电的那部分电量省下来。联合优化之所以能省钱本质上是利用了三个错配时间上的错配发电高峰和负荷高峰不同步、电价上的错配谷段电价和峰段电价差异大、空间上的错配本地绿电和远方火电的替代关系。这三个错配单独看靠人去判断也能处理一部分但一旦考虑天气预测误差、负荷波动和储能SOC约束人工经验就很难面面俱到了这就到了数学优化模型该上场的时候。2. Matlab Gurobi的组合优势求解器选型与建模框架设计2.1 为什么选Gurobi求解混合整数线性规划园区微电网协同规划本质上是一个带时序的优化调度问题。决策变量包含连续变量各时刻的功率、SOC和0-1整数变量储能充放电状态、设备启停目标函数和约束是线性的所以它是一个混合整数线性规划问题英文简称MILP。MILP的求解难度比普通线性规划高不少。Matlab自带的intlinprog在小规模问题比如24个时段、单台设备上还能对付但一旦把决策时段细化到15分钟一天96个时段、再加多台储能和多台风机求解时间会肉眼可见地变长有时候还会陷入长时间不收敛的困境。我知道不少同学在数学建模比赛里就吃过这个亏。我这次选Gurobi核心原因是它在MILP求解上的性能确实能打。在处理几百个整数变量、几千个约束的中等规模问题上Gurobi通常能在几秒到几十秒内给出高质量解而开源求解器可能要跑好几分钟甚至更久。学术版license也比较好申请用校园邮箱就能拿到对高校老师和学生来说基本是零成本。此外Gurobi还提供了热启动、不可行性分析IIS、MIP gap控制等实用功能对调试模型帮助很大。2.2 Matlab环境下Gurobi和Yalmip的接入配置Matlab调用Gurobi我建议走Yalmip这个建模层而不是直接调用Gurobi的Matlab接口。原因很简单Yalmip允许你用接近数学表达式的语法声明决策变量和约束写起来非常直观而且求解器可以随时切换比如先用intlinprog跑通逻辑再换Gurobi做正式求解代码不用大改极大降低调试成本。环境配置分三步。第一步安装Gurobi并申请学术版license安装完以后用命令行跑一遍grbgetkey激活。第二步把Gurobi的Matlab接口路径添加到Matlab搜索路径一般是在Gurobi安装目录下的matlab文件夹。第三步把Yalmip的源码目录也添加进来。配置完成后在Matlab命令窗口跑yalmiptest如果输出里显示Gurobi一栏是“found”状态就说明接口打通了。实操中有一个容易踩的坑Matlab、Gurobi和Yalmip三者版本不兼容。我在一处旧环境里试过Gurobi 9.x配Yalmip R2020a完全正常但换到Gurobi 11.x配旧版Yalmip就出现求解器无法识别的报错。所以安装的时候建议先查一下当前Yalmip支持的求解器版本列表别盲目装最新版。2.3 建模框架的分层设计与数据预处理模型要写得清晰、可复用我把整个建模框架分成了三层数据层、模型层和求解层。数据层负责把负荷曲线、风光出力预测曲线、分时电价参数、储能参数汇总成结构化数据。这里提醒一点所有时间序列数据必须对齐到同一个时间尺度我建议统一使用1小时或15分钟为步长。我遇到过一次事故负荷数据是15分钟一个点风光数据是1小时一个点直接丢进模型功率平衡约束在部分时段怎么都对不上查了半天才发现是时间轴没对齐。模型层用Yalmip声明决策变量、目标函数和约束。求解层调用Gurobi求解并解析输出结果。分层的好处是后续想更换数据源、调整约束或切换求解器改动的代码量都很小不必把整个脚本推倒重来。数据预处理也要专门提一下风光出力曲线的异常点比如负值、超过装机容量的值要提前处理否则会直接影响优化结果的物理合理性。3. 从独立运营到联合优化的模型拆解与实操实现3.1 目标函数设计两种模式的关键差异独立运营模式下每个环节各算各的账风电的目标是自身收益最大储能的目标是峰谷套利收益最大光伏也是如此。这种模型拼起来之后整体并不是最优的因为不同环节之间没有信息共享无法形成协同。联合优化的目标函数我习惯写成一天内的总运行成本最小化min ∑[C_buy(t) * P_buy(t) - C_sell(t) * P_sell(t)] ∑[α * (P_w_avail(t) - P_w(t)) β * (P_pv_avail(t) - P_pv(t))]其中第一项是购电成本减去售电收益第二项是弃风弃光惩罚α和β是惩罚系数。注意如果没有弃风弃光惩罚项模型可能会为了省事而随随便便切掉风光出力反正只要不从电网买电就不花钱这会导致优化结果明显偏离实际经营逻辑。惩罚系数一般设得比购电电价略高一点让它自动权衡是弃电划算还是多买电划算。这是两种模式最大的差异点独立运营时风光出力通常按“最大可用出力”硬性给定联合优化时风光出力是决策变量模型可以决定是否削减——但削减它会付出惩罚代价。这个“可削减”的设计给了模型更大的调度自由度效果立竿见影。3.2 约束条件怎么列才完整目标函数定了约束必须跟上否则求解出来的结果在物理上根本不可能实现。我这次建模中用的核心约束如下第一功率平衡约束。任意时刻风机出力、光伏出力、储能放电功率加上购电功率等于负荷功率加上储能充电功率再加售电功率P_w(t) P_pv(t) P_dis(t) P_buy(t) P_load(t) P_ch(t) P_sell(t)第二储能SOC动态约束。下一时刻的SOC等于当前SOC加上充电电量和放电电量的差充电效率和放电效率要分别处理SOC(t1) SOC(t) (η_ch * P_ch(t) - P_dis(t) / η_dis) * Δt / E_battery第三储能运行边界约束。包括充电功率上限、放电功率上限、SOC上下限以及最重要的充放电互斥约束。因为物理上储能无法同时充放电我引入两个0-1变量u_ch和u_dis让它们满足u_ch u_dis ≤ 1并和充放电功率通过大M法关联起来。第四电网交互约束。购电功率和售电功率不能同时为正也需要引入0-1变量同时购电功率要受并网容量限制不能无限大。第五风光出力约束。每时段的实际出力不能超过该时段的可用功率且不能为负。约束条件里最容易被忽略的是SOC的初始和终止状态。如果第一天开始和结束时的SOC不一致跨天连续运行就会出问题。工程上常用的做法是设置SOC(1) SOC(N1)保证储能一天运行后回到初始状态这样结果才能代表稳态运行的一天。3.3 核心代码结构与两种模式的求解结果对比我贴一段YalmipGurobi的核心代码结构方便大家快速上手%% 参数定义 N 24; % 时段数 dt 1; % 时间步长单位h E_bat 2000; % 储能容量kWh P_ch_max 500; % 最大充电功率kW P_dis_max 500; % 最大放电功率kW SOC_min 0.2; % SOC下限 SOC_max 0.9; % SOC上限 eta_ch 0.95; % 充电效率 eta_dis 0.92; % 放电效率 alpha 0.8; % 弃风惩罚系数 beta 1.0; % 弃光惩罚系数 %% 决策变量 P_w sdpvar(N, 1, full); % 风机出力 P_pv sdpvar(N, 1, full); % 光伏出力 P_ch sdpvar(N, 1, full); % 储能充电功率 P_dis sdpvar(N, 1, full); % 储能放电功率 P_buy sdpvar(N, 1, full); % 购电功率 P_sell sdpvar(N, 1, full); % 售电功率 SOC sdpvar(N1, 1, full); % 荷电状态 u_ch binvar(N, 1); % 充电状态 u_dis binvar(N, 1); % 放电状态 %% 约束 Constraints []; Constraints [Constraints, ... P_w P_pv P_dis P_buy P_load P_ch P_sell]; for t 1:N Constraints [Constraints, ... SOC(t1) SOC(t) (eta_ch * P_ch(t) - P_dis(t) / eta_dis) * dt / E_bat]; Constraints [Constraints, ... P_ch(t) 0, P_ch(t) u_ch(t) * P_ch_max]; Constraints [Constraints, ... P_dis(t) 0, P_dis(t) u_dis(t) * P_dis_max]; Constraints [Constraints, ... u_ch(t) u_dis(t) 1]; Constraints [Constraints, ... P_w(t) 0, P_w(t) P_w_avail(t)]; Constraints [Constraints, ... P_pv(t) 0, P_pv(t) P_pv_avail(t)]; Constraints [Constraints, ... P_buy(t) 0, P_buy(t) P_buy_max]; Constraints [Constraints, ... P_sell(t) 0, P_sell(t) P_sell_max]; end Constraints [Constraints, SOC(1) SOC(N1)]; Constraints [Constraints, SOC_min SOC SOC_max]; Constraints [Constraints, P_buy * u_sell 0]; % 可用线性化处理购售互斥 %% 目标函数 Objective sum(price_buy .* P_buy) - sum(price_sell .* P_sell) ... sum(alpha * (P_w_avail - P_w)) ... sum(beta * (P_pv_avail - P_pv)); %% 求解 ops sdpsettings(solver, gurobi, verbose, 2); result optimize(Constraints, Objective, ops); P_w_opt value(P_w); P_pv_opt value(P_pv); P_ch_opt value(P_ch); P_dis_opt value(P_dis);上面这是一个比较朴素的写法目的是把逻辑讲清楚。实际项目中我还会用稀疏矩阵或者repmat方式提高构建速度但核心约束就这些。我同样用某园区典型日数据跑了独立运营和联合优化两种模式的对比如下指标独立运营联合优化日购电成本元85306120弃风弃光率8.2%2.4%储能循环次数次/天1.22.8储能峰时放电占比35%78%整体日运行成本元87206050联合优化方案在典型日场景下日运行成本下降了约30%弃风弃光率从8.2%降到2.4%储能利用率大幅提升。这里也提醒一下上面这些数据是我项目里某一天的仿真值不同园区的结果会有差异但优化的方向和趋势是一致的。4. 典型问题排查与工程化避坑指南4.1 Infeasible问题的定位思路用Gurobi跑MILP最让人头疼的报错就是“infeasible”也就是模型无解。这个问题出现的时候不是Gurobi不行而是你的约束之间有矛盾或者数据本身有问题。我最常遇到的情况有三种。第一种负荷数据里某个时段的数值异常大导致风光储能电网购电的上限加在一起都不够用功率平衡约束自然无处可满足。第二种SOC初始和终止状态约束设得太紧储能不可能在给定时长内从初始SOC充到终止SOC。第三种充放电互斥约束和功率上下限冲突比如P_dis_max设得太小导致哪怕储能全力放电也满足不了某些时段的负荷缺口。排查的顺序很有讲究。先把时段数缩到1到2个小时看看模型是否还无解然后逐个注释掉约束找到导致冲突的约束子集。Gurobi的IIS功能也很好用开启IIS后它会输出一组导致不可行的约束列表帮你直接定位。不过IIS输出的是因果线索不是完整结论你还需要结合数据逻辑去判断到底是谁的锅。单位不一致也是常见问题kW和kWh混用、MWh和kWh混用都会让约束看起来合理实际却完全不可行。4.2 数据量变大后求解变慢的优化手段模型跑通了但求解速度不够快也是一个高频问题。特别是当我们把时间尺度从1小时细化到15分钟时段数从24变成96之后0-1变量数量一下子翻四倍MILP的求解复杂度显著上升。我常用的提速手段有五条。第一放宽MIP gap容忍度在Gurobi参数里设置MIPGap0.01允许1%的次优性很多时候能把求解时间缩短一个数量级而成本偏差几乎可以忽略。第二提供初始可行解我先用一套简单的启发式规则生成储能充放计划和购电计划再通过Yalmip的assign操作赋给决策变量作为Gurobi的热启动解。第三减少0-1变量数量比如对储能充放电状态做时段分块让同一状态至少保持若干时段减少整数变量自由度。第四削弱对称性比如多台同类型储能设备建模时如果不加区分模型会花大量时间在完全等价的解之间反复切换合理做法是加入排序约束打破对称。第五检查是否有不必要的非线性项一些隐式的非线性比如P_buy * u_sell这种会直接拖垮求解速度能线性化的都线性化。从实际效果看大多数24时段模型在Gurobi下都是毫秒到秒级求解96时段模型在合理设置下也能控制在数十秒内基本能满足日常仿真需求。4.3 模型从仿真到落地运营的几点经验模型求解结果是“最优解”但离“可直接执行”还有一段距离。这里分享几条我在工程化中积累的经验。第一数据质量决定了模型价值的上限。如果负荷预测和风光出力预测误差很大优化结果也好看不到哪里去。我一般会在进优化模型之前对预测曲线做一轮平滑和异常值修正。第二规划类问题按典型日计算运行类问题按全年8760小时计算。如果你做的是容量规划比如确定储能装机容量建议至少取春、夏、秋、冬四个典型日或者直接跑全年数据否则结果会被单日天气工况带偏。如果你做的是日常运行调度则用未来24小时的预测数据做滚动优化即可。第三不要把SOC跑得太满或放得太空。模型解出来可能为了省钱让SOC长期贴着0.9的上限运行但这在实际运维中会加速电池衰减。稳妥的做法是设置更保守的SOC上限比如0.85同时在目标函数里加入一个很小的SOC均衡项鼓励储能回归到舒适区间。第四如果想做不确定性分析初版先用确定性模型再逐步扩展为随机优化或鲁棒优化。直接上全套不确定性模型往往导致求解时间爆炸调试体验很差也不利于理解问题本质。5. 环境配置与License相关的几个实操细节5.1 Gurobi学术版License的申请与激活注意事项很多第一次用Gurobi的同学在申请学术版license这一步就被卡住了。流程其实不难用学校邮箱注册账号申请学术许可证官方会返回一串许可证编号在联网环境下运行grbgetkey 你的许可证编号就能激活。激活成功后默认情况下license会和用户名绑定换机器或换用户就得重新激活。我实际使用中遇到一个坑实验室服务器通常没有公网访问权限或者只允许通过代理访问。这种情况下联网激活会失败需要在Gurobi官网申请离线激活文件生成一个gurobi.lic文件放到用户主目录下。千万别小看这一步我见过不少组因为服务器离线项目卡在环境配置上大半天。还有一点需要注意学术版license明确限定只能用于教学和科研用途。如果有商业项目需求需要另行购买商业许可这也是合规底线别图省事。5.2 Matlab、Yalmip和Gurobi的版本兼容性核对表版本兼容性问题安装的时候不觉得真跑起来报错才头疼。我整理了一个简表供大家参考组件建议版本备注MatlabR2020a及以上越新越好推荐R2022b之后Gurobi9.5.x / 10.x学术版下载链接在官网Yalmip最新release版本建议直接从GitHub下载master分支需要说明的是Gurobi每年更新速度很快较新的Gurobi版本可能不再兼容很老的Matlab版本。先装Gurobi再根据Gurobi官方文档确认支持的Matlab版本这个顺序比较稳妥。如果碰到“找不到gurobi_mex”这类错误多半是Matlab路径没配对或者Matlab的进程没重启路径配置完以后必须重启Matlab再测试。5.3 快速验证求解器是否配置成功的小实验配置完环境别急着直接跑大模型先做一个最小验证实验。我常用的是一个只有2个变量、2个约束的小MILP比如x binvar(2, 1); Constraints [x(1) x(2) 1]; Objective -2*x(1) - 3*x(2); ops sdpsettings(solver, gurobi, verbose, 0); optimize(Constraints, Objective, ops); value(x)如果输出结果是[1;1]那说明Yalmip到Gurobi的链路已经通了。这个1分钟就能跑完的小实验能帮你把环境问题和大模型问题完全隔离开排查起来省很多工夫。我每次换电脑或者换服务器都会先跑这个验证脚本比直接开大模型试错高效得多。写在最后的一点经验这套模型做完之后我最大的感受是联合优化带来的收益往往不是某个单一环节的“技术指标提升”而是系统层面的“成本错配弥合”。实际中风光和储能的配合不单是建个模型求个解那么简单数据质量、约束合理性、求解器配置都是硬功夫。根据我的经验初学阶段可以把那几行核心约束代码反复看熟SOC动态约束和充放电互斥约束是微电网模型中最重要的骨架。最后再提醒一下Gurobi的学术license在离线服务器上一定要提前做好离线激活别等到要跑结果时才发现环境没打通。如果你也正在用Matlab和Gurobi做微电网方向的规划欢迎在评论区聊聊你的模型思路我一般都会上来看看。