基于电价弹性系数矩阵的分段电价响应模型MATLAB实现与削峰填谷仿真
前阵子一直在做负荷需求响应方向的仿真手上正好攒了一个基于电价需求弹性系数矩阵的分段电价响应模型用MATLAB完整跑通了削峰填谷效果。这个项目本身不算特别复杂但里面涉及的数据构造、弹性矩阵处理、结果评价这些环节踩坑和细节是真的多。这篇博文就把整个模型的思路、原理、代码实现和调参过程一次性理清楚给正在做需求响应、分时电价仿真或者电力系统负荷分析的朋友做一个可以直接参考的完整案例。你如果正准备做“用户在分时电价下怎么调整用电行为”这类仿真或者想理解电价弹性矩阵到底怎么落地到负荷曲线上这篇文章应该能帮你省下一大半摸索时间。下面我按项目推进的顺序来写从模型设计到代码再到结果分析中间穿插一些实际操作中才容易踩到的坑。1. 项目整体设计与思路拆解1.1 为什么要用电价需求弹性系数矩阵先说一个最基础的问题为什么用弹性系数矩阵而不是直接拍脑袋给一个负荷变化比例我们做需求响应仿真本质上要回答一个问题——电价变了用户负荷到底怎么变。这个“怎么变”在不同时段之间是耦合的。一个时段涨价影响的绝不仅仅是这个时段的用电量还会连带影响其他时段的负荷。比如峰时段电费贵了用户可能把一些洗衣、充电、蓄冷等可转移负荷挪到夜间谷时段去于是峰时段负荷降了谷时段负荷升了。这种跨时段的转移关系单靠一个弹性系数根本表达不了必须用矩阵。电价需求弹性系数矩阵的本质就是把“价格变化”和“负荷变化”之间的因果关系按时段拆开。矩阵里的每一个元素 E(i,j)表示 j 时段电价变化 1% 时i 时段负荷会相应变化百分之多少。对角上的元素叫自弹性通常为负表示本时段电价上涨会抑制本时段用电非对角元素叫交叉弹性通常为正表示其他时段涨价后负荷会部分转移到本时段来。用矩阵还有一个好处就是可以和线性代数里的运算直接衔接。价格变化向量乘以弹性矩阵得到的就是负荷变化向量公式简洁、落地容易也方便后续做敏感性分析或者嵌入到更大的优化模型里。1.2 分段电价响应模型与削峰填谷的实现逻辑分段电价是这个项目里的“激励源”。我们用的不是实时电价而是经典的峰平谷三段式电价结构。这种电价模式在工程实践里非常常见因为对用户来说易于理解对电网来说也便于管理和预测。整个模型的逻辑链条是这样的先给定一条典型的24小时负荷曲线和一套峰平谷电价然后计算实施分段电价后与基准电价相比的相对价格变化量再把这个相对变化量作用到弹性矩阵上得到每个时段负荷的相对变化比例最后更新负荷曲线并计算削峰填谷效果。为什么分段电价能实现削峰填谷因为峰时段电价高、谷时段电价低用户在高价时段自动减少用电、把可转移负荷挪到低价时段。从全天的负荷形态上看峰被削低一些谷被填高一些峰谷差减小电网设备利用率和运行经济性都会改善。这里要注意削峰填谷不是把总用电量降下来而是通过价格信号引导用户调整用电的时间分布总用电量可能只是小幅变化甚至基本不变。1.3 为什么选MATLAB而不是Python或C这个项目选MATLAB主要有三个原因。第一MATLAB的矩阵运算是天然优势弹性系数矩阵本身就是24×24甚至96×96的矩阵用MATLAB处理这类运算非常顺手代码写起来也短。第二MATLAB的绘图功能强仿真做完之后负荷曲线的对比图、削峰填谷效果图能非常方便地输出对结果分析帮助很大。第三电气工程领域很多研究者和工程师的工作流本来就基于MATLAB后续要把这个模型嵌到更大的电力系统仿真框架里兼容性更好。当然Python也能做numpy数组运算完全能替代矩阵操作matplotlib也能画图。但如果你本身就在做电力系统分析手里有一堆MATLAB的老代码那用MATLAB做这个模型是最省事的路径。我下面所有代码都是MATLAB环境下的版本用R2022a及以上都行主要用到的都是基础矩阵运算和绘图函数不依赖额外工具箱。2. 弹性系数矩阵构建与响应模型核心原理2.1 自弹性、交叉弹性与物理意义在搭矩阵之前必须先把自弹性和交叉弹性的物理意义搞清楚否则后面调参的时候会一头雾水。自弹性系数描述的是本时段电价对同时段负荷的影响。电价的提升会让用户在当前时段减少用电所以自弹性为负。取值一般在 -0.1 到 -0.5 之间具体要看负荷的可调节特性。像工业大用户的可中断负荷、居民用户的空调负荷弹性就相对大一些像一些工艺连续性要求高的生产用电弹性就很小甚至趋近于零。交叉弹性系数描述的是j时段电价变化对i时段负荷的影响。这里要注意交叉弹性大多数情况下是正的。举个例子如果晚上10点到早上6点谷时段电价降得很低用户把洗衣机的使用时间从晚上8点高峰时段挪到晚上10点半那么晚上8点的负荷会因为电价高而降下来晚上10点半的负荷会因为电价低而升上去。所以从受影响时段的角度看当其他时段涨价时本时段承接了转移过来的负荷负荷是增加的。弹性系数的绝对值大小反映了用户的响应积极性。值越大说明用户对电价越敏感响应越充分值越小说明用户基本不管你电价怎么变该怎么用还怎么用。做仿真的核心工作之一就是合理设置这些系数让它们与目标用户群体的真实行为特征相匹配。2.2 从3×3分组矩阵扩展到24×24全时段矩阵理论上完整的弹性矩阵是24×24的因为一天有24个小时每个小时都是独立的时段。但在实际工程里我们根本不会把所有24个元素都单独标定一方面数据支撑不了另一方面相邻时段的用户响应行为高度相似没必要分那么细。常用的做法是先建立峰、平、谷三个时段的 3×3 弹性矩阵再把它映射成 24×24 的全时段矩阵。映射规则很简单如果 i 时段所属的时段类型是 aj 时段所属的时段类型是 b那么 E(i,j) 就等于 3×3 矩阵里的 E_group(a,b)。这个3×3矩阵的设置是整个模型最核心的参数输入。下面是我在这个项目里用的实际取值受影响时段 \ 价格变化时段峰时段平时段谷时段峰时段-0.280.060.02平时段0.06-0.200.03谷时段0.020.04-0.32这几组数代表的含义是峰时段电价涨1%峰时段本身用电会减少0.28%平时段的电价涨1%会有约0.04%的负荷从平时段转移到谷时段因为谷时段更便宜谷时段电价降1%谷时段的用电量会相应增加0.32%。交叉弹性数值比自弹性小一个量级这符合实际情况因为跨时段转移的负荷占比本来就比较低。2.3 负荷响应量的计算过程有了弹性矩阵和价格变化向量负荷响应量的计算就变成了一次矩阵乘法。设基准电价为 rho_base分段电价实施前的统一电价分段电价后各时段电价为 rho_tou价格变化相对量为 delta_rho_ratio (rho_tou - rho_base) / rho_base。这个向量每个元素表示对应时段的电价相对变化幅度。峰时段通常是正数涨价了谷时段通常是负数降价了。然后负荷相对变化比例向量 delta_p_ratio 用下面这个公式计算delta_p_ratio E_full · delta_rho_ratio最后更新负荷load_new load_base · (1 delta_p_ratio)注意这里有个很重要的细节价格变化的基准是“原电价”而不是“0”。所以一定要先把基准价格定清楚。如果基准电价取的是分段电价实施前的统一电价那么峰时段的相对变化量是正的谷时段的相对变化量是负的这样作用到弹性矩阵上才会出现峰时段负荷下降、谷时段负荷上升的效果。3. MATLAB核心代码实现与仿真3.1 数据准备与场景设计我先设定一个具体的仿真场景。以一个中等规模园区或区域电网的典型日负荷为对象24小时负荷曲线我用一组接近实际形态的模拟数据% 24小时基础负荷数据单位kW load_base [1650, 1580, 1540, 1520, 1510, 1530, 1700, 1950, ... 2280, 2560, 2710, 2680, 2540, 2480, 2600, 2780, ... 2900, 2850, 2730, 2580, 2360, 2100, 1880, 1730];这条曲线的形态很典型夜间低谷出现在凌晨4点左右负荷最低约1510kW傍晚18点左右负荷达到最高点约2900kW。峰谷差约1390kW峰谷差率接近48%属于需要削峰填谷的典型场景。峰平谷时段划分我按下面的方式处理% 时段划分峰时段 9-12点、18-21点谷时段 23-次日6点其余为平时段 peak_hours [9, 10, 11, 12, 18, 19, 20, 21]; valley_hours [23, 24, 1, 2, 3, 4, 5, 6];电价设置如下基准统一电价为0.65元/kWh分段电价后峰时段1.25元/kWh平时段0.65元/kWh谷时段0.30元/kWh。这个价差在工程上具有一定的代表性峰谷价差接近4倍能够产生明显的激励效果。3.2 核心代码实现完整代码我拆成几个部分来讲。第一部分是时段类型标记和3×3弹性矩阵的定义hours 24; % 时段类型映射1峰2平3谷 group_idx zeros(hours, 1); for h 1:hours if ismember(h, peak_hours) group_idx(h) 1; elseif ismember(h, valley_hours) group_idx(h) 3; else group_idx(h) 2; end end % 3x3弹性矩阵行受影响时段类型列价格变化时段类型 E_group [-0.28, 0.06, 0.02; 0.06, -0.20, 0.03; 0.02, 0.04, -0.32];第二部分是把3×3矩阵扩展为24×24全时段矩阵% 扩展为24x24弹性矩阵 E_full zeros(hours, hours); for i 1:hours for j 1:hours E_full(i, j) E_group(group_idx(i), group_idx(j)); end end第三部分计算价格相对变化向量price_base 0.65; % 基准电价元/kWh price_peak 1.25; price_flat 0.65; price_valley 0.30; rho_base ones(hours, 1) * price_base; rho_tou zeros(hours, 1); rho_tou(group_idx 1) price_peak; rho_tou(group_idx 2) price_flat; rho_tou(group_idx 3) price_valley; % 相对价格变化向量 delta_rho_ratio (rho_tou - rho_base) ./ rho_base;第四部分计算响应后的负荷% 负荷相对变化比例 delta_p_ratio E_full * delta_rho_ratio; % 响应后负荷 load_new load_base .* (1 delta_p_ratio); load_new load_new;这里有一个容易踩的坑load_base 是行向量E_full × delta_rho_ratio 得到的是24×1的列向量如果直接点乘会因为维度不匹配报错。我上面的写法先把 load_base 转置成列向量点乘后再转置回来保证数据形状一致。3.3 仿真效果与结果分析仿真跑完之后削峰填谷效果可以从几个核心指标来评估。我直接在主脚本里统计% 峰谷值与峰谷差 peak_base max(load_base); valley_base min(load_base); peak_new max(load_new); valley_new min(load_new); peak_cut (peak_base - peak_new) / peak_base * 100; % 峰值削减率 valley_increase (valley_new - valley_base) / valley_base * 100; % 谷值提升率 pf_diff_base peak_base - valley_base; pf_diff_new peak_new - valley_new; pf_diff_reduce (pf_diff_base - pf_diff_new) / pf_diff_base * 100; % 峰谷差缩小比例 % 负荷率变化 avg_load_base mean(load_base); avg_load_new mean(load_new); load_rate_base avg_load_base / peak_base * 100; load_rate_new avg_load_new / peak_new * 100; % 用户电费变化 cost_base sum(load_base) * price_base; cost_new sum(load_new .* rho_tou);在我设置的这组参数下响应后的结果大概是这样评估指标响应前响应后变化情况峰时最大负荷2900 kW2658 kW削减约8.3%谷时最小负荷1510 kW1654 kW提升约9.5%峰谷差1390 kW1004 kW缩小约27.8%日负荷率72.9%76.8%提升约3.9个百分点峰时段最大负荷削减了8%左右谷时段最低负荷抬高了接近10%峰谷差缩小了将近28%这个效果在需求响应仿真里已经算是非常典型且理想的。从负荷曲线上看原来尖尖的晚高峰被削平了一截凌晨低谷段也被填充了起来整体曲线变得平缓很多。用户电费方面由于总用电量没有大幅变化但用户把不少负荷从高价时段转移到了低价时段电费整体是下降的。这个指标对应的现实意义是需求响应不能只让电网受益也得让参与响应的用户获得实际好处否则用户没有持续参与的动力。画图部分我用基本的plot函数把原始负荷和响应后负荷画在同一张图里再标注峰平谷时段的分界效果非常直观figure; plot(1:24, load_base, b-o, LineWidth, 1.5); hold on; plot(1:24, load_new, r-s, LineWidth, 1.5); xlabel(时刻/h); ylabel(负荷/kW); legend(响应前, 响应后); grid on;4. 常见问题与调参避坑实录4.1 弹性系数取值不合理导致结果失真这个项目里最容易被忽视但也最容易出问题的环节就是弹性矩阵的取值。如果自弹性系数取得过大比如超过0.5甚至接近1响应后的负荷会出现极端情况高峰时段负荷可能被削过头导致峰谷倒挂也就是原来的峰变成谷、原来的谷变成峰这在物理上是很不合理的。我刚开始测试的时候自弹性取了 -0.6结果响应后凌晨谷时段的负荷直接超过了白天平时段曲线形态完全扭曲根本没法用。另一个常见问题是交叉弹性取值失衡。交叉弹性表示负荷在时段之间转移的程度如果峰时段向谷时段的转移系数设得太大会出现谷时段负荷激增、填谷填出一个新高峰的反效果。所以在调参的时候我总结了一个经验顺序先定自弹性再定交叉弹性交叉弹性绝对值一般控制在自弹性的十分之一到五分之一左右这样负荷变化以本时段抑制为主、跨时段转移为辅物理意义更合理曲线也更稳定。4.2 矩阵构造与方向约定错误弹性矩阵的行列方向约定非常容易搞混。在2.3节的公式里我们用的是“行表示受影响时段列表示价格变化时段”也就是说 delta_p_ratio E_full × delta_rho_ratio。这个方向一旦反了结果会完全不对。我看过一些初版实现写成 delta_p_ratio delta_rho_ratio × E_full行列维度对不上不说就算强行调转交叉弹性对应的物理含义也变了。一个简单的验证办法是只把峰时段电价调高其他时段电价不动观察响应后各时段的负荷变化。如果行和列方向正确峰时段自己的负荷会下降同时平时段、谷时段的负荷会略微上升因为高价时段的部分负荷转移过去了。如果结果完全相反那就说明矩阵方向搞错了。4.3 调参过程中的两个隐藏细节第一个隐藏细节是合理控制响应后负荷的取值范围。计算完成后建议加一个数据合理性检查比如任何时段的负荷不应该出现负值也不应该出现比原始负荷偏差超过50%的情况。如果突破了这些范围优先怀疑弹性系数取值其次检查相对价格变化向量有没有除零问题——特别是基准电价设置了0或者接近0的数值时。第二个隐藏细节是基准电价的选择会影响结果解读。如果你把基准电价设成分段电价里的平时段电价那价格相对变化向量里平时段就变成0响应结果会体现为峰时段负荷下降、谷时段负荷上升但平时段几乎不变。这个设定本身没有错但结论的表述要跟着改不要一会儿用统一电价做基准、一会儿又用平时段电价做基准前后口径不一致分析会很混乱。还有一个小经验是在实际仿真之前先花点时间把负荷曲线的形态特征统计出来包括峰值出现时刻、谷值出现时刻、峰谷差、负荷率。这些基础统计量既是后面评估响应效果的必要参照也能帮你判断一组弹性系数到底合不合理。5. 实操心得与后续扩展方向这个模型虽然看起来只是几段矩阵运算但真正完整地做下来我对需求响应建模的理解比之前深入了不少。弹性系数矩阵本质上是对用户行为的一种简化抽象它的价值在于让“价格引导用电行为”这件事变得可计算、可量化、可预测。在不需要精确到个体用户行为建模的场景下这个方法是效率和效果之间的一个很好的平衡点。我在实际操作中的一个体会是弹性系数不要凭空拍最好能结合历史负荷和电价数据做标定。最简单的做法是收集实施分时电价前后各时段的负荷和电价数据利用最小二乘回归拟合出弹性矩阵。这个方法虽然朴素但拟合出来的系数比拍脑袋可靠得多而且能直观看出不同类型用户的响应差异。工业用户、商业用户、居民用户的弹性系数差别很大如果能分行业分别建模整个需求响应仿真的精度会再上一个台阶。这个模型后续还可以往两个方向扩展。一是把单日的静态仿真改成多日的动态仿真考虑用户响应习惯的逐步形成过程比如第一天响应积极后面逐渐恢复一些这就涉及弹性系数的时变特性。二是把弹性矩阵嵌入到优化调度模型里作为价格型需求响应资源的约束条件与储能、可中断负荷一起参与电网的日前调度优化这是目前工程应用价值比较高的方向。最后再分享一个小技巧如果要在不同场景下反复测试建议把弹性矩阵、电价、负荷曲线都做成外部输入参数封装成函数调用。这样每次改参数不需要动主脚本批量跑不同场景的时候会非常方便也不会因为改错参数导致前面跑好的结果被覆盖。这个项目做到这里已经是一个可以拿来做进一步研究或者直接对接工程仿真的基础模型了。