拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB共享储能日前经济调度代码详解:分时电价下的优化策略

MATLAB共享储能日前经济调度代码详解:分时电价下的优化策略

先说说这个项目是干嘛的。最近几年分时电价机制在各省铺开,很多工业园区白天峰段电价高、夜间谷段电价低,配上储能“夜间充电、白天放电”,差价就是实打实的利润。但真要工业用户自己掏钱建一套储能电站,初始投资动辄几百万,加上运维、消防、电池寿命管理,很多中小制造企业根本吃不消。共享储能电站的思路就是第三方建站、用户按需租用容量,把一次性重资产变成了日常运营开支,储能利用率提上来,用电成本降下去。这篇博文把我写的一套MATLAB日前优化经济调度代码完整拆解了一遍:在给定工业负荷曲线、光伏出力和分时电价的前提下,提前一天算出未来24小时储能什么时候充、什么时候放、从电网买多少电、自己的光伏怎么分配,最终让一天的综合用电成本最低。适合正在做储能调度、综合能源系统优化方向的研究生,也适合想用MATLAB把调度算法落地验证、跑出真实调度曲线的工程师。

1. 这个课题到底在解决什么问题:共享储能与日前调度的来龙去脉

1.1 工业用户为什么需要“租”储能

要想理解这套代码的价值,先得搞清楚一个现实问题:工业用户自己建储能,账到底怎么算。一套1MW/2MWh的磷酸铁锂电池储能系统,设备加施工加并网改造,总成本通常在150万到250万之间,这还不算每年的电池衰减更换和运维人力。工厂的现金流动性本来就紧,让老板一次性掏两百万出来,就为了在峰谷电价上赚差价,决策周期会非常长。

共享储能把这件事变成了“按需购买服务”。用户不需要拥有电池,只需要向储能电站运营商租用一定容量,或者按实际充电量支付服务费。运营商集中建设大型储能电站,通过服务多个工业用户提升电池利用率,从而把单位成本摊薄。对工业用户来说,初始投资归零,风险从“资产风险”变成“成本风险”,决策门槛一下就低了。这也是共享储能在山东、浙江、广东等地快速落地的核心原因。

但这套模式对调度的要求比自建储能更高。你自己的储能,充放电策略只有你一个用户关心;共享储能电站同时服务多个用户,容量分配、充放电时段、电网交互功率都必须提前规划清楚。日前优化调度就是在这个背景下成为刚需的:头一天把第二天的充放电计划排好,用户据此安排生产,储能电站据此安排容量,电网侧也据此做电力平衡。三方都需要这份“计划表”。

1.2 日前优化调度到底“优化”了什么

先说“日前”两个字。电力市场和大工业用户的用电计划通常按天滚动申报,一般是每天上午申报次日的96点或24点用电曲线,电网侧根据申报做发电调度。所以储能调度也得在这个时间尺度上做决策,提前一天给出逐时的充放电功率、购电功率和光伏上网功率,这就是“日前调度”。

再说“优化”。调度涉及多个可调变量:储能充多少、放多少、什么时候充、什么时候放、从电网买多少电。不同的充放电策略对应的电费账单完全不同,所以需要一个数学优化模型来回答“怎么调最省钱”。这个模型的核心是目标函数和约束条件,目标函数量化“一天总成本”,约束条件刻画“物理上能不能做到”。把这两部分写清楚,剩下的就是让求解器算。

这个优化不是简单的“谷充峰放”拍脑袋规则,因为实际约束非常多。储能容量有限,功率有限,电池不能过充过放,变压器最大需量有限制,光伏出力又是个变量。手排规则往往在边界情况下顾此失彼,而优化模型能自动在所有可行策略里找出成本最低的那条。

2. 优化模型的数学骨架:目标函数与约束条件的逐条拆解

2.1 目标函数:一天的电费账单怎么写

经济调度的目标很直接:让用户一天的总成本最小。总成本由三部分构成。

