ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

光储充换电站分时电价与用户负荷互动双层优化模型及Matlab实现

2026/9/9 23:50:34 拓冰建站 浏览量
光储充换电站分时电价与用户负荷互动双层优化模型及Matlab实现 1. 项目背景与核心思路拆解1.1 光储充换电站为什么需要“分时电价互动”这个优化模型先说说这类项目到底解决什么问题。光储充换电站名字听着长拆开就是光伏、储能、充电桩、换电设备打包在一起的一体化能源站。常见场景包括高速服务区、城市公交场站、物流园区等。相比传统充电站它多了一个储能系统和光伏电源本意是提升可再生能源就地消纳比例、降低对电网的依赖。但实际运行中会碰到一个很现实的问题时段不同光伏出力和充电需求往往错配。中午光伏出力最大但多数车辆不在场站傍晚大家集中下班回来充电光伏却已经归零。如果只是“发多少、充多少”被动运行储能利用率低用户充电成本高峰时还要从电网高价购电整个站的经济效益并不好。这就是这个项目标题里“考虑用户充电负荷-最优分时电价互动”这一长串描述背后真正想解决的痛点借助分时电价这个杠杆把充电负荷主动挪到光伏出力高或电网电价低的时段。所谓“互动”不是用户被动接受电价而是电站运营商先制定一套内部充电价用户根据价格信号自主选择充电时段运营商再根据用户的反应动态调整电价和储能充放电策略形成双向影响。本质上这是一个典型的主从递阶决策问题。电站是领导者用户是跟随者。领导者不能直接命令用户什么时候充电只能通过价格去诱导。这也是为什么不能简单做一个“固定电价下优化储能”的模型必须要把用户充电负荷的决策行为建模进来。1.2 从“固定电价优化”到“电价-负荷互动优化”的演进逻辑很多刚开始接触这类项目的朋友容易有一层误解觉得只要先算出一个最优分时电价再把这个电价代入储能优化模型里跑一遍就行了。但在实际系统里这种串行做法是有问题的电价一旦变了用户的充电行为就会跟着变需求曲线不再是原来固定的那条。举个最简单的例子。假设原始负荷高峰出现在1800到2100电站侧优化发现此时段应该定最高价来限流。但如果真把价格调高一部分用户会把充电挪到夜间或者午间负荷曲线整体形状都变了。此时储能系统的充放电计划还按旧曲线去优化就是“刻舟求剑”结果自然不是全局最优的。这个项目标题点名的“互动”实际要求的是将两个决策过程耦合在一起求解上层电站制定各时段充电价格和储能运行策略下层用户基于价格决策自己的充电时段和充电量。上下层之间通过负荷曲线发生信息交换最终收敛到一个双方都能接受的稳定解也就是博弈论里的Stackelberg均衡。在复现模型时这个上下层互动的建模方式决定了整个代码框架。如果只搭一个单层优化模型哪怕模型再复杂、约束再精细也和标题里的核心贡献不匹配。所以第一步不是急着写代码而是先把双层决策的框架梳理清楚谁是上层决策变量谁是下层决策变量哪部分约束属于哪一层信息是如何传递的。2. 优化模型的数学原理与关键参数设计2.1 上层电站目标函数与决策变量设计先看上层模型。电站运营商的优化目标通常设为日净收益最大化也就是一天下来所有收入减去所有成本的最大值。收入项包含两部分充电服务收入和向电网卖电的余电收入有些模型还会把换电服务收入单列。成本项包括向电网购电的成本、储能运行维护成本、电池充放电循环造成的退化成本等。目标函数表达式常见如下[ \max \sum_{t1}^{T} \left( \lambda_t^{ch} \cdot P_t^{ch} \cdot \Delta t \lambda_t^{sell} \cdot P_t^{sell} \cdot \Delta t - \lambda_t^{buy} \cdot P_t^{buy} \cdot \Delta t - C_{op} \right) ]其中 \lambda_t^{ch} 是电站向用户收取的充电价格上层决策变量P_t^{ch} 是时段t所有充电桩的总充电功率这个值不是上层直接定的而是下层用户优化结束后反馈回来的\lambda_t^{buy} 和 \lambda_t^{sell} 是向电网购电和卖电的价格外部参数P_t^{buy} 和 P_t^{sell} 是购电和卖电功率。这里有一个关键认知上层模型里用户侧的充电负荷 P_t^{ch} 不是固定参数而是下层优化的输出结果。它的大小取决于上层决策的 \lambda_t^{ch}。这就是双层的耦合点也是很多复现代码出错的重灾区——有朋友直接把负荷写成常量导致整个“互动”机制丢失。储能充放电状态、光伏出力分配、与电网交换功率这些都是上层可以直接控制的变量。此外还要给充电价格加一个取值范围约束比如不得低于向电网购电成本的一定比例、不得高于某个上限。不加约束的话求解器可能为了避峰而报出极端价格这在工程上不可行。2.2 下层用户充电负荷优化模型下层模拟的是用户群体的充电决策。这里的“用户”是聚合后的群体行为不是单个车主所以不需要刻画每个用户的具体出行链只需要刻画群体对价格的整体响应趋势。常见建模方式有两种。第一种是弹性系数法。先设定一个基准负荷曲线然后用价格弹性矩阵去修正。如果充电价格比基准价格低负荷上移价格升高负荷下移。弹性系数越大用户对价格越敏感负荷迁移越明显。实现起来最简单只需要在约束里写一个线性方程[ P_t^{ch} P_t^{base} \cdot \left( 1 \varepsilon \cdot \frac{\lambda_t^{base} - \lambda_t^{ch}}{\lambda_t^{base}} \right) ]其中 \varepsilon 是价格弹性系数\lambda_t^{base} 是基准电价P_t^{base} 是基准负荷。这种模型参数少、好收敛适合作为第一版实现。第二种是基于用户效用的优化模型。假设每个用户有若干可选的充电时间段每个时间段有一个基础效用值同时用户要支付充电费用。用户目标是在总充电需求满足的前提下最大化总效用减去充电成本的差值。这种模型更贴近经济学里的消费者行为理论但需要额外标定效用参数求解也更重。在复现时我建议先用弹性系数法把整体框架跑通确认双层迭代能收敛之后若时间和数据允许再升级到效用模型。两个模型在上层求解器的眼里只是下层反馈函数不同代码框架不需要大改。如果一开始就直接上复杂的用户行为模型调试时问题叠加很难判断是下层问题还是上下层交互出了问题。2.3 关键约束条件与物理参数标定无论上层还是下层模型都必须满足一系列物理约束。缺了这些约束优化结果看起来收益很高实际却没法运行。我记得第一次跑通双层模型时因为漏写了储能SOC连续性约束结果求解器给出的储能策略是每小时满充满放收益虚高到离谱一看曲线就知道物理上不成立。储能系统是最容易出问题的一块。约束包括SOC状态转移方程、SOC上下限、单时段充放电功率限值、充放电不能同时进行、日始日终SOC相等。其中日始日终SOC相等这个约束很多初学者会漏但不加的话求解器会为了一天之内的收益把SOC在日末放光第二天无法继续运行长期来看不可持续。功率平衡约束是整个模型的物理基石。每一时段光伏出力加上储能放电加上电网购电必须等于充电负荷加储能充电加换电负荷。多出来的光伏若无法就地消纳可以考虑弃光或者卖给电网但一定要在平衡式里体现。描述如下[ P_t^{pv} P_t^{dis} P_t^{buy} P_t^{ch} P_t^{c} P_t^{exchange} ]交换功率约束要看项目设定有的模型允许向电网售电有的不允许在代码里对应的是 P_t^{sell} 这个变量的存在与否。2.4 价格弹性、负荷迁移与用户心理阈值在实际复现里用户模型中最难的不是公式而是参数标定。价格弹性系数 \varepsilon 取多少直接决定优化结果是“小波小动”还是“剧烈迁移”。我建议做三组敏感性实验低弹性0.1中弹性0.3高弹性0.5。观察峰谷差缩减比例和电站收益的变化趋势。多数文献里弹性系数落在0.1到0.6之间取太大容易出现负荷整体搬到价格最低时段的“极端聚集”这既不符合实际用户行为也容易让模型陷入数值上的不合理解。另外还要给负荷迁移加一个最大可转移比例约束。比如每个时段最多只能迁移该时段基准负荷的40%否则模型会允许午间容量不足以接纳全部迁移负荷。这个约束在实际场景里的含义是很多用户的行程是刚性的不是所有充电需求都能被价格撬动。加了上限之后模型结果会稳妥不少。3. Matlab代码实现与复现过程详解3.1 代码总体架构与模块划分整个Matlab工程建议按功能拆分成模块化文件不建议一个大脚本从头写到尾。项目文件结构和各文件的职责如下main.m整个程序主入口定义全局参数、调用各功能模块、输出结果图表。input_data.m配置光伏出力序列、基准负荷序列、电网分时购电价、储能参数等基础数据。upper_model.m构建上层电站优化问题调用求解器求解返回最优充电电价和储能策略。lower_model.m构建下层用户响应模型输入电价返回调整后的充电负荷曲线。dual_update.m实现上下层迭代求解逻辑判断收敛条件更新拉格朗日乘子或价格变量。plot_results.m将优化前后负荷曲线、储能充放电曲线、电价曲线、SOC曲线可视化。模块化设计最大的好处是方便调试。你可以单独跑一个模块验证输出是否符合物理直觉再进入下一步。如果全部写在一个脚本里一旦出问题排查成本高好几倍。3.2 Yalmip建模与求解器配置要点Matlab里求解这类优化问题我个人最推荐的做法是搭配Yalmip工具箱加商用求解器。Yalmip的语言风格接近数学表达写约束非常直观出错率远低于手动矩阵拼装。求解器方面如果问题最终转化为混合整数线性规划或二次规划Cplex和Gurobi都是可靠选择。教育版许可证可以申请学生或者高校研究人员基本都能拿到。Yalmip建模的核心代码结构如下% 决策变量定义 lambda_ch sdpvar(24, 1); % 各时段充电价格上层决策 P_buy sdpvar(24, 1); % 各时段购电功率 P_dis sdpvar(24, 1); % 储能放电功率 P_ch sdpvar(24, 1); % 储能充电功率 SOC sdpvar(25, 1); % 储能SOC状态变量含两端边界 % 约束条件定义 Constraints []; Constraints [Constraints, SOC(1) SOC_init]; % 初始SOC for t 1:24 Constraints [Constraints, SOC(t1) SOC(t) (P_ch(t)*eta_ch - P_dis(t)/eta_dis) * dt / E_bat]; Constraints [Constraints, SOC_min SOC(t1) SOC_max]; Constraints [Constraints, 0 P_ch(t) P_ch_max * binary_ch(t)]; Constraints [Constraints, 0 P_dis(t) P_dis_max * (1 - binary_ch(t))]; Constraints [Constraints, P_pv(t) P_dis(t) P_buy(t) P_load_lower(t) P_ch(t)]; end注意这里储能充放电互斥是通过引入二进制变量 binary_ch(t) 实现的这是混合整数规划的标准做法。求解器选型时如果问题规模不大24个时段变量数量几百个以内Cplex的默认精度配置就够用了。如果上层采用了非线性效用函数则要考虑换成非线性规划求解器或者用线性化手段把问题转回线性。3.3 双层迭代求解的具体实现路径双层模型在Matlab中落地的核心难点在于上下层信息交换。我在实际复现里采用并行评估思维来加速收敛具体流程如下第一步初始化一组充电价格序列通常用当前电网分时电价作为初值。 第二步把价格传入下层用户模型求解得到用户的充电负荷曲线。 第三步把负荷曲线返回上层电站模型固定为负荷参数求解得到新的储能策略和新的最优充电电价。 第四步比较新旧电价差异如果满足收敛精度要求比如2范数小于某个阈值循环结束否则更新电价并回到第二步。这个逻辑看起来简单真正麻烦的是收敛性问题。上下层问题解完一轮之后价格可能来回震荡不收敛到同一个稳定点。这时候可以参考的思路是引入阻尼系数每一轮将新旧价格按比例混合更新lambda_new lambda_old alpha * (lambda_solved - lambda_old);alpha 取值通常在0.2到0.5之间调得越小越稳定但收敛变慢。这是一个经验性的参数不同数据场景需要微调。我在标准测试算例上跑时alpha取0.3大约在12到15轮左右稳定收敛。如果只是想快速验证模型逻辑可以先不追求严格收敛固定迭代轮数比如20轮观察负荷曲线和电价曲线是否趋于平稳。3.4 数据准备与典型场景参数设置好的模型需要好的数据但这个项目往往没有公开的标准测试数据集。复现时可以通过合理假设构造一份接近实际的数据。我在测试时采用的典型参数如下光伏出力序列可以找NREL或本地气象站的光照数据折算也可以直接构造一个典型日曲线中午12点到14点达到峰值峰值功率按光伏装机容量计算。充电负荷基准曲线则参考典型城市通勤场景早晨低谷傍晚下班后出现高峰夜间回落。储能系统参数设一个1000kWh的磷酸铁锂电池组额定功率250kW充放电效率0.95SOC运行范围0.1到0.9。电网购电价按照典型工商业峰谷电价设置峰时段1.2元/kWh平时段0.8元/kWh谷时段0.4元/kWh时段划分按照当地电网文件设定。这些参数不能照抄所有项目但作为模型验证和数据敏感性分析的起始点是够用的。跑通之后再针对目标场景的实际数据进行替换即可。4. 复现中的常见问题与排查技巧实录4.1 求解器报“Infeasible Problem”的排查路径这是复现双层模型时最常碰到的错误提示。很多人第一反应是改求解器设置但根因绝大多数情况下出在约束条件自相矛盾。最典型的场景是功率平衡约束与储能约束冲突。比如某时段光伏出力为0电网购电功率上限为500kW充电负荷需求却高达800kW储能SOC又处于下限无法放电此时平衡约束无解。排查思路是先在Matlab代码里加一段约束可行性检查逐条注释掉约束看哪一条注释后问题变可行。另一种常见原因是SOC运行范围设置过窄。比如设定SOC下限0.9但初始SOC又是0.5这类矛盾在模型内部几乎不可能自动消除。建议先把SOC范围调大确认模型能跑通后再逐步收紧到目标范围。4.2 双层迭代不收敛震荡与慢收敛处理价格迭代震荡的直观表现是第5轮电价峰值很高第6轮又压得很低来回大幅摆动。这是典型的无阻尼迭代发散。解决思路就是上面提到的阻尼系数法。如果阻尼系数降到0.1还是不收敛要考虑是不是下层模型弹性和上层目标函数之间天然存在多解问题——此时可以通过给上层目标增加一个电价的平滑惩罚项来改善。慢收敛的表现则是价格每一轮变化很小但200轮之后也没达到设定精度。处理办法是放宽收敛精度或者在工程可接受范围内直接固定迭代次数取最后几轮的平均值。学术论文里的双层博弈模型往往会强调收敛性证明但复现阶段首要目标是拿到合理结果不一定要在所有数据场景下都追求严格数学收敛。4.3 结果不符合物理直觉的检查清单跑通模型后还需要仔细检查输出结果。常见异常有几类储能一天之内反复多次充放电说明缺少储能循环次数成本项或互斥约束失效电价谷段低于成本价说明缺了价格下限约束光伏大发时段从电网购电说明功率平衡式或购电约束有误。我建议将结果分为三张图来检查第一张是优化前后负荷曲线对比图第二张是储能SOC和充放电功率时序图第三张是各时段购售电量和光伏出力、弃光的堆叠图。三张图一起看物理逻辑一目了然。出现异常时不要急于调参数先回到约束定义里去核对公式多数问题出在写代码时下标错位或者系数漏乘。4.4 模型扩展与改进方向参考基础版本跑通后可以根据实际需求扩展。比较常见的扩展方向包括将单日优化扩展为多日滚动优化加入光伏出力和负荷预测的不确定性建模将用户弹性和最大转移比例设为动态参数把储能寿命衰减成本细化到每次循环。这些扩展在原框架上都是增量修改不需要推倒重来。模块化代码的好处再次体现只需要替换或增加对应模块的约束和参数主流程保持不变。比如加入光伏不确定性可以在上层输入数据环节用蒙特卡洛生成多个光伏场景将目标函数改为期望收益最大化约束里逐场景刻画整体复杂度提升有限但模型适用性会提升不少。5. 开源代码调试经验与效果评估5.1 代码调试顺序建议调试这套双层模型我建议按“先单层后双层、先线性后复杂”的顺序推进。第一步先把上层电站模型单独跑通充电负荷用固定预测值验证储能策略和购售电行为是否合理。第二步再实现下层用户模型先不接上层固定电价直接算负荷响应单独验证用户侧逻辑是否正确。第三步再把两个模型通过迭代循环接起来。这个顺序的好处在于每一层的错误都能被及时定位。如果你一上来就写完整双层代码一旦结果不对你需要同时排查上层约束、下层约束和迭代逻辑工作量大且容易头晕。5.2 关键评价指标与效果对比模型跑通后建议从几个维度量化评估优化效果。最核心的是峰谷差改善率即优化前后充电负荷峰值与谷值的差值的缩减比例。这个指标直接反映分时电价对用户负荷的引导效果。其次是电站日收益提升率和固定电价场景做对比看储能和电价联动策略带来的收益增量。最后是光伏就地消纳率即光伏出力中直接供给充电负荷和储能的部分占光伏总出力的比例。以一个1000kW光伏、1000kWh储能的典型场景为例固定电价模式下光伏消纳率大概在65%左右加入分时电价互动优化后通过将部分充电负荷引导至午间光伏消纳率可以提升到82%以上电站日收益提升幅度在15%到25%之间具体取决于电价弹性系数和峰谷价差。5.3 运行效率方面的个人优化心得整套模型在24时段时间尺度下规模并不大但双层迭代会让单次求解重复多次。如果数据规模扩大或者用户模型改成多类用户聚合计算时间会明显上升。一个实用的优化思路是提前用向量化约束替代循环逐时段写约束。Yalmip支持整体表达式的向量约束24个时段可以一次性写入求解速度会明显更快。另外如果使用Gurobi求解MILP问题可以开启并行计算并根据问题规模设置合适的MIPGap。设置太严格的MIPGap比如1e-6会导致求解时间暴增而结果相对次优解的改进微乎其微。工程上把MIPGap设在1e-3到1e-4之间求解稳定性和时间花费最平衡。从个人实际操作体会来说这类“电价-负荷互动”模型最大的价值不在于那套数学公式有多精巧而在于把用户侧行为纳入了电站运行决策的闭环。单看某一天效果可能不明显但放到一个月、一个季度的运行周期里峰谷负荷的错峰效果和储能利用率的提升是实打实的。后续项目中还可以在用户分组、多类型负荷聚合、长时间尺度调度这些方向继续深入框架搭好了扩展只是工作量的问题。