分时电价和需求响应这两个词放到一起,外行听起来像政策文件里的概念,真正做过负荷仿真的人知道,这背后是一套很有嚼头的建模逻辑。这两年我做电网侧负荷分析时,被问得最多的就是:给定一条24小时基线负荷曲线,再给一套峰平谷分时电价,用户到底会挪多少负荷到谷时?削峰填谷能到什么程度?用户电费和负荷率会怎么变?这些问题不建模跑数据,靠拍脑袋完全答不准。
我当时用MATLAB搭了一套完整的负荷需求响应仿真脚本,用来对比不同分时电价方案的差异。这篇文章把这套仿真的建模思路、代码实现、结果分析和坑点完整展开,适合电气、能源相关专业的学生,也适合准备用MATLAB做电力市场或需求响应课程设计的读者。看完以后,你可以直接拿这套思路改成自己的仿真项目。
1. 分时电价下的需求响应,为什么要靠仿真说话
1.1 分时电价到底在解决什么问题
电网运行最头疼的问题之一,是负荷曲线的峰谷差。白天工厂开工、空调制冷,傍晚照明和家电叠加,负荷快速爬升;到了后半夜,大部分用电设备休息,负荷又掉到低谷。如果全天的负荷都像一条平线,发电机组可以安稳地满发,输电通道利用率也高。但现实是峰谷差可能达到最大负荷的40%以上,电网必须在高峰时段投入成本更高的调峰机组,低谷时段又面临机组低负荷运行甚至停机。
分时电价的核心思路,就是把一天切成峰、平、谷三种时段,峰时段电价高,谷时段电价低,让用户在价格信号的引导下自发调整用电行为。能转移到谷时的负荷转移出去,高峰时段的负荷压力就降下来。这个机制听起来不复杂,但效果很难靠经验估算,因为用户对电价的响应不是线性的,也不是简单平移一段负荷就能模拟的。
1.2 仿真在这个环节里承担什么角色
需求响应效果评估有三个层面:单个用户的用电行为变化、聚合后的负荷曲线变化、系统层面的削峰填谷指标变化。这三个层面串起来需要一个可重复计算的模型,MATLAB正好合适。
我选择MATLAB的原因很直接:矩阵运算方便,数据可视化顺手,而且弹性矩阵这类公式表达起来非常接近教科书写法。对于需要对比多种电价方案的场景,写一套参数化脚本,后续只改时段划分、电价数值和弹性系数,就能批量跑出结果。这一点在课程设计和实际项目预研里都特别实用。
2. 建模核心:弹性矩阵是如何把价格信号翻译成负荷变化的
2.1 自弹性、交叉弹性与24小时联动响应机制
需求响应建模最经典的方法是基于价格弹性系数。用户对电价的响应体现在两个维度:一个是自身时段的电价变化带来的需求变化,叫自弹性;另一个是其他时段电价变化对本时段需求的影响,叫交叉弹性。自弹性一般是负数,电价升高,本时段用电量下降;交叉弹性一般是正数,表示某个时段电价升高后,用户把一部分用电转移到了当前时段。
以小时为单位建模时,第 ( i ) 个时段响应后的负荷 ( Q_i ) 可以这样表述:
$$ Q_i = Q_i^0 + Q_i^0 \sum_{j=1}^{24} e_{ij} \cdot \frac{P_j - P_j^0}{P_j^0} $$
其中 ( Q_i^0 ) 是基线负荷,( P_j^0 ) 是基线电价,( P_j ) 是新的分时电价,( e_{ij} ) 是弹性系数矩阵里的元素。这个公式的意思是说:每个小时的新负荷,等于原来的负荷加上所有24个小时价格变化带来的影响之和。自弹性系数放在对角线位置,交叉弹性放在非对角线位置。
实际写代码时,很少直接用24乘24的完整矩阵,因为那需要大量交叉弹性数据。我的做法是先按峰、平、谷三个时段聚合,时段内各小时共享相同的自弹性,峰谷之间设定交叉弹性,平时段作为缓冲。这样既保住了价格联动的逻辑,数据需求又可控。
2.2 弹性系数的取值边界与调参经验
弹性系数的取值是这套模型最敏感的地方。查阅许多文献和实际项目的反馈,自弹性通常在 -0.5 到 -0.05 之间,峰谷交叉弹性在 0.01 到 0.1 之间。数值调得越大,需求响应效果越显著,但越容易偏离实际。
我的建议是,课程设计或预研阶段不要追求极端弹性值,取保守的中位数比较稳。比如自弹性 -0.2、交叉弹性 0.05,这样跑出来的结果既有可见的削峰效果,又不会出现高峰负荷暴跌一半这种不合理现象。仿真报告里最好补一段敏感性分析,把弹性系数从低到高扫描一遍,说明结果随参数变化的趋势,这样即使有人质疑参数选取,你也拿得出数据说话。
另一个容易忽略的是弹性系数矩阵的对称性约束。理论上交叉弹性要满足某种一致性条件才符合经济学直觉,比如总用电量变化不大时,转移效应应该近似守恒。虽然在简化模型里不必严格要求,但跑批时要注意结果是否出现用电总量大幅偏离基线。
2.3 峰平谷时段划分与电价差的设定逻辑
时段划分直接决定了响应的分布。我这次仿真采用了一个典型的工商业用户时段划分:
- 峰时段:9:00-11:00,18:00-21:00
- 谷时段:1:00-7:00,23:00-24:00
- 平时段:其余时间
电价方面,我设定平时段价格 0.55 元/千瓦时,峰时段 0.90 元/千瓦时,谷时段 0.30 元/千瓦时,峰谷价差比 3 倍。这个比例接近国内许多地区分时电价的价差水平,既足以刺激用户响应,又不至于偏离现实。
设计场景时,我额外设置了一个更激进的方案:峰价上调到 1.10 元,谷价下调到 0.25 元,用来观察价差扩大对响应幅度的边际影响。这里有一个经验:价差扩大后需求响应幅度不会线性增长,因为用户能灵活转移的负荷是有限的,比如生产工序不能随意中断、生活作息不能全盘打乱。(\Delta 响应) 的增幅一定小于 (\Delta 价差) 的增幅。仿真里如果看到线性甚至超线性增长,大概率是模型少了约束条件。
3. MATLAB仿真落地:从基础参数到多场景跑批
3.1 基线负荷和电价向量的初始化
仿真的第一步是构造输入数据。我习惯把所有参数集中放在脚本开头,用结构体或者命名变量管理,后续改动一处,不用翻遍全文件。
% 仿真步长:1小时 hours = 24; % 典型夏季工作日基线负荷,单位MW Q0 = [2800 2700 2600 2550 2500 2600 2900 3200 3500 3800 3900 ... 3700 3500 3400 3450 3500 3700 4000 4300 4100 3900 ... 3500 3100 2900]; % 基线统一电价,单位元/kWh price_base = 0.55 * ones(1, 24); % 分时电价方案A:峰0.90,平0.55,谷0.30 price_TOU = 0.55 * ones(1, 24); price_TOU(9:11) = 0.90; price_TOU(18:21) = 0.90; price_TOU(1:7) = 0.30; price_TOU(23:24) = 0.30;这里有个容易踩的坑:MATLAB数组索引从1开始,所以 9:11 表示9点、10点、11点三个小时,对应实际一天的9:00-11:59。写成 9:11 不是头尾都包含的意思,而是生成一个整数向量。如果你习惯用C语言风格,第一时间容易写成 half-open 区间,然后发现峰时段覆盖错了范围。跑完第一条曲线,先输出一下各时段的电价标签,确认无误再往下走。
3.2 弹性矩阵构建与响应计算主程序
我用峰平谷聚合的方式来建弹性矩阵。把24小时映射到三个类别,然后构造类别之间的交叉弹性,再扩展回小时级矩阵。这样做既避免24乘24的大矩阵稀疏难调,又能保证峰谷时段的负荷可以相互转移。
% 弹性参数 e_self = -0.20; % 自弹性 e_peak_flat = 0.05; % 峰对平的交叉弹性 e_peak_valley = 0.08; % 峰对谷的交叉弹性 e_valley_peak = 0.06; % 谷对峰的交叉弹性 e_valley_flat = 0.04; % 时段类别标签:1谷,2平,3峰 period_type = zeros(1, 24); period_type(1:7) = 1; period_type(23:24) = 1; period_type(9:11) = 3; period_type(18:21) = 3; period_type(period_type == 0) = 2; % 构建24x24弹性矩阵(简化版) E_matrix = zeros(24, 24); for i = 1:24 type_i = period_type(i); for j = 1:24 if i == j E_matrix(i, j) = e_self; else type_j = period_type(j); if type_i == 3 && type_j == 2 E_matrix(i, j) = e_peak_flat; elseif type_i == 3 && type_j == 1 E_matrix(i, j) = e_peak_valley; elseif type_i == 1 && type_j == 3 E_matrix(i, j) = e_valley_peak; elseif type_i == 1 && type_j == 2 E_matrix(i, j) = e_valley_flat; elseif type_i == 2 && type_j == 3 E_matrix(i, j) = e_peak_valley; elseif type_i == 2 && type_j == 1 E_matrix(i, j) = e_valley_flat; end end end end类别交叉放进矩阵后,还要按小时求和的详细公式体现在计算步骤里。响应后的负荷,每一小时都等于基线负荷乘以“1加所有时段价格变化加权求和”。
% 价格变化比例 delta_price_ratio = (price_TOU - price_base) ./ price_base; % 需求响应后负荷,初始化为基线 Q_new = zeros(1, 24); for i = 1:24 gain = 1 + sum(E_matrix(i, :) .* delta_price_ratio); Q_new(i) = max(Q0(i) * gain, 0); end为什么用 ( 1 + ) 而不是 ( 1 - )?因为 ( delta_price_ratio ) 在峰时段是正数,自弹性又是负数,两个乘完自然变成负贡献,负荷下降;谷时段价格变化比例是负数,乘上负的弹性后变成正贡献,负荷上升。这个正负号处理非常关键,初学者常在这里把交叉弹性的符号写反,导致峰时负荷不减反增。
3.3 三套仿真场景的设计与对比思路
单跑一套方案没有说服力,我做的是多场景对比。场景设计逻辑如下:
- 基线场景:全天空一价0.55元,作为对照。
- 方案A:常规分时电价,峰0.90元,谷0.30元。
- 方案B:激进分时电价,峰1.10元,谷0.25元。
- 方案C:在方案A基础上,设置谷时最低负荷保护,约束负荷不低于某下限。
方案C是为了模拟现实中“谷时负荷不能无限制堆高”的场景。峰谷价差拉大后,谷时负荷确实会上升,但配电网设备和用户自身用电能力都可能设了上限。仿真中加入约束,结果才更贴近实际。
% 方案C:谷时段负荷约束 Q_new_c = Q_new; valley_idx = find(period_type == 1); lower_limit = 2800; % MW Q_new_c(valley_idx) = max(Q_new_c(valley_idx), lower_limit);这只是最简单的事后约束修正。如果需要更严谨,应该在优化模型里做成不等式约束,而不是跑完后直接截断。截断的目的是给你快速看趋势,真要写论文,建议用线性规划或者fmincon处理约束,这会引出另一套代码。
3.4 敏感性分析在仿真流程里怎么加
敏感性分析说白了就是对某个参数做 grid scan,看结果怎么变。我在程序里额外包了一层循环,针对自弹性系数从 -0.05 到 -0.35 取7个值,记录每组参数下的峰值负荷和峰谷差,画成曲线。
e_self_scan = -0.05: -0.05: -0.35; peak_load_record = zeros(size(e_self_scan)); peak_valley_diff = zeros(size(e_self_scan)); for k = 1:length(e_self_scan) % 更新弹性矩阵对角元素后重新计算Q_new E_matrix_scan = E_matrix + diag((e_self_scan(k) - e_self) * ones(24, 1)); gain_scan = 1 + sum(E_matrix_scan .* repmat(delta_price_ratio', 1, 24), 2); Q_new_scan = max(Q0' .* gain_scan, 0); peak_load_record(k) = max(Q_new_scan); peak_valley_diff(k) = max(Q_new_scan) - min(Q_new_scan); end这种方式写起来简洁,但如果你追求高效,可以先把弹性矩阵参数化成一个函数句柄,然后放进 arrayfun 里跑。我实测发现24小时规模的数据量很小,循环就够用了,真正需要优化的反而是程序外的输入数据清洗。
4. 结果分析:从负荷曲线到经济效益的量化解读
4.1 削峰填谷效果如何用指标量化
仿真跑完后,第一步是画出基线负荷和响应后负荷的对比曲线。我在每个场景里都保存了24小时负荷序列,然后计算四个核心指标:最大负荷、最小负荷、峰谷差、负荷率。
以我采用的假设数据为例:
| 场景 | 最大负荷(MW) | 最小负荷(MW) | 峰谷差(MW) | 负荷率 |
|---|---|---|---|---|
| 基线 | 4300 | 2500 | 1800 | 0.78 |
| 方案A | 3880 | 2760 | 1120 | 0.87 |
| 方案B | 3690 | 2950 | 740 | 0.91 |
| 方案C | 3900 | 2650 | 1250 | 0.86 |
方案A的最大负荷从4300兆瓦降到3880兆瓦,削峰约9.8%;峰谷差从1800兆瓦缩到1120兆瓦,降幅接近38%。负荷率从0.78提高到0.87,说明全天负荷曲线的平整度明显提升,这就是调度喜欢看到的效果。方案B继续拉大峰谷价差后,响应更加剧烈,但是你会发现谷时负荷从2760兆瓦涨到2950兆瓦,进一步抬升,同时日总用电量也显著低于基线,这就是弹性模型里“节能效应”混入的结果。
这里必须提醒一句:如果模型只设自弹性且没有交叉平衡,价格一涨,总用电量就跟着掉。这到底属于真正的需求削减,还是用户把负荷转移走了,在结果分析里一定要分清楚。否则削峰效果可能被高估。
4.2 用户侧电费支出变化算清楚才叫完整
需求响应不能只谈系统侧削峰,用户之所以参与,是因为想省钱。我在仿真里同时统计了响应前后用户侧的电费支出(按分时电价计算)。
| 场景 | 日用电量(MWh) | 日电费(万元) | 单位电费(元/kWh) |
|---|---|---|---|
| 基线 | 78800 | 43340 | 0.55 |
| 方案A | 78200 | 39810 | 0.51 |
| 方案B | 77400 | 38120 | 0.53 |
注意一个反直觉的结果:方案B的日电费总额比方案B的单价反而高?我这张表里的方案B单位电费是0.53,比方案A高,这是因为虽然谷时段电价更低了,但峰时段电价涨得太猛,而用户剩余在峰时的负荷仍然支付高价,整体平均电价被峰价拉高。也就是说,大幅拉大峰谷价差并不一定给用户带来更多实惠,这可能降低用户的参与意愿。
表格数据仅用于说明趋势,实际仿真的数值取决于负荷构成和弹性参数,但分析逻辑是通用的。真正给用户算账时,最好把响应的机会成本也考虑进去,比如某些工业负荷挪到夜间需要增加人工成本,那用户能接受的最大价差空间又会变化。
4.3 拉大峰谷价差的边际效果递减
从方案A到方案B,峰价从0.90涨到1.10,谷价从0.30降到0.25,价差扩大近50%,但最大负荷只多下降了约5%,峰谷差多缩减了约34%。这说明需求响应在这个弹性水平下已经接近饱和。
为什么会出现边际递减?因为可转移负荷并不是无限的。照明、医疗设备、数据中心这类负荷对电价几乎没有弹性,生产流程中真正能灵活调节的负荷占比有限。仿真做到这一步,应该有意识地在结论里说明:单纯靠加价差的边际收益有限,下一步应该考虑需求响应与储能、新能源消纳的联合优化。
5. 仿真过程中的常见坑与可复现建议
5.1 总用电量莫名变化,先检查弹性矩阵是否对称
我跑第一批结果的时候,发现方案A的日用电量比基线少了600兆瓦时。问题是,我起初只想模拟用户把负荷从峰时挪到谷时,并不想模拟节能。那这个用电量下降来自哪里?查处后发现自弹性让所有时段都有独立的价格响应,峰时段负荷确实大幅下降,但谷时段负荷上升的幅度没完全抵消,总量就掉了。
解决办法有两个:一是把自弹性设得小一些,让价格水平对总量的影响弱于时分迁移影响;二是使用对称矩阵,保证交叉弹性在转移关系上近似守恒。如果目标是纯粹模拟削峰填谷,建议把交叉弹性设为主因素,自弹性只作为次要因素。
5.2 负荷曲线在时段边界出现尖刺
峰平谷时段切换的边界点,比如11点和12点之间,价格从0.90跳回0.55,模型计算后负荷可能出现一个明显的回落尖刺。这在实际用户行为里通常不会发生,因为用户的负荷调整不可能在一瞬间完成。解决方式可以是在边界时段做平滑处理,或者把时段边界设置成渐变价格带。
我最初没有处理这个问题,画出来的曲线难看,领导一眼就觉得数据不合理。后来我做了简单的三点滑动平均,只作用于时段切换邻近的两个点,效果就好多了。但这样做会略微改变峰谷指标,报告里要向读者说明平滑处理方式。
5.3 MATLAB脚本组织与数据单位统一
工程实践里最大的隐性问题是单位。负荷数据可能来自SCADA系统,单位是兆瓦;电价数据来自营销系统,单位是元每千瓦时;电量数据又可能是万千瓦时。仿真里只要单位不统一,结果就会对不上。我在脚本开头统一转成“兆瓦+小时”组合,电量全部用兆瓦时,电价用元每千瓦时,算电费时直接乘起来,再除以10000得到万元。
脚本组织方面,推荐按“参数区-数据区-计算区-结果区”划分,用注释块分隔。后续换数据时,只需要修改参数区和数据区,计算区完全不用动。这样做的好处是可以快速退化到其他用户或者时段,复用性提高很多。
5.4 绘图与报告输出的几个细节
我习惯在结果区直接生成三张图:负荷曲线对比图、电费分时段柱状图、敏感性分析曲线图。负荷曲线对比图用线性图最直观,基线用灰色虚线,方案用实线,峰谷时段背景用不同透明度色块标出。绘图颜色尽量用色盲友好的配色,避免全用红绿对比。
柱状图用来展示各时段电费支出结构,能直观看出峰时支出占比下降了多少。敏感性分析曲线图横坐标放自弹性绝对值,纵坐标放峰值和峰谷差,两条曲线分别用不同marker,方便在报告里解释参数边界。
写在最后
这套仿真模型真正跑通之后,最让我意外的不是削峰效果有多好,而是模型对弹性系数和时段划分的敏感程度。参数稍微改一点,结论就可能从“方案最优”变成“方案不可行”。所以做这类分析,一定要把模型假设写清楚,把敏感性分析附上,结论才有说服力。
如果你想继续扩展,可以考虑三个方向:第一,把刚性负荷、柔性负荷、可平移负荷分开建模,替代现在的统一弹性矩阵,结果会精细得多;第二,加入储能系统模型,看分时电价和储能联合起来对用户经济性有什么影响;第三,把这种确定性仿真升级成功率场景下的蒙特卡洛模拟,给每条曲线加上置信区间。每个方向都能单独拉出一篇完整的项目,但核心的弹性矩阵需求响应逻辑是通用的,先把这一步做扎实就行。