基于非合作博弈的居民负荷分层调度与双层鲸鱼算法实现

基于非合作博弈的居民负荷分层调度与双层鲸鱼算法实现 1. 居民负荷分层调度的需求拆解从“削峰填谷”到“峰谷倒置”做电力系统优化调度这几年我有一个很深的体会居民侧负荷调度的难度其实一点不比发电侧低。发电源侧的机组组合、经济调度模型再复杂至少可控对象清清楚楚——机组就是那些机组约束就是爬坡、最小启停时间这一套。居民侧就不一样了负荷分散、数量庞大、单个用户容量小、行为随机性极强再加上现在电动汽车、空调、热水器这些大功率设备越来越多如果不分层、不博弈直接丢给集中优化器去算通常算出来的调度指令根本落不了地。最常见的一个反直觉场景就是分时电价下的“峰谷倒置”。电网为了削峰填谷把谷时段电价压得很低本意是引导用户在低谷用电。如果所有用户都响应确实在理想情况下谷时段负荷会被抬高。但现实是如果所有用户同时涌向同一个谷时段谷时段反而变成了新的高峰原来的峰时段被削平之后可能又变成新的谷。这个现象不是个别案例我在仿真里反复见过而且越是响应程度高的用户群体越容易出现。根本原因在于每个用户在决策自己的用电计划时只考虑自己的电费最小化并不关心其他用户的决策会怎样影响自己。用户之间天然是一种非合作博弈关系。所以要解决居民负荷调度问题必须回答三个问题第一负荷是按什么层次组织起来的第二用户与电网、用户与用户之间的利益冲突怎么描述第三用什么算法能在可接受的时间里求出这个多层博弈的均衡解这篇文章就围绕这三个问题展开。我用Matlab实现了一套完整的“基于非合作博弈的居民负荷分层调度模型”求解方法用的是双层鲸鱼算法把Stackelberg主从博弈和用户之间的Nash博弈放进同一个优化框架里求解。整个代码框架、数学模型、参数整定过程以及我在实际调试中踩过的一系列坑都会在这篇里讲清楚。先说结论这套模型跑下来分时电价下的峰谷差率能比传统的单层优化再降大概8%到15%具体数值取决于用户数量、负荷构成和电价区间设置。更重要的是做完之后你会得到一个能解释“为什么用户不听话”的调度框架而不是一个只能出结果的黑盒子。1.1 为什么居民负荷值得单独建模很多早期的负荷调度工作喜欢把居民负荷打包成一个大的可调负荷池用一条聚合的负荷曲线去参与优化。这种方法在宏观分析里可以接受但当你要制定真正下发到用户侧的执行策略时有问题不同的用户负荷构成完全不同舒适度偏好完全不同响应意愿也完全不同。有一户人家有电动汽车有一户没有有一户白天没人有一户老人全天在家。如果把他们强行打包成一个“虚拟负荷”要么约束太紧导致无解要么约束太松导致计划根本不可行。居民负荷分层建模的思路是把问题拆成“设备层—用户层—系统层”三个层次。设备层是单个负荷设备的物理模型和运行约束用户层是每个用户根据外部电价或激励信号优化本户内所有设备的用电计划目标是本户用电成本最小同时兼顾舒适度系统层是电网或负荷聚合商目标是让全体用户的总负荷曲线尽量平缓、峰谷差最小化同时还要付费给参与响应的用户。这三个层次之间不是简单的自上而下下发指令的关系而是双向互动的。系统层定电价用户层据此做响应系统层看到响应结果后可能调整电价用户再根据新电价调整计划……这个反复博弈的过程用Stackelberg主从博弈来描述最合适电网是领导者用户是跟随者。而同一层的多个用户之间由于共用同一个电价结构和输配电网又存在相互竞争的Nash博弈关系。1.2 分层调度模型里的“双层”到底指哪两层很多人看到“双层”两个字就不太清楚到底哪两层这个说法在学术论文里有的时候指“上层电网—下层用户”的主从博弈结构有的时候指“先调度——再分配”的两阶段流程。在这套实现里我用的“双层”指的是优化问题的嵌套结构外层是电网的定价策略优化内层是用户群的用电计划优化。具体来说外层优化的决策变量是分时电价向量或者激励系数向量目标函数是系统侧的削峰填谷效果内层优化的决策变量是每个用户内部各设备在各时段的用电功率目标函数是每个用户的用电成本加不舒适度惩罚最小。内层求解的结果——也就是用户的实际响应负荷——会返回到外层影响外层目标函数的评估。外层每改一次电价内层就要重新求解一遍所有用户的优化问题。这个嵌套关系如果直接硬解计算量会非常大所以我们需要用启发式算法来做双层迭代求解。这里多说一句博弈模型和普通优化模型的区别。普通优化模型只有一个目标函数所有约束都在同一个问题里求出来就是全局最优或者局部最优。博弈模型里有多个决策主体每个主体有自己的目标函数并且每个主体的可行域和目标都会受到其他主体决策的影响。我们的居民负荷调度问题天生就是一个多主体问题所以用博弈论框架来建是顺理成章的不是故意把问题搞复杂。1.3 关键词“非合作博弈”在这个场景里的具体含义非合作博弈英文叫Non-Cooperative Game大家都听过但放到负荷调度里到底意味着什么我的理解是这样每个居民用户都是一个独立的理性决策者他只关心自己的用电成本和舒适度他不会主动为电网的峰谷差买单也不会主动去配合其他用户。这听起来有点“自私”但实际情况就是如此用户之间没有合作协议也没有一个中央指令让他们听指挥。非合作博弈的用处在于我们不需要假设用户是“听话”的只需要假设用户是“理性”的。只要电价结构合理用户为了自己的利益最大化就会调整用电行为而这种调整恰好能实现全局的削峰填谷目标。这就是机制设计的思想——通过价格信号让每个个体的自利行为转化为对系统整体有利的结果。当然这里还要注意一个前提条件每个用户的决策会影响到其他用户的决策结果吗影响是必然的。最典型的就是前面说的谷时段拥堵效应。如果电价里只有“峰谷平”三个时段那么所有用户都会尽可能把可转移负荷挪到谷时段结果是谷时段被填出一个新的峰。要避免这种情况要么把时段划分得更细、调整电价曲线本身要么在用户目标函数里加入“同时用电程度”的惩罚项。后者相当于把用户之间的相互影响内生化在数学上会让Nash均衡的计算变得更复杂但得到的结果会更符合实际。2. 居民负荷分层调度模型里各层目标函数的数学表达模型具体怎么建我把负荷设备分了类然后给出设备模型、用户目标函数、系统目标函数最后还要交代清楚约束条件是怎么处理的。2.1 设备层的负荷分类与数学模型居民负荷各不相同但做优化调度的时候没必要对每台设备都精细建模那样状态变量会爆炸。我按“可调控性”把负荷分成了三类刚性负荷、可转移负荷、可削减负荷。刚性负荷就是照明、电视、冰箱这类要么必须实时供电、要么调度意义不大的设备。这类设备我直接作为固定功率序列放到功率平衡约束里不做优化。可转移负荷包括洗衣机、洗碗机这类设备。特点是总耗电量固定、工作时长固定但工作时间窗口可以平移。比如洗衣机洗一锅衣服需要1小时、耗电1千瓦时只要在用户设定的允许时间窗口内完成就行具体从几点开始可以由优化算法决定。这类负荷的建模关键是引入一个“工作状态开关变量”它的值只能是0或1表示设备在该时段是否运行。还得保证状态的连续性一旦启动必须连续运行完整个工作周期不能在中间断开。这个约束在代码里用滑动窗口求和来实现具体写法后面讲。可削减负荷最典型的就是空调、电热水器这类温控负荷。它们的特点是功率可以连续调节或者分档调节而温度在一定范围内波动是用户可以接受的。比如空调设到26度允许室内温度在24到28度之间波动那么空调功率就可以在很大范围内调节。这类设备我等效成一个热容模型来表达室内温度与空调功率、室外温度、墙体热阻之间的关系。公式化表达就是室内温度下一时段值 当前时刻室内温度 影响系数×室外温度 - 室内温度 增益系数×空调功率这个式子看着简单但实际仿真时必须取足够小的时间步长否则温度会失真。我一般把调度步长设为1小时但温度计算按15分钟步长做热力学积分一小时结束时的温度作为下一个小周期的初始值。这样精度和速度可以兼顾。2.2 用户层模型成本最小化与舒适度惩罚的平衡用户的目标函数是每个用户在一个调度周期内的总用电成本最小我这里取了24小时为周期。总用电成本由三部分组成从电网买电的费用、对削峰负荷的补偿收入如果有需求响应激励的话、以及调整用电计划带来的不舒适度惩罚。电费部分好理解就是各时段购电功率乘以对应电价然后累加。不舒适度惩罚是个需要仔细设计的项。比如空调功率被压低室内温度偏离了用户设定的最舒适温度偏离越大用户越难受洗衣机被从晚上8点挪到凌晨3点虽然电费省了但用户实际上很不方便。这个不舒适度我用偏离量的平方来度量再乘一个权重系数。权重系数的选取直接影响优化结果权重取得太大可调负荷基本不动削峰效果出不来权重取得太小用户会接受非常离谱的调度方案完全不考虑实际感受。我在仿真里用一个经验做法先在其他条件不变的情况下分别跑一两次观察可转移负荷的位移程度和温控负荷的温度偏离范围然后反推一套合理的权重让温度偏离控制在2摄氏度以内、可转移负荷的位移时长不超过4小时。还要注意一个细节不同用户的不舒适度权重应当不同。有的用户对温度敏感有的无所谓有的用户作息规律洗衣机只能在固定时段洗有的用户时间灵活。如果模型里所有用户参数完全一样那这个模型就退化成一个大用户谈不上博弈了。我在代码里给每个用户分配了随机的偏好参数和不同的负荷基线确保用户之间有差异性。2.3 系统层目标峰谷差、新能源消纳与购电成本的多目标处理系统层的目标函数在论文里五花八门有的是最小化配电网的峰值负荷有的是最小化网损有的是最大化新能源消纳比例。实际操作中这几个目标不完全是冲突的但也不完全是同一个方向。峰值负荷压低之后网损通常会下降这两个是一致的但追求峰值最低可能导致负荷过度集中在某些时段反而和新能源消纳冲突。比如风电在夜间大发如果电价谷时段也设置在夜间用户确实会在夜间多用电这有利于消纳但夜间负荷抬高后可能超过原本的峰值峰谷差率反而变差。所以在目标函数里我没有只盯单一指标而是用了加权多目标的形式第一项是峰谷差相对值第二项是负荷曲线的方差第三项是用户总购电成本。前两项刻画波形质量第三项刻画用户总支出。三个目标加权求和。权重怎么定我倾向把峰谷差率当成主目标因为它是电网调度里最直观的指标负荷曲线方差作为辅助平滑指标用户总支出权重放小一点只作为约束性监督防止系统层为了削峰而把电价抬得过高。这里有个均衡点如果过分压低用户总成本系统层就没有足够的激励信号去引导用户削峰效果也会变差。系统层的决策变量是电价结构。我用的不是简单峰谷平三段式而是24个时段的连续电价向量。这样做有两个好处一是模型更一般化只要把相邻时段电价压成相等就能退化回三段式二是连续电价能给鲸鱼算法提供更大的搜索空间更容易逼近均衡解。但24维连续变量也意味着搜索空间很大对算法的收敛性要求更高这就是为什么需要双层结构——内层把用户响应算准外层才能对目标函数做出正确评估。2.4 功率平衡与电网安全约束设备层、用户层、系统层各自有约束但最终的物理底盘是功率平衡。在任何一个时段配电网注入的功率也就是所有用户的购电总功率必须等于所有负荷设备的耗电功率之和再加上网损的近似项。用户之间的相互影响正是通过这个功率平衡通道传递的某个用户把自己的洗衣机挪到凌晨相当于他在那个时段多买了电总购电功率变大电网潮流和损耗会随之变化电网对全体用户的供电压力也会变化。在博弈模型里这个通道就是用户之间策略相互依赖的物理载体。除了功率平衡还有几个偏向工程实际的约束。比如变压器的容量上限不管用户怎么调度任意时段的系统总负荷都不能超过配变额定容量。这个约束在纯数学模型里容易忽略但在Matlab仿真里如果遇到容量超过的情况结果往往不是不收敛而是目标函数出现不连续的跳变非常容易把鲸鱼算法的种群引到错误的区域。再比如电价约束——电价不能为负不能超过用户可接受的上限否则反馈到内层用户优化里会导致负荷不响应甚至反向响应。我在外层优化里给电价加了一个连续且光滑的可行域映射保证鲸鱼算法生成的每个电价向量都天然满足这些边界条件这样就不需要额外写惩罚函数了。3. 双层鲸鱼算法为什么适合这个多主体问题讲完了数学模型接下来是求解。很多人拿到这个双层博弈结构会习惯性地想能不能用KKT条件把下层问题化简成约束然后一层求解理论上可以但实际做起来会发现下层用户问题里含有整数变量可转移负荷的启停变量、非线性温度约束、还有Nash博弈的均衡条件KKT条件推导会非常复杂甚至可能出现没有满足约束条件的伪均衡。我自己的体会是对这种非凸、非线性、嵌套结构的问题启发式算法虽然不能保证全局最优但胜在灵活、实现快、不容易推导出错。粒子群、遗传算法、差分进化都能做但双层鲸鱼算法在这里有它特别贴合的地方。为什么选鲸鱼算法这是我被问得最多的问题。客观讲鲸鱼算法Whale Optimization AlgorithmWOA和马群算法属于同一代启发式算法核心思想不算特别新奇。但它有三个特点特别适合我们这个模型嵌套结构第一WOA不依赖梯度信息这对目标函数里含有整数变量和非线性温度项的混合问题是天然优势第二WOA的三种位置更新机制包围捕食、气泡网攻击、随机搜索在参数上的自适应调节能力很好用在前期能保持很强的全局探索能力后期又能收敛到精细区域这对我们从随机初始电价出发找均衡很重要第三WOA实现起来非常简洁主循环就一个for加若干if判断在Matlab里基本不会写错调试成本低。3.1 鲸鱼算法的三种位置更新机制WOA模拟的是座头鲸的捕食行为。座头鲸捕食的时候会先绕着猎物转圈收缩包围圈然后螺旋上升吐出气泡网把磷虾群往中心逼。这两种行为之外它还会随机游走探索其他区域。数学化之后算法有三个核心更新公式。第一种是收缩包围机制如果随机数p小于0.5且|A|小于1鲸鱼向当前最优个体靠拢位置更新公式是X(t1) X*(t) - A·D其中D |C·X*(t) - X(t)|。这里的A和C是系数向量A由控制参数a和随机向量r1计算C是另一个随机向量。当|A|小于1时算法认为当前最优位置附近有希望鲸鱼向它靠拢当|A|大于等于1时算法不怎么信任当前最优位置让鲸鱼随机找其他位置更新。第二种是螺旋气泡网攻击如果随机数p大于等于0.5鲸鱼按照对数螺旋线向着猎物运动更新公式是X(t1) D·exp(b·l)·cos(2πl) X*(t)其中D |X*(t) - X(t)|b是控制螺旋形状的常数l是[-1,1]的随机数。这个螺旋更新给了算法更强的局部搜索能力让鲸鱼不是直线飞去最优位置而是绕螺旋接近可以有效搜索最优位置附近的邻域。第三种是随机搜索如果p小于0.5但|A|大于等于1算法认为当前最优位置不值得信赖就在整个搜索空间里随机选一个位置作为参考方向更新公式X(t1) X_rand - A·DD |C·X_rand - X(t)|。这一项是保证全局搜索能力的关键避免算法早熟收敛到局部最优。这三套机制我理解成一种“先广后精”的思路随机搜索负责大范围探索收缩包围负责定向逼近螺旋攻击负责高精度局部细化。在双层嵌套求解里外层电价向量的搜索空间是24维的维度不算低如果缺了随机搜索这一项很容易只找到局部最优电价。内层用户问题里由于有整数启停变量目标函数有很多不连续点螺旋更新恰恰能在这些不连续点附近做细致的搜索这是单纯用粒子群容易漏掉的。3.2 双层结构怎么嵌入WOA双层的含义前面说了是嵌套问题。我的设计是外层用一个WOA主种群搜索电价向量内层对每个电价向量再启动一个WOA求解各用户的负荷响应。这个结构听起来计算量很大但实际可以做一些技巧性简化。外层种群我设30条鲸鱼每条鲸鱼代表一套24维电价向量。每次评估一条鲸鱼的适应度时需要把当前电价向量下发到每个用户然后让每个用户分别做自己的优化。如果用户有50户单次适应度评估就要解50个内层优化问题。内层每个问题又是一个24维变长的优化问题。直接算当然可以50户×30条鲸鱼×100次迭代15万次内层优化这在实际中无法在Matlab里跑完。但我做了一些加速措施后面会讲。总之现在要建立的一个概念是双层嵌套问题不是“一次性优化”而是“优化里面套优化”计算量是乘数级别的不是加法级别。在外层WOA里适应度函数是整个系统层目标函数峰谷差、曲线方差和用户总购电成本的加权和。在内层WOA里适应度函数是当前用户的用电成本加舒适度惩罚。内层的优化结果会输出一组各个时段的用电功率计划把50个用户的计划叠加起来就是当前电价下的总负荷曲线。这条曲线输入外层适应度函数完成一次完整评估。之所以用同一个算法嵌套而不是外层用鲸鱼、内层用别的图的主要是代码复用和参数调起来方便。当然也有缺点后面专题讲。3.3 收敛判断与均衡解的验证思路两个WOA嵌套跑完之后怎么判断我们真的找到了Stackelberg均衡一个实用判断标准是让外层再单独尝试改变任意一个时段的电价用户重新响应后的总目标函数是否变好。如果外层的任意偏移都不能让系统侧目标继续改善我们就认为这个电价策略是一个局部最优的领导者策略。用户侧的Nash均衡判断类似在给定电价下任意一个用户单方面改变用电计划都不能降低自己的成本我们就认为这群用户达到了Nash均衡。不过说实话在启发式算法框架下验证“真正”的均衡点是一件很难做到完美的事。实际工程上的做法是记录外层WOA收敛后的最优电价向量然后用一个比较细的网格在这个最优值附近做局部扰动观察目标函数是否单调上升。如果单调上升说明至少在这个邻域里是稳定的这个解可以作为参考下发。同时我还会和传统的固定分时电价方案做对比看峰谷差率下降了没有——这是工程上最有说服力的验证。我在Matlab实现里收敛判据用的是双条件一是迭代代数达到上限二是最近20代最优适应度变化不超过设定阈值。外层迭代上限设在120代内层设在80代。这个设定是经过多次试验折中的结果——再往上加代数峰谷差率下降不超过1%但计算时间能翻倍。4. Matlab代码实现的分模块拆解代码怎么组织我习惯把Monte Carlo结构、模型参数、WOA算法、结果可视化分成四块。每个模块做成单独的function主程序只做流程控制。4.1 数据与场景设置从用户生成到基线计算第一步是生成用户。我在代码里用一个generate_users函数生成指定数量的用户每个用户包含随机的基础负荷曲线、可转移负荷设备参数、温控负荷参数、以及偏好权重。基础负荷曲线我用一个带随机噪声的典型居民用电曲线作为模板再乘上每户的随机缩放系数。可转移负荷设备参数包括功率、运行时长、允许调度时间窗口。温控负荷参数包括热容、热阻、能效比、初始室温、温度设定点和允许波动范围。生成完用户之后还要计算一个基线——就是用户在固定电价下、不参与任何需求响应时的总负荷曲线。这个基线曲线非常有用一是后面算削峰率要用没有基线就没有比较二是可以给内层WOA提供初始化参考让求解不至于从完全随机的状态开始。我在这步还做了一个很值得推荐的细节把基线曲线的峰值所在时段、谷值所在时段标记出来作为外层电价初始化的依据让电价向量的初始种群有一部分偏向“峰时扣钱、谷时补钱”的合理结构。这样WOA起跑就在一个比较合理的区域收敛速度快很多。4.2 WOA核心函数参数、位置更新与边界处理WOA的核心函数我写成一个通用的woa_optimize输入为目标函数句柄、搜索维度、上下界、种群大小和最大迭代次数。输出为最优位置和最优适应度。这个函数不关心你优化的是什么问题内层外层都复用它。这里把关键代码逻辑讲一下初始阶段函数随机生成初始种群计算每个个体的适应度找出历史最优位置Leader_pos和最优适应度Leader_score。主循环里对每个个体、每个维度首先生成随机数a它由2线性递减到0公式是a 2 - 迭代次数×(2 / 最大迭代次数)。然后计算A 2·a·r1 - a、C 2·r2再生成决策概率p。如果p 0.5且|A| 1走收缩包围D abs(C×Leader_pos - X)X_new Leader_pos - A×D。如果p 0.5且|A| 1走随机搜索随机选一个个体作为参照X_new X_rand - A×abs(C×X_rand - X)。如果p 0.5走螺旋气泡网D abs(Leader_pos - X)X_new D×exp(4×l)×cos(2πl) Leader_pos其中l在[-1,1]均匀随机。每个维度更新完之后做边界裁剪。这里有个我踩过的坑直接裁剪到边界会导致大量鲸鱼贴边种群多样性很快消失。我的改进做法是如果越界不是硬截断到边界而是把越界维度映射回随机的内部位置比如新值 下界 随机数×(上界 - 下界)。这样种群不容易“卡墙”收敛后期鲁棒性更好。4.3 内层用户优化目标函数与约束处理的实现细节内层WOA的决策变量是用户各可调设备在各时段的功率序列。但直接用功率作为决策变量有个问题可转移负荷的启停是整数变量WOA的连续浮点更新机制没法直接产生0或1。我的处理办法是用连续变量x表示“启动时刻的偏移量”然后通过转换函数生成启停状态序列。比如洗衣机允许启动窗口是从第3时段到第18时段运行需要3个时段完成那么决策变量就是一个[0,1]的连续值映射关系是实际启动时段 窗口起点 取整(决策变量×(窗口长度 - 运行时长))。这样就保证了启停变量一定能满足连续运行约束。代价是在优化过程中离散化导数信息彻底没了——不过WOA本来也不用导数所以没关系。温控负荷方面决策变量是每个时段的功率调节系数范围0到1之间。约束是室内温度不能超出允许范围。这个约束我用惩罚函数处理温度每超出边界1度在目标函数中加一个较大惩罚值。这里权重的调节比较敏感惩罚太大导致算法不敢碰温度边界削峰能力受限惩罚太小导致温度超出边界很多结果不可用。经验值是让惩罚量级刚好等于如果这部分额外耗电引起的电费成本的三到五倍。内层WOA的种群大小设在20迭代80次。每个用户的目标函数计算都独立并且多个用户之间无耦合这部分主要耗时间内层每个用户80次迭代乘以30条外层个体的结果在并行时是可以接受的。4.4 双层迭代主程序流程控制与数据流转在主程序里大循环结构是这样外层每次迭代开始调用外层WOA生成一组电价向量。对种群里的每条鲸鱼一套电价先做边界映射保证电价在可行区间然后对每个用户调用内层WOA求出该电价下用户的最优响应功率计划。把全部用户的功率计划叠加起来得到该电价下的总负荷曲线。接着计算系统层目标函数值分配为这条鲸鱼的适应度。全部鲸鱼评估完之后外层WOA根据适应度更新种群。需要注意的一个隐藏问题是内层WOA是一个随机算法同样的电价输入两次可能给出略微不同的用户响应。这种随机性传导到外层会使外层适应度评估带噪声。为了降低这种噪声我采取了两种措施一是同一个电价在评估时内层WOA跑两次取最坏情况二是在外层收敛后半段把内层的随机种子固定。这样外层适应度在后期评估是稳定的不容易出现“明明已经收敛却因为噪声跳动”的假象。4.5 结果可视化与数据导出调度完成后我用Matlab画几张图基线负荷曲线与优化后负荷曲线的对比图分时电价曲线图单个用户的设备调度甘特图以及种群收敛曲线。画图部分有个容易被忽略的问题如果直接用plot画电价和负荷两条不同量纲的曲线峰值会被拉扁。我的做法是用yyaxis left和yyaxis right把负荷和电价放两个坐标轴读者一眼就能看出在电价的引导下负荷向谷时段转移的关系。数据导出方面我们把优化后的用户负荷计划按用户编号、设备编号、时段分列写入Excel这样可以方便地用其他工具做进一步分析。基线数据保留一份方便给参与需求响应的用户算补偿金额。5. 参数整定与性能优化我跑了几十组仿真之后的调整这部分是整篇文章里最“值钱”的部分因为模型和代码大家照着书都能写但参数怎么整定到好用往往要踩很多坑才摸得着。5.1 内层迭代次数和外层迭代次数的搭配关系双层的计算量是乘法关系所以内外层迭代次数的搭配非常关键。我测试过几组搭配外层100次×内层50次、外层150次×内层30次、外层80次×内层100次。结论是外层迭代次数更关键内层不需要迭代太多内层能收敛到用户最优响应的一个较优解即可。原因在于内层求解的问题是凸性相对好的用户成本最小化问题虽然有整数变量但规模小、维度低30次WOA迭代已经能逼近不错的解而外层搜索空间24维非线性强需要更多代来勘探电价空间中哪些区域能带来更好的削峰效果。我最后定的组合是外层120代、内层40代。这个组合跑50户、每户大约5个可调设备、24个时段的场景在Matlab R2023b、12代i5处理器的笔记本上单次完整仿真大约需要8到12分钟。如果你需要更快可以考虑把内层WOA的种群从20降到10、最大适应度评估次数减半代价是用户响应质量会损失几个百分点。个人建议是先保持精度跑通后续再做加速优化。5.2 WOA参数a的衰减方式与螺旋常数b的取值WOA的标准参数里控制探索和开发平衡的关键就是衰减系数a。标准WOA里a是线性递减的从2减到0。我试过多个衰减方式线性衰减、指数衰减、余弦衰减、分段固定。测试结果是指数衰减在80代以后收敛精度更好余弦衰减在前期探索强、后期收敛快综合效果也还可以。本文代码里默认用自然指数衰减公式是a 2×exp(-迭代次数×3 / 最大迭代次数)。这个衰减速度比较合适太快容易早熟太慢收敛不了。螺旋常数b我取1这是标准配置。b越小螺旋越紧局部搜索更细致但可能跳过最优区域b越大螺旋越松局部搜索更粗放更多依赖收缩包围机制。在我这个模型里由于外层目标函数有很多局部平坦区域类似大平原里的浅坑适当的局部搜索细节比全局粗扫更重要b取1算是中庸之道。种群大小方面外层我取30条鲸鱼外层维度是24维电价根据经验种群大小约等于维度数的1到2倍比较合适。再大不是不行但收益递减明显。内层是单用户设备调度问题维度通常5到10之间种群取20就够了。5.3 约束处理的惩罚系数怎么设定适合WOA处理约束的是罚函数法但罚系数的整定是我调试中花时间最多的一件事。惩罚系数太小违反约束的方案会混进种群惩罚系数太大算法把所有精力都放在满足约束上目标函数本身反而搜索不充分。以温度约束为例。空调温度允许范围是[24, 28]度如果不舒适度惩罚系数设为1度对应10元而正常电费一天大约几十元那么这个惩罚不会起太大作用。我跑过一次系数取太小的仿真结果算法为了省电费把室温压到30度以上虽然在数学上“最省钱”但完全不符合实际。反过来系数取太大一度500元算法会为了保证温度约束而几乎不调节空调功率削峰能力就被浪费了。我的做法不是一次设死而是自适应调节先跑一次预仿真记录最大温度偏移量如果偏移超过1度就把惩罚系数提高50%再跑一次如果偏移小于0.1度就把惩罚系数降低20%。两三次迭代之后就能稳定在一个“大部分方案满足约束、边界上有探索余地”的状态。这个方法虽然土但非常可靠。5.4 随机种子的影响与多次运行取最优启发式算法的固有问题就是每次运行的结果有随机性。同一个场景跑十次峰谷差率最好和最坏可能差3到5个百分点。所以做结果汇报或写论文时绝不能只跑一次就下结论。我在主程序里留了一个rng种子参数支持用户指定种子。做方案对比实验时我让每类场景跑10次取平均值和最差值用箱线图展示稳定度。有一件事容易忽略如果只想看单次效果可以设置固定种子比如rng(2024)这样结果可复现调试起来也方便。但如果是要评估算法鲁棒性就得多换几个种子。另外有个小技巧在多次运行中保存历史最优解而不是只保存最后一次结果。因为WOA在晚期可能因随机搜索策略短暂离开已找到的好解如果不保留历史最优最后一次迭代的解反而不如倒数第30次的好。我在代码里用一个leader_score_history变量持续跟踪历史最优不管当前解怎样最终输出的一定是全程最优。6. 我在调试过程中踩过的坑与最终的扩展建议最后分享几个我在把整套模型从论文变成可运行代码时踩过的最深的坑以及按照当前模型框架可以继续扩展的方向。这些都是课堂上不会教、论文里也不会写的内容。6.1 坑一用户之间的耦合被忽略了Nash均衡根本不存在最初我建用户层模型的时候把每个用户的优化问题完全独立出来每个用户的价格只取自当前电价不考虑其他用户的决策对电网实际电压或潮流的影响。当时觉得反正用户都是价格接受者不考虑别的用户也无所谓。但跑出来的结果很奇怪不管外层怎么调电价系统总负荷曲线总是会在某几个时段集中凸起。看了用户的调度结果才知道所有用户的内层WOA都找到了同一个最优决策——把负荷都挪到最便宜的谷时段。每个用户单独看都非常合理但合在一起就是灾难性的同步效应。这就是典型的缺乏博弈意识导致的模型失真。要做非合作博弈建模就必须把用户之间的影响在目标函数里体现出来。后来我在用户目标函数里加上了一个“时段拥挤惩罚项”该项与所有用户在该时段的平均负荷相关。这样每个用户在决策时就必须考虑“如果我挤到某个时段我的成本会不会因为大家一起来反而升高”。加上这个惩罚之后不同用户的调度决策才真正出现了差异化和分散化整体负荷曲线也随之平滑下来。这个改动实际上把Nash博弈映射成了每个用户的“含交叉项的付费函数”求解仍然可以用最佳响应迭代来逼近均衡。在外层WOA迭代过程中用户响应是逐步演化的正好天然带有这种迭代求均衡的性质。6.2 坑二内层WCA引入的随机噪声差点毁掉外层收敛最开始我直接用内层WOA的最终结果作为用户的响应外层每次评估电价时内层都从随机初始点开始优化。结果外层适应度曲线在收敛后期剧烈抖动。一开始我还以为是算法本身不收敛后来发现是内层解的不确定性传导到外层。同一套电价上一轮评估适应度是0.32下一轮变成了0.27但其实不是因为电价变好了而是内层恰好搜索到一个更好的用户响应方案。解决办法前面说过一是取两次内层运行的最坏值作为评估二是收敛后期固定内层随机种子。这两个方法合在一起外层收敛曲线终于变得平滑调参也终于有了可预测性。这个坑在几乎所有双层优化文章里都不会提但在真实工程里非常常见强烈建议遇到类似问题时先检查这个噪声源。6.3 坑三Matlab循环太慢用向量化改写内层评估提速Matlab里用for循环逐个计算50个用户的响应是非常慢的尤其是在对30条外层鲸鱼做适应度评估时50×301500次内层WOA调用每次内部还有20个个体×40代迭代总计算量相当大。我做了一个关键优化把所有的内层目标函数评估改成“向量化输入”。具体说woa_optimize的适应度函数句柄接收的不再是单个用户的决策变量而是一次接收一个种群的全部决策变量一个矩阵每行是一个个体然后利用Matlab的数组运算一次性算出全部个体的适应度。这样虽然还是同一个优化流程但每次迭代的循环次数从“种群大小”次降为1次。整个运行时间大约缩短了60%。如果你的场景规模更大下一步可以考虑把内层用户的独立优化用parfor并行处理。6.4 从单时段到多时间尺度以及多目标扩展方向这套模型目前的调度周期是24小时步长1小时属于日前调度的尺度。实际工程里往往还需要日内滚动修正因为天气预报和用户实际用电行为会变化。扩展思路是把模型改成模型预测控制结构比如每小时滚动一次每次只优化未来4到6小时到了下一小时再根据最新数据重新优化。这样计算量更小而且能处理突发情况。目标函数方面当前是峰谷差、曲线方差、用户总成本的加权和。如果后续想加入碳减排目标或者新能源消纳最大化建议用NSGA-II这类多目标进化算法来替代外层WOA直接输出一套帕累托前沿而不是把多个目标揉成一个权重。不过这样做会显著增加计算量我一般建议先把单目标版本做扎实再考虑多目标扩展。另外一个值得尝试的方向是用户分组。当前模型假设所有用户都是同质理性个体只是偏好参数随机。现实中用户可能分成几类价格敏感的、舒适度敏感的、时间固定型的、时间灵活型的。对这些用户先做聚类然后对不同类别的用户下发差异化的电价套餐会让整个调度更贴合实际情况。聚类用K-means即可聚类特征就是用户在基线调度下的负荷曲线形状和可调设备占比。做完聚类之后每个类别内部的用户行为高度一致博弈复杂度会大幅下降因为类别之间才存在显著的利益冲突类别内部可以近似认为同质。我在实际运行这套代码时的一个体会是模型的价值不在于一次仿真出几个漂亮的指标而在于它把“居民负荷调度”这个从前只存在于概念层面的问题变成了一个可以通过调电价、调权重、调约束就能观察各种可能结果的实验台。比如你可以直接在代码里把某个时段的电价上限调高50%马上就能看到用户响应会怎样变化、总负荷曲线会怎样转移。这种“可实验性”对理解和改进调度策略非常有帮助。最后补充一个实用的小建议跑正式实验之前先用一个只有5户用户的场景把整套代码跑通、参数调顺再去跑50户甚至100户的完整场景。5户场景下计算量小单次运行几秒就能完成你可以快速验证代码逻辑是否正确、曲线是否合理。等逻辑确认无误再切到大规模场景这时候遇到的只会是性能问题而不会是正确的崩溃。我这套代码在这个流程里完善了很长时间如果你也准备做类似的双层博弈调度工作希望这篇里写的思路和踩坑经历能帮你少走一段弯路。