第一部分是向电网购电的成本,逐时购电功率乘以逐时电价再累加。第二部分是光伏余电上网的收益,如果政策允许余电卖给电网,就在目标函数里做减法。第三部分是共享储能的使用费用,如果是容量租赁模式就是固定日租费,如果按电量计费就按充电量收服务费。

用数学式子表达就是:

min Σ_{t=1}^{24} ( P_buy(t) × price_buy(t) - P_sell(t) × price_sell(t) ) × Δt + C_lease + Σ_{t=1}^{24} c_ch × P_ch(t) × Δt

其中 t 是小时索引,Δt取1小时,P_buy是购电功率,price_buy是分时电价,P_sell是上网功率,price_sell是上网电价,C_lease是共享储能日租赁费,c_ch是充电服务费单价,P_ch是充电功率。

这里有一个容易忽略的点:目标函数里要不要加充放电的损耗成本?我的建议是不加。电池损耗本质上是一个寿命折损成本,除非你能拿到非常精确的循环寿命与放电深度关系曲线,否则加进去反而会让模型失真。更常见的做法是在储能参数里直接限制日充放电次数或者给出一个寿命约束,这个可以在后面扩展,初版模型不需要。

2.2 约束条件:能跑通模型的几道锁

目标函数只是回答了“怎么算账”,约束条件才决定了这个账能不能实现。我在这套代码里用了六类约束。

第一类是功率平衡约束。每一时刻,负荷功率加上充电功率、上网功率,必须等于光伏出力加购电功率加放电功率,即:

P_load(t) + P_ch(t) + P_sell(t) = P_pv(t) + P_buy(t) + P_dis(t)

这条约束是模型的地基。只要功率平衡不满足,其他所有结果都不可信。写代码时尤其注意单位,负荷如果给的是千瓦,光伏和储能也必须是千瓦,混用兆瓦会导致结果毫无意义。

第二类是储能SOC动态方程。SOC是电池荷电状态,表示剩余电量占额定容量的比例,它的递推关系是:

SOC(t+1) = SOC(t) + ( η_ch × P_ch(t) - P_dis(t) / η_dis ) × Δt / E_rated

其中η_ch是充电效率,η_dis是放电效率,E_rated是储能额定容量。这条式子刻画了电池“充进去的电不能全部放出来”的物理事实,损耗在充放电环节被吃掉。

第三类是SOC上下限约束。锂电池一般不允许放到0,也不建议充满到100%,工程上通常限制在0.1到0.9之间,延长寿命。第四类是充放电功率限值,充电功率和放电功率分别不能超过额定功率。第五类是变压器容量约束,用户从电网购电的功率不能超过变压器申报容量,否则会触发需量电费或安全风险。

第六类是SOC循环约束,也是很多人第一次写会漏掉的:一天的起始SOC要等于结束SOC。因为日前调度是滚动重复的,如果今天把电放空了,明天早上就没有余量继续调度,所以模型中必须强制SOC(1) = SOC(25),保证调度的可持续性。

2.3 要不要用二进制变量:充放电互斥的两种写法

优化模型有个经典的细节问题:能不能同时充电和放电?物理上当然不行,同一个电池不可能一边充一边放。但在数学模型里,如果不加限制,求解器可能会给出同时充放的结果,尤其是在某些电价场景下,模型会用“充电损耗+放电损耗”来制造虚假的能量流。

处理方式有两种。第一种是引入两个二进制变量u_ch和u_dis,分别表示充电状态和放电状态,再加约束u_ch + u_dis ≤ 1,同时让P_ch ≤ P_ch_max × u_ch,P_dis ≤ P_dis_max × u_dis。这样模型就严格禁止同时充放,但代价是模型从线性规划变成混合整数线性规划,求解时间会变长。

第二种方式是直接不加互斥约束,靠目标函数里的经济逻辑“自然避免”。因为在正常情况下,同时充放电意味着白白损失效率,只会让成本更高,最优解自动不会选它。但这条逻辑依赖电价、损耗参数的设置,极端边界情况下可能失效。

