首页 / 资讯中心 / 文章详情

微电网日前经济调度与需求响应建模:Python+Gurobi实现

微电网日前经济调度与需求响应建模:Python+Gurobi实现 ★ FEATURED ARTICLE
1. 问题建模先把“微电网日前经济调度”拆明白做微电网调度之前我建议你先别急着写代码先把脑子里那团“我要优化”的想法梳理清楚。这个项目的核心关键词是“日前调度”意思是提前24小时以1小时为步长规划出未来一天内微电网里每一台设备风电、光伏、储能、可调负荷的出力曲线和运行状态。1.1 核心决策变量一天有24个断面每个断面都有一堆变量要定我习惯把微电网里的可控资源分成这么几类每一类都会在模型里产生决策变量风机出力实际上更多是“可用功率少发一点”标记为P_w[t]光伏出力同理弃光也是一种调度标记为P_pv[t]储能充放电功率充电为正或者放电为正看你怎么约定标记为P_ch[t]和P_dis[t]储能荷电状态SOC标记为SOC[t]从主网购电功率P_buy[t]需求响应后的负荷变化量标记为ΔP_load[t]对于24小时的调度周期如果你用的优化求解器比如Gurobi或者Cplex这些变量少说也有几百个加上约束条件之后一个典型的微电网优化问题也就几秒钟就能解完根本算不上大规模。真正让问题变复杂的不是变量数量而是你加了什么约束——约束不合理求解器分分钟给你一个“数值困难”的报错。1.2 目标函数省钱就是硬道理经济调度的目标函数非常朴素就是让系统一天的总运行成本最小。成本项一般包含这么几部分从主网购电的费用按分时电价计储能充放电带来的损耗成本折算成每度电的运维成本需求响应的补偿成本你让用户少用了电总要给点好处弃风弃光的惩罚成本名义上是“如果因为系统约束弃了风或光会记一个惩罚项”本质上是为了让求解器优先消纳新能源这里有一个新手容易踩的坑如果你不给弃风弃光加惩罚成本求解器很可能肆无忌惮地弃掉所有风电和光伏因为它找不到必须用它们的理由。所以目标函数里加一个相对较高的弃光弃风惩罚系数才能模拟出“优先消纳新能源”的策略。1.3 约束条件有约束的优化才是工程问题约束条件就是告诉求解器“哪些事情不能干”。 微电网调度里至少要有以下几类功率平衡约束每个时刻发电加购电加上储能放电必须等于负荷加充电加需求响应调整。储能动态约束SOC的递推关系、充放电功率上下限、SOC上下限、以及“不能同时充电和放电”这种逻辑约束。可调负荷约束每个时刻的可调量有上限一天的总可调量也有一个上限因为用户不能一天到晚都在响应。如果做削峰填谷可能还要加“某个时段之后负荷要恢复”的约束。购电约束微电网和主网之间的联络线功率上限。2. 核心建模细节与需求响应机制解析看标题就知道这个项目除了“风光储”还重点强调了“需求响应”。很多人不理解需求响应在调度模型里的角色我展开讲讲。2.1 需求响应的本质把负荷从一个“死数字”变成“活变量”常规调度里负荷曲线是给定的像一个固定长度的烤肉串你只能围着它调配发电侧。需求响应的思路是既然用户端的空调、热水器、工业生产线这些东西在特定时段有弹性为什么不把它变成可调变量呢具体到数学模型里我通常这么处理定义每个时段的基础负荷为Load_base[t]实际负荷为Load_actual[t] Load_base[t] ΔP_load[t]其中ΔP_load[t]就是需求响应调节量。它可以是正的增加用电比如在风电大发时鼓励用户多用电也可以是负的削减用电比如在晚高峰电价贵时。为了模型简单很多论文里会限制它单方向也就是只允许削减。但我个人认为双向调节才更符合“需求响应”的完整含义——尤其是在风电大发、甚至可能弃风的时段如果负荷能增加弃风量会明显下降。2.2 需求响应补偿成本怎么定这是建模时最容易“想当然”的地方。你不能既让用户响应又不给补偿优化模型就会逼着用户无限削负荷这不现实。我的做法是把补偿函数设成一个分段线性或二次函数C_DR[t] a * ΔP_load[t]^2 b * ΔP_load[t]为什么用二次函数因为实际场景中让用户削减10度电很轻松削减50度电就得动用生产线停产等手段边际成本会迅速上升。如果只用线性项可能算出来的削负荷量过大工业用户根本不认这套方案。这个细节虽然只是目标函数里一个小项但直接决定调度方案能不能落地。2.3 储能建模的一个常见坑储能SOC的递推关系谁来写都能写对但下面这两个坑我踩过不止一次第一充放电同时发生。如果你只写了P_ch[t]和P_dis[t]各自的上限约束没有加“二者不同时为正”的约束求解器有可能会给出“同一时刻既充电又放电”的荒谬结果——因为这样恰好能满足某些诡异的边界条件。解决办法是引入一个0-1变量写成P_ch[t] B[t] * P_ch_max P_dis[t] (1 - B[t]) * P_dis_max第二充电效率的安放位置。很多教材把递推关系写成SOC[t1] SOC[t] - P_dis[t] / η等于把损耗全算在放电侧。严格来说应该充电和放电两侧都体现效率不过为了工程简化单侧折算也完全可接受。要注意的是你代码里怎么写的结果分析时就得按同一套逻辑解释。2.4 为什么要加弃风弃光惩罚项我先举个例子。假设某个时刻光伏出力很大但负荷很低储能也满了主网购电又有下限约束某些场景下不允许倒送那唯一可行的办法就是弃光。但如果目标函数里没有任何“弃光代价”求解器会在所有类似的边界情况下优先选择弃光而不是尝试别的调度手段——因为弃光这个操作本身不需要成本。这会导致你的优化结果远不如预期新能源消纳率惨不忍睹。正确做法是给弃风弃光设一个比较高的惩罚系数比如每度电1到2元让求解器在“弃掉200度光伏”和“稍微动一下储能/需求响应”之间主动选择后者。3. Python代码实现从参数初始化到求解全流程我把一个可运行的参考实现拆成几个关键模块重点展示数据怎么组织、模型怎么搭、结果怎么处理。3.1 数据准备部分不论你用的是Gurobi、Cplex还是开源的CBC数据结构的统一是关键。我习惯用pandas管理时序数据下面是一个典型的数据构造方式import numpy as np import pandas as pd # 模拟一组典型日数据时间颗粒度1小时 T 24 # 风机预测出力MW P_wind_forecast np.array([ 0.8, 0.7, 0.6, 0.6, 0.5, 0.4, 0.3, 0.2, 0.3, 0.5, 0.7, 0.9, 1.0, 1.1, 1.2, 1.1, 1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3 ]) # 光伏预测出力MW P_pv_forecast np.zeros(T) for t in range(T): P_pv_forecast[t] max(0, 1.5 * np.sin((t - 7) * np.pi / 12)) # 基础负荷MW P_load_base np.array([ 2.0, 1.9, 1.8, 1.7, 1.6, 1.7, 1.8, 2.1, 2.5, 2.8, 2.9, 3.0, 3.1, 3.0, 2.9, 2.8, 2.9, 3.2, 3.5, 3.4, 3.2, 2.8, 2.4, 2.1 ]) # 分时电价元/MWh elec_price np.ones(T) * 0.6 elec_price[8:12] 0.9 elec_price[17:22] 1.0 elec_price[0:6] 0.3这种模拟数据的做法在项目初期非常省事不用去翻公开数据集就能把整套代码逻辑跑通。等后面需要论文级实验或者工程落地再把这里的模拟数据替换成天气预报数据、负荷预测输出、历史实测数据代码主体完全不用动。3.2 模型构建代码框架下面给出一个用Gurobi的Python接口构建的确定性优化模型供参考import gurobipy as gp from gurobipy import GRB # 储能参数 E_capacity 3.5 # MWh SOC_max 0.9 SOC_min 0.2 charge_eff 0.95 # 充电效率 discharge_eff 0.95 # 放电效率 P_ch_max 1.0 # 最大充电功率 P_dis_max 1.0 # 最大放电功率 model gp.Model(microgrid_day_ahead) # 决策变量 P_ch model.addVars(T, lb0, ubP_ch_max, nameP_ch) P_dis model.addVars(T, lb0, ubP_dis_max, nameP_dis) P_buy model.addVars(T, lb0, ub5.0, nameP_buy) delta_load model.addVars(T, lb-0.5, ub0.5, namedelta_load) SOC model.addVars(T, lbSOC_min, ubSOC_max, nameSOC) # 储能充放电互斥变量 B_ch model.addVars(T, vtypeGRB.BINARY, nameB_ch) # 弃风弃光变量对应惩罚项 curtail_w model.addVars(T, lb0, ubP_wind_forecast[t], namecurtail_w) curtail_pv model.addVars(T, lb0, ubP_pv_forecast[t], namecurtail_pv)注意上面代码里lb0, ubP_wind_forecast[t]这种写法需要谨慎因为gurobipy的addVars方法里不能直接用一个数组给每个变量单独指定上下界一般要用ub[P_wind_forecast[t] for t in range(T)]这样的写法传入列表。这个细节写错了会在运行时直接报numpy.ndarray object is not callable之类的错误。3.3 添加约束条件接下来是模型的灵魂——约束条件的拼装。我按约束类型分块既方便调试也方便后续查阅# 功率平衡约束 pb_eq {} for t in range(T): pb_eq[t] model.addConstr( P_wind_forecast[t] - curtail_w[t] P_pv_forecast[t] - curtail_pv[t] P_dis[t] P_buy[t] P_load_base[t] delta_load[t] P_ch[t], namefpower_balance_{t} ) # 储能充放电互斥 for t in range(T): model.addConstr(P_ch[t] B_ch[t] * P_ch_max, namefch_binary_{t}) model.addConstr(P_dis[t] (1 - B_ch[t]) * P_dis_max, namefdis_binary_{t}) # SOC动态 SOC0 0.5 # 初始SOC for t in range(T): if t 0: model.addConstr( SOC[t] SOC0 (P_ch[t] * charge_eff - P_dis[t] / discharge_eff) / E_capacity, namefsoc_init_{t} ) else: model.addConstr( SOC[t] SOC[t-1] (P_ch[t] * charge_eff - P_dis[t] / discharge_eff) / E_capacity, namefsoc_dynamic_{t} ) # 终端SOC保持约束可选 model.addConstr(SOC[T-1] SOC0, namesoc_terminal) # 需求响应总调节量限制 model.addConstr(gp.quicksum(delta_load[t] for t in range(T)) 0, namedr_total)最后那个dr_total约束是我个人比较倾向加的一个约束一天内削减的总量和增加的总量相互抵消这样既参与了调峰又不会把用户的用电总量“优化没了”。实际项目中这个约束可以根据激励政策做调整比如只允许削减不允许增加或者允许总电量有一定程度的弹性变化。3.4 目标函数与求解# 成本系数 cost_punish_curtail_w 1.8 # 弃风惩罚 cost_punish_curtail_pv 2.0 # 弃光惩罚 dr_cost_coef_a 0.1 # 需求响应二次项系数 dr_cost_coef_b 0.2 # 需求响应一次项系数 # 目标函数 obj_expr ( gp.quicksum(elec_price[t] * P_buy[t] for t in range(T)) # 购电成本 gp.quicksum(dr_cost_coef_a * delta_load[t] * delta_load[t] dr_cost_coef_b * delta_load[t] for t in range(T)) # 需求响应补偿 gp.quicksum(cost_punish_curtail_w * curtail_w[t] for t in range(T)) gp.quicksum(cost_punish_curtail_pv * curtail_pv[t] for t in range(T)) ) model.setObjective(obj_expr, GRB.MINIMIZE) model.optimize() # 结果提取 if model.status GRB.OPTIMAL: P_ch_opt [P_ch[t].X for t in range(T)] P_dis_opt [P_dis[t].X for t in range(T)] P_buy_opt [P_buy[t].X for t in range(T)] SOC_opt [SOC[t].X for t in range(T)] delta_load_opt [delta_load[t].X for t in range(T)] print(最优成本: , model.objVal)3.5 数据可视化做调度项目结果可视化是非常重要的环节。我习惯画在一张图里对比上面画功率平衡图风电、光伏、储能充放电、购电、各类负荷堆叠成柱状图下面画SOC曲线和需求响应量曲线。这种图给导师或者合作方看的时候说服力远强于一张干巴巴的表格。import matplotlib.pyplot as plt plt.figure(figsize(14, 12)) plt.subplot(3, 1, 1) plt.bar(np.arange(T), P_wind_forecast, label风电, alpha0.7) plt.plot(np.arange(T), P_load_base, label基础负荷, linewidth2, colorblack) plt.legend() plt.grid(True, alpha0.3) # 第二张储能充放电和SOC plt.subplot(3, 1, 2) plt.plot(np.arange(T), P_ch_opt, label充电功率, colortab:green, linewidth2) plt.plot(np.arange(T), P_dis_opt, label放电功率, colortab:red, linewidth2) plt.plot(np.arange(T), SOC_opt, labelSOC, colortab:blue, linewidth2) plt.legend() plt.grid(True, alpha0.3) # 第三张需求响应量 plt.subplot(3, 1, 3) plt.bar(np.arange(T), delta_load_opt, colortab:orange, alpha0.8) plt.axhline(y0, colorblack, linewidth0.8) plt.legend([需求响应调节量]) plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(schedule_result.png, dpi200)4. 结果分析与方案对比求解完成只是第一步你得能从结果里读出故事。我通常从以下三个维度分析结果是否有价值。4.1 看功率平衡图里的“填谷”效果一个好的调度方案在中午光伏大发时储能应该充电把多余的光伏电量存起来在傍晚负荷尖峰时储能放电削减从主网购电的功率。如果你画出来的图里储能整天都纹丝不动大概率是你的储能相关成本设置过高或者输入的风光数据不够“极端”没有触发电网功率约束。4.2 对比有无需求响应的成本一个很有效的验证手段是跑两遍模型一遍允许需求响应一遍把delta_load上下界都设为0。然后对比两种方案下的总成本和购电峰谷差。通常有需求响应的方案会便宜一些因为它在电价高的晚高峰削掉了负荷在电价低的时段把负荷补回来。这组对比数据也是论文里非常有力的火花图。4.3 检查SOC曲线的合理性我这里说的“合理性”有两层意思。一是SOC应该在上下限范围内平滑波动不应出现“从0.9瞬间掉到0.2”这种不符合储能充放电功率限制的跳变——如果你设置了合理的充放电功率上限和SOC递推关系这种跳变理论上不会发生但实际求解时数值误差可能导致细微越界。二是终端SOC约束很重要如果不加求解器可能在最后几个时段把储能电量耗干虽然24小时总成本低但第二天就无法运行了这在实际调度中是致命的。5. 常见问题与排查技巧实录这个项目我问过自己很多次“为什么结果这么怪”下面整理几个高频问题都是我能拍胸脯说“你迟早会遇到”的。5.1 求解器报“Infeasible”怎么办模型无解是新手最容易恐慌的场景。我的排查顺序非常简单第一步检查功率平衡约束两边有没有单位不统一比如算成了MWh和MW混用。这是我见过的低级错误里发生率最高的。第二步把负荷和发电的总量画出来看一眼全天总发电能力是否小于总负荷。如果全天光伏风电预测出力加起来连负荷都不够那无解是物理正常不是模型写错了。第三步把储能初始SOC终端约束临时去掉看模型能不能求解。如果能说明终端SOC约束和储能容量之间产生了不可行的组合——比如总充电量超过储能物理容量。5.2 结果里储能不工作如果你的储能电池装了和没装一样先看是不是充放电效率乘以功率上限算出来的有效能量太小不足以改变功率平衡。更常见的原因是电价差不够大充电时电价0.7元放电时电价0.72元扣掉充放电效率损耗储能根本赚不回成本自然选择闲置。这时候不用急着调求解器参数你应该先算一算“储能套利盈亏平衡点”。简单估算一下放电时的有效成本 ≈ 充电电价 / (充电效率 * 放电效率)如果放电时段电价低于这个值储能在经济上就亏了求解器不充不放电反而是最优解。5.3 需求响应量被优化成极端值如果delta_load[t]跑到了你设定的边界上而且大面积等于边界最常见的原因是你没加“总调节量约束”。这时相当于给了解一个免费的手套它当然会把所有可调量都用满哪怕只是为了让计算过程更容易满足功率平衡。我记得有一个项目做居民小区微电网需求响应补偿设得很真实但忘了加全天总量约束结果优化出的方案是让所有用户每天在中午集体停空调这个方案虽然“数学最优”但没有一个真实的家庭会配合。所以约束条件的工程合理性往往比目标函数更值得花时间打磨。6. 从确定性模型到更贴近实际的扩展思路如果你已经跑通了上面的确定性版本下一步做进阶扩展是很自然的。我盘点几个性价比比较高的方向。6.1 改成滚动时域控制日前调度最大的风险是预测不准。第二天实际的风光出力可能和预测偏差很大这时候固定执行昨天算出来的计划就吃亏了。常见的改进是滚动时域控制每隔4小时重新算一次未来24小时的计划只执行下一步。这种“在线调度”在实际工程里才是常态它也能发挥你手里这种模型最大的价值——每次重算只需要几秒钟完全跟得上节奏。6.2 引入不确定性建模鲁棒优化或场景法另一个方向是把风光预测的误差显式地放进模型。我推荐从一个简单的场景法入手准备三组风电预测序列悲观、基准、乐观每个场景给一个权重把目标函数变成多场景期望成本最小。这样你就从“确定性优化”升级到了“随机优化”模型健壮性会明显改善。实现的时候只需要把每个场景复制一套变量约束里多处理一个场景下标代码结构仍然清晰。6.3 调整时间颗粒度很多论文为了提高效率把决策步长从1小时缩短到15分钟这会让储能SOC曲线更加平滑需求响应的削峰效果也更有说服力。代价是变量数量和约束数量翻4倍求解时间可能从几秒变成几十秒。不过对这个体量的微电网模型来说还是完全可以接受的。7. 实操收尾从跑通代码到讲清成果最后说点过程层面的体会。很多同学跑通代码后在写报告或论文时容易犯一个毛病把核心篇幅花在描述“代码怎么写的”上面。其实一份好的微电网经济调度工作最该讲清楚的是“你为什么这样建模”和“模型结果说明了什么”。比如你用了需求响应那么你应该在结果分析部分给出“削峰了多少MW”“节省了多少购电费”“储能SOC的日循环次数”这样的量化指标然后再把这些指标和没有需求响应的基准工况做对比。数值摆在面前审稿人或者导师的眼睛会明显亮起来。再比如储能SOC的终端约束你加了这个约束就应该在报告里解释这样做是出于“日滚动运行可持续性”的考量——如果每天调度结束把电池电量掏空第二天早上初始SOC只能取一个很不利的起点运行方式就不可持续。这样一个小建模细节会显现出你对调度工程的理解深度远超“会用求解器”的层面。我在实际项目中养成的一个习惯是最终交付物里永远附一张“模型变量表”一张“约束条件表”和一张“参数取值表”。三个表并列放在报告附录里不用多余解释任何一个懂行的人都能靠它们复现出你的全部工作。这些表格看起来不起眼却是整个项目可复现性和专业度的基石。
阅读完成 · 觉得有帮助?
咨询建站