ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

分时电价下居民需求响应负荷优化仿真研究

2026/9/17 4:12:49 拓冰建站 浏览量
分时电价下居民需求响应负荷优化仿真研究 分时电价机制下的居民用户需求侧负荷响应研究我在Matlab里前后搭过两个版本的仿真才把“电价-用户-负荷”这条链路完整跑通。简单来说这个项目做的是给定一条居民用户的典型日负荷基线在已知分时电价时段与价格的前提下通过优化模型模拟用户在“省电费”和“保舒适”之间的决策最终输出响应后的负荷曲线、电费变化、峰谷差削减水平等指标。用Matlab实现的好处是优化工具箱成熟、绘图方便、改参数重跑非常顺手。这篇东西适合正在做需求响应、电力市场、负荷聚合相关课题的同学也适合想复现论文仿真又不想从零啃优化的朋友。1. 为什么说居民负荷是需求响应里最“难啃”也最“有肉”的部分1.1 居民侧负荷响应不是简单地把峰段负荷往后挪很多初学者看到“分时电价下的需求响应”第一反应是“这不就是让用户少开空调、晚洗衣服嘛拉一条弹性曲线不就行了”但真做起来完全不是这么回事。居民侧的单户负荷极小可能只有几千瓦但它设备类型多、行为随机、用户体验敏感用一套宏观价格弹性系数去描述往往会得到非常乐观的响应潜力却没办法解释“用户为什么在这个时段响应、那个时段不响应”。从电网角度看居民负荷占比越来越高尤其是空调、电热水器、电动汽车充电桩这类负荷不仅总量可观而且具备短时调节能力。分时电价是当前最直接的价格型需求响应手段核心逻辑很简单把一天划分为峰、平、谷几个时段用高电价抑制峰段用电用低电价鼓励谷段用电。但要注意电网关心的是整体曲线形状用户关心的是电费和舒适度这两者之间需要一个可量化的折中机制。这个机制就是我们在Matlab里建模的主体。1.2 这个仿真研究的输入输出边界我建议在做代码之前先想清楚模型的边界。输入不应该是“一个神秘的用户”而应该是一套可更新的数据包包含典型日负荷基线曲线24点或96点单位kW居民用户的负荷构成比例固定负荷、可转移负荷、可削减负荷各自占比分时电价时段划分及对应价格用户舒适度权重、设备运行约束等。输出则是响应后的24h负荷曲线峰荷削减率和峰谷差变化用户总电费及节省比例各类负荷的转移/削减电量。研究目标不同模型复杂度也不同。如果只是宏观潜力评估用价格弹性矩阵就够了但要解释具体设备行为必须走设备级优化。我这个项目采用的是后者因为标题里的“需求侧对负荷响应”更偏向用户实际响应行为不是一个统计弹性系数能覆盖的。2. 把一条家庭负荷曲线拆成三类“积木”固定、可转移、可削减2.1 为什么必须分类建模居民家里的负荷五花八门但不是所有负荷都能响应电价。如果不分类直接把整体负荷乘以一个响应系数那等于默认冰箱、电灯这些刚性负荷也能随便关很明显不符合物理实际。我把居民负荷拆成三类负荷类型典型设备是否可转移是否可削减典型响应方式固定负荷照明、电视、电脑、路由器否否不参与响应可转移负荷洗衣机、洗碗机、电动汽车充电是否运行时段平移总用电量不变可削减负荷空调、电热水器否是降低功率或缩短运行时长这个分类在Matlab里对应不同的决策变量固定负荷是常数列可转移负荷是0/1整数变量优化器决定它在哪个时段运行可削减负荷是连续变量优化器决定削减比例。2.2 数学建模的具体形式设一天按小时离散为T24个时段。t时刻的基线总负荷为[ L_{\text{base}}(t) L_{\text{fix}}(t) L_{\text{trans}}(t) L_{\text{cut}}(t) ]其中 (L_{\text{fix}}(t)) 是固定负荷(L_{\text{trans}}(t)) 是可转移负荷基线值(L_{\text{cut}}(t)) 是可削减负荷基线值。可转移负荷我按设备建模。假设有 (N_{\text{trans}}) 个可转移设备设备j的功率为 (P_j)需要运行 (d_j) 小时允许运行的时间窗是 ([a_j, b_j])。引入0/1变量 (x_j(t))如果设备j在t时段运行则取1否则取0。核心约束有两个必须在允许窗口内运行(x_j(t)0, \forall t \notin [a_j, b_j])总运行时长必须达到要求(\sum_{ta_j}^{b_j} x_j(t) d_j)。如果进一步要求设备连续运行比如洗衣机一开就是一个完整程序中途不能停那还要加连续运行约束这个在第6章会说。可削减负荷用一个连续变量 (\alpha(t)) 表示t时段的削减比例范围取 ([0, \alpha_{\text{max}}])。那么响应后的可削减负荷为[ L_{\text{cut,after}}(t) L_{\text{cut}}(t) \times (1-\alpha(t)) ](\alpha_{\text{max}}) 通常设0.2~0.5表示空调最多只能调低20%~50%功率不能全关。2.3 基线负荷从哪里来很多人在这一步卡住说自己手里没有真实负荷数据。实际上仿真研究不要求数据绝对真实但要求曲线形状合理。我常用的做法是用Matlab的randn在典型曲线基础上加噪声或者从居民负荷数据集中提取一条日曲线再自己组合。关键是三类负荷要分开因为后面优化模型要让不同负荷走不同决策路径。比如固定负荷设置为早晚两个小高峰可转移负荷集中在晚间某个时段可削减负荷在午间和傍晚较高。这个“积木式”拆解在调试时特别有用因为你可以单独验证每个类别的响应是否合理再拼起来看整体效果。3. 从价格信号到用户行为分时电价机制和决策模型怎么设计3.1 先给出一套可复现的分时电价时段不同地区的峰谷时段划分差异很大我仿真时用的是一组典型的峰平谷三段式时段类型具体小时电价元/kWh谷段23:00-次日7:000.4平段12:00-17:00、22:00-23:000.8峰段8:00-11:00、18:00-21:001.2这套设定的特点是峰谷价差达到3倍能明显看出响应效果。实际项目里你可以把本地电价直接替换进参数区不影响模型结构。3.2 用户决策模型最小化“电费舒适度损失”用户看到分时电价之后不会像电网期望的那样“自动化削峰”而是会在省钱和舒适之间权衡。所以我的目标函数不是单纯最小化电费而是[ \min \quad \sum_{t1}^{T} \text{price}(t) \cdot L_{\text{after}}(t) C_{\text{comfort}} \cdot \sum_{t1}^{T} L_{\text{cut}}(t) \cdot \alpha(t) ]这里前半部分是电费后半部分是舒适度惩罚。(C_{\text{comfort}}) 是舒适度权重系数取值越大用户越不愿意削减空调取值越小用户越倾向于把空调功率调低。这样就避免出现“为了省电费把空调全关”这种不符合实际的解。对于可转移负荷它的总用电量不改变所以目标函数里只有电费项。因为低谷电价低优化器自然会尽量把洗衣机、充电任务排到谷段这是非常直观的行为模拟。3.3 为什么不用价格弹性矩阵价格弹性矩阵 (E_{ij}) 表示j时段电价变化对i时段负荷的影响优点是非常简洁可以直接用矩阵运算得到响应负荷。但它的缺陷也很明显它假设所有负荷都可以连续弹性变化无法处理“洗衣机必须一次运行2小时”这种逻辑约束同时它需要标定弹性矩阵不同地区、不同季节差异很大很容易被审稿人质疑。设备级优化模型虽然求解稍慢但每个约束都有物理含义至少知道这个结果是从哪来的调试和论文解释都方便。后面所有Matlab代码都是基于这个思路。4. Matlab代码实现从优化变量定义到仿真主循环4.1 程序结构怎么搭建议按下面四个模块组织代码而不是写一个几百行的脚本init_params.m定义电价、负荷基线、设备参数、舒适度系数build_model.m根据参数生成优化变量、目标函数、约束矩阵solve_model.m调用intlinprog求解plot_results.m重构响应后负荷曲线绘制对比图。这样改参数、换场景都很方便。下面这段代码是build_model.m的核心逻辑我把它拆开讲。4.2 关键代码设备可转移负荷的0/1变量建模首先定义基本参数T 24; % 按小时离散 price zeros(T,1); price(8:11) 1.2; % 峰 price(12:17) 0.8; % 平 price(22:23) 0.8; % 平 price(18:21) 1.2; % 峰 price(1:7) 0.4; % 谷 % 可转移设备参数功率(kW)运行时长(h)允许窗口[h1 h2] P_trans [2.0; 1.5]; d_trans [2; 1]; window [7 10; 18 21]; nTrans length(P_trans); % 固定负荷和可削减负荷基线示例曲线 L_fix [0.55 0.45 0.4 0.35 0.35 0.5 0.8 1.1 1.2 0.9 0.8 0.7, ... 0.75 0.8 0.7 0.65 0.8 1.3 1.5 1.3 1.0 0.8 0.7 0.6]; L_cut [0.3 0.25 0.2 0.15 0.15 0.2 0.4 0.5 0.6 0.5 0.4 0.5, ... 0.6 0.55 0.45 0.4 0.5 0.8 0.9 0.7 0.5 0.4 0.3 0.25];然后是决策变量索引设计。我采用的变量顺序是先排所有可转移设备在所有时段的0/1变量共nTrans * T个再排可削减负荷在T个时段的连续变量共T个。总的决策变量个数为nX nTrans * T T。nAlpha T; nX nTrans * T nAlpha; intcon 1:nTrans * T; % 可转移设备启停变量是整数 lb zeros(nX,1); ub ones(nX,1); ub(nTrans*T1:end) 0.3; % 可削减负荷最大削减比例30%4.3 目标函数和约束矩阵怎么填目标函数系数向量f对应每个决策变量在目标中的系数。可转移设备在t时段运行的边际成本就是该时段电价乘以功率所以f zeros(nX,1); for t 1:T for j 1:nTrans idx (j-1)*T t; f(idx) price(t) * P_trans(j); end idxAlpha nTrans*T t; % 可削减负荷电费项为 -price(t)*L_cut(t)*alpha舒适度惩罚为 C_comfort*L_cut(t)*alpha f(idxAlpha) L_cut(t) * (C_comfort - price(t)); end这里常数项 (price(t) \cdot L_{\text{cut}}(t)) 不影响优化结果可以不写。约束条件分两部分。第一个硬约束是每个可转移设备必须完成规定的运行时长写成等式约束Aeq zeros(nTrans, nX); beq d_trans; for j 1:nTrans for t window(j,1):window(j,2) idx (j-1)*T t; Aeq(j, idx) 1; end end第二个约束是窗口外不能运行写成不等式约束每个窗口外的变量强制为0A []; b []; for j 1:nTrans for t 1:T if t window(j,1) || t window(j,2) row zeros(1,nX); row((j-1)*T t) 1; A [A; row]; b [b; 0]; end end end4.4 调用intlinprog并重构结果在Matlab里直接调用优化工具箱[x_opt, fval] intlinprog(f, intcon, A, b, Aeq, beq, lb, ub);求解完成后从x_opt里恢复响应后的负荷L_trans_after zeros(T,1); for j 1:nTrans for t 1:T idx (j-1)*T t; L_trans_after(t) L_trans_after(t) P_trans(j) * x_opt(idx); end end alpha_opt x_opt(nTrans*T1:end); L_cut_after L_cut .* (1 - alpha_opt); L_base L_fix sum(P_trans .* ???); % 注意需要自己定义可转移负荷基线 L_after L_fix L_trans_after L_cut_after;这里有一点要提醒L_base中的可转移负荷基线必须和你设置的设备时间窗口一致否则对比出来的转移量就是错的。比如原始设定洗衣机18点启动那基线曲线在18点、19点各加2kW如果你窗口设置成允许挪到凌晨优化后它就会跑过去。4.5 扩展到蒙特卡洛场景单个用户的单条负荷曲线说服力有限。实际研究里我建议跑至少100次蒙特卡洛每次随机生成设备启动时间、天气影响下的空调基线功率再求平均响应曲线。核心循环如下rng(2024); % 固定随机种子 N_mc 100; L_after_sum zeros(T,1); for k 1:N_mc % 随机扰动设备窗口或L_cut L_cut_mc L_cut .* (0.8 0.4*rand(T,1)); % 调用上面模型求解得到L_after_k % L_after_sum L_after_sum L_after_k; end L_after_avg L_after_sum / N_mc;固定随机种子这一点非常重要否则每次运行结果都不一样论文里的图和数值就对不上。5. 结果怎么评价峰谷差、电费、舒适度一个都不能少5.1 常用评估指标跑完模型后别只看一张图要有一组量化指标。我通常用下面这些指标公式说明峰值负荷(L_{\text{peak}} \max_t L(t))反映对配电网容量的需求峰谷差(L_{\text{peak}} - L_{\text{valley}})反映负荷曲线平滑程度峰荷削减率((L_{\text{base,peak}} - L_{\text{after,peak}}) / L_{\text{base,peak}})衡量削峰效果用户电费(\sum_t price(t) \cdot L(t))需求响应给用户带来的直接收益削减比例(\sum_t L_{\text{cut}}(t)\alpha(t) / \sum_t L_{\text{cut}}(t))反映舒适度损失程度电费和舒适度是矛盾的需要结合曲线图一起分析。5.2 画一张能进入论文的结果图Matlab里最方便的是双纵轴图左边画功率曲线右边画电价。选用stairs画电价能体现时段边界。figure; yyaxis left plot(1:T, L_base, -o, LineWidth, 1.5); hold on; plot(1:T, L_after, -s, LineWidth, 1.5); ylabel(功率/kW); yyaxis right stairs(0.5:T, [price; price(end)], k--, LineWidth, 1); ylabel(电价/(元/kWh)); ylim([0 max(price)*1.2]); xlabel(时刻/h); xticks(1:24); legend({响应前,响应后,电价}, Location, northwest); grid on;这张图能清楚看出峰段负荷下降、谷段负荷上升、电价曲线与负荷曲线趋势错开。如果响应后曲线在谷段出现“驼峰”说明转移负荷过度集中需要回看第6章的处理方法。5.3 指标表自动生成建议用writetable把指标汇总输出方便对比多组参数T_result table({峰值负荷;峰谷差;峰荷削减率;用户电费;削减比例}, ... [peak_base; peak_base - valley_base; peak_reduce_rate; cost_base; 0], ... [peak_after; peak_after - valley_after; peak_reduce_rate; cost_after; cut_ratio], ... VariableNames, {指标,响应前,响应后});先算peak_after、cost_after等变量即可。6. 我踩过的一些坑连续运行约束、反弹负荷、随机种子6.1 可转移负荷断断续续运行用户会直接砸电脑第一次跑通时我发现洗衣机被优化成凌晨1点开半小时、3点再开半小时电费确实省了但现实中洗衣机程序根本没有这种启停方式。原因是我只加了“总运行时长”约束忘了要求连续运行。要处理这个问题最简单的办法是给每个设备定义若干个可选的连续运行时段块每次选择其中一个块。或者使用额外的启动变量 (y_j(t))约束 (x_j(t) - x_j(t-1) \le y_j(t))同时限制总启动次数 (\sum_t y_j(t) \le 1)并把 (x_j(t)) 在窗口内强制连续。后者在Matlab里需要多加一组整数变量模型规模会变大但逻辑清晰。如果只是做课程设计或初版仿真也可以给窗口设定得窄一些比如洗衣机窗口就定在19:00-21:00留出2小时窗口而设备恰好运行2小时不需要额外约束。但一旦窗口大于运行时长就必须处理连续运行问题。6.2 谷段电价太低会出现“新峰”和反效果分时电价的初衷是削峰填谷但低电价也可能造成所有可转移负荷挤在深夜的谷段形成一个新的负荷高峰。我在一组参数里遇到过谷段负荷直接从0.8kW涨到2.5kW的情况虽然峰荷削减了但谷段也没好到哪去。解决办法是在约束里增加谷段转移负荷上限% 新增不等式谷段(1:7)的可转移总功率不超过某阈值比如1.5kW base_idx 1:7; cap 1.5; for t base_idx row zeros(1,nX); for j 1:nTrans idx (j-1)*T t; row(idx) P_trans(j); end A [A; row]; b [b; cap]; end这样能抑制过度转移也更符合配电网实际承载能力。注意cap要根据小区变压器容量或用户进线容量设定不要拍脑袋。6.3 舒适度权重和削减上限必须一起调comfortCost和alphaMax是耦合的。alphaMax设得很大但comfortCost也很高那整体削减比例会很低反过来如果comfortCost太低空调会被直接削到上限舒适度指标难看。我的经验是先固定alphaMax0.3扫描comfortCost从0.1到1.0看削减比例曲线和电费曲线找到一个拐点。这个拐点附近通常是比较合理的折中参数。6.4 蒙卡特罗场景要固定随机种子蒙特卡洛模拟里如果不定rng每次跑出来的平均曲线和指标都不一样审稿人或者导师只要重跑一次就会发现结果对不上。代码开头固定rng(2024)并且每次随机扰动时尽量用独立的随机数流比如RandStream能极大提升可复现性。6.5 单位统一kW 和 kWh 别搞混优化模型里功率乘时间才是电量。我在代码里用的是1小时步长所以功率数值(kW)乘以1小时就是电量(kWh)。如果你改成15分钟步长目标函数里电费项就要乘以0.25否则电费会虚高4倍。这种小错误很容易藏到最后指标复核才发现。最后再分享一点个人体会。我调试这个模型花时间最多的不是求解器而是把设备约束写仔细。最初我只写了总时长约束结果洗衣机在凌晨两点断断续续启动了三四次电是省了现实中这根本不能开机。后来加了连续运行约束曲线才像话。如果你也在复现类似的需求响应仿真建议先跑通单场景再上蒙特卡洛先验证单设备行为再扩展到多设备。这样排错会快很多也不会被一个隐秘的整数约束卡住一整天。