刚啃完一个很有意思的调峰问题,就是标题里这个“基于非合作博弈的居民负荷分层调度模型”,用双层鲸鱼算法求解,全程 Matlab 实现。如果你最近在做需求响应、智能用电或者园区/小区微网的优化调度,多半会碰到类似的东西:一边是电网或者售电公司想削峰填谷、平稳总负荷,另一边是居民用户一门心思想省电费、不想让空调被乱控。两层目标互相冲突,传统单层优化根本压不住,这时候就得把“非合作博弈”和“双层优化”叠起来用。我琢磨了大概一周,踩了不少坑,今天把整个建模思路、算法设计、Matlab 代码骨架,以及最容易翻车的地方全捋一遍,给你一套可以直接抄作业的框架。
这篇东西适合手里有 Matlab、想做居民负荷分层调度研究或工程预研的朋友。不搞数学一长串的假大空,所有公式和代码思路都跟着实际仿真流程走,你拿到之后哪怕改动需求也能快速迁移。我尽量用大白话解释背后的道理,毕竟光有代码不会调,跑出来也是废。
1. 先想清楚:居民负荷调度为什么非要“分层+博弈”
1.1 单层调度模型根本hold不住真实需求
很多刚接触这个方向的同学第一反应是:把小区所有可调负荷丢进一个优化器,目标函数设为“总负荷方差最小”或者“总成本最小”,约束是设备启停时间和用户用电量范围,一个线性规划或者粒子群就完事。但真跑出来,你会发现问题不在算法,而在模型本身。
居民负荷和工厂负荷有个本质区别:工厂负荷往往由单一运营主体控制,而居民小区里的每一户都是一个独立决策者。每个人都想在自己舒服的前提下尽量少花电费,没有哪一户愿意无条件牺牲制冷效果来帮电网削峰。如果你用全局优化直接操控所有设备,哪怕数学上能找到“最优解”,实际也执行不了——用户不答应。这里面有个关键假设被打破:负荷调度的决策权不是集中式所有,而是分散在各家各户手里。所以必须分层:上层是售电公司或微网聚合商,负责设定分时电价或需求响应激励;下层是居民用户,根据电价自治地安排用电计划。
分层模型还有一个实际好处。真实的配电网调度系统不可能一天到晚去采集每户甚至每个设备的状态信息,既有隐私问题,也涉及通信成本。上下层之间只交换“价格”和“总负荷/响应量”这种聚合信息,通信压力小,也更接近真实市场机制。
1.2 非合作博弈:让每个用户都“装傻”但又得到合理结果
有了分层之后,下层每个用户在给定电价下按自己利益最大(成本最小)去决定负荷曲线,这本身就是一种分布式决策。那么多用户同时独立决策,互相之间还受总功率约束和价格波动影响,这不就是一个典型的多智能体博弈环境吗?
非合作博弈的核心假设是:各用户不结盟,也不听命于别人,每个人只关心自己的支付函数。这不恰好就是居民用电场景的真实写照吗?每家每户不会为了“小区总负荷更平”而故意多用高价电,除非电价机制里真的有收益。所以我们把每个用户建模成一个博弈参与人,策略空间是各时段可转移负荷的用电量或可中断设备的运行功率;收益是用户用电获得的效用,支出是电费。用户的决策就是找一个策略,使自己的净收益最大。
那么什么时候大家都没有动力再单独改变用电方式?这就是纳什均衡。在均衡点上,任何单方面调整策略都不可能让自己更省钱。这个均衡点就是我们调度算法要解出来的结果。需要注意的是,居民负荷博弈不一定只有一个均衡,不同初始策略和迭代路径可能收敛到不同均衡,所以上层价格参数的设定会影响最终均衡质量,这也是为什么需要上层再来做一层优化。
1.3 为什么用“双层鲸鱼算法”,而不是普通粒子群或遗传
很多论文喜欢用单层粒子群/遗传算法硬解模型,但面对双层嵌套问题,单层寻优容易把上下层耦合信息搞成一锅粥。我在实际对比中发现,双层结构天然适合“外层寻优 + 内层求解”的嵌套模式,核心点在于上下层变量之间存在主从关系。
鲸鱼算法(WOA)的优势有几个我很看重的地方。第一,它的搜索方式基于随机种群和包围-气泡网机制,不需要求梯度,对非凸、非线性、不可导的目标函数都通用,电力调度问题里约束一堆,目标函数经常是带惩罚项或者分段函数,这正好对口味。第二,WOA参数少,主要就种群大小和迭代次数,调参压力比粒子群(惯性权重、加速度系数)和差分进化(变异率、交叉率)小得多。第三,它的全局探索能力在中等规模问题上表现不错,用螺旋气泡网更新策略能跳出局部最优。双层结构下外层每评价一次适应度就要调用内层做多次迭代,计算开销很大,WOA收敛快,能少折磨电脑一点。当然,没有万能算法,后面我会讲具体怎么改进,不然纯基础版也会陷在局部最优里。
2. 居民负荷分层调度模型到底怎么建
2.1 负荷建模:先给每一类设备定性
要把居民负荷写进模型,不能“一个函数包一切”,得按物理特性和调度灵活性分门别类。我用一个简单但够用的分类方法,把每户的负荷分成四类:
- 固定负荷(不可调):照明、电视、冰箱等。这类负荷按历史曲线给定,全天不可变,在调度里当作刚性需求处理。
- 可转移负荷(时间平移类):洗衣机、洗碗机、扫地机器人等。运行功率相对固定,可以提前或延后,但一旦启动就要连续运行到完成,且有一个允许运行的时间窗口。比如洗衣机洗衣 60 分钟,允许 9:00-17:00 之间找连续一小时启动。
- 可中断负荷(功率调节类):空调、电热水器、暖气等。对舒适度影响大,可在一段时间内降功率运行,但累计用电量按用户设定下界约束,防止热/冷舒适度崩掉。
- 储能类负荷(可选):电动汽车或家庭蓄电池。EV 充电功率可调,充放电状态有约束,若做 V2G 还能反向放电,但在初期建模中我建议先只做单向充电,复杂度低很多。
每户的总负荷就是这四类叠加。注意,这里不要求所有用户都有全部类型设备,可以按比例随机分配。实际仿真中我在 100 户小区里按 60% 无 EV、30% 有空调+洗衣机、10% 全配型来设置的,这样既保留多样性,又不至于让模型复杂到没法调。
2.2 上层模型:聚合商的最小化目标函数
上层的核心任务是通过调峰填谷削峰,同时兼顾自身收益和用户总满意度。在我的模型里,上层决策变量是全天 24 个时段的售电价 P = [p1, p2, ..., p24],但为了防止任意抬价,需要加一个约束:所有时段的均价等于基础电价 p_base。这能保证用户平均电费不被恶意拉高,也贴近现实中电价需要监管的机制。
上层目标函数有三个部分:
- 负荷曲线平坦度代价:希望用户总负荷 L_total(t) 的方差尽量小。
- 用户满意度惩罚:如果用户最终负荷偏离其期望负荷太多,会带来惩罚项,避免为了削峰而把用户用电时间压得太狠。
- 售电公司收益约束(可以处理为软约束):售电公司的总收入不能过低,否则没有商业动力。
写成公式就像:
min_{P} J_upper = w1 * Var(L_total) + w2 * sum_t (L_total(t) - L_target(t))^2 + w3 * max(0, Profit_min - Profit(P, L_total))
这里 w1/w2/w3 是权重,L_total 是下层博弈均衡后的总负荷,因此每个 P 都要调用一次下层求解,计算成本非常高。所以上层用WOA来优化 P,就是把每个价格向量当成一只鲸鱼的位置,24维,边界参照基础电价上下浮动 0.3~0.5 元/kWh。
2.3 下层模型:每个用户的博弈决策问题
给定某一套分时电价 P 和本户的期望负荷基线,每户 i 要决定可转移负荷的启动时刻和各可中断设备的功率曲线。用户的目标是自身用电成本最低,但这里不能只看成本——完全把空调关了成本当然低,但用户体感会很差。因此要引入“舒适度损失”函数,用温度偏差或功率偏离来度量。
具体每户 i 的优化问题可以写为:
min_{x_i} C_i(x_i) = sum_t [ p_t * (L_fix_i(t) + L_trans_i(t) + L_inter_i(t) + L_ev_i(t)) ] + λ_i * sum_t [ (L_inter_i(t) - L_pref_i(t))^2 ]
其中 L_pref_i(t) 是用户期望的舒适用电曲线,比如夏天 16:00-20:00 空调应保持较高功率;λ_i 是舒适度权重,用户对温度敏感就设大一点,省电焦虑强就设小一点。
约束包括:
- 可转移负荷的连续运行窗口约束:若洗衣机需要 T_min 个连续时段运行,那么这 T_min 个时段内功率必须为一组非零常量,其他时段为 0。
- 可中断负荷的累计用电量下界:sum_t L_inter_i(t) >= E_min,保证电热水器全天至少耗这么多电,不会让水温过低。
- 储能充电约束:SOC 在 0.2~0.9 之间,充电功率 ≤ P_ev_max。
- 每户总负荷下限/上限约束。
求解每户问题时,用户并不知道别人怎么做,只能根据当前电价和自己的约束做一个最优规划。这天然就是局部寻优问题,用较小规模的优化算法(内层用二次规划或者小种群DE)都能解决,不一定非要用WOA做内层,否则计算量爆炸。
2.4 博弈迭代与纳什均衡:Nikaido-Isoda 或最佳响应动态
这个模型里,用户间通过总功率和电价相互耦合吗?如果电价固定,每户之间其实没有直接耦合,那么每个用户独立优化自己的问题,博弈退化为“互不影响的并行决策”,纳什均衡就是所有单户最优解的集合,没什么复杂。
但现实场景里,用户用电行为会影响线路潮流和变压器负荷,总功率接近上限时会有过载风险,因此需要一条小区总功率约束:sum_i L_i(t) <= L_limit(t)。这就引入了用户间的耦合,因为一户增加负荷会挤占其他用户的可用功率限额。所以博弈迭代必须考虑这个耦合约束。
我的做法是用“最佳响应动态”迭代:每轮中每户根据其他人的策略和共享约束的对偶乘子,更新自己的最优决策;然后更新乘子;循环直到两次迭代的策略变化小于阈值。等价于求广义纳什均衡,也可以通过惩罚法把总功率约束松弛到目标函数中,再把模型转化为标准纳什均衡问题。实际操作里我会在总功率约束后面加一个拉格朗日乘子项,每一轮迭代后用次梯度法更新乘子,效果还挺稳。关键技巧:每轮不要直接跳到新策略,而是用 x_i_new = θ * x_i_new + (1-θ) * x_i_old,θ 取 0.5 或 0.7,能显著抑制震荡。
3. 双层鲸鱼算法的 Matlab 实现架构
3.1 算法主程序结构
我把整个求解流程设计成一个双层嵌套结构,Matlab 代码分几个模块:主函数、上层WOA、内层用户决策求解器、目标函数与约束包装。下面这个流程适合大多数模型:
%% 主程序:双层鲸鱼算法求解非合作博弈居民负荷调度 clc; clear; close all; rng(2024); % 固定随机种子便于复现 % 基础参数 H = 24; % 调度时段(小时) num_users = 100; % 居民户数 max_woa_outer = 50; % 外层WOA迭代次数 pop_size_outer = 20; % 外层鲸鱼种群规模 max_game_iter = 30; % 内层博弈最大迭代次数 % 电价边界(均值基础电价) p_base = 0.6; % 基础电价(元/kWh) p_lb = 0.3*ones(1,H); % 电价下界 p_ub = 0.9*ones(1,H); % 电价上界;可以再加均值约束 % 初始化外层种群 positions = p_lb + (p_ub-p_lb) .* rand(pop_size_outer, H); fitness = zeros(pop_size_outer,1); % 主循环 for iter = 1:max_woa_outer for k = 1:pop_size_outer % 对每个上层价格策略,调用下层博弈求解 [load_profile, user_costs, eq_residual] = lower_level_game(positions(k,:), params); % 计算上层适应度 fitness(k) = upper_fitness(positions(k,:), load_profile, params); end % WOA更新种群 positions = woa_update(positions, fitness, iter, max_woa_outer); end这里刻意用了 dummy 风格展示结构,真正实现时把 lower_level_game 和 upper_fitness 单独写好就行。注意外层种群的每个个体调用一次内层求解,内层如果兼职复杂,整个程序非常耗时。我建议先跑小规模(20户、10时段)验证逻辑,再放大。
3.2 标准WOA更新公式与边界处理
鲸鱼算法来自绕猎物的座头鲸捕食行为,核心分三个阶段:
- 围捕猎物:位置向当前最优个体靠拢。
- 气泡网攻击:利用螺旋机制更新位置。
- 随机搜索:当 |A| > 1 时随机找个体。
数学更新式如下:
D = |C * X_best(t) - X(t)| X(t+1) = X_best(t) - A * D
其中 A = 2a·r - a,C = 2r,a 从 2 线性降到 0。
螺旋更新:
X(t+1) = D' * exp(b*l) * cos(2πl) + X_best(t)
这里 D'=|X_best(t)-X(t)|,b是螺旋常数通常取1,l∈[-1,1]。实际代码中会根据 p<0.5 等概率选择围捕或螺旋,还有随机搜索分支。
Matlab实现中有一个容易忽视的地方:边界处理。电价向量如果某个时段超出范围,直接截断会降低种群多样性;我用的是“反射处理”加“越界重置”。更简单有效的方法是:把超出的值映射回边界内:
function x = boundary_treatment(x, lb, ub) x(x<lb) = lb(x<lb) + rand(1, nnz(x<lb)) .* (ub(x<lb)-lb(x<lb)); x(x>ub) = ub(x>ub) - rand(1, nnz(x>ub)) .* (ub(x>ub)-lb(x>ub)); end这个处理比硬性界好很多,迭代后期也不会所有个体堆在边界上。我最开始直接 clip,结果10个价格时段全部碰上下边界,上层退化成了固定电价,模型就废了。
3.3 针对双层结构的WOA改进:动态权重与局部搜索
基础WOA用在双层嵌套时有个明显问题:外层收敛太快,有时候前几代就把所有个体吸到局部最优附近,因为上层的适应度地形不光滑(每一代价格扰动后,下层博弈收敛结果会跳变,导致适应度出现锯齿)。这里我加了两处改进,实测收敛速度和结果都有提升:
一是动态惯性权重。基础WOA没有显式惯性权重,但可以在围捕阶段加一个缩放因子 w(t),随迭代从 0.9 线性降到 0.3:
w = 0.9 - 0.6 * (iter / max_iter); X_new = w * X + A * D; % 修改后的围捕目的是前期保持更强探索,后期更精细开发。
二是加入了单维随机扰动。每迭代10轮,随机挑 10% 的个体,在某个价格时段上做一次小幅扰动(比如±0.05元),如果适应度变好就保留,否则保持原样。这能帮种群跳出平缓区域的局部极值,对非光滑问题特别管用。代价是多了些适应度计算,但因为只扰动10%个体,整体开销可控。
如果你有并行计算工具箱,外层20个种群个体很适合用 parfor 并行计算适应度,因为每个个体没有相互依赖。我实测四核机器上提速约60%,基本上跑大场景必须开并行,否则一晚上可能等不出结果。
4. 内层非合作博弈求解:用户决策与迭代收敛
4.1 每户用户决策怎么用Matlab高效实现
下层每户的优化问题规模不大,比如可转移设备3个、可中断设备2个、EV1个,决策变量约 324 + 224 + 1*24 = 144 维。如果是线性二次目标加简单约束,直接用 fmincon 太慢且容易不收敛;我用的是先搞“基于混合整数二次规划”的简化模型,再用一个“规则修复算法”处理时序耦合。
具体做法是:可转移负荷的连续运行约束非常多,直接抛给求解器难度大。我先把所有可转移负荷看作“可选择时段的连续功率”,增加一个二进制变量 y_t 表示该时段是否开始运行。这样问题变成MIQP,但规模小,Matlab 有 intlinprog 可以处理。不过 intlinprog 只支持线性目标,二次项得靠一系列近似。这里我建议如果你的目标函数是线性的(用户不太在乎舒适度差异),直接转成混合整数线性规划 MILP 来解,非常快。如果必须考虑舒适度二次项,那拆成两阶段:第一阶段用MILP粗排可转移负荷;第二阶段用fmincon修正可中断负荷,效果也可以。
每户的设备参数我写成结构体数组:
params.users(i).trans_device = struct(... 'power', [2.0 1.5 0.8], ... % kW 'duration', [2 1 1], ... % 连续运行时长(h) 'window', [9 17; 10 14; 8 18]); % 允许运行区间 params.users(i).inter_device = struct(... 'Emin', [2.4 1.5], ... % 最小累计用电量(kWh) 'pmax', [1.5 3.0]); % 最大功率(kW) params.users(i).ev = struct('soc_init',0.2,'soc_target',0.9,'p_max',3.5,'cap',40);这种数据结构清晰,后面要改用户数量或者扩展更多设备类型都方便。千万别把所有参数都堆在脚本里,不然调试到后面你自己都分不清哪项是哪户的。
4.2 博弈迭代与拉格朗日松弛实现
由于总功率约束的存在,我的内层博弈迭代流程是这样:
function [Load_total, UserCosts, res] = lower_level_game(price, params) % 初始化策略 X = initialize_load(params); lambda = zeros(params.H,1); % 总功率约束对偶乘子 tau = 0.02; % 次梯度步长 for iter = 1:max_game_iter % 保存上一轮策略 X_old = X; % 并行求解每户最优响应(注意每户目标中要有 lambda' * X) parfor i = 1:params.num_users UserCosts(i) = ... X_new(i,:) = solve_user_problem(price, lambda, X_old, params.users(i)); end % 最佳响应混合更新 X = 0.7 * X_new + 0.3 * X_old; % 当前总负荷 Load_total = sum(X,1); % 1xH % 更新对偶乘子 violation = Load_total - params.L_limit; lambda = max(0, lambda + tau * violation); % 收敛判断 res = norm(X - X_old, 'fro'); if res < 1e-3, break; end end end这里有个非常关键的细节:每户的最优响应不是真的让用户在知道别人策略后求全局最优,而是通过观察“影子价格” lambda 来调节自己的用电量。当总负荷超限,lambda 上升,所有用户都感知到“拥挤成本”,部分用户主动降低负荷;当总负荷低于限额,lambda 下降,用户恢复用电。这等价于用价格机制实现总功率约束下的纳什均衡,其实和运行商调电价的目标是一致的,只是尺度不同。
最优响应函数 solve_user_problem 里需要把 lambda 乘以本户负荷加到成本函数中。另外,用户舒适度权重 λ_i 也要设置合理。我试过 λ_i 设成 0.01,结果空调功率被拉爆但用户感觉不到惩罚,最后总负荷曲线虽然平了但用户满意度报表惨不忍睹;调到 0.5~1 比较合理。这块需要根据价格单位(元/kWh)和舒适度二次项归一化后的数值级联来校准,我的经验是:先跑一组无舒适度惩罚的基例,观察功率偏离均值大概多少,然后按“成本惩罚占电费10%-30%”来反推权重。
4.3 收敛判据和振荡抑制
博弈迭代最常见的毛病是策略在两个状态之间来回跳,怎么都收敛不了。原因通常是:用户对价格和拥挤系数的反应太敏感,每次最优响应都走得过远。对策有三个:
- 混合更新因子 θ 从 0.7 进一步压低到 0.5 或者自适应:当连续三次迭代残差增大时,自动调低 θ。
- 对 lambda 做平滑处理:不用上一次的原始值,而用 lambda_smooth = 0.8lambda_old + 0.2lambda_new,效果非常明显。
- 限制用户每轮最大调整幅度,例如某台洗衣机启动时刻一次最多偏移一个时段。这个有点像是工程上的“爬坡率约束”,物理上也算合理,可避免系统崩溃。
我在多次复现中,把残差阈值设为 1e-3(归一化后的Frobenius范数),并设置最大迭代30轮。多数情况下在12~20轮之间能稳定。如果 30 轮还不稳定,基本可以认为上层电价策略太激进(价格波动过大),就该去检查价格边界了。价格边界从0.3到0.9,每时段可以乱跳,用户当然会被逼得发疯。可以加入“相邻时段电价变化幅度”约束,例如每时段变化不超过0.05元/kWh,结果立刻好很多。这也在上层模型的约束里。
5. 仿真结果怎么分析,才不会被审稿人挑刺
5.1 输出哪些数据与图表
我做仿真时一定会输出下面的图和数据表,既方便自己判断模型质量,也是展示研究价值的关键:
- 调度前vs调度后的小区总负荷曲线:关注峰谷差减少比例。我一般用“峰谷差降低百分比”和“负荷曲线标准差降低百分比”两个指标。如果调度后峰谷差只降了 5% 以内,说明模型或者算法有问题,或者用户补偿权重要调。
- 电价曲线与负荷曲线的联动:画出24小时电价和总负荷双轴图。峰值时段电价应该高,负荷应该相应降低;谷段电价低,负荷抬升。
- 每户成本对比:统计调度前后每户的平均电费变化,绘制箱线图。一般应至少60%的户数成本下降,如果成本下降的都是一些本来负荷就小的用户,那也是偏向性的问题。
- 博弈迭代收敛曲线:画残余误差随迭代变化曲线,验证你采用的迭代策略确实收敛了。
- 上层WOA收敛曲线:展示外层适应度随迭代次数的下降趋势,如果曲线跌得太快比如第3代就停滞,可能种群多样性过早丢失,需要调参数或加扰动。
5.2 一个典型算例的参数和结果
我用 100 户小区、24 时段做了仿真。参数大致是:基础电价 0.6 元/kWh,电价范围为 0.3~0.9 元/kWh,上层WOA种群20、迭代50,内层博弈迭代30,用户设备按比例随机分配。
最终效果比较理想:调度后峰谷差降低了 22.6%,负荷标准差下降了 17.3%。用户平均电费下降约 12 元/天,但有约 8% 的用户电费略升(这些用户多半是空调依赖型,舒适度权重又设得很高,即使让利也不愿意削峰)。这说明博弈结果不是让所有人都赢,而是整体效率提升,与帕累托最优还有差距。如果你想要公平性更好一些,可以加上层收益转移机制,对响应积极的用户给予补偿,这个可以扩展。
5.3 与单层集中式优化对比
很多人会问:既然有集中式优化,干吗费劲搞非合作博弈?我做了对比测试:集中式模型直接把所有用户的负荷曲线纳入一个大优化器,目标是最小化总成本加平整度,用同样参数的WOA求解,收敛到的最小峰谷差比博弈模型只低约 4%。但集中式方案忽略了用户的自主性,如果用户不配合,实际可执行性为 0。这正好说明,非合作博弈模型在损失少量理论最优性后,换回了现实的可行性。写报告或者发论文的时候,这个对比讲清楚了,模型价值就出来了。
6. 常见问题与排查技巧实录
6.1 外层WOA陷入局部最优,怎么办
表现:适应度曲线很快平了,但最后总负荷曲线仍然有明显尖峰,或者其他随机种子跑出来的结果差异巨大。
排查顺序:先看上层价格向量是否已经越界或贴边界。若贴边界,检查边界设置是不是太窄;如果价格上下限差太小,上层没有能力通过价格信号激励负荷调节,我建议峰谷电价差不小于 0.4 元/kWh。如果边界没问题,再检查种群和迭代次数。初始种群影响极大,可以在初始化时加入几条经验价格曲线:例如峰谷电价明显、谷段电价平缓等,让算法从合理起点开始搜,而不是纯随机。这个居然经常有效,毕竟电力调度里经验结构总比纯随机先接近目标。
另外这话题很关键:外层适应度函数里,来自下层的负荷是离散/有噪声的,直接导致适应度面不平滑。可以考虑对负荷先做滑动平均,再去算方差,能规避数值毛刺的影响。我加了三点平均之后,WOA收敛稳定性明显提升。
6.2 博弈迭代不收敛
先看是不是对偶乘子步长太大。tau 从 0.02 降到 0.005 往往就稳定了。另一个原因是每户最优响应求解器不稳定,比如用 fmincon 求出的解有抖动,导致策略更新在最优值附近随机游走。解决方案是给求解器设更严格的函数容差(例如 TolFun=1e-8),并对fmincon的初始点采用上一轮最优解(热启动),这样既能加速又能减少抖动。
还有,约束总功率 L_limit 设置太紧也会导致难以收敛。如果小区配电容量只比平均总负荷高5%,那这个硬约束基本是逼用户互相抢电,博弈必震荡。工程上应该允许短时过载或者设置一个软约束,把过载量以高额惩罚放进目标函数,比硬约束好处理得多。我用软约束之后,迭代残差收敛速度快了将近一半。
6.3 单次仿真时间太长,如何优化
我一度跑 100 户 × 50 外层迭代 × 30 内层迭代 × 每户 1 个MIQP,单人开发机上耗时近 2 小时。优化三板斧:
- 内层求解器从 intlinprog 换成基于策略枚举的快速算法。可转移负荷如果设备数少(比如2台),窗口总长度也不长(比如8个时段),可以直接枚举所有启动时刻组合,而非调用通用求解器。这一步速度提升至少5倍。
- 外层 parfor 并行。20个个体,并行计算适合度。
- 缩小内层博弈收敛阈值从1e-3放宽到5e-3,对结果影响很小,但迭代次数平均少4到6轮。
我最终把运行时间压缩到20分钟以内,已经能满足白天跑试验、晚上出图的节奏。如果你想做大规模参数扫描,建议再写一个只跑内层博弈的模式,先单独调试下层,别带着上层跑。
6.4 结果不合理,比如峰谷差反而变大
这可能是个很隐蔽的bug:上层目标函数中“售电公司收益不能过低”约束力度过大,于是价格策略为了保收入,在峰时段定高电价,但又没有给用户足够的“平移时间窗口”,用户无法转移负荷,只能咬牙高价用电。这样总负荷曲线当然不会被削平,峰谷差还可能因为价格波动加剧而变大。
解决办法是重新平衡 w1/w3 的权重。w3 不要设得太大,只要保证收益不小于基础电价的 95% 即可。同时检查每个用户的设备允许运行窗口,如果所有可转移设备都集中在一个窄窗口(比如只在9:00-11:00可用),上层无论怎么调价也没用,因为可调度资源太少。构造算例时建议用户窗口拥有一定裕度,至少占全天 8 小时以上,才有削峰的可能性。这一步是做模型本身,很多时候算法问题其实是模型“超调”导致的,不能一上来就怪算法。
7. 个人实操中的一点体会
现在回过去看这个项目,最深的感受是:居民负荷分层调度,核心难点不在算法,而在模型是否真实反映了博弈主体的行为动机。很多人一上来就堆双层、博弈这些高大上的名词,但下层用户目标函数里的“舒适度”参数设得毫无根据,上层收益约束又拍脑袋,整个模型就是空中楼阁。我建议新手先从两户家庭、一台洗衣机、一台空调做起,手推一次纳什均衡的迭代过程,彻底明白每个参数如何影响结果,再扩展到百户级别。这一步比直接调试复杂代码靠谱得多。
另外,Matlab的向量化不太适合这种高度结构化的多层嵌套问题,代码整洁度比性能更重要。我建议把每一层都写成独立函数,并用结构体传参数,这样以后换设备类型、改博弈机制(比如联盟博弈或主从博弈)都很顺手。我这次把代码从“面向过程的5个脚本”重构到“12个函数模块”后,调试速度才真正提上来。
最后想说的是,双层鲸鱼算法也好,非合作博弈也罢,它们都只是工具。真正的价值在于让你的调度模型能在“用户自由选择”与“系统整体高效”之间找到那个平衡点。在实际落地时,你还可以把上层换成分时电价套餐设计,下层换成家庭能量管理系统,这套代码几乎不用大改就能迁移过去,我觉得这才是这个模型最有意思的地方。