简介:共享储能电站用户日前优化经济调度的 Matlab 完整源码,面向电力系统研究者、电气工程专业学生及储能调度算法入门者,重点解决多时段电价与负荷波动下的充放电策略优化问题。压缩包内共 2 个文件,均为 m 脚本源码,整体仅 4KB,轻量易读,资源结构清晰,便于快速定位与修改参数;其中基础案例构建了包含充放电效率、容量与功率约束的储能模型,进阶案例引入电价波动、负荷预测误差等不确定性因素,便于对照学习不同场景下的调度方法。目前已有 401 人学习下载。通过运行与拆解代码,可掌握日前调度建模流程、约束处理技巧与结果分析方法,也能延伸到粒子群、动态规划等优化算法的实践应用,适合用于课程设计、论文复现或实际项目预研。
1. 共享储能经济调度:从“各自为政”到“统一出清”的日前优化
共享储能电站的日前优化经济调度,解决的不是“怎么充放电”的物理问题,而是“在已知次日负荷、电价和储能状态的前提下,如何安排每个用户的充放电计划,使整体经济性最优”的决策问题。它把传统单用户储能变成了多用户共用的资源池,通过集中式优化或分布式协调,把峰谷价差收益、需量电费削减和服务费分摊统一进一个目标函数。这个场景在工业园区、商业楼宇和新能源配储项目中非常常见,Matlab + YALMIP 是当前最主流的建模求解组合。本文直接从数学模型讲到可复现的完整源码,覆盖约束构造、求解器选型、参数整定和结果校验,适合正在做储能调度算法、写毕业论文或做项目落地的工程师。
2. 共享储能日前调度的数学本质:目标函数与约束边界
2.1 为什么日前优化必须“统一建模”而不能逐户独立优化
共享储能电站的核心特征是“一个电池,多个用户”。如果每个用户单独优化自己虚构的储能,那么电站的总充放电功率必然超配,因为没有任何机制保证“你想充电时别人恰好也在放电”。日前优化的意义就在于提前一天,用负荷预测数据和分时电价曲线,把所有用户的功率需求叠加上储能功率变量,在统一的约束条件下求全局最优解。这里的关键不是算法多复杂,而是约束能不能精确表达物理边界。
日前优化的时间尺度通常是 24 小时、15 分钟或 1 小时一个时段。时段越细,越能反映电价尖峰和负荷陡坡,但决策变量和约束数量成倍增长。对于 10 个用户、96 个时段的模型,决策变量大约有 10×2×96=1920 个连续变量,加上储能 SOC 状态变量,规模并不大,用线性规划或混合整数线性规划都能在几秒内解完。
2.2 目标函数:购电成本 + 服务费 + 惩罚项的标准结构
目标函数通常写成最小化总成本的形式:
% 目标函数:minimize 总成本 % C_grid : 从电网购电成本(用户侧) % C_fee : 支付给储能电站的服务费 % C_penalty : 负荷越限或 SOC 越界的惩罚项 objective = sum(sum(C_grid .* P_buy)) ... % 分时电价购电成本 + sum(sum(C_grid .* P_sell)) * 0.8 ... % 余电上网收益(折价系数) + sum(sum(fee_rate .* (P_dis + P_ch))) ... % 储能服务费 + C_penalty;参数说明:
- 1 到 3 行把电网交互功率分成购电 P_buy 和售电 P_sell 两个非负变量,避免用单一净功率导致目标函数在多时段耦合时失去单调性。
- 服务费按充放电功率绝对值之和计算,是共享储能电站回收投资的主要方式。fee_rate 是每 kWh 的服务费率,通常在 0.1~0.3 元/kWh 之间。
- C_penalty 用来处理软约束,比如“用户申报负荷违约”或“SOC 未回到初始值”,惩罚系数取 1000 以上才会对目标产生实质影响。
2.3 约束条件:功率平衡、SOC 递推和共享容量分配
约束条件分为三类,缺一不可。
2.3.1 用户功率平衡约束
这里有个关键逻辑:用户负荷由电网购电、储能放电和自己可能的光伏出力共同满足。公式为:
% 每个用户每个时段的功率平衡 % P_load(i,t) + P_ch(i,t) = P_buy(i,t) + P_dis(i,t) + P_pv(i,t) Constraints = [Constraints P_load + P_ch == P_buy + P_dis + P_pv];注意:P_ch 和 P_dis 是同一个储能对不同用户分配的充放电功率,不是用户自己的储能。它们必须满足“同用户同时段不能同时充放电”的逻辑,但这是由共享储能的物理约束统一限制的,不需要每个用户单独加二进制变量。
2.3.2 储能 SOC 递推约束
储能核心的动态约束,也是模型能否反映电池特性的分水岭:
% SOC 递推:SOC(t+1) = SOC(t) - P_dis*dt/eta_dis + P_ch*dt*eta_ch for t = 1:T-1 Constraints = [Constraints SOC(:, t+1) == SOC(:, t) ... - P_dis(:, t) * dt / eta_dis ... + P_ch(:, t) * dt * eta_ch]; end参数说明:
- eta_dis 常取 0.9~0.95,eta_ch 取 0.92~0.97。这两个效率参数直接决定了储能的实际可用容量,设错会导致调度结果过于乐观。
- SOC 的初值通常取 0.2~0.5,终值要求等于初值,否则第二天无法继续循环调度。这个“SOC 回位约束”是日前优化最容易遗漏的约束。
2.3.3 共享储能的物理上限约束
% 电站总充放电功率不能超过额定功率 % 总 SOC 不能超过电站总容量 Constraints = [Constraints sum(P_ch, 1) <= P_rated * ch_ratio ... sum(P_dis, 1) <= P_rated * dis_ratio ... sum(P_ch, 1) + sum(P_dis, 1) <= P_rated ... % 功率耦合约束 SOC_total >= SOC_min ... SOC_total <= SOC_max];这里最容易出错的是“用户维度”和“电站维度”的混淆。同一个 P_ch 变量,如果定义成 N_user × T 的矩阵,那 sum(P_ch, 1) 是电站总充电功率;如果定义成单个变量,就无法区分每个用户的调用量。建模前必须把变量维度想清楚,我一般在代码开头用注释明确写出每个变量的行列含义。
2.4 求解器选型:为什么用 YALMIP 而不是手写梯度下降
日前优化经济调度本质是线性规划问题,可以用 linprog 手写,但约束修改成本高。更主流的是用 YALMIP 建模,后端接 Gurobi、CPLEX 或开源的 GLPK。Gurobi 对大模型支持最好,但需要 License;教学和演示用 GLPK 也够用。选型标准就两条:一是模型是否有二进制变量,有则强制用 MILP 求解器;二是变量规模超过 5000 个时,优先用 Gurobi。
3. Matlab 完整源码实现:从 YALMIP 建模到结果落盘
3.1 变量定义与维度设计
源码严格按“先定义集合、再定义变量、后写约束”的顺序组织。完整代码结构如下:
%% 共享储能电站的用户日前优化经济调度 % 清理环境 clear; clc; close all; % 基础参数 T = 96; % 时段数,15分钟一个点 N = 10; % 用户数量 dt = 0.25; % 时间步长,小时 % 储能系统参数 E_rated = 2000; % 电站总额定容量,kWh P_rated = 500; % 电站额定功率,kW SOC_min = 0.1; % SOC 下限 SOC_max = 0.9; % SOC 上限 SOC_init = 0.3; % 初始 SOC eta_ch = 0.95; % 充电效率 eta_dis = 0.92; % 放电效率 % 用户负荷数据(模拟生成,实际使用时替换为实测) rng(2025); % 固定随机种子保证可复现 P_load = 100 + 80 * rand(N, T) + ... 50 * sin((1:T)/T * 2 * pi); % 每个用户96点负荷曲线 P_pv = 20 * max(0, sin((1:T)/T * 2 * pi)); % 光伏出力 P_pv = repmat(P_pv, N, 1); % 假设每个用户都有光伏 % 分时电价 price = zeros(1, T); price(1:32) = 0.4; % 23:00-7:00 谷段 price(33:48) = 1.0; % 8:00-12:00 峰段 price(49:72) = 0.8; % 12:00-18:00 平段 price(73:96) = 1.2; % 18:00-22:00 尖峰段3.2 YALMIP 变量建模与约束组装
%% 定义优化变量 P_buy = sdpvar(N, T, 'full'); % 用户购电功率 P_sell = sdpvar(N, T, 'full'); % 用户售电功率 P_ch = sdpvar(N, T, 'full'); % 用户调用储能充电功率 P_dis = sdpvar(N, T, 'full'); % 用户调用储能放电功率 SOC_user = sdpvar(N, T+1, 'full'); % 每个用户视角的 SOC 分配 % 储能总 SOC 是用户 SOC 之和 SOC_total = sum(SOC_user, 1); %% 约束条件 Constraints = []; % 1. 功率平衡 Constraints = [Constraints, P_load + P_ch == P_buy + P_dis + P_pv]; % 2. SOC 递推与边界 Constraints = [Constraints, SOC_user(:, 2:end) == SOC_user(:, 1:end-1) ... - P_dis * dt / eta_dis + P_ch * dt * eta_ch]; Constraints = [Constraints, SOC_user(:, 1) == SOC_init * E_rated / N / E_rated * N]; % 初值分配 Constraints = [Constraints, SOC_user(:, end) == SOC_user(:, 1)]; % 回位约束 % 3. 电站层约束 Constraints = [Constraints, sum(P_ch, 1) + sum(P_dis, 1) <= P_rated]; Constraints = [Constraints, SOC_total >= SOC_min * E_rated, ... SOC_total <= SOC_max * E_rated]; Constraints = [Constraints, P_buy >= 0, P_sell >= 0, P_ch >= 0, P_dis >= 0];3.3 求解配置与参数说明
%% 求解配置 % 目标函数 fee_rate = 0.15; % 服务费,元/kWh objective = sum(sum(price .* P_buy)) ... - 0.8 * sum(sum(price .* P_sell)) ... + fee_rate * sum(sum(P_ch + P_dis)); % 调用求解器 ops = sdpsettings('solver', 'gurobi', ... 'verbose', 1, ... 'showprogress', 1, ... 'savesolveroutput', 1, ... 'solveroutputfile', 'solution.mat'); optimize(Constraints, objective, ops);参数说明:
- sdpsettings 的 solver 字段指定后端求解器。如果没有 Gurobi,改成 'glpk' 或 'linprog' 也能跑,但大模型速度会慢数倍。
- showprogress 设为 1 可以看到 YALMIP 各阶段的耗时,便于定位瓶颈在建模还是求解。
- savesolveroutput 会把求解器返回的原始数据结构存到 solution.mat,调试时非常有用。
3.4 结果提取与误差检查
求解完成后,需要从 sdpvar 对象中提取数值并做合理性检查:
%% 结果提取 P_buy_opt = value(P_buy); P_dis_opt = value(P_dis); P_ch_opt = value(P_ch); SOC_opt = value(SOC_total); % 约束违约检查(应该有符号误差极小) residual = P_load + P_ch_opt - P_buy_opt - P_dis_opt - P_pv; max_residual = max(abs(residual(:))); fprintf('最大功率平衡误差:%.2e kW\n', max_residual);如果 max_residual 大于 1e-6,说明模型定义有误,优先检查 SOC 递推公式里的 dt 单位是否统一,以及效率参数放置位置是否正确——很多入门者把 eta_ch 和 eta_dis 写反,导致 SOC 越算越少,最终模型无解。
4. 参数敏感性分析与场景对比:工程落地的关键一步
4.1 四类关键参数的取值影响
同一个模型,参数不同,调度策略完全不同。以下参数在实际项目中需要重点标定:
| 参数 | 典型范围 | 对结果的影响 | 错误设置的后果 |
|---|---|---|---|
| eta_ch / eta_dis | 0.9~0.97 | 决定储能净收益,效率每降 1%,峰谷套利空间缩水 3%~5% | 模型可解但方案亏损 |
| fee_rate | 0.1~0.3 元/kWh | 直接决定用户是否愿意调用储能 | 费率过高,用户全部不调用 |
| SOC_min | 0.05~0.2 | 保护电池,避免过放 | 设置过高浪费可调度容量 |
| 服务费模式 | 功率计费 / 容量计费 | 改变用户行为模式 | 计费方式和调度目标不匹配 |
4.2 有光伏与无光伏场景的对比实验
在共享储能电站的实际项目中,用户是否配光伏会明显改变最优调度策略。用以下脚本做两组场景对比:
%% 场景 A:无光伏(P_pv 全为 0) P_pv = zeros(N, T); optimize(Constraints, objective, ops); cost_no_pv = value(objective); P_dis_no_pv = value(P_dis); %% 场景 B:有光伏(恢复 P_pv) P_pv = repmat(20 * max(0, sin((1:T)/T * 2 * pi)), N, 1); optimize(Constraints, objective, ops); cost_with_pv = value(objective); P_dis_with_pv = value(P_dis); fprintf('无光伏总成本:%.2f 元\n', cost_no_pv); fprintf('有光伏总成本:%.2f 元\n', cost_with_pv); fprintf('储能放电量变化:%.2f kWh\n', ... sum(P_dis_with_pv(:)) - sum(P_dis_no_pv(:)));运行这段脚本后通常会看到两个现象:有光伏时总成本下降,但储能放电量可能不降反升,因为光伏出力在午间压低电价,储能会把充电时段挪到午间低价段,把放电集中到晚间尖峰段。这就是把“按天优化”的价值——它不是把光伏和储能割裂开,而是合并成一个决策单元。如果光伏出力和电价曲线不同步,模型会自动找到最优充放策略。
4.3 Milp 与 LP 的混合整数扩展
如果加入了“储能禁止同时段充放电”的严格约束,就必须引入二进制变量。YALMIP 支持 binvar 变量,但会把模型从 LP 变成 MILP,求解时间从秒级增加到分钟级。工程上多数情况不需要显式加这个约束,因为目标函数中充放电都增加服务费成本,模型天然会避免同时充放电。如果你在结果中发现了同时充放电,优先检查价格参数的符号是否写反,而不是急着加二进制变量。
5. 从仿真到项目落地:结果校验与调整技巧
5.1 校验约束是否满足的标准化测试序列
调度结果不能只看目标函数值,必须逐条核对约束。我的做法是写一个独立的校验脚本,读取优化结果后重新计算所有约束,而不是信任求解器返回的求解状态。重点校验三个地方:SOC 终值是否等于初值(误差需小于 1e-3)、每个时段的功率平衡是否严格满足、储能的累计充放电量与 SOC 变化是否一致。下面的代码片段能快速定位问题:
%% 约束校验核心代码 soc_check = SOC_init; for t = 1:T soc_check = soc_check ... - sum(P_dis_opt(:, t)) * dt / eta_dis ... + sum(P_ch_opt(:, t)) * dt * eta_ch; diff(t) = SOC_opt(t+1) - soc_check; end if max(abs(diff)) > 1e-6 warning('SOC 递推不一致,检查 dt 与效率参数'); end5.2 收敛慢或求解失败时的参数整定顺序
当 Gurobi 报 infeasible 时,按以下顺序排查:先把约束里所有非零初值设为 0,验证模型骨架是否正确;再逐步加入 SOC 回位约束、功率耦合约束、效率系数。如果加了效率系数才报无解,说明初始 SOC 设置不合理——常见情况是 SOC_init 设成 0.5,而 SOC_max 只有 0.55,加上充放电损耗,根本无法在一天内完成有效调度。
5.3 网格搜索服务的费率定价技巧
共享储能电站的运营方需要回答“服务费定多少能覆盖成本同时保证用户参与度”。用一层简单的参数扫描就能算出盈亏平衡点:
fee_list = 0.05:0.05:0.4; for i = 1:length(fee_list) fee_rate = fee_list(i); optimize(Constraints, objective, ops); revenue(i) = fee_rate * sum(value(P_ch + P_dis), 'all'); cost_battery(i) = value(objective) - revenue(i); end plot(fee_list, revenue, '-o', fee_list, cost_battery, '-s'); legend('服务费收入', '用户购电成本'); grid on;如果收入曲线与成本曲线没有交点,说明储能投资回报模型本身有缺陷,需要重新核算电池循环寿命和维护成本。这个技巧在向决策层汇报时非常实用——一张交叉图就能说清楚定价逻辑,而不是给出一堆 sdpvar 对象和约束方程。
本文还有配套的精品资源,点击获取