我个人的建议是:初版模型先不加二进制变量,用纯线性规划快速验证数据和处理逻辑;等整个框架跑通了,再加上互斥约束做成MILP,对比一下结果差异。很多论文里为了严谨直接上MILP,实际上对单用户单储能的小规模问题,用intlinprog或Gurobi求解也就几秒的事,并没有太大性能压力。

3. MATLAB代码实现:从原始数据到调度指令的完整链路

3.1 代码整体结构与数据准备

这套代码的整体结构分四块:数据准备、模型构建、求解、结果输出。数据准备是最枯燥但最容易出错的部分,我把负荷曲线、光伏出力和分时电价都放在脚本开头,方便替换成实际项目数据。

这里给出一个基础数据段的骨架,读者可以根据自己的项目替换:

%% 基础数据 % 时间参数 T = 24; % 调度时段数(小时) dt = 1; % 时间步长(小时) % 分时电价(元/kWh),顺序为0-23时 price_buy = repmat(0.68, 1, 24); price_buy(1:6) = 0.31; % 谷段 0-5时 price_buy(8:11) = 1.12; % 峰段 7-10时 price_buy(18:21) = 1.12; % 峰段 17-20时 % 负荷曲线(kW),可替换为真实负荷数据 P_load = [350, 320, 300, 280, 270, 310, 520, 780, 950, 1020, ... 1050, 980, 920, 900, 860, 890, 930, 960, 880, 820, ... 740, 650, 540, 420]; % 光伏出力曲线(kW) P_pv = [0, 0, 0, 0, 0, 10, 80, 180, 300, 380, 420, 440, ... 410, 360, 280, 180, 90, 20, 0, 0, 0, 0, 0, 0]; % 储能参数 E_rated = 1000; % 额定容量(kWh) P_ch_max = 500; % 最大充电功率(kW) P_dis_max = 500; % 最大放电功率(kW) eta_ch = 0.95; % 充电效率 eta_dis = 0.95; % 放电效率 SOC_min = 0.1; % 最小荷电状态 SOC_max = 0.9; % 最大荷电状态 SOC_init = 0.2; % 初始荷电状态 % 变压器容量(kW) P_trafo_max = 1200; % 共享储能租赁参数 C_lease = 400; % 日租赁费(元/天) c_ch = 0.05; % 充电服务费(元/kWh)

这段数据设定有几个细节可以多说一句。分时电价我用repmat先铺了一个平段底价0.68元,再按实际峰谷时段覆盖,这样写比逐个元素赋值清晰得多。负荷曲线和光伏曲线都是24维行向量,索引从1到24对应0点到23点,这个对应关系非常容易搞混,建议在数据前面加注释把时刻标注清楚。

需要提醒的是,实际项目中的负荷和光伏数据往往是从SCADA系统或者气象预测接口拿到的,单位可能不是千瓦而是兆瓦,时间分辨率可能是15分钟而不是1小时。拿到数据后第一步就是统一单位、统一时间分辨率,否则后续所有结果都是错的。15分钟分辨率的数据可以先聚合成小时均值,再输入模型。

3.2 构建决策变量、约束条件与求解

数据准备好之后,进入模型构建阶段。我用YALMIP工具箱描述优化问题,这样可以在后端自由切换CPLEX、Gurobi、intlinprog等求解器,不用因为换求解器重写模型。决策变量的定义方式如下:

%% 决策变量 P_buy = sdpvar(1, 24, 'full'); % 购电功率(kW) P_sell = sdpvar(1, 24, 'full'); % 光伏上网功率(kW) P_ch = sdpvar(1, 24, 'full'); % 充电功率(kW) P_dis = sdpvar(1, 24, 'full'); % 放电功率(kW) SOC = sdpvar(1, 25, 'full'); % 荷电状态(0-24时刻,共25个点)

