
上周跟一位做能源规划的同行聊项目方案对方拿来的需求里有一个光储充换电站优化模型要求把分时电价当成决策变量同时要反映用户充电负荷对电价的响应。聊到一半他卡住了电价和负荷既然是互动的Matlab里到底怎么把两者写进同一个模型这个问题我正好一周前复现过一个相关模型多少有些发言权。这篇文章就把整个复现思路拆开讲设备建模怎么搭、电价与负荷的互动怎么写、目标约束怎么设、代码怎么落、参数怎么调、坑在哪里尽量把一条能直接照着走的路径给出来。1. 站内模块建模先把站里那几套设备管清楚1.1 光伏出力的建模粒度复现这类模型的第一步不是急着写代码而是把站里的物理模块逐个理清。光储充换电站通常包含四块核心资产光伏阵列、储能电池、电动汽车充电桩、换电柜。四者的运行特性完全不同时间尺度也不一样——光伏出力跟着辐照度走储能SOC按小时变化充电负荷带有随机性换电则是一天若干次的离散需求。建模的第一步是决定时间分辨率通常用1小时或15分钟一个时段。1小时粒度适合看电价互动和成本趋势但会把充电桩的功率峰值削平15分钟粒度精度更高但电价数据规模翻4倍对严格线性规划问题影响不大一旦引入非线性约束求解时间会明显上升。复现演示一般选1小时把一天分成24个时段就够了。光伏出力建议用标幺化曲线作为初值。实测站点的辐照度数据通常拿不到但典型日的辐照度曲线是公开易得的。按效率折算P_pv(t) η_pv × A × G(t)其中A是光伏板有效面积η_pv是光电转换效率G(t)是当前时段水平面辐照度kW/m²。如果只有装机容量数据就把容量作为出力上限再乘以一个日辐照度形状因子来模拟曲线形状。复现时要注意光伏出力在夜间的约束是强制为0而不是让模型自由决定否则算法可能造出半夜光伏满发这种离谱解。1.2 储能电池的状态约束怎么搭储能是站内最灵活的调节资源也是模型里约束最多的部分。核心状态量是SOC递推公式必须写对SOC(t1) SOC(t) (P_c(t) × η_c − P_d(t) / η_d) × dt / E_ess这里有几个容易踩坑的地方。第一充放电不能同时为正。简化处理可以用二进制变量做互斥约束也可以在Yalmip里用逻辑约束表达。如果走线性规划路线互斥约束的标准写法是用一个binvar变量uu1表示充电然后把P_c和P_d的功率上限分别乘以u和(1-u)即可。第二SOC上下限不要设成0到1。锂电池日常运行一般限制在[0.1, 0.9]甚至[0.2, 0.8]防止过充过放同时给模型留调节空间。你把上下限缩得越紧储能的套利能力越差收益曲线会明显改变。第三日循环约束SOC(1)SOC(24)很多人第一次会漏掉。加上这个约束工程上意味着储能每天回到起始电量否则模型会把储能当成免费电池——第一天把电全放光然后一直不充得出的成本严重失真。复现时看到储能SOC曲线天天贴在地下限爬行先检查这条约束。1.3 充电桩与换电柜的负荷表达充电桩负荷用基础负荷曲线表达通常把该站服务的电动汽车充电行为统计成不同时段的功率需求。换电柜的建模不一样换电需求是离散事件但在一小时粒度下可以聚合成该时段需要换出的电池数量对应功率是给备用电池充电的功率。很多复现版本把换电负荷当成不可调度的刚需——用户把车开过来换个满电电池这个服务必须满足但备用电池的充电时间其实是可以调的换电柜本身也是储能资源让它在夜里电价低时把备用电池充满是模型的合理优化空间。我复现时把换电柜的备用电池充电功率一起并进储能侧的变量里相当于站内天然多了一层可调度空间。如果你把换电负荷完全当成刚性需求模型的调节能力会少一块结果会偏向保守。2. 电价与充电负荷的互动到底怎么建模2.1 用户侧响应电价的数学抓手价格弹性互动建模是整个模型最灵魂的部分也是最容易做成花架子的地方。最常用的抓手是价格弹性系数。价格弹性是负荷变化率与电价变化率的比值e (ΔL/L) / (Δp/p)比如弹性系数e −0.3电价上涨10%充电负荷下降3%。这个系数一给用户的响应就变成显式函数不需要模拟每个车主的心理活动。实际操作时我更推荐用转移率模型而不是直接弹总体负荷因为充电负荷有一部分本质上不可时移——快充用户赶时间电价再高他这一单也得充但慢充用户完全可以晚上回家插枪第二天早上取车这类负荷有灵活时移空间。所以模型里把充电负荷拆成两部分刚性负荷加上可转移比例γ可转移部分依据电价差在时段间移动。给一个具体算例假设基础负荷在18:00为200 kW当前电价0.8元全天平均电价0.6元弹性系数α取0.2单位换算成kWh/元那么该时段负荷增量为−0.2×(0.8−0.6)×200 −8 kW负荷变为192 kW。同一逻辑下谷时段的负荷会上涨。这个式子简单直观也方便你手工验算代码结果对不对。2.2 负荷响应与总量平衡的关系可转移负荷移动时必须保证一天的总充电量守恒谷时段增加的负荷等于峰时段减少的负荷。这个守恒约束经常被漏掉漏掉之后模型会出现一个明显的bug——电价高的时候用户负荷全部消失总电量凭空蒸发目标函数立刻变成最大收益。复现时看到这种结果先别急着调参数通常是总量约束漏写。总量守恒的数学表达是Σ L_new(t) Σ L_base(t)也就是响应后的负荷在各时段的总和等于基础负荷的总和。这个约束放在约束区里就一行但它决定了负荷响应是转移而不是销毁。模型里如果有这个约束你优化得到的电价曲线一定会引导出削峰填谷的形态用户可以明显看到电价峰谷和负荷峰谷发生错位。2.3 最优分时电价是谁的决策变量最优分时电价这句话里有一个重要的角色定位问题电价是运营商的决策变量还是政府固定给的三段式费率标题里的最优说明它应当被当成决策变量。模型里通常设定电价在各时段可调但限制它落在峰谷平三档区间内——比如峰时电价不超过某上限谷时电价不低于某下限或者直接用连续变量加区间约束。决策变量增多会让可行域变大但同时需要防止算法把电价全推到上限。所以还要给一个约束或者惩罚项要么约束峰谷价差不超过某个比例要么把用户满意度作为一项写入目标。如果不加任何限制优化结果往往就是电价全天最高、负荷全天最低这不是互动是单方面宰用户。这个反直觉的结果我在初版复现时亲眼见过当时第一反应是代码写错了。3. 优化模型的数学表达与求解设定3.1 目标函数电网侧的收益视角完整模型的原目标有两层电网公司定电价用户响应后决定负荷再反馈回电网形成主从博弈结构。复现时如果原样做双层优化Matlab里会涉及内层问题的KKT变换复杂度大增很多初学者在这里放弃。我复现时采取的做法是把用户响应写成一个显式函数嵌入合并成单层优化这是复现这类文章最常见的简化路径。需要注意在文末代码注释里标明这是简化版本否则在汇报或答辩时会被追问。单层化之后的目标函数就是站内运营商的日运行净收益最大化收益 售电收入 − 购电成本 − 储能与光伏的运维成本售电收入是各时段电价与用户实际负荷的乘积购电成本是向上级电网买电的功率乘以购电价。购电价与售电价之间的差是运营商利润空间光伏相当于零边际成本的电源储能负责把低价电搬到高价时段卖出。目标函数写成Matlab表达式就是一行多项式但每一项的物理含义都得能解释清楚这样别人问起来你能答得明白。3.2 约束条件的完整清单优化模型跑得稳不稳就看约束写得全不全。我通常把约束分成三类第一类是设备自身的物理约束光伏出力介于0和预测值之间储能SOC在上下限之间充放电功率受限充电桩和换电柜的功率上限约束。第二类是系统平衡约束在任何时段光伏出力加储能放电功率加购电功率必须等于充电负荷加换电负荷加储能充电功率。这一条是硬性等式每个时段都不能违背。第三类是市场与价格约束电价落在规定区间内可转移负荷满足总量守恒储能满足日循环SOC回收。这三类约束少了任何一类求解结果都会偏离实际。把这些约束放在一张检查清单里是排错最快的工具。我复现时习惯在代码注释里按这个分类标注每条约束后期排查效率高很多。3.3 双层问题的单层化处理策略如果原文章是严格的双层博弈模型而你不想只做个简化版那需要掌握把下层问题用KKT条件替换的方法。下层是用户在给定电价下最小化充电费用把它写成线性规划取KKT条件加上互补松弛条件整个下层就被并进上层模型。互补松弛条件会引入对偶变量与原始变量的乘积项导致模型变成带互补约束的数学规划求解器对这类问题支持不太好。复现时建议先用外点化方法跳过互补松弛或者干脆用弹性系数法做近似。多数中文复现文章最后落地的都是近似模型这一点你读别人代码时要有预期不要看到一个简化版本就以为原文只做到这一步。4. Matlab代码实现的关键流程4.1 环境与工具箱准备Matlab版本建议2021以上需要安装Optimization Toolbox再额外安装Yalmip和合适的求解器。Yalmip不是MathWorks官方工具箱而是Lund University的Lofberg教授维护的开源建模层好处是建模语法非常接近数学表达写约束几乎是一比一翻译。求解器方面线性规划和混合整数线性规划用Cplex或Gurobi都可以学术许可申请很方便如果不想额外装求解器直接用linprog和intlinprog也能跑小规模模型只是非线性约束会受限。Cplex在Yalmip里的调用方式是sdpsettings(solver, cplex)注意装好求解器后要先在Matlab里addpath到求解器目录再运行yalmiptest确认Yalmip能找到它。这个步骤很多人忽略结果报错Could not find solver实际上只是路径没配好。4.2 模型主体的代码骨架给一个最简代码框架方便把注意力放在模型结构上。数据区定义光伏预测、基础负荷、分时购电价、弹性系数和储能参数决策变量区定义每个时段的购电功率、储能充放电功率、SOC、售电电价和响应后的负荷约束区把上一节的三类约束逐一写进一个约束元胞数组目标区把收益表达成线性表达式最后调用求解器。%% 数据区 T 24; dt 1; P_pv ...; % 光伏预测出力1x24 P_load_base ...; % 基础充电负荷1x24 c_pool ...; % 购电价1x24 alpha 0.3; % 需求价格弹性 E_ess 500; % 储能容量kWh P_ess_max 100; % 储能功率上限kW P_buy_max 500; % 购电上限kW p_min 0.3; % 电价下限 p_max 1.5; % 电价上限 c_op 0.01; % 储能单位运维成本 %% 变量区 P_buy sdpvar(1, T); P_c sdpvar(1, T); P_d sdpvar(1, T); SOC sdpvar(1, T 1); p_sell sdpvar(1, T); P_load sdpvar(1, T); u binvar(1, T); % 储能充电状态标志 %% 约束区 constraints []; for t 1:T % SOC递推 constraints [constraints, SOC(t1) SOC(t) ... (P_c(t) * 0.95 - P_d(t) / 0.95) / E_ess]; constraints [constraints, 0.1 SOC(t) 0.9]; % 充放电互斥 constraints [constraints, 0 P_c(t) u(t) * P_ess_max]; constraints [constraints, 0 P_d(t) (1 - u(t)) * P_ess_max]; % 功率平衡 constraints [constraints, P_buy(t) P_pv(t) P_d(t) ... P_load(t) P_c(t)]; constraints [constraints, 0 P_buy(t) P_buy_max]; % 电价区间与负荷响应 constraints [constraints, p_min p_sell(t) p_max]; constraints [constraints, P_load(t) P_load_base(t) ... alpha * (mean(p_sell) - p_sell(t))]; end % 总量守恒与日循环 constraints [constraints, sum(P_load) sum(P_load_base)]; constraints [constraints, SOC(T 1) SOC(1)]; %% 目标区 objective sum((p_sell .* P_load - c_pool .* P_buy) * dt) - ... sum(P_c P_d) * c_op; %% 求解 optimize(constraints, -objective, sdpsettings(solver, cplex));这里需要说明两点。第一SOC(t1)的索引到T1最后用SOC(T1) SOC(1)实现日循环。第二负荷响应式里mean(p_sell)是全天平均电价这个写法把用户参考平均电价决定转移量的逻辑固化进去了是最简化的互动表达。如果你的模型里想用峰谷平三段电价把p_sell改成分段常数即可。4.3 结果的可视化与验证求解完别急着看日志。先把关键输出画成图第一张图把优化前后的负荷曲线和分时电价画在同一坐标轴看电价峰谷与负荷峰谷是否出现错位。这是验证互动是否生效的最直观标准。第二张图把SOC曲线画出来看储能是否在谷时段充电、峰时段放电。如果SOC在白天没充满就放电大概率是储能容量和光伏出力不匹配。第三张图是功率平衡图把光伏、储能、购电、负荷四类功率堆叠起来验证每个时段都满足等式。如果负荷曲线优化前后完全重合先查电价是不是根本没进入优化变量——我在初版代码里犯过这个错把电价写成常数了。可视化这部分建议直接用Matlab的plot和bar叠加图用hold on。如果要导出报告图记得设置figure的大小和字体别用默认字号打印出来根本看不清。5. 复现中的参数调优与实测经验5.1 电价上下限与负荷弹性系数的相互作用调参时最意外的发现是最优分时电价的结果对弹性系数特别敏感。弹性系数从-0.1调到-0.5峰谷价差几乎翻了一倍同时储能充放电循环次数也明显增加。原因很直观弹性越大用户越听话运营商有动力把峰时电价抬高、谷时电价压低拉大价差引导负荷转移负荷越平储能套利空间越小反而会抑制储能的利用率。电价上下限也要配套调整。如果p_min太低、p_max太高模型会倾向于把谷电价压到下限、峰电价顶到上限价差达到最大这时候弹性系数稍微大一点负荷曲线就会被压成一条非常平的线这在现实中不太可能。通常我会把峰谷价差比限制在2倍到3倍之间这样结果更符合电网售电侧的定价逻辑。下表是我复现时总结的参数影响方向方便快速定位问题出在哪参数取值范围对结果的影响方向需求弹性系数0.1~0.5弹性越大峰谷价差越大负荷曲线越平储能容量0~1000 kWh容量越大电价峰谷差越大套利收益越高光伏容量0~1000 kW容量越大购电量越小对电价制定影响间接购电价水平峰谷三段与售电价差决定利润空间价差越小收益越低5.2 光伏和储能容量如何影响优化解在算例里把光伏容量依次设为0、500 kW、1000 kW观察购电量和收益。光伏容量升高会显著降低购电量和购电成本但对峰谷电价的制定影响不那么直接。储能容量的影响则相反储能越大模型越倾向于拉大峰谷价差因为这样才能靠低充高放赚取更多套利收益。这里有一个很实用的经验如果优化电价峰谷差非常平缓多半是储能容量不够或储能效率设得太低导致套利空间变得无利可图。反过来如果你看到SOC曲线一天内来回循环七八次储能频繁切换充放电状态那不是模型聪明而是你给储能功率上限设定得太宽松容量又太小模型在拿储能当快速功率平衡器用实际工程中这会严重缩短电池寿命。这种情况建议给储能加一个最小连续充放电时间约束或者干脆限制每天的最大循环次数。5.3 求解报错与收敛问题的排除思路复现过程中最常见的报错有两个。第一个是infeasible problem也就是无可行解。通常原因是SOC日循环约束与充放电功率限制冲突。比如储能容量太小但模型又要求它必须回到起始SOC于是任何功率序列都满足不了。排查办法先把日循环约束去掉看看结果如果去掉后有解再逐步加回来调参数缩小SOC范围或者降低充放电功率上限往往就能解决。第二个是求解器报告Nonlinear constraints not supported。这是因为在Yalmip里用了exp或sdpvar的除法模型退化成非线性而选了Cplex。解决办法是把价格弹性的转移率模型改写成分段线性形式或用binvar做分段电价把非线性项全部线性化。下表是我整理的快速排错对照报错信息常见原因排查方向infeasible problemSOC日循环与功率上限冲突先去掉日循环约束验证再调SOC范围Nonlinear constraints not supported模型中使用了exp或除法改为分段线性函数或用binvar线性化Could not find solverCplex/Gurobi路径未配置运行yalmiptest并确认addpath路径还有一类情况不是报错而是求出来的目标值高得离谱。这时候别高兴太早先检查总量守恒约束有没有写。模型如果允许负荷凭空消失收益会膨胀到不合理的水平。6. 这个模型的边界条件和后续扩展6.1 单目标单站简化的失真风险必须泼一盆冷水标题里考虑用户充电负荷与最优分时电价互动听起来很完整但复现模型的假设其实不少。用户被抽象成可转移的弹性负荷没有考虑交通耗时、排队等待和换电服务半径光伏出力用典型曲线替代没有考虑云层遮蔽和预测误差电网侧则假设随时可以按固定购电价买电没有考虑功率阻塞和需求响应的配额考核。这些都是学术模型的合理简化但落到工程方案里要把预测误差和用户决策的随机性加进去至少做一个蒙特卡洛模拟来验证收益的置信区间。如果项目汇报时只说我优化出了最优电价评审大概率会追问预测不准怎么办用户不响应怎么办准备一版带随机性的扩展分析会更有说服力。6.2 从单站到区域的扩展思路如果后续想扩展比较自然的方向是把这个单站模型扩展到多站互联。多个站共享同一个上级电网的购电容量各站之间存在电价联动——这就变成多主体博弈问题求解会困难很多。典型的思路是用分布式优化的ADMM把问题分解成每个站一个子问题子问题之间只交换边界功率和电价信息。这个扩展对Matlab的并行计算和求解器选型都有更高要求但做完以后模型的工程价值会明显上一个台阶你可以回答区域内三个站谁降价促销能提高整体收益这类运营问题。不过建议不要贪多先把单站模型吃透再考虑区域联动。最后再分享一个小技巧这类优化模型我在复现时一定会在代码里保留一个开关变量比如用include_response 1控制是否启用负荷响应约束。这样可以在调试时轻松对比有互动和无互动两组结果验证模型行为是否符合预期。这个小习惯帮我在排查问题时省了很多时间写代码时加一行注释的成本几乎为零但排查问题时价值极大。