最近被一个实际问题逼着研究了一圈主动配电网里的线路阻塞。分布式光伏一多、电动车一充,峰时段变压器出口那条支路过载是老毛病。我试过传统最优潮流、试过单纯分时电价,效果都差点意思,后来发现把主从博弈塞进来,反而能很自然地解决“运营者想消纳、用户想省钱”的冲突:上层定电价,下层调用电,两边互相牵引,阻塞就被引导着消掉了。这个思路听起来不难,真正落地时全在细节上——怎么把双层模型写成MATLAB,怎么把自适应粒子群跟下层优化套在一起迭代,怎么处理线路潮流约束突然爆掉,每一步都有坑。我现在把这套玩法从建模逻辑到代码框架完整整理一遍,主要面向做配电网优化调度研究、或者想用MATLAB复现主从博弈算法的同学,读完至少能少走三周弯路。
1. 线路阻塞的根因:主动配电网里“谁在跟谁抢通道”
1.1 源荷两侧都是变量,线路开始“不够用”
传统配网是单向潮流、辐射状、负荷可预测,导线容量按最恶劣工况留够裕度就行。主动配电网完全不是这个玩法:屋顶光伏、储能、充电桩、微网一股脑涌进来,潮流不再是“变电站到负荷”的单向流动。午间光伏大发时,功率可能倒送;晚高峰充电时,某些支路直接过载。这两个场景扭在一起,线路阻塞就从偶发事件变成了日常约束。
我处理过的一个典型场景是10kV馈线带着中等密度的分布式光伏,峰谷差拉大以后,几条关键支路在中午和傍晚两个时段负载率都逼近甚至超过90%。某个渗透率38%的case里,两条主干线在光伏出力最高时段接近满载,傍晚负荷爬坡时又复现一次。这已经不是调一两个变压器分接头或者靠人工拉路能解决的问题了,必须有一种机制让不同利益主体自动调整行为。
1.2 传统的“集中式削峰”为什么总觉得僵硬
最理想的做法当然是调度中心直接下发每台光伏、每个储能、每辆充电桩的功率指令,全局做一次最优潮流,所有线路约束一次性满足。但问题是:现实里光伏和储能的产权归属用户,调度指令落不下去;用户自己也不愿意被无条件降功率;再叠加隐私顾虑,配网管理系统根本拿不到每台设备的完整运行数据。
所以真正的矛盾是信息不对称和利益不一致。用电高峰时如果只是把整体电价调高,用户没有感知到“到底是哪条支路过载”,响应往往集中在隔壁支路,阻塞等于换了个位置继续存在。这在治理线路阻塞时算第一大坑:你试图用统一信号解决局部问题,结果就是局部问题被稀释成全局问题。
1.3 主从博弈的角色分配:谁来定规则,谁来响应规则
主从博弈也叫Stackelberg博弈,核心是“先动者”和“后动者”分层决策:后动者对先动者的策略做出最优响应,先动者预判这个过程,提前优化自己的策略。放到主动配电网里,角色划分非常自然:
- 上层领导者:配电网运营商(DSO),负责制定节点电价或者激励信号;
- 下层跟随者:分布式光伏、储能、充电桩聚合商或者产消者,在给定电价下最大化自己的收益。
这个架构贴合现实:电价不是强制指令,但能改变经济动机。DSO不需要知道每户的隐私数据,只需要预判“如果定这样的电价,用户会怎么优化”,就能算出线路潮流会不会越限。双层模型的两个层面也由此而来——上层是DSO的优化问题,下层是用户自己的优化问题,两层通过电价信号和用电量耦合在一起。
我习惯在动手建模前列一张要素表,把决策主体、变量、目标和约束全部写清楚,后面推导公式时就不容易乱:
| 层级 | 决策主体 | 核心决策变量 | 主要目标 | 关键约束 |
|---|---|---|---|---|
| 上层(领导者) | 配电网运营商 | 节点电价/激励信号 | 最小化购电成本+网损+阻塞风险 | DistFlow潮流方程、电压上下限、线路容量 |
| 下层(跟随者) | 产消者/聚合商 | 购电功率、光伏出力、储能充放电 | 最大化自身收益 | 功率平衡、出力上下限、储能SOC |
建模时第一步先把这个表写明白,后面才不会出现“目标函数逻辑对但不知道往哪个模型里填”的问题。小算例可以从统一分时电价起步,跑通后再扩展成节点差异化定价,逐步增加复杂度。
2. 双层模型搭建:上层定电价,下层定出力
2.1 上层模型:DSO怎么把“阻塞”写进目标函数
上层目标函数我强烈建议写成运营成本和阻塞风险的和,不要把线路约束当成铁板一块的硬约束。原因是配电网潮流可行域对电价变量来说高度非凸,硬约束直接加进去,求解器很容易在边界处直接失败。用罚函数或者软约束,至少能让粒子在搜索时顺着潮流越限的方向往里收。
简化形式如下:
[ \min_{\boldsymbol{\pi}} ; C_{\text{purchase}} + C_{\text{loss}} + \rho \sum_{l\in\mathcal{L}} \max\left(0, |P_l|-P_l^{\max}\right)^2 ]
其中(\boldsymbol{\pi})是DSO发布的电价信号,(C_{\text{purchase}})是向上级电网购电的成本,(C_{\text{loss}})是网损成本,第三项是线路潮流越限的二次罚项,(\rho)是惩罚系数,(\mathcal{L})是所有需要重点监视的支路集合。
这里的设计意图很关键:罚项让算法在迭代前期就能“看到”越限有多严重、位置在哪,引导电价朝缓解阻塞的方向调整,而不是一上来就把搜索空间压到不可行区域。(\rho)不能太小,太小了越限在目标函数里占比过低,DSO干脆不管阻塞;也不能太大,太大了罚项主导目标函数,电价会被带飞,用户看到电价离谱直接不响应,结果反而更差。具体取值范围我在第6章细讲。
上层还需要满足配电网的物理规律:节点电压保持在[0.95, 1.05] pu区间内,各支路潮流满足DistFlow方程。这些约束无法像普通线性约束一样直接写进粒子群,必须每次给定电价后调用潮流计算才能求出来,所以潮流计算一定要封装成独立函数,不能散落在主循环里。
2.2 下层模型:用户对电价的最优响应
下层可以是一个用户,也可以是一组同类产消者的聚合体。为方便MATLAB实现,我用一个常见的二次效用函数来建模。用户收益等于用电效用减去购电成本,再加上储能参与调节的收益:
[ \max_{p_d, p_s} ; a p_d - \frac{1}{2} b p_d^2 - \pi p_d + \lambda_s p_s ]
约束条件:
[ p_d^{\min} \le p_d \le p_d^{\max}, \quad p_s^{\min} \le p_s \le p_s^{\max} ]
其中(\pi)是电价,(p_d)是购电功率,(p_s)是储能出力(正为放电,负为充电),(\lambda_s)是用户对储能参与响应的单位收益系数。二次效用函数有个至关重要的好处:下层问题变成凸二次规划(QP),MATLAB里quadprog或者KKT解析解都能快速求到全局最优。
这个性质在双层迭代里极其值钱,因为上层每个粒子都要调用下层求解,下层如果本身不稳定或者求解慢,整个算法效率会变得非常难看。我以前试过在下层用fmincon直接上非线性模型,规模不大但双层嵌套后单次迭代慢了好几倍,完全得不偿失。
如果用户侧还有可调负荷(温控负荷、充电桩),还可以再引入时移约束,本质还是线性或二次约束,模型结构不用改。用户决策变量也不需要每个设备单独建,按节点聚合之后,每个节点对应一个下层优化子问题,维度完全可控。
2.3 两层到底怎么“咬合”在一起
双层耦合的闭环是这样的:上层给定电价向量,下层求解得到用电和储能计划,再回到上层做潮流计算得到线路潮流和目标函数,然后更新电价,如此往复直到达到Stackelberg均衡。通俗点说,均衡就是:给定电价,下层已经做到最优;反过来,在这个电价和下层响应下,DSO也已经没有更优的电价策略。
实际数值求解时,我不追求严格的数学证明,而是看收敛曲线是否平稳、连续若干次迭代目标函数变化是否小于阈值。真正需要警惕的是下层多解问题:如果某个电价下用户的最优解不唯一,上层每次调用quadprog可能返回不同解,导致上层目标函数出现随机抖动,粒子群的pbest更新逻辑会被彻底带乱。
解决办法通常有两个:一是给下层目标加一个很小的二次正则项,比如(+\varepsilon|x|^2),打破平局;二是对储能这类变量设置统一的终止条件,让求解器尽量稳定返回同一方向的解。两个方法可以叠加用,实测效果都不错。
3. 为什么选自适应粒子群:双层模型求解的现实困境
3.1 双层模型求解的三条主流路径
在双层规划里,圈内主流做法分三条路,我分别说下优缺点。
第一条是KKT条件转换:把下层优化问题用KKT条件替换,双层变成单层带互补约束的MPEC问题,交给商业求解器处理。这条路在小规模、线性或二次约束下非常漂亮,但配电网潮流约束一进来,非凸性立刻让求解器头大,而且KKT推导对初学者来说非常劝退,模型稍微加一个约束就得重新推一遍。
第二条是元启发式嵌套:外层用粒子群、差分进化这类算法搜索上层电价,内层调用确定性求解器解下层。优点是原理清晰、容易改模型,缺点是计算量大、没有严格收敛保障。不过配合自适应策略,绝大多数工程场景都能覆盖。
第三条是数学逼近类方法,比如变分不等式、联盟博弈、分布式ADMM,收敛性质更好,但工程实现复杂度高。对配电网这种带一堆潮流约束的实际系统,工作量和数值稳定性问题会远超预期。
本文讲的是第二条路:上层自适应粒子群加下层确定性求解。它不完美,但它是最容易在MATLAB里跑通并验证模型完整性的方案。先把框架跑通,后续再上更先进的求解器也不迟。
3.2 标准PSO在电价优化上为什么容易翻车
标准PSO在连续变量优化上确实好用,但配电网电价优化问题有几个特点:目标函数多峰,存在大量局部极值;下层响应是隐式的,目标函数要套好几层才能求出来,梯度基本没法算,只能靠无导数类算法。这两个特点叠加,标准PSO很容易在迭代中后期陷入停滞。
我复现过几次,标准粒子群基本跑到80代左右gbest就不动了,而真实更优解往往藏在某条支路潮流临界点附近,需要把搜索重新“踢”起来。这个现象根源在于粒子的惯性权重和速度更新公式太“机械”,前期探索不足、后期又缺乏跳出局部最优的能力。
自适应思想的动机就在这里:让算法在探索和开发之间自动找平衡。常见手段包括:
- 惯性权重动态调整,前期大权重加强全局探索,后期小权重加强局部精细搜索;
- 学习因子动态调整,前期c1大重视自身经验,后期c2大重视群体经验;
- 停滞检测与变异,当群体聚集度很高或gbest长期不更新时,对部分粒子施加扰动或重新初始化。
3.3 我搭的自适应PSO具体参数配置
下面是我在类似题目上反复试出来的参数,可以作为起点再调:
| 参数 | 设定值 | 作用 |
|---|---|---|
| 粒子数 | 40~60 | 维度在10~20时够用,再多收益不大 |
| 最大迭代 | 200 | 配合上层计算量,够收敛即可 |
| 惯性权重ω | 0.9线性降至0.4 | 全局搜索到局部搜索平滑过渡 |
| 学习因子c1/c2 | c1由2.5降至1.5,c2由1.5升至2.5 | 前期探索、后期收敛 |
| 停滞阈值 | gbest连续15代不变 | 触发变异/重启 |
| 变异幅度 | 对粒子位置加N(0, 0.1·范围)高斯扰动 | 跳出局部极值 |
实测下来,同样的双层模型,带停滞变异的自适应PSO比标准PSO的目标值低8%~15%,具体数值取决于阻塞的严重程度,而且多次运行的方差明显更小。这个提升放在论文里可能不够惊艳,但工程上意味着不会三天两头撞到一个离谱的局部解。
代码层的自适应逻辑大概是这样:
% 自适应惯性权重 for it = 1 : max_iter w = 0.9 - 0.5 * (it / max_iter); if gbest_no_improve > 15 % 对20%左右维度的位置做高斯扰动 mask = rand(size(sol)) < 0.2; sol(mask) = sol(mask) + 0.1 * (ub(mask) - lb(mask)) .* randn(sum(mask), 1); end % 速度更新、位置更新代码... end注意扰动千万别施加在所有维度上,只扰动20%左右的维度,不然会把好不容易找到的优质解结构给破坏掉。这个比例我试过5%、10%、50%,20%效果最好。
4. MATLAB实现的关键环节:从模型到能跑的代码
4.1 算例选择与数据接口设计
我使用IEEE 33节点系统做标准算例。这个系统有32条支路、5组联络开关,基准电压12.66kV,基准容量10MVA,参数在公开资料里很容易找到,是配电网优化的“默认试验台”。下面代码都是在33节点上验证过的思路,换系统时要重点修改支路阻抗、节点负荷、分布式电源位置这些参数。
工程上我建议把系统数据放到一个struct里统一管理:
sys = struct(); sys.base_kv = 12.66; sys.base_mva = 10; sys.branch = branch_data; % 每条支路的首端、末端、电阻、电抗、容量 sys.bus = bus_data; % 节点负荷、光伏、储能配置把潮流计算、目标函数、下层求解分别写成独立函数,接口只传sys和电价向量。这样换算例、调约束时不用重写主循环,能省掉大量调试时间。
4.2 双层目标函数:内部到底怎么调用下层
这是整个实现的核心,逻辑是:输入电价向量,求解下层用户模型得到每个节点的用电计划,再把用电计划带回潮流函数,计算支路潮流和目标函数。
下层求解我用quadprog而不是fmincon。原因在于下层是二次效用函数,属于凸QP,quadprog在线性约束下能保证快速稳定收敛;fmincon虽然也能做,但在双层嵌套场景下调用次数极多,速度差距会被放大成几分钟和几十分钟的差别。如果以后改成非线性约束下层模型,再换fmincon不迟。
代码骨架如下:
function [f, congestion, load_ratio] = bi_level_obj(pi_vec, sys) % 步骤1:逐节点求解下层用户优化 for k = 1 : N_node H = diag([b_k, eps]); % 二次项系数 f_lin = -[a_k - pi_vec(k), -lambda_s_k]; % 上下限约束组装... [x_opt, ~] = quadprog(H, f_lin, A_lb, b_ub, [], [], lb, ub, x0); demand(k) = x_opt(1); storage(k) = x_opt(2); end % 步骤2:DistFlow潮流计算 [P_line, V_bus, loss] = distflow_calc(sys, demand, storage); % 步骤3:目标 = 购电成本 + 网损成本 + 阻塞罚项 over = max(0, abs(P_line) - P_line_max); f = purchase_cost + loss_cost + rho * sum(over.^2); end这里有个实现细节容易踩坑:quadprog每次调用都要传初值x0,我建议把上一轮求得的解作为热启动传下去。双层迭代后期电价变化不大,下层解变化也很小,热启动能明显缩短求解时间。我统计过,加了这个优化之后,整个双层迭代时间能缩短30%以上。
4.3 阻塞约束的罚函数处理与自适应粒子群主循环
外层自适应PSO的主循环比较标准,核心是让每个粒子代表一组电价向量,不断更新速度和位置,同时检测停滞并执行变异。线路阻塞的约束处理再次强调,用二次罚函数而不是硬约束。原因在于硬约束会让大批粒子直接掉进不可行区域,而电价稍微扰动就导致约束剧烈跳变;罚函数则可以让粒子在不可行区域平滑地往可行方向移动。
罚项计算我做了归一化处理:
function f_penalty = congestion_penalty(P_line, P_line_max, rho) over = max(0, abs(P_line) - P_line_max) ./ P_line_max; f_penalty = rho * sum(over.^2); end把越限量归一化后再平方,可以让罚函数在不同量级的支路之间更公平。不然低压配网支路容量小、算出来的越限数值天然偏小,会被高压侧支路带偏,罚函数的引导作用就失效了。
4.4 收敛判定与结果存储
双层问题没有严格的“精确最优”可验证,我一般结合两个判据:一是gbest目标函数连续30代变化小于(10^{-4})(标幺值下);二是负载率排名靠前的几条支路连续若干代保持稳定、不再换位。第二个判据很实用,因为目标函数可能有数值噪声,但阻塞支路的组合是稳定的。如果支路负载率排名一直在跳,说明罚函数系数或者自适应参数还没调好,算法其实还在混乱摸索。
每次迭代结束建议保存四样东西:迭代代数、gbest目标值、最大支路负载率、越限总平方和。四个指标放一列,复盘的时候一眼就能看清算法是“正常收敛”还是“假装收敛”。
5. 仿真结果怎么看:阻塞缓解到什么程度才算有效
5.1 算例设置与参考基准
我在33节点系统上做了这样的对照:原始负荷基础上叠加一定比例的可调负荷和光伏,让两条关键支路在高峰时段负载率达到105%和112%,形成真实越限。在这个基础上,分别用标准PSO和自适应PSO做DSO电价优化,同时保留一个什么都不做的baseline作为对照组。
5.2 收敛行为对比
标准PSO的收敛曲线非常典型:前期下降快,中后期缓慢爬行,最后停在某个局部解。自适应PSO前期下降略慢一点点,因为自适应策略需要积累停滞信息,但中后期得益于变异机制,往往在80到120代之间还能有一次明显下降,最终目标值稳定地低一截。
我最关注的指标是越限总平方和。标准PSO后期基本是维持在一个平台,APSO会把它压到接近0或者一个可接受的小范围。从多次运行的趋势看,APSO找出的电价方案确实能让越限支路“退烧”,而且不会出现反复横跳的情况。
5.3 线路负载率的结果评判方法
阻塞治理有效性我习惯看这组指标:
- 最大线路负载率从112%降到多少;
- 负载率超过90%的支路数量;
- 全系统负载率方差;
- 用户购电成本的变化幅度。
理想结果是最大负载率压到95%以下,重载支路数量明显减少,同时用户总成本不至于涨太多。主从博弈的意义是调节利益,不是一方通吃。如果只追求阻塞最小而电价离谱上涨,用户下层模型很快就会“摆烂”不响应,那优化的根基就没了。
5.4 自适应PSO的收益到底在哪里
我从多次运行中观察到一个很有价值的规律:APSO对电价初值不敏感,标准PSO对初值很敏感。随机生成10组初始粒子群,标准PSO至少要换3组才能跑出像样的解;APSO基本10组都能收敛到相近质量。这个特性在工程里非常重要,你不想研究一套算法,每次运行结果都像开盲盒。
另外,APSO参数一旦调好,对负荷规模、光伏渗透率变化有不错的鲁棒性。我从22节点改到33节点再改到45节点,并没有大幅重调参数。标准PSO换个系统就得重新找参数,这算是自适应策略真正省心的地方。
6. 我做这套模型踩过的坑
6.1 下层多解导致上层目标“抖”
这个问题在2.3节提过,实际踩的时候非常迷惑:明明固定电价调度,双层目标却忽高忽低,pbest更新逻辑完全乱掉。后来定位到问题根源是下层quadprog在某些储能约束边界上返回了不同的角点解。不同角点解对应的上层潮流和目标值差异不小,看起来就像是随机噪声。
处理方法就是给下层目标加一点正则项,比如0.001倍的变量平方和,让优化器稳定偏向同一个方向;如果还不行,可以对角点解做归一化处理。加了正则项之后,上层目标函数的抖动幅度小了一个数量级,粒子群收敛也顺利了很多。
6.2 罚函数系数不要一拍脑袋定
(\rho)太小,比如小于10,越限在目标里占比太低,DSO干脆不管阻塞;(\rho)太大,比如大于1e6,目标函数被罚项完全主导,电价被带飞,用户直接不响应,结果反而更差。
我给一个经验搜索区间:从100开始,每次乘5往上试探,观察三个指标的变化——最大负载率、用户成本、收敛代数。找到一个“最大负载率明显下降但用户成本增幅可接受”的区间就定下来。记住,这个系数和系统容量的标幺化方式强相关,别人论文里的系数不能直接抄,一定要在自己的算例里重新标定。
6.3 粒子编码顺序影响搜索效率
上层粒子编码的是电价向量,编码顺序对结果影响极大。我一开始按节点顺序一行排开,33个节点就是33维,粒子群在高维空间里搜索效率很低,而且定价结果既难解释又难收敛。
后来我把节点按用户类型聚成居民、商业、工业三类,按类别编码,粒子维度直接从33降到四五维,搜索效率提升非常明显。这个优化完全不需要改模型,只是工程上的编码技巧,但收益立竿见影。如果你做的系统节点很多,强烈建议先做聚合。
6.4 潮流不收敛不要先怀疑算法
嵌套迭代里一旦运行变慢或者结果异常,很多人第一反应是粒子群参数没调好。我给自己定的排查顺序是:先查潮流函数,用零负荷做收敛测试,再逐步加负荷;再查下层求解,固定电价看解是否稳定;最后才查上层优化。
原因是潮流是每次迭代都要调的底层模块,它一旦有小错误,会像传染病一样污染整个双层迭代。一个带正常负荷的33节点系统,DistFlow收敛测试几十次都不该出现NaN或虚数。如果出现了,先别急着优化,回头把潮流函数跑一遍单测。
最后分享一个小技巧:在跑完整双层迭代前,先把下层求解单独调好。方法是对一组随机电价批量调用下层,观察返回的负荷曲线是否平滑、是否违反自己的上下限。这一步做好,后面双层迭代就会顺畅很多。主从博弈处理线路阻塞的思路,再往后可以扩展多时段耦合、节点边际电价、储能联合调度几个方向。核心的“上层定价,下层响应,潮流反馈”闭环是先要扎扎实实跑通的,这套MATLAB框架是目前我觉得性价比最高的起点。