前几天一个做电力市场方向的同学来问我分时电价对用户负荷到底能有多大影响用什么工具能快速算出来。这类问题在需求响应、虚拟电厂、负荷聚合商的前期评估里非常常见而我手里刚好有一个反复用过很多次的Matlab仿真脚本——基于价格弹性矩阵的分时电价负荷响应模型。它要解决的核心就三件事给定一条基准日负荷曲线设计一个分时电价方案再算一算在电价引导下负荷曲线会怎么变峰降了多少、谷抬了多少。这篇内容会把模型原理、参数设法和Matlab实现完整拆开讲适合正在做配电网规划、售电业务或储能充放电策略的工程师也适合写课程设计和论文的电气专业学生直接套用。1. 需求响应和分时电价先搞清楚模型在算什么1.1 电网削峰填谷为什么这么重要先聊一个很多入门者容易忽略的问题削峰填谷到底解决的是谁的问题电网侧要保安全发电侧要保经济用户侧要保成本这三方的诉求其实指向同一个物理指标——负荷曲线的峰谷差。一条典型的日负荷曲线白天和傍晚会出现两个高峰后半夜跌到低谷。峰谷差越大意味着电网在高峰时段必须投入更多高成本机组甚至启动备用机组来顶上而到了低谷时段这些机组又得降出力甚至停机这种频繁调节浪费燃料也增加设备损耗。从调度的角度看同等电量如果能让负荷曲线更平稳系统运行效率会高很多。特别是光伏、风电大规模接入后净负荷曲线的波动更剧烈削峰填谷已经从优化问题变成了刚需问题。分时电价是需求侧管理里最直接的经济手段。它不靠行政命令而是通过价格信号引导用户在低谷时段多用电、高峰时段少用电把一部分可转移负荷从高峰挪到低谷。这个过程在学术上叫需求响应而在工程实现层面我们需要一个量化模型来回答电价怎么调、负荷会怎么变。1.2 分时电价如何驱动用户让电分时电价起作用的前提是用户对价格有感知、有反应。工厂可以把部分生产工序挪到夜间商业楼宇可以提前蓄冷居民用户可以把洗衣机、充电桩的使用时间延后。这些行为共同表现为高电价时段负荷下降低电价时段负荷上升。要量化这种行为经济学里有一个现成的工具叫需求价格弹性。它的含义很直观电价变化1%用电量变化百分之几。因为电价上涨用电量通常会减少所以自弹性系数一般是负数电价下跌用电量会增加效果相反。不过只盯着自弹性还不够现实中用户在高峰时段少用的电很大一部分会转移到低谷时段去用这就是不同时段之间的交叉弹性。也就是说某一个时段的负荷变化不仅取决于本时段电价变化还受其他时段电价变化的影响。具体到Matlab仿真里这套逻辑用24×24的价格弹性矩阵来表达非常自然建模思路清晰代码实现也不复杂。下面的章节就沿着这个思路一步步展开。2. 基于价格弹性矩阵的负荷模型设计2.1 弹性矩阵模型数学表达与直观理解需求响应模型的数学表达并不复杂。把一天分成24个时段对于时段i响应后的负荷可以写成[ L_i L_{0i} \left( 1 \sum_{j1}^{24} e_{ij} \frac{\Delta p_j}{p_{0j}} \right) ]其中 (L_{0i}) 是响应前时段i的基准负荷(p_{0j}) 是时段j的基准电价(\Delta p_j) 是时段j的电价变化量(e_{ij}) 是弹性系数矩阵中的元素。这个公式用文字翻译过来就是时段i的负荷变化率等于所有时段电价相对变化率的加权和权重就是弹性系数。当 (i j) 时对应自弹性表示本时段电价变化对本时段负荷的影响通常为负当 (i \neq j) 时对应交叉弹性表示其他时段电价变化对本时段负荷的影响通常为正因为低谷电价下降会把高峰负荷吸引过去。在Matlab里上面的公式就是一次矩阵运算[ L_{new} L_0 \cdot \left( 1 E \times \frac{\Delta p}{p_0} \right) ]注意这里的符号(\frac{\Delta p}{p_0}) 是逐元素除法得到24×1的列向量乘以E之后得到24×1的负荷变化率向量最后和基准负荷向量逐元素相乘。整个过程三行代码就能算完非常利落。2.2 参数设定弹性系数从哪里来弹性系数的取值是很多初学者最容易卡住的地方。文献里常见的自弹性系数范围在-0.1到-0.5之间交叉弹性系数在0.02到0.15之间。具体取多少取决于用户类型和负荷特性。一般来说工业用户的可调负荷多、生产计划弹性大自弹性绝对值偏高居民用户的刚性用电需求多弹性偏低商业用户居中。同一个用户在不同时段的弹性也不一样晚高峰时段用户对涨价的反应通常比凌晨时段更敏感。实际项目中如果有历史负荷和电价数据可以通过线性回归或最小二乘拟合来标定弹性系数如果没有数据采用文献里的典型值是完全可以的。我做仿真时常用的一组典型参数如下时段类型自弹性对峰时段交叉弹性对平时段交叉弹性对谷时段交叉弹性峰-0.3000.050.08平-0.200.0500.03谷-0.150.060.030这里对角线上的自弹性均为负非对角线上的交叉弹性均为正符合高价抑制用电、低价吸引用电的基本规律。需要提醒的是交叉弹性矩阵并不一定要求对称但在多数简化研究中为了减少标定工作量经常会假设对称阵。下面我要给的Matlab实现里就用了一个简化版本同类型时段之间的交叉弹性取小值不同类型时段之间统一取一个交叉弹性常数。2.3 案例场景与基础数据为了让仿真结果有实际参考价值我设定一个典型夏季城市日负荷场景。系统最大负荷按100MW考虑基准负荷曲线用标幺值表示峰值出现在傍晚19时左右低谷在凌晨4时。分时电价的时段划分按国内常见的三段式执行谷时段23时至次日6时平时段7时至9时、15时至17时、21时至22时峰时段10时至14时和18时至20时。基准统一电价取0.5元/kWh分时方案在基准价基础上做倍率调节。这个场景设定比较贴近实际既有明确的峰谷时段又有可以调整的价差空间非常适合用来演示模型逻辑。3. Matlab完整实现从数据到曲线3.1 构建基准负荷数据打开Matlab编辑器新建脚本第一步是把24小时的基准负荷数据写进来。我用标幺值再加基准容量换算%% 1. 基础数据准备 hours 0:23; % 24小时 P_base_pu [0.62 0.58 0.55 0.53 0.52 0.55 0.65 0.78 0.85 0.88 0.86 0.88 ... 0.90 0.92 0.89 0.87 0.88 0.90 0.95 0.97 0.88 0.80 0.75 0.70]; S_base 100; % 系统基准容量100MW L0 P_base_pu * S_base; % 转为实际负荷(MW)这段代码的关键点是负荷数据的排序要和hours一一对应0时对应第一个元素23时对应第24个元素。我日常写脚本时习惯先把曲线在脑子里过一遍清晨负荷低上午爬升午间小幅回落傍晚达到峰值再下滑到低谷。上面这个数组基本符合这个形态。3.2 可配置的分时电价模块分时电价模块我建议做成独立的一段方便后续做灵敏度分析时反复调用。核心思路是先定义时段类型编号1代表峰2代表平3代表谷然后根据倍率生成实际电价数组%% 2. 分时电价设定 price_base 0.5 * ones(1, 24); % 基准电价0.5元/kWh % 时段划分(注意Matlab索引从1开始0时对应第1个元素) type_code ones(1, 24) * 2; % 默认平时段 peak_hours [10 11 12 13 14 18 19 20]; valley_hours [23 1 2 3 4 5 6]; type_code(peak_hours 1) 1; % 峰时段 type_code(valley_hours 1) 3; % 谷时段 peak_ratio 1.5; % 峰时电价倍率 valley_ratio 0.6; % 谷时电价倍率 price_new price_base; price_new(type_code 1) price_base(type_code 1) * peak_ratio; price_new(type_code 3) price_base(type_code 3) * valley_ratio;这里我最开始踩过的一个坑就是索引错位Matlab数组索引从1开始0时实际上是第1个元素所以type_code(peak_hours1)1而不是type_code(peak_hours)1。如果你写的是Python这类从0开始索引的语言就不需要加1但Matlab里这个细节一定要留意不然峰谷时段会整体偏移一个小时仿真结果差之毫厘谬以千里。还有一个容易忽略的点平时段电价保持基准价不变所以只需要给峰时段和谷时段赋值。用逻辑索引操作数组比for循环简洁得多也是Matlab比较推荐的做法。3.3 价格弹性响应计算核心代码构建弹性矩阵是所有步骤里最核心的部分。我采用分块逻辑先初始化24×24零矩阵再按时段类型填充对角线自弹性和非对角线交叉弹性%% 3. 构建价格弹性矩阵 e_self [-0.30, -0.20, -0.15]; % 峰、平、谷自弹性 e_cross_diff 0.05; % 不同类型时段之间的交叉弹性 e_cross_same 0.02; % 相同类型时段之间的交叉弹性 E zeros(24); for i 1:24 ti type_code(i); for j 1:24 tj type_code(j); if i j E(i, j) e_self(ti); else if ti tj E(i, j) e_cross_same; else E(i, j) e_cross_diff; end end end end %% 4. 需求响应计算 d_price price_new - price_base; price_ratio d_price ./ price_base; % 各时段电价相对变化率 d_load_rate E * price_ratio(:); % 矩阵乘法得到24x1变化率 L_new L0(:) .* (1 d_load_rate); % 响应后负荷这段代码里有两个地方值得展开说明一下。第一price_ratio是逐元素相除得到的是24个时段的电价相对变化率向量。在Matlab里price_ratio本身已经是1×24行向量但为了和E的乘法维度匹配我用price_ratio(:)把它强制转成24×1列向量这是一个很实用的小技巧可以避免很多维度报错。第二L0(:)同样是把基准负荷转成列向量然后通过.逐元素乘上变化率调整量。矩阵乘法E * ratio用了普通乘法符号逐元素乘法用了点乘符号这两个符号在Matlab里语义完全不同新手容易搞混。你要是把E * price_ratio写成E .price_ratio维度直接不匹配会报错但如果两个变量恰好维度一样结果就是完全错误的数值这种错误最难排查。3.4 削峰填谷指标计算与绘图仿真的结果不能只停在负荷曲线还需要一组量化指标来评估分时电价的效果。我常用的指标主要有四个削峰率、填谷率、峰谷差改善率、负荷率变化。计算代码如下%% 5. 效果指标计算 max0 max(L0); max1 max(L_new); min0 min(L0); min1 min(L_new); mean0 mean(L0); mean1 mean(L_new); shave_rate (max0 - max1) / max0 * 100; % 削峰率 fill_rate (min1 - min0) / min0 * 100; % 填谷率 peak_valley_diff (max0 - min0) - (max1 - min1); % 峰谷差减少量(MW) load_rate0 mean0 / max0; load_rate1 mean1 / max1; fprintf(响应前最大负荷: %.2f MW\n, max0); fprintf(响应后最大负荷: %.2f MW\n, max1); fprintf(削峰率: %.2f%%\n, shave_rate); fprintf(填谷率: %.2f%%\n, fill_rate); fprintf(峰谷差减少: %.2f MW\n, peak_valley_diff); fprintf(负荷率变化: %.4f - %.4f\n, load_rate0, load_rate1);绘图部分我建议用双Y轴因为负荷和电价的量纲不同画在同一个坐标里要么负荷被压扁要么电价曲线看不清楚。代码可以这样写figure(Color, w, Position, [100 100 800 450]); yyaxis left; plot(hours, L0(:), b-o, LineWidth, 1.8, MarkerSize, 6); hold on; plot(hours, L_new(:), r-s, LineWidth, 1.8, MarkerSize, 6); ylabel(负荷 (MW)); ylim([40 110]); grid on; yyaxis right; stairs(hours, price_new, k--, LineWidth, 1.5); ylabel(电价 (元/kWh)); ylim([0 1.2]); xlabel(时刻 (h)); xlim([0 23]); legend(响应前负荷, 响应后负荷, 分时电价, Location, northwest); title(分时电价下的负荷需求响应结果);使用stairs函数画电价曲线有两个好处一是能直观体现分时电价在时段切换点的阶跃变化二是和负荷曲线的平滑连续形成对比视觉上层次分明。峰谷时段的分界点通过黑色虚线一眼就能看明白。4. 仿真结果分析不同参数下的负荷变化4.1 基准场景结果解读按上面设定的参数跑完脚本得到的曲线形态非常有代表性。以基础电价0.5元/kWh、峰时倍率1.5、谷时倍率0.6这一组为基准场景响应后最大负荷由97MW左右降到80.5MW附近削峰率大约在17%最低负荷从53MW左右抬升到接近57.5MW填谷率约8.5%。这个结果背后的逻辑值得展开讲一下。晚高峰时段电价从0.5元涨到0.75元相对涨幅50%自弹性-0.3直接贡献了15%的负荷削减同时由于谷时段电价降到0.3元低价又把一部分高峰负荷吸引到了夜间这体现在交叉弹性项上又追加了大约2%的削减。两部分叠加晚高峰负荷明显被削平了一截。而低谷时段正好相反电价下跌40%自弹性效应让负荷上涨6%再叠加高峰时段转移过来的负荷凌晨的曲线被填起来一块。从电网角度看峰谷差从原来的44MW左右缩小到大约23MW减少接近一半这就是分时电价最直接的价值。负荷率也从0.82提升到约0.87意味着设备利用率提高系统运行更加经济。当然不同负荷曲线和弹性参数下具体数字会有差异但这个量级和趋势是稳定的。如果你跑出来的削峰率只有2%或者超过50%大概率是参数或者代码有问题可以回头检查一下弹性矩阵的取值是否合理。4.2 灵敏度分析弹性系数和峰谷价差的影响单一场景的结果不够有说服力实际项目中经常需要回答峰谷价差设定多少合适这个问题。我习惯把分时电价模块和响应计算封装成一个函数然后在循环里批量测试不同参数组合。按照峰时倍率从1.2变化到1.7、谷时倍率从0.8变化到0.5我做了三组对比试验结果汇总如下方案峰时倍率谷时倍率峰谷价差(倍)削峰率(%)填谷率(%)11.20.81.57.04.021.50.62.517.08.531.70.53.423.511.0从趋势看峰谷价差拉大确实能换来更明显的削峰填谷效果但边际效益在递减。价差从1.5倍拉到2.5倍削峰率提升了10个百分点再从2.5倍拉到3.4倍只提升了6.5个百分点。这说明电价激励存在一个饱和区间一味拉大价差并不划算用户对价格的敏感度会下降而且过高的峰时电价可能引发用户不满和舆论风险过低的谷时电价又会挤压售电公司的利润空间。这里还想多说一句弹性系数本身也不是固定不变的它会随季节、用户类型、甚至宣传推广力度变化。做方案比选的时候至少要做高低两种弹性假设看看结论是否稳健而不是拿着一组参数就拍板。这个习惯在工程项目里非常关键。5. 实操踩坑与模型扩展建议5.1 Matlab实现中的几个常见坑仿真脚本结构虽然简单我在实际调试和帮别人排查问题的过程中还是遇到过不少套路化的问题集中整理一下。第一类是索引偏移问题。Matlab从1开始索引0时对应第1个元素。凡是涉及23时、1时、6时这样的谷时段划分都要记得做加一处理。用peak_hours1这种写法可以避免手工算错。第二类是矩阵乘法与逐元素乘法混淆。E * price_ratio是标准矩阵乘法得到24个时段的复合变化率L0 .* (1 rate)是逐元素乘法对应每个时段各自调整。如果把第一个乘号写成.*Matlab会直接因为维度不匹配报错还算容易发现真正危险的是两个维度恰好相同的时候比如E矩阵是24×24price_ratio也是24×24的情况出现时不会报错但结果毫无物理意义。第三类是结果不合格却不自知。模型跑完L_new如果出现负值肯定是弹性系数设得过大或者电价变化太夸张。实际中就见过有人把自弹性设成-0.8结果谷时段负荷直接变成负数这是明显不合理的结果仿真时必须做数据合理性检查。第四类是单位混乱。负荷用MW电价用元/kWh绘图时一定要用双Y轴或者归一化处理不然你会在图上看到一条完美的直线而不是两条有意义的曲线。5.2 后续可以怎么扩展这个模型这个弹性矩阵模型虽然简单却是很多更复杂需求响应分析的底盘。想往深了做有几个方向我觉得很实用。一是把线性弹性模型升级成分段响应模型。现实中用户不会因为电价涨了0.01元就调整生产安排需要价格变化超过某个阈值才会触发行为改变。可以在模型里加一个死区区间只有电价变化率超过阈值时才计入响应这样更贴近真实。二是加入分布式储能和可转移负荷的时序约束。分时电价的一个重要应用场景就是引导储能系统在低谷充电、高峰放电。在现在的模型基础上叠加储能充放电功率变量和容量约束就可以直接测算配电网配置储能的收益边界。三是用真实负荷数据标定弹性系数。如果你手头有某条馈线或某个台区一年的负荷曲线和对应时段的电价数据可以用最小二乘拟合的方式反推弹性矩阵。拟合结果再嵌入仿真模型预测精度会比用文献值好很多。还有一个我最近在尝试的方向是把分时电价和温度、天气等因素关联起来建立温变负荷的响应模型。空调负荷是夏季削峰的重要对象温度每升高1度空调负荷的响应行为和弹性系数都会变化这种动态弹性的模型很有意思能解答更贴近实际调度的明天下午34度峰谷价差调多少合适这类问题。最后提醒一句Matlab版本差异偶尔会带来小问题比如我用的stairs函数在旧版本里也可以正常运行但如果你在自己的环境里遇到函数找不到的情况先检查工具箱是否完整。这类问题在MathWorks官方文档里都有详细说明搜索函数名就能找到对应版本支持情况。仿真项目里积累一套自己的模块函数库把分时电价生成、弹性矩阵构建、指标计算都做成独立函数后面做任何优化算法或者对比方案都能直接调用省下的时间远比你想象的多。