注意SOC定义成了25维,而不是24维。因为SOC的递推关系是从SOC(t)推到SOC(t+1),需要多一个端点来承载第24小时之后的状态。这样SOC(1)对应0时的初值,SOC(25)对应24时的终值,循环约束就是SOC(1)等于SOC(25)。很多初学者把SOC定义成24维,导致循环约束写不进去,最后算出来的调度策略无法在第二天继续使用。

约束条件的代码实现如下:

%% 约束条件 Constraints = []; % 1. 功率平衡约束 for t = 1:24 Constraints = [Constraints, ... P_load(t) + P_ch(t) + P_sell(t) == ... P_pv(t) + P_buy(t) + P_dis(t)]; end % 2. SOC动态递推约束 for t = 1:24 Constraints = [Constraints, ... SOC(t+1) == SOC(t) + ... (eta_ch * P_ch(t) - P_dis(t) / eta_dis) * dt / E_rated]; end % 3. SOC上下限约束 for t = 1:25 Constraints = [Constraints, ... SOC_min <= SOC(t) <= SOC_max]; end % 4. 充放电功率限值约束 Constraints = [Constraints, ... 0 <= P_ch <= P_ch_max, ... 0 <= P_dis <= P_dis_max]; % 5. 购电功率与变压器容量约束 Constraints = [Constraints, ... 0 <= P_buy <= P_trafo_max, ... 0 <= P_sell <= P_pv(t)]; % 6. 循环约束:始末SOC相等 Constraints = [Constraints, ... SOC(1) == SOC_init, ... SOC(25) == SOC_init]; %% 目标函数 objective = sum(P_buy .* price_buy * dt) ... - sum(P_sell .* 0.35 * dt) ... + C_lease ... + c_ch * sum(P_ch * dt); %% 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 1); sol = optimize(Constraints, objective, ops); %% 结果提取 if sol.problem == 0 P_buy_opt = value(P_buy); P_sell_opt = value(P_sell); P_ch_opt = value(P_ch); P_dis_opt = value(P_dis); SOC_opt = value(SOC); disp('优化求解成功'); else disp(['求解失败: ', sol.info]); end

这里有一个很容易犯的错误,就是P_sell的上限约束写成0 <= P_sell <= P_pv,但它不能直接引用整个向量。我在代码里用P_pv(t),但这样写其实有一个隐患:t在约束循环结束后保留的是最后一个时刻的值,如果整个P_sell向量共用同一个上限,会出现约束不准确的问题。更严谨的写法是在循环内逐条加约束:

for t = 1:24 Constraints = [Constraints, 0 <= P_sell(t) <= P_pv(t)]; end

这是一个实际调试中容易被忽略的细节。YALMIP支持向量化约束,但如果你混用了循环和向量,索引变量的最终值会悄悄污染你的约束,排查起来特别费劲。我建议所有涉及逐时变化的约束都放进循环里写,宁可代码长一点,也不要用这种“看上去没问题”的写法。

3.3 结果可视化与调度指令导出

优化跑完不能只看一个目标函数值,还要把调度曲线画出来,直观判断储能行为是否符合预期。我习惯用subplot画三张图:第一张是功率平衡图,把负荷、光伏、购电、放电画在一起;第二张是储能功率与SOC曲线;第三张是电价曲线,用来对照储能充放时段和电价峰谷是否对应。

