ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python实现微电网日前经济调度:风光储与需求响应优化

Python实现微电网日前经济调度:风光储与需求响应优化 1. 项目概述做微电网调度这个方向有一段时间了经常有朋友问我“风光储并网之后到底怎么安排机组出力才最省钱”说实话这个问题真要回答清楚并不是简单套个优化模型就能糊弄过去的。它牵扯到新能源的随机性怎么处理、储能系统什么时候充什么时候放、需求响应资源怎么调用以及这些因素放在一起怎么通过一个统一框架求解——这就是我整理这个Python实现项目的出发点。这个项目选取的是微电网场景下的日前经济调度问题时间尺度是未来24小时步长取1小时一共24个调度时段。系统内部包含风力发电、光伏发电、微型燃气轮机、储能系统以及需求响应资源。核心目标相对清晰在满足系统功率平衡和各类设备运行约束的前提下把一天的总运行成本压到最低。这里的总运行成本包括微型燃气轮机的燃料成本、启停成本储能系统的运行损耗成本以及调用需求响应资源产生的补偿费用。为什么非要用Python来做这件事而不是直接用MATLAB这几年微电网调度的研究里Python占比明显在往上走核心原因有三个第一Python的生态足够完整数据预处理可以用pandas建模优化可以用Cplex、Gurobi的Python接口结果可视化用matplotlib一套流程下来不用来回切工具第二Python适合做算法迭代后面如果你想从确定性优化扩展到鲁棒优化、随机优化代码重构成本会比MATLAB低很多第三开源社区里有大量现成的案例可以参考遇到问题搜一下基本都有解决方案。当然这不是说MATLAB不行如果你建模习惯已经成熟用哪个都是个人选择但如果你刚开始接触这个方向Python的入门曲线确实更友好。这篇文章适合三类人阅读一是电气工程、能源系统相关专业的研究生正在做微电网经济调度方向的开题或小论文二是刚入行做综合能源系统、园区能源管理系统的工程师需要快速搭建一个能跑通的原型三是想了解需求响应建模方法的从业者。文中的代码思路和参数设置都来自我实际跑过的项目可以直接作为基础框架复用替换成你自己的系统参数就能用。1.1 核心需求解析先把这个项目的边界条件拆清楚。微电网日前经济调度的“日前”二字指的是调度员在每一天开始之前通常是前一天某个固定时刻制定接下来24小时各个设备的出力计划。因为风光出力、负荷数据都是预测值所以本质上这是一个基于预测数据的开环优化问题——结果好不好很大程度上依赖预测精度和模型本身的鲁棒性。系统约束方面功率平衡约束是核心它要求任意时刻系统内所有电源出力加上储能放电功率、需求响应削减量必须等于负荷需求加上储能充电功率。这个约束不满足其他所有优化结果都没有物理意义。除此之外微型燃气轮机有出力上下限约束和爬坡约束储能系统有充放电功率约束、荷电状态SOC上下限约束以及充放电互斥约束需求响应量也有最大可调比例限制。经济性目标方面我把成本拆成了三块燃气轮机的运行成本用二次函数近似存储成本用一个线性系数近似需求响应补偿成本按响应量线性结算。整篇文章的代码实现都围绕“让总成本最小”这一个目标展开。1.2 方案选型思路建模完成之后面临一个关键选择用什么方法求解。目前主流做法有几类数学规划方法混合整数线性规划/混合整数二次规划、元启发式算法粒子群、遗传算法、以及强化学习方法。这个项目我选择用混合整数规划求解主要原因在于需求响应量的引入通常伴随0-1变量决定是否启动需求响应、储能是充电还是放电这使问题天然成了混合整数规划问题而Cplex、Gurobi这类商业求解器在中小规模问题上能在毫秒到秒级别找到全局最优解。用群体智能算法虽然也能解但结果受参数影响大且无法保证全局最优对初学者不太友好。工具链方面我用的是cplex的Python接口当然换成gurobipy几乎不需要改动核心逻辑两者的API风格很像。如果你的环境里没有商业求解器也可以用scipy.optimize.milp模块不过求解效率会差一些而且对大规模问题的支持有限。我建议有条件的话优先上Cplex或Gurobi学生可以申请免费学术许可工程师可以走试用版实际体验差距很大。2. 模型设计细节与关键参数说明很多初学者拿到这类题目第一反应是直接写代码。我个人的建议正好相反——先在纸上或者说在文档里把模型写清楚再来翻译成代码。这个顺序如果颠倒了后期排查问题会非常痛苦因为你会发现很难判断是代码bug还是模型本身出了问题。2.1 目标函数建模目标函数我在前面已经大致描述过这里给出更完整的数学表达。整个目标函数由四部分构成第一项是燃气轮机的燃料成本。工程中通常用二次函数拟合热耗率曲线即F a * P^2 b * P c其中P是燃气轮机出力单位kWa、b、c是拟合系数。需要注意二次函数是非线性的直接丢给Cplex会增加求解难度。实际项目里我通常用分段线性化处理也就是把出力范围切成几段每段用线性函数逼近精度完全够用。第二项是燃气轮机的启停成本。引入一个0-1变量u_t表示机组在时段t是否运行再引入一个启动动作变量v_t当u_t从0变为1时取1成本就是start_cost * v_t。第三项是储能的运行成本。锂电池储能每次充放电循环都会产生寿命折损这个成本虽然不像燃料成本那么直观但在经济调度中必须考虑。简化处理时可以按充放电电量乘以一个很小的成本系数c_ess来近似。第四项是需求响应成本。需求响应本质上是调度中心通过价格激励或协议约定让用户削减部分用电负荷。为了鼓励用户参与调度中心需要支付补偿费用。我用的模型是线性补偿即DR_cost price_dr * dr_amount单位是元/kWh。目标函数把这四项加起来就是总运行成本要求它最小。用代码表达的话大概是这个结构objective ( # 燃气轮机燃料成本分段线性近似 sum(fuel_cost[t] for t in T) # 启停成本 sum(start_cost * start_vars[t] for t in T) # 储能运行成本 sum(c_ess * (p_ch[t] p_dis[t]) for t in T) # 需求响应成本 sum(dr_price[t] * dr_amount[t] for t in T) )2.2 约束条件拆解约束条件是这个模型里最容易出错的地方。我按类别梳理一遍每类约束补充说明一下数学含义和代码实现时的注意事项。功率平衡约束这是最核心的约束任意时段t都必须满足风电出力 光伏出力 燃气轮机出力 储能放电功率 - 储能充电功率 需求响应削减量 基础负荷 - 需求响应削减量这里要特别注意需求响应量的符号处理。在我这个模型中需求响应只考虑可削减负荷也就是用户同意在特定时段减少用电量所以它相当于降低了净负荷。功率平衡约束我习惯写成P_wt[t] P_pv[t] P_mt[t] P_dis[t] - P_ch[t] P_load[t] - DR[t]。左侧是供电侧右侧是负荷侧原始负荷减去需求响应量。燃气轮机约束出力上下限P_mt_min * u[t] P_mt[t] P_mt_max * u[t]爬坡约束-ramp_down P_mt[t] - P_mt[t-1] ramp_up最小启停时间约束可选中小型机组可以简化忽略储能约束充放电功率限制0 P_ch[t] P_ch_max * x_ch[t]0 P_dis[t] P_dis_max * x_dis[t]充放电互斥x_ch[t] x_dis[t] 1SOC递推SOC[t] SOC[t-1] eta_ch * P_ch[t] * dt / capacity - (1/eta_dis) * P_dis[t] * dt / capacitySOC上下限SOC_min SOC[t] SOC_max调度周期始末SOC相等可选用于日循环调度这里有一个非常关键的细节充放电互斥约束必须用0-1变量实现否则可能出现“既充电又放电”的荒谬结果——从数学上看可能更省成本但物理上完全不可行。因此储能的建模是混合整数规划的核心驱动因素。需求响应约束响应量上限0 DR[t] dr_max_ratio * P_load[t]总响应量上限sum(DR[t] for t in T) dr_total_max可分时段约束某些合同可能规定一天最多响应几次可以引入整数变量限制这个项目暂未涉及。风光出力约束0 P_wt[t] P_wt_forecast[t]0 P_pv[t] P_pv_forecast[t]。因为风电光伏在日前调度中一般按“可发尽发”处理所以实际中它们通常取预测值的上限。2.3 需求响应的建模方式需求响应是这个项目区别于传统经济调度模型的核心亮点。很多初学者对需求响应理解得比较抽象其实就是“通过价格或激励手段改变用户用电行为”。在日前调度里我们把它建模为一种“虚拟电源”——当系统供电紧张或电价较高时通过削减负荷来缓解供需矛盾。在代码实现上需求响应是作为优化变量参与的所以它和燃气轮机、储能一样有自身的调节成本和调节上限。成本设置得很重要如果需求响应成本设置得太低优化器会倾向于大量调用需求响应结果就会变成负荷被疯狂削减这不符合实际如果设置得太高需求响应形同虚设又失去意义。合理的做法是参考当地电网的需求响应补偿标准比如尖峰负荷削减补偿标准是每千瓦时4~5元这个数值远高于燃气轮机的发电成本因此在大多数时段不会被调用只在峰值时段发挥作用。值得注意的是需求响应成本可以按时段差异化设置比如峰时段的补偿价格高于谷时段这能引导用户优先在高峰时段削减负荷。这个做法在工程项目中很常见可以加进模型里。3. Python代码实现与实操过程模型框架定下来之后开始进入代码实现。这一部分我会按照实际开发流程来写先准备数据文件再搭建优化模型框架然后写求解主程序最后分析结果。3.1 数据准备与预处理数据是整个模型的基石。你需要准备以下数据文件我用的都是CSV格式因为方便用Excel查看和修改风电预测出力24个时段的风电功率预测值单位kW光伏预测出力24个时段的光伏功率预测值单位kW基础负荷数据24个时段的负荷预测值单位kW分时电价24个时段的购电/售电价格如果考虑与主网交互本模型不含主网交互所以不需要系统参数表燃气轮机、储能、需求响应的各类参数数据预处理有一个容易踩的坑所有数据的单位必须统一。这个项目统一使用kW和kWh时间步长是1小时所以能量等于功率乘以时间1小时数值上功率与能量刚好相等但单位不同计算储能SOC时要特别注意时间步长。下面是一个简化的数据组织示例import pandas as pd import numpy as np # 读取24小时风电、光伏、负荷预测数据 wt_forecast pd.read_csv(data/wind_forecast.csv)[power_kw].values pv_forecast pd.read_csv(data/pv_forecast.csv)[power_kw].values load_forecast pd.read_csv(data/load_forecast.csv)[power_kw].values T range(24) # 24个时段 # 燃气轮机参数 mt_params { p_min: 30, # 最小出力 kW p_max: 150, # 最大出力 kW ramp_up: 50, # 上爬坡 kW/h ramp_down: 50, # 下爬坡 kW/h start_cost: 20, # 启动成本 元/次 fuel_a: 0.002, # 燃料成本二次系数 fuel_b: 0.5, # 燃料成本一次系数 fuel_c: 10, # 燃料成本常数项 } # 储能参数 ess_params { capacity: 200, # 额定容量 kWh p_ch_max: 60, # 最大充电功率 kW p_dis_max: 60, # 最大放电功率 kW eta_ch: 0.95, # 充电效率 eta_dis: 0.95, # 放电效率 soc_min: 0.2, # 最小荷电状态 soc_max: 0.9, # 最大荷电状态 soc_init: 0.5, # 初始荷电状态 c_ess: 0.05, # 储能运行成本系数 元/kWh } # 需求响应参数 dr_params { price: 1.5, # 响应补偿单价 元/kWh max_ratio: 0.15, # 每个时段最大响应比例 total_max: 200, # 全天最大响应量 kWh } # 风光出力上限比例默认全部消纳 wt_ratio np.ones(24) pv_ratio np.ones(24)我在实际项目中习惯把参数集中放在一个config.py文件里方便修改和复用。再强调一次参数取值一定要结合你自己的系统情况上面这些数值只是我在某个园区微电网项目中的示例不要直接照搬到你的场景里。3.2 优化模型搭建接下来是核心部分用Cplex Python接口搭建优化模型。整个模型定义可以分为四步创建模型、定义变量、添加约束、设置目标函数。import cplex from docplex.mp.model import Model # 使用docplexCplex的Python建模库更友好 mdl Model(microgrid_dispatch) # ---------- 定义变量 ---------- # 燃气轮机 p_mt {t: mdl.continuous_var(lb0, ubmt_params[p_max], namefp_mt_{t}) for t in T} u_mt {t: mdl.binary_var(namefu_mt_{t}) for t in T} start_vars {t: mdl.binary_var(namefstart_mt_{t}) for t in T} # 储能 p_ch {t: mdl.continuous_var(lb0, ubess_params[p_ch_max], namefp_ch_{t}) for t in T} p_dis {t: mdl.continuous_var(lb0, ubess_params[p_dis_max], namefp_dis_{t}) for t in T} x_ch {t: mdl.binary_var(namefx_ch_{t}) for t in T} x_dis {t: mdl.binary_var(namefx_dis_{t}) for t in T} soc {t: mdl.continuous_var(lbess_params[soc_min], ubess_params[soc_max], namefsoc_{t}) for t in T} # 需求响应 dr_amount {t: mdl.continuous_var(lb0, ubdr_params[max_ratio] * load_forecast[t], namefdr_{t}) for t in T} # 风光出力设为预测值范围内的变量 p_wt {t: mdl.continuous_var(lb0, ubwt_forecast[t], namefp_wt_{t}) for t in T} p_pv {t: mdl.continuous_var(lb0, ubpv_forecast[t], namefp_pv_{t}) for t in T}定义变量时最容易忽视的是0-1变量的数量。这个模型里每个时段有3个二元变量燃气轮机启动、储能充电、储能放电24个时段一共72个二元变量问题规模不算大。如果你的系统里有多台机组、多个储能单元二元变量数量会成倍增加这时候求解速度就会明显变慢需要考虑分解算法或者Benders分解之类的进阶方法。接下来添加约束条件。把核心约束一一列出来这样代码清晰也方便审查。# ---------- 功率平衡约束 ---------- for t in T: mdl.add_constraint( p_mt[t] p_dis[t] p_wt[t] p_pv[t] load_forecast[t] - dr_amount[t] p_ch[t], fbalance_{t} ) # ---------- 燃气轮机约束 ---------- for t in T: # 出力上下限 mdl.add_constraint(p_mt[t] mt_params[p_min] * u_mt[t], fmt_min_{t}) mdl.add_constraint(p_mt[t] mt_params[p_max] * u_mt[t], fmt_max_{t}) # 爬坡约束注意第一个时段特殊处理需要与初始状态比较 for t in T: if t 0: # 假设初始出力为0初始状态为停机 mdl.add_constraint(p_mt[t] - 0 mt_params[ramp_up], framp_up_{t}) mdl.add_constraint(0 - p_mt[t] mt_params[ramp_down], framp_down_{t}) else: mdl.add_constraint(p_mt[t] - p_mt[t-1] mt_params[ramp_up], framp_up_{t}) mdl.add_constraint(p_mt[t-1] - p_mt[t] mt_params[ramp_down], framp_down_{t}) # 启动状态约束start_vars[t] 1 表示 u_mt[t] - u_mt[t-1] 1 # 用线性化表达 for t in T: if t 0: mdl.add_constraint(start_vars[t] u_mt[t], fstart_linear_1_{t}) else: mdl.add_constraint(start_vars[t] u_mt[t] - u_mt[t-1], fstart_linear_1_{t}) mdl.add_constraint(start_vars[t] 0, fstart_linear_2_{t})这里有一个重要的建模技巧启动动作变量start_vars的线性化表达。如果要表达“从停机到启动”这个动作直觉上的写法是start_vars[t] u_mt[t] - u_mt[t-1]但这种等式在整数规划中通常写成不等式形式start_vars[t] u_mt[t] - u_mt[t-1]。原因在于当u_mt[t]1且u_mt[t-1]0时右侧等于1于是start_vars[t]被强制为1其他情况下右侧小于等于0不等式自然松弛。这样写既准确又不会过度约束模型。储能SOC递推约束的写法是另一个高频出错点# ---------- 储能SOC递推约束 ---------- dt 1 # 时间步长 1小时 for t in T: if t 0: mdl.add_constraint( soc[t] ess_params[soc_init] (ess_params[eta_ch] * p_ch[t] - p_dis[t] / ess_params[eta_dis]) * dt / ess_params[capacity], fsoc_{t} ) else: mdl.add_constraint( soc[t] soc[t-1] (ess_params[eta_ch] * p_ch[t] - p_dis[t] / ess_params[eta_dis]) * dt / ess_params[capacity], fsoc_{t} )注意SOC更新公式中的方向充电时SOC升高乘以充电效率eta_ch放电时SOC降低要除以放电效率eta_dis这是因为放电过程中电池内部有损耗实际消耗的SOC比输出能量要大。如果效率设置反了出来的结果会非常奇怪——储能越放SOC越高这种情况在工程现场一旦发生调度员会崩溃的。充放电互斥约束for t in T: mdl.add_constraint(x_ch[t] x_dis[t] 1, fess_exclusive_{t}) mdl.add_constraint(p_ch[t] ess_params[p_ch_max] * x_ch[t], fch_link_{t}) mdl.add_constraint(p_dis[t] ess_params[p_dis_max] * x_dis[t], fdis_link_{t})这里x_ch[t]和x_dis[t]是0-1变量p_ch[t] 60 * x_ch[t]这条约束的意义是当x_ch[t]0时充电功率被强制为0当x_ch[t]1时充电功率可以取到上限之内。如果不加这条约束光靠上下限约束0 p_ch[t] 60并不能阻止模型在x_ch[t]0的时候也给p_ch[t]赋正值从而破坏互斥性。需求响应约束# 单时段响应量上限 for t in T: mdl.add_constraint(dr_amount[t] dr_params[max_ratio] * load_forecast[t], fdr_ratio_{t}) # 全天总响应量上限 mdl.add_constraint(mdl.sum(dr_amount[t] for t in T) dr_params[total_max], dr_total_max)目标函数设置# ---------- 目标函数 ---------- objective mdl.sum( mt_params[fuel_a] * p_mt[t] * p_mt[t] mt_params[fuel_b] * p_mt[t] mt_params[fuel_c] * u_mt[t] for t in T ) objective mdl.sum(mt_params[start_cost] * start_vars[t] for t in T) objective mdl.sum(ess_params[c_ess] * (p_ch[t] p_dis[t]) for t in T) objective mdl.sum(dr_params[price] * dr_amount[t] for t in T) mdl.minimize(objective)严格来说fuel_a * p_mt[t] * p_mt[t]是二次项会让模型变成混合整数二次规划。有的Cplex版本对MIQP支持得不错但求解速度会比线性版本慢不少。如果你希望模型保持线性可以提前做分段线性化或者把二次项近似拆成线性项。由于示例中的fuel_a数值比较小0.002即便直接用二次项求解速度也不会有明显问题。3.3 求解与结果输出模型搭建完成后调用求解器# 求解 solution mdl.solve(log_outputTrue) # 检查求解状态 if solution: cost_total solution.get_objective_value() print(f总运行成本: {cost_total:.2f} 元) # 提取优化结果 results pd.DataFrame(indexT) results[P_wt] [p_wt[t].solution_value for t in T] results[P_pv] [p_pv[t].solution_value for t in T] results[P_mt] [p_mt[t].solution_value for t in T] results[P_ch] [p_ch[t].solution_value for t in T] results[P_dis] [p_dis[t].solution_value for t in T] results[SOC] [soc[t].solution_value for t in T] results[DR] [dr_amount[t].solution_value for t in T] results.to_csv(output/dispatch_result.csv, index_labelhour) print(结果已保存至 output/dispatch_result.csv) else: print(模型未找到可行解请检查约束条件)用docplex的好处很明显写起来很像自然语言约束命名方便排查变量的solution_value方法提取结果也很直观。如果你习惯直接用cplex的Python API代码会更底层一些但核心逻辑差不多。我个人推荐初学者用docplex因为它允许你把精力放在建模逻辑上而不是纠结API调用格式。3.4 结果可视化调度结果不画图的话很难直观判断合理不合理。我通常画三张图第一张是“电源出力堆叠图”横轴是24小时纵轴是功率风电、光伏、燃气轮机、储能放电依次堆叠能直观看到一天之内各种电源的配合情况第二张是“SOC变化曲线”用来检查储能是否在电价低谷充电、峰时放电第三张是“负荷与需求响应削减量对照图”看看需求响应主要作用在哪些时段。用matplotlib画堆叠图的核心代码import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) plt.stackplot( range(24), results[P_wt], results[P_pv], results[P_mt], results[P_dis], labels[风电, 光伏, 燃气轮机, 储能放电], colors[#87CEEB, #FFD700, #CD853F, #98FB98] ) plt.plot(range(24), load_forecast, r--o, label原始负荷, linewidth1.5) plt.plot(range(24), load_forecast - results[DR], g--s, label需求响应后负荷, linewidth1.5) plt.xlabel(时段 (h)) plt.ylabel(功率 (kW)) plt.legend() plt.grid(alpha0.3) plt.savefig(output/dispatch_curve.png, dpi300, bbox_inchestight) plt.show()画完之后重点看几件事需求响应后的负荷曲线有没有明显削峰效果储能SOC曲线是否在早晚高峰时段处于放电状态、在凌晨低谷时段处于充电状态燃气轮机出力曲线有没有频繁启停如果有的话说明启动成本设置太低或爬坡约束拧得太紧。4. 算例设计与结果讨论模型写完之后需要用一组算例来验证模型的有效性。单纯看模型“能跑出结果”是不够的还需要通过对比实验来分析结果背后的物理意义。4.1 典型日数据与基础场景我设计了一个典型园区微电网的日场景风电出力在凌晨2点到5点达到高峰大约120kW白天中午光伏出力达到峰值大约180kW用电负荷呈现明显的双峰特征——早高峰10-11点大约180kW晚高峰18-20点大约220kW凌晨低谷时段大约60-80kW。燃气轮机额定功率150kW储能容量200kWh最大充放电功率60kW需求响应最大单时段削减比例15%。在这个场景下模型求出来的总运行成本我跑出来大约在3200元左右。分布上看燃气轮机燃料成本占大头需求响应成本和储能运行成本相对较小。我还做了三组对比分别是不含储能、不含需求响应、以及同时含储能和需求响应的完整模型。这样能清晰看到储能和需求响应各自的价值。对比结果用表格整理如下场景总成本元燃气轮机发电量kWh弃风弃光kWh负荷峰值kW无储能、无需求响应3785256045220仅含储能3510239012220仅含需求响应3650251040205含储能需求响应332022800191从表格可以清晰看到储能的主要作用是减少弃风弃光、减少燃气轮机发电量需求响应的主要作用则是削峰降低峰值负荷。两者组合在一起效果最好总成本从3785元降到3320元降幅约12%。4.2 需求响应的削峰填谷效果把需求响应前后的负荷曲线放在一起看效果非常明显。18到20点的晚高峰时段需求响应削减了大约30kW的负荷占当时负荷的13.6%左右恰好把峰值从220kW削到了191kW。这不只是账面上的成本降低从系统运行角度看峰值负荷下降意味着配电变压器和线路的容量压力减小对设备寿命也是正向影响。需求响应为什么主要作用于晚高峰而不是早高峰仔细观察成本结构会发现晚高峰时段的负荷基数更大同样的响应比例下能削减的绝对量更多相对成本效益更高。同时晚高峰光伏出力已经降到接近零、风电也不大系统供电压力最大削减这部分负荷带来的边际效益最高。4.3 储能的低位充电、高峰放电策略再看储能的SOC曲线基本上呈现出一个典型的“两充两放”模式凌晨1-5点电价低且风力充足储能充电上午早高峰时段9-11点放电一次中午光伏大发时段再次充电晚上晚高峰时段18-20点再次放电。这个模式完全符合预期——储能本质上就是“低谷充电、高峰放电”的套利机制。有一个值得注意的现象如果储能容量设置得很大比如400kWh以上优化结果会出现一次充满然后整天不动的现象因为边际收益递减储能的“最后一度电”可能赚不回成本。这种情况是合理的说明系统已经逼近储能经济性应用的边界。4.4 不同需求响应成本下的敏感性分析需求响应补偿价格的取值对结果影响很大。我做了敏感性分析把响应成本从0.3元/kWh一直扫到3元/kWh观察需求响应量的变化。结果很有意思当补偿价格低于0.4元/kWh时需求响应被大量调用几乎每个时段都触达15%的上限当补偿价格在0.5到1.5元/kWh区间时需求响应量逐渐减少主要集中在晚高峰时段当补偿价格超过2元/kWh时需求响应量已经很少因为燃气轮机发电比调用需求响应更便宜。这个规律告诉我们需求响应能否真正发挥作用价格定得合理是关键。价格太低用户没有意愿参与价格太高调度中心用不起——两头都不好。这个折中点的确定正是需求响应建模中最重要的经济性分析内容。5. 常见问题与排错经验我在跑这个项目时踩了不少坑其中有几个非常典型这里整理成速查表帮大家省点弯路。5.1 模型不可行Infeasible最让人头疼的问题就是一跑求解器直接提示不可行而且还不告诉你是哪条约束出了问题。排查路径我总结为三步第一步检查功率平衡约束。把风电、光伏、负荷数据画出来对比一下看有没有出现“夜里负荷极高但风光出力极低、燃气轮机最大出力加上储能最大放电也不够”的时段如有就是数据本身有缺口。第二步逐个注释约束。先把需求响应约束注释掉重新求解再注释掉储能互斥约束找到是哪个约束组导致不可行。这个过程虽然笨但非常有效。第三步看变量边界是否矛盾。比如SOC的上下限如果设置得非常极限比如SOC_min0.1、SOC_max0.9再加上强制末时段SOC等于初时段的约束可能因为储能容量不够完成循环而无解。解决方法是给末时段SOC约束加一个松弛变量或者放宽上下限。5.2 结果里储能“既充电又放电”出现这个现象几乎可以断定互斥约束没有正确生效。检查两处一是x_ch[t] x_dis[t] 1这条约束是否真的加进去了二是p_ch[t] p_ch_max * x_ch[t]和p_dis[t] p_dis_max * x_dis[t]这两条“链接约束”不能省略。只加互斥约束、不加上限链接约束仍然可能出现特殊情况下的充电放电同时进行因为优化器会利用松弛空间作弊。5.3 求解速度慢对于24时段的单微网模型理论上不应该出现求解慢的问题。如果确实很慢先检查二元变量的数量是不是爆炸了。比如把燃气轮机启停细化成多个机组、或把储能分成多个单元变量数就可能翻倍。其次是尽量避免添加非线性的目标项把二次项分段线性化后速度会有明显提升。如果规模实在太大可以考虑分解策略把需求响应量当成参数做灵敏度分析把储能运行当作一个子问题迭代求解用交替方向乘子法ADMM的思路来解。这个属于进阶玩法后续可以单独写一篇展开。6. 实用建议与经验总结这个项目做完之后我最大的感受是微电网日前经济调度这件事难点不在于算法本身而在于模型的抽象与细节的处理。很多人在网上看到论文里的目标函数和约束条件觉得挺简单但真正实现起来才发现处处是坑——效率方向写反、互斥约束缺失、SOC递推公式里的效率放错位置这些都是典型错误。给准备做这个方向的朋友几条实操建议第一建模之前先花时间把物理过程理解透。储能充电时能量往里走、SOC上升放电时能量往外走、SOC下降。效率是充电打折扣、放电再打折扣。理解清楚能量流的方向再去写公式就不容易出错。第二把参数和数据分离。所有系统参数建议放在配置文件里不要散落在代码各处。这样不仅能提高代码复用性还能方便做敏感性分析——改一个参数重新跑一遍对比结果效率高很多。第三从简单场景开始逐步加复杂度。第一个版本先只跑“燃气轮机风电光伏”的简单经济调度确认模型没有问题后再加储能、再加需求响应。如果一上来就上全模型出了问题根本无从排查。第四结果分析比求解本身更重要。算出成本数字只是第一步你要能解释清楚为什么是这个数字、哪个成本项占比最高、储能为什么在这个时段充电而不在另一个时段充电。只有把结果背后的机制想明白你的方案才站得住脚论文或者项目汇报也才有内容可讲。这个Python框架后续可以扩展的方向很多把确定性模型改成两阶段随机优化处理风光预测的不确定性加入分时电价实现微电网与主网的互动把需求响应从激励型扩展为价格型用价格弹性矩阵建模。每一块都是独立的课题不过地基就是这个确定性日前调度模型把它吃透了后面的事会顺很多。
返回列表