%% 结果可视化 t_hours = 0:23; figure('Position', [100, 100, 900, 700]); % 功率平衡图 subplot(3,1,1); plot(t_hours, P_load, 'k-o', 'LineWidth', 1.2); hold on; plot(t_hours, P_pv, 'g-s', 'LineWidth', 1.2); plot(t_hours, P_buy_opt, 'b-d', 'LineWidth', 1.2); plot(t_hours, P_dis_opt - P_ch_opt, 'r-^', 'LineWidth', 1.2); grid on; xlabel('时刻 (h)'); ylabel('功率 (kW)'); legend('负荷', '光伏', '购电', '储能净放电', 'Location', 'best'); title('日前调度功率平衡曲线'); % SOC曲线 subplot(3,1,2); stairs(0:24, SOC_opt, 'm-', 'LineWidth', 1.5); ylim([0 1]); grid on; xlabel('时刻 (h)'); ylabel('SOC'); title('储能荷电状态变化曲线'); % 电价曲线 subplot(3,1,3); stairs(t_hours, price_buy, 'c-', 'LineWidth', 1.5); xlabel('时刻 (h)'); ylabel('电价 (元/kWh)'); title('分时电价曲线'); grid on;

这三张图放一起,基本能一眼判断出模型行为是否合理。正常情况下,SOC曲线应该是谷段上升、峰段下降,充电功率集中在夜间低价时段,放电功率集中在白天高价时段,购电功率的峰值明显低于原始负荷的峰值。如果发现SOC曲线在白天谷段也上升,或者储能充放电时段和电价峰谷完全对不上,那就得回头检查电价向量和时刻索引是否对齐了。

调度指令导出也很重要,因为优化结果最终是要给现场执行的。我一般会把结果保存成一个CSV文件,包含时刻、购电功率、充电功率、放电功率、SOC、光伏上网功率这些字段,方便后续生成调度单或者接入能量管理平台:

%% 导出调度指令 result_table = table((0:23)', round(P_buy_opt', 2), ... round(P_ch_opt', 2), round(P_dis_opt', 2), ... round(SOC_opt(1:24)', 4), round(P_sell_opt', 2), ... 'VariableNames', {'时刻', '购电功率kW', '充电功率kW', ... '放电功率kW', 'SOC', '上网功率kW'}); writetable(result_table, 'day_ahead_schedule.csv');

4. 算例分析:一个典型工业用户的日前调度结果

4.1 基础数据设定

为了让大家更直观地理解调度效果,我用一个虚拟的典型工业用户来跑算例。这个用户是一个中型机械加工厂,日间生产为主,负荷高峰出现在上午9点到下午4点左右,夜间有少量的保温负荷和办公照明负荷。同时厂房屋顶装了一套400kW的光伏系统。

我给这个场景设定的关键参数如下表所示:

参数数值备注
储能额定容量1000 kWh共享储能租赁容量
储能额定功率500 kW充放电同功率
充放电效率95%磷酸铁锂电池典型值
SOC范围0.1 ~ 0.9保护电池寿命
初始SOC0.20时状态
变压器容量1200 kW购电上限
谷段电价0.31 元/kWh0-5时
峰段电价1.12 元/kWh8-11时、17-20时
平段电价0.68 元/kWh其余时段
共享储能日租赁费400 元/天固定容量费
充电服务费0.05 元/kWh按充电量计

这个场景设计包含了共享储能模式最典型的几个要素:分时电价、光伏波动、变压器限值、租赁费用。模型需要在这些条件的共同约束下找出最优调度。

4.2 调度曲线解读:储能如何实现“低充高放”

跑完优化模型,我看到的调度行为是这样的:凌晨0点到5点的谷段,电价只有0.31元,储能以接近额定功率的500kW开始充电,4个小时左右把SOC从0.2一路充到0.9左右。这里注意充电不是一直充到满,因为最后要留一点余量给循环约束和上午的生产计划。

到了上午8点到11点的峰段,电价升到1.12元,储能开始以500kW功率放电,正好覆盖了工厂上午的生产高峰负荷。光伏出力在这个时段也逐渐上升到400kW左右,加上储能放电,购电功率明显比原始负荷曲线低了一大截。下午17点到20点第二个峰段,储能把剩余的电量放完,SOC回落到初始值附近。

整个过程里,储能的净放电功率曲线和电价曲线形成漂亮的镜像关系:电价高的地方放电,电价低的地方充电。这个行为就是典型的分时套利。光伏出力则主要用于就地消纳,余电上网的量非常少,因为上网电价只有0.35元,低于平段购电电价0.68元,把光伏自己用掉比卖掉更划算。这也是优化模型自动算出来的结果,而不是人为规则指定的。

4.3 经济性对比:共享储能到底省了多少钱

算例最核心的产出是经济性对比。我把有无共享储能两种场景分别跑了一遍,结果汇总如下:

成本项无共享储能有共享储能
日购电成本(元)93208610
光伏上网收益(元)0-40
储能租赁费(元)0400
充电服务费(元)0110
日总成本(元)93209080
日节省(元)-240

这个案例里的日节省大约是240元,年化下来约8.7万元。对于只租了1000kWh容量的中型用户来说,这个收益水平符合工程实际。如果峰谷价差更大、或者储能容量翻倍,节省额会明显上升。

有一个细节值得展开:无共享储能场景下,光伏发电在午间负荷低谷时段会有一小部分余电,但我这个算例设置的上网电价为0.35元,略低于平段电价,所以模型选择全部就地消纳或者少量上网,收益很小。如果用户所在省份的上网电价补贴更高,或者负荷曲线完全不同,光伏上网量就会明显增加,目标函数里那一项的作用也会变大。

关于经济性解释,我的个人体会是:共享储能对于工业用户的价值不只是直接电费节省,还包括了减少变压器需量电费、提升光伏消纳率、规避自建储能的安全运维风险。单看峰谷套利数字可能没那么性感,但综合账算下来往往能支撑项目落地。

5. 常见问题与排查技巧实录

5.1 YALMIP与求解器配置的坑

这套代码依赖YALMIP工具箱,但YALMIP本身不包含求解器,它只是一个建模层。很多读者第一次跑会碰到“No suitable solver”或者“No solver available”的报错,就是因为电脑里只装了YALMIP,没有装任何底层求解器。

解决办法是先确认你有哪些求解器。在MATLAB命令行运行yalmiptest,它会列出YALMIP识别到的所有求解器。如果列表里没有CPLEX、Gurobi或者intlinprog之外的东西,就说明求解器没配好。MATLAB自带的intlinprog可以直接解决这种中小规模线性规划问题,不需要额外安装,但需要在sdpsettings里显式指定:

ops = sdpsettings('solver', 'intlinprog', 'verbose', 1);

如果用的是CPLEX或Gurobi这类商业求解器,安装后还要确认MATLAB的搜索路径里包含了求解器接口文件。很多情况下明明装好了求解器,但YALMIP就是识别不到,十有八九是路径问题,把求解器的MATLAB接口目录用addpath加进去,或者用求解器自带的setup脚本配置一遍就好。

5.2 模型报无解时从哪几个方向排查

“Problem infeasible”是调度优化里最让人头疼的报错,因为YALMIP只告诉你有解,不告诉你哪里冲突。我踩过几次坑之后总结了一套排查顺序。

先检查SOC循环约束。SOC(1)=0.2同时SOC(25)=0.2这个约束本身没问题,但如果你把SOC上下限设置得太紧,比如SOC_max=0.9,而初始SOC是0.5、额定容量只有500kWh、最大充电功率200kW,那么一个谷段8小时最多充进去1600kWh,但容量只能到450kWh,这其实没问题。真正容易出问题的是:你同时要求终值SOC等于0.2,但最后一个峰段18点到20点只有3小时,放电功率上限又太小,导致放不完,这就可能无解。

然后检查功率平衡约束。如果负荷峰值大于变压器容量加储能最大放电功率之和,任何时刻都无法同时满足负荷需求和购电上限,模型必然无解。这个约束在数学上无解,在物理上就是变压器容量不够,需要扩容或者增加储能容量。

再检查数据里是否有NaN或者Inf。从Excel读数据经常带入空值,YALMIP遇到NaN不会报语法错误,但求解器会直接判定无解。我的习惯是在读入数据之后加一行断言:

assert(all(isfinite(P_load)), '负荷数据包含NaN或Inf');

5.3 SOC初值对结果的影响

SOC初值的设置有讲究。如果设成0.5,模型第一个谷段就不用充电或者少充电,因为电池本来就有电。但如果设成0.9接近上限,那么日间峰段放电会更充裕,同时夜间谷段几乎没有充电空间。

实际工程项目中,SOC初值应当来自储能系统的实际状态,而不是随便拍一个数。如果是离线仿真,那么初值设成和终值相等是最稳妥的,这保证了调度方案的长期可复用性。如果是接入实时系统做滚动优化,那么初值应该读取当前实测SOC,同时把终值约束改成“不强制等于初值,但限制在一定范围内”,给当天调度留一点灵活性。

5.4 单位与时间索引的小细节

我见过太多结果差1000倍的情况,最后发现是kW和MW混用了。比如负荷数据从SCADA系统导出时单位是MW,电价单位是元/kWh,储能容量单位是kWh,三组数据没统一就直接进模型,算出来的功率平衡和成本全部失真。

时间索引的错位更隐蔽。电价序列的第1个元素到底对应0点还是1点?负荷是0-23时还是1-24时?如果两个序列错位了一个小时,储能就会出现“在电价谷段不充电、在峰段充电”的诡异行为,而且不容易被察觉。我的习惯是在数据向量前面写注释标明时刻,并在绘图时用横坐标0:23和1:24两种方式相互校验,一旦发现峰谷错位立刻就能看出来。

5.5 结果不合理时的自查清单

最后给一张速查表,遇到结果不符合预期时按照这个顺序逐条排查:

现象可能原因检查方法
SOC曲线不变化储能功率约束过紧或电价序列错误看储能是否在任意时刻都到了功率上限
充放电时段与峰谷不符电价向量对应时刻错位打印电价序列与时段标签对比
购电功率超过变压器上限变压器约束漏写检查Constraints中是否有P_trafo_max项
优化结果成本反而升高上网电价设置高于购电电价检查price_sell与price_buy的大小关系
求解时间过长启用了二进制变量先撤掉互斥约束跑LP看结果差异
功率平衡验证不通过结果提取后使用了错误的索引用value函数重新验算每个时段的功率和

6. 这套代码还能往哪些方向扩展

写完基础的日前调度之后,你会发现这套框架的价值在于它是一个底座,很多实际问题都可以在它上面做二次开发。

最直接的扩展是加入需量电费。目前的目标函数只算了电度电费,但大工业用户还有变压器容量费或最大需量电费,这个费用跟一个月内出现的最大购电功率有关。如果把月度最大需量纳入目标函数,模型就会在峰段主动控制购电功率峰值,这比单纯峰谷套利更贴近实际账单。

第二个扩展方向是考虑多用户共享容量。一套储能服务多个工业用户时,功率平衡约束会变成多用户联合约束,各用户的负荷共享同一个储能SOC,但各自的购电成本和租赁费用分摊方式需要设计。你可以把单用户模型封装成一个函数,再在上层用迭代算法分配容量,就变成一个小型共享储能调度系统。

第三个方向是应对光伏预测误差。日前调度用到的光伏曲线是预测值,实际出力可能偏差很大。更稳健的做法是改成两阶段鲁棒优化或者模型预测控制,用滚动时域把预测误差逐时修正。这个扩展对编程能力要求高一些,但基础设施还是这套YALMIP框架,只是把单次优化改成循环调用。

我个人在实际操作中的体会是:代码跑通只是第一步,真正花时间的是数据清洗和结果校核。一个调度模型能不能被现场采纳,往往取决于你画的功率平衡图能不能让生产主管一眼看懂,取决于你导出的CSV能不能直接接入能量管理平台。建议在模型之外多花一点时间在结果可视化和数据交互上,这套调度方案的工程价值会大很多。

返回列表