
简介面向配电网韧性提升的应急移动电源MPS动态调度MATLAB复现资源以Lei等2019年IEEE Trans. Smart Grid论文第二阶段为蓝本利用YALMIP工具箱完成混合整数规划建模与求解。资源聚焦事件发生后MPS动态派遣与配电网运行协同处理不同时间尺度及道路-电力网络耦合难题适合电力科研人员、研究生用于顶刊算法复现、对比与拓展研究。压缩包共5个文件包括3个.m源文件主程序、结果展示脚本与IEEE33节点系统数据、1个.p受保护辅助代码和1个.vsdx拓扑示意文件整体仅49KB结构紧凑便于直接定位模型与数据目前已有322人学习/下载。借助该资源可直接运行第二阶段动态调度算例观察MPS路由与调度策略对系统恢复力的提升效果。该套代码还可作为论文实验、课程设计或韧性评估课题的基础工具。1. 配电网韧性提升为什么离不开“预配置 MPS动态调度”这一个组合做配电网韧性课题时我常被问到一个问题应急移动电源Mobile Power SourceMPS买了不少灾前也提前布防了为什么实际抢修时负荷恢复还是慢答案往往不在设备数量上而在动态调度策略上。同一批MPS灾后先恢复哪个负荷、走哪条路、什么时候转移决策不同恢复的负荷电量能差出30%以上。这个标题里真正值钱的不是“预配置”那半篇而是“下”这半篇灾中MPS怎么动态调度。预配置解决的是“电源放在哪”动态调度解决的是“电源在什么时候、以什么路径、给谁供电”。适合配电自动化、应急指挥和韧性评估方向的工程师读也适合刚接触两阶段优化模型的研究生照着搭第一版程序。2. 从预配置到MPS动态调度两阶段优化框架先立住2.1 为什么预配置不是“拍脑袋放几台车”很多单位的应急预配置是凭经验做的往负荷重、历史故障多的变电站门口放一台发电车就完事。但在配电网韧性评估的框架里预配置是一个典型的“第一阶段决策”它必须在故障场景不确定的情况下先落子等故障真的发生了再通过“第二阶段决策”也就是动态调度去弥补。两阶段之间的核心矛盾是预配置做早了信息不全动态调度做晚了电源来不及到位。我一般会把预配置建模成“场景随机优化”里的第一阶段变量。目标函数里除了预配置成本还要带上所有故障场景下动态调度的期望代价。这样预配置点选在哪不是看哪个变电站负荷大而是看“在所有可能故障场景下MPS从预配置点出发能不能在关键时间窗内覆盖到关键负荷”。这是一个典型的选址-路径耦合问题先想清楚这个框架后面读任何一篇顶刊复现代码都不会迷路。2.2 灾前到灾中两阶段优化框架的分工与衔接两阶段优化框架里第一阶段是灾前的MPS预配置决策第二阶段是灾后的MPS动态调度决策。两者的衔接点在于第一阶段输出的预配置方案直接作为第二阶段的“初始位置”输入而第二阶段的调度成本又反过来参与第一阶段的目标函数评估。常见做法是用样本均值近似Sample Average Approximation把随机场景离散成若干个确定性场景每个场景跑一次动态调度再把期望值求出来。第二阶段动态调度里决策变量通常包括MPS在哪个时刻从当前位置出发、沿哪条道路移动、在哪个节点接入配电网、接入后以什么功率放电、什么时候断开并转移到下一个节点。这些决策要满足的约束包括交通网络的时间约束、MPS容量约束、配电网潮流约束、负荷恢复的时序约束。动态调度不是把MPS当成“移动的充电宝”用而是要把“移动轨迹”和“供电时序”联合优化这两者常常是冲突的——走得越远留给供电的时间越短。2.3 定义电网与MPS参数一份可落地的Python数据准备动手写模型前我会先把电网拓扑和MPS参数定义清楚。下面这份是简化但能跑通的初始数据准备代码覆盖了IEEE 33节点系统的骨干结构和两台移动储能车的参数。import numpy as np # 电网节点0为变电站根节点1~32为负荷节点 nodes list(range(33)) # 负荷数据{节点: (有功功率kW, 重要度权重)} # 权重越大表示越重要的负荷医院、通信、供水优先恢复 load { 2: (100.0, 1.0), 4: (120.0, 1.0), 7: (200.0, 10.0), 9: (60.0, 1.0), 14: (120.0, 5.0), 18: (90.0, 1.0), 24: (420.0, 10.0), 25: (420.0, 5.0), 30: (200.0, 1.0), } # MPS参数{编号: (容量kWh, 最大放电功率kW, 移动速度km/h)} mps { M1: (500.0, 100.0, 30.0), M2: (800.0, 150.0, 25.0), } # 预配置点灾前MPS所在的候选节点阶段一输出 pre_position {M1: 7, M2: 14} # 交通网络弧段(起点, 终点, 距离km) road_edges [ (7, 9, 2.0), (7, 14, 1.5), (14, 18, 2.0), (14, 24, 2.5), (24, 25, 0.8), (25, 30, 3.0), ]这份代码里最值得注意的参数是负荷权重和MPS容量。负荷权重直接进入目标函数权重设置不合理调度结果就会偏离真实应急优先级MPS容量决定它在几个节点之间能转移几次容量太小的话调度模型会自动让MPS“少跑多供”这是对的行为不用额外加惩罚项去约束它。road_edges 是后面动态调度里最短路径搜索和时序约束的数据基础。提一句关于预配置点和动态调度衔接的做法我把预配置输出直接当成 mps 字典的初始位置字段然后在该约束里固定住。这样第二阶段模型天然接受第一阶段的结果不用额外写接口转换省掉一个常见的“数据对不上”的坑。3. MPS动态调度模型时空网络与路径-时序联合优化3.1 把MPS移动过程写成“时空网络”MPS动态调度和静态应急电源调度最大的区别在于“位置随时间变化”。如果不考虑时间维度模型会给出一个荒谬的结果MPS同时出现在两个节点上。所以第一步要做时空网络展开把时间轴离散成若干个时隙每个时隙内MPS要么在某节点供电要么在某条路上移动。我一般把时间步长设为1小时故障恢复窗口设为72小时这样每个MPS的决策维度就是72个时段。空间上每个时段MPS可以选择“停留在当前节点”或“沿某条道路移动到相邻节点”。移动需要时间移动时间内不能供电这就是路径与时序耦合的本质。把这种“节点×时段”的二维决策空间建出来MPS动态调度就从“调度问题”变成了“时空网络路径选择问题”。构建时空网络时有个细节容易忽略配电网的供电路径和移动电源的移动路径是两张不同的网。MPS在交通网上移动在配电网节点上接入。两个网络通过“接入节点”这个集合关联。如果项目里只有配电网拓扑没有交通网数据常见做法是把交通时间近似为节点间欧氏距离除以平均车速再加一个1.2~1.5倍的绕行系数误差可以接受。3.2 动态调度的决策变量与约束有了时空网络MPS动态调度的决策变量可以分成这么几类z[p][t][n]MPS p 在时段 t 是否接入节点 n0/1变量w[p][t][a]MPS p 在时段 t 是否在道路弧段 a 上移动0/1变量Pdis[p][t]MPS p 在时段 t 的放电功率s[p][t]MPS p 在时段 t 结束时的剩余电量。约束条件里最关键的是“位置唯一性”约束和“能量平衡”约束。位置唯一性要求每个MPS每个时段要么在一个节点供电要么在一条弧段上移动不能既供电又移动能量平衡要求剩余电量等于上一时段剩余电量减去放电消耗且任何时候不能超过容量上限、低于安全下限。第三个关键约束是“移动时间一致性”如果MPS在t时段进入弧段a而这条弧段要走2个时段那它在t1时段必须仍在该弧段上不能中间瞬移。import pulp as lp prob lp.LpProblem(MPS_Dispatch, lp.LpMaximize) # 时空网络参数 T 6 # 时段数这里用6做小规模示例实际取72 nodes [2, 4, 7, 9, 14] # 候选接入节点 arcs [(7, 9, 1), (7, 14, 1), (14, 18, 1)] # (起点, 终点, 通行时段数) MPS [M1, M2] init_pos {M1: 7, M2: 14} # 决策变量 z lp.LpVariable.dicts(z, (MPS, range(T), nodes), catBinary) w lp.LpVariable.dicts(w, (MPS, range(T), arcs), catBinary) pdis lp.LpVariable.dicts(pdis, (MPS, range(T)), lowBound0) soc lp.LpVariable.dicts(soc, (MPS, range(T 1)), lowBound0)变量定义好后约束分三组加进去。位置唯一性约束保证MPS在每个时段只能做一件事初始位置约束强制t0时MPS在预配置节点能量约束把soc、pdis和放电深度关联起来。完整模型代码不在这里展开重点说一下pulp建模时容易踩的位置LpVariable.dicts 用嵌套字典生成变量索引顺序是 (MPS, T, nodes)后面引用约束时经常有人把索引顺序写反导致“KeyError: (0, M1, 7)”这类错误。建议固定一种顺序并在注释里写明。3.3 目标函数恢复电量优先还是恢复时间优先动态调度的目标函数直接决定MPS先救谁。常见目标有二一是最大化恢复负荷总量带重要度权重二是最小化重要负荷的停电时间。两者并不等价——前者可能让MPS长时间趴在一个普通但电量大的负荷上后者则倾向于“蜻蜓点水”式地每个重要节点都去停一下。我在实际项目里的做法是混合目标主目标是重要度加权恢复电量最大化次目标是最晚恢复时间最小化。把次目标以极小权重放进目标函数里或者做成epsilon约束。注意次目标权重不能设太大否则模型会牺牲恢复总量去“抢时间”反而拖慢整体恢复进程。这类“目标打架”在动调度里很常见建议先跑一版单目标再对比混合目标看结果差异。另外目标函数里的负荷恢复量不是一个独立变量它和z变量强相关只有MPS接入负荷节点并放电才有恢复量。这个逻辑可以在目标函数里直接用 z * load * 时段时长 表达也可以额外引入 r[n][t] 表示恢复状态。显式引入恢复状态变量的好处是方便加“恢复后不可中断”的约束这对医院、通信基站这类负荷是刚需。4. 求解MPS动态调度从MILP到分解算法的选择4.1 先判断模型复杂度再选求解器MPS动态调度模型本质上是一个混合整数线性规划MILP变量里既有0/1赋值的z和w又有连续功率变量Pdis和soc。小规模案例节点数几十、时段数十几个、MPS两三台直接用开源求解器就能跑但到了真实配电网上百个节点、72个时段、十几台MPS整数变量规模会膨胀到几十万单机直接求解会卡到怀疑人生。判断模型能不能直接跑我习惯看一个指标0/1变量数量。经验上单机MILP求解器能处理的0/1变量上限在10万量级超过这个量级就该考虑分解算法或者启发式。以33节点系统、72时段、5台MPS为例z变量是5×72×33≈11880个w变量还要加上道路弧段数×时段数总量大约3万以内Gurobi或CBC都能在可接受时间内求解。4.2 用Python MILP求解器跑通最小动态调度模型下面这个求解代码是我常用的一套最小可运行结构。它把第3章的模型加上目标函数、求解器设置和结果解析跑通之后改参数就能往大系统上扩。import pulp as lp # 目标最大化重要度加权恢复电量 prob lp.lpSum( z[p][t][n] * load[n][0] * load[n][1] * 1.0 # 功率 × 权重 × 时段小时 for p in MPS for t in range(T) for n in nodes if n in load ) # 位置唯一性约束每个MPS每个时段只能接入一个节点 for p in MPS: for t in range(T): prob lp.lpSum(z[p][t][n] for n in nodes) 1 # 初始位置约束t0时MPS必须在预配置节点 for p in MPS: prob lp.lpSum( z[p][0][n] for n in nodes if n init_pos[p] ) 1 # 能量约束soc(t1) soc(t) - 放电消耗 EFF 0.95 # 放电效率 for p in MPS: prob soc[p][0] mps[p][0] # 初始容量 for t in range(T): prob soc[p][t 1] soc[p][t] - pdis[p][t] / EFF # 求解 solver lp.PULP_CBC_CMD(msgTrue, timeLimit600, gapRel0.05) prob.solve(solver) # 输出调度方案每个时段MPS接入的节点 for p in MPS: for t in range(T): for n in nodes: if lp.value(z[p][t][n]) 1: print(f{p} 在时段{t} 接入节点{n})这里的PULP_CBC_CMD参数值得说明timeLimit600 是给求解器10分钟上限gapRel0.05 表示允许5%的最优性间隙。做工程验证时我一般先把gapRel放宽到0.1确认模型没有语法错误和不可行约束后再收紧到0.02~0.05。0/1变量多的时候gapRel太小会让求解器把大量时间耗在证明最优性上实际调度方案早就收敛了这个“证明最优”的时间不值。4.3 大系统怎么办分解算法与滚动时域之间的选择当模型大到单机MILP跑不动时两条路线最常用别一上来就上强化学习。第一是Benders分解把动态调度问题按MPS分解成主问题和子问题主问题决定接入节点和路径子问题在给定接入方案下算潮流可行性和能量平衡迭代加割平面。因为主问题是纯整数问题、子问题是线性问题这种结构天然适合Benders。第二是滚动时域Rolling Horizon。这是我在工程里更常用的办法不一次性求解72个时段而是每12个时段一个窗口求解完把前4个时段的决策下发执行然后窗口向前滚动4个时段重新求解。滚动时域不保证全局最优但它把整数变量规模砍到原来的六分之一还能在线更新故障信息——比如某条道路因为次生灾害中断了下一轮优化立刻就知道。滚动时域里有两个容易翻车的细节。一是窗口重叠长度重叠太短会导致前后窗口决策不衔接MPS在窗口边界附近反复横跳重叠太长退化成全局求解。一般取窗口长度的1/3到1/2。二是求解完成后的“冻结”机制已下发执行的决策要作为硬约束传给下一轮不能让模型把过去时段改掉了。不写这个约束滚动时域的结果会“追悔过去”调度方案每一轮都不一样现场根本没法执行。5. MPS动态调度落地的5个典型坑从现象到根因排查5.1 现象MPS还没到节点计划里的恢复时间已经到了第一次跑完动态调度模型我盯着输出表发现MPS计划在t2给节点24送电但按道路距离和车速算它t4才能到。模型居然给出了一个“时间穿越”的方案。原因道路弧段的通行时间没有建模进z变量的可用时段里。我检查发现约束里只限制了“移动中和供电中不能同时发生”但没有限制“还没到达就不能开始供电”。时间网络里漏了“可达性”约束。解决给每个MPS在每个节点的最早可用时段加一个下界。具体做法是先用最短路径算法算出预配置点到每个节点的最短通行时间然后加约束当 t 最短通行时间 时z[p][t][n] 强制为0。这个约束在优化前预处理就能做不用增加整数变量。5.2 现象预配置方案和动态调度结果“打架”灾前的预配置选了A点和B点灾后动态调度却几乎不用A点的MPS只调度B点的MPS。表面上看A点的预配置白做了实际上问题出在两阶段模型的信息传递上。原因预配置阶段的目标函数里每个故障场景的概率权重设置不对。低频高损场景权重太低预配置优化就“放弃”了那个方向的覆盖等这个场景真发生了动态调度面对的是无解的局面。解决把场景概率和目标函数做成参数而不是写死。我一般先跑一版所有场景等权重的预配置看哪些节点被选中再把概率权重调成真实故障统计值对比两次预配置的差异。如果差异太大说明场景生成不充分需要检查故障集是不是只考虑了N-1而忽略了N-1-1的级联故障。5.3 现象求解器报“infeasible or unbounded”这个报错几乎是MPS调度建模的必经之路。我第一次看到这个提示直接懵了因为模型是照着论文公式敲的觉得逻辑没问题。实际上这个报错只说明求解器在预处理阶段发现模型既不可行也无界具体原因还得自己查。原因最常见的是能量约束初始条件没给。soc[p][0] 没有赋值能量平衡方程里的变量就自由了求解器可以无限放电得到无界解也可以怎么都不满足容量约束得到不可行解。另一个常见原因是MPS数量为0或者load字典里某个节点只有权重没有负荷功率导致目标函数里出现 None 参与运算。解决先跑一遍“可行性诊断”。把目标函数临时设成常数0只保留约束看求解器能不能找到可行解。如果可行说明约束没问题问题出在目标函数如果不可行用Gurobi的IIS不可行子系统功能或者CBC 打印冲突约束来定位。这个诊断步骤值得写在代码注释里省得下次再浪费两小时。5.4 现象单次故障场景下的最优调度在故障演化场景里持续翻车用单场景动态调度算出来的方案投到实际故障演化比如台风过程中线路一条接一条跳闸里MPS刚接好一个节点另一个关键节点又失电了。调度方案跟不上故障动态。原因动态调度模型假设故障集合在决策时刻已经确定属于开环决策。真实配电网里故障是演化的部分线路会在不同时刻先后跳闸。开环方案对后续故障无法反应现场只能靠人为干预模型自然“翻车”。解决切换到闭环的滚动时域调度。每个控制周期比如1小时重新读取当前故障状态和MPS实际位置更新负荷重要度参数后重新求解。这事第4章提到的滚动时域正好是同一个框架只是把“预测故障”换成“实时故障更新”。注意重新求解时不要冷启动把上一轮的MPS位置和电量作为初始条件输入求解速度会快很多。5.5 现象重要负荷权重高但调度结果里它反而被跳过了我给医院节点设了权重10结果调度方案把MPS派给了一个权重1的小用户医院节点一直没恢复。目标函数明明在最大化加权恢复电量怎么结果反了原因负荷节点的“可接入性”约束没有建完整。医院节点虽然权重最高但它的上级线路处于故障断开状态MPS接入该节点后无法通过配电网向周围供电或者该节点是孤岛节点MPS容量不够支撑它的启动电流。换句话说模型认为“接入了也没法供电”所以干脆不选它。解决把“节点是否具备接入条件”作为参数预处理进模型。只有与故障隔离区域连通的节点才出现在z变量的候选集合里。另外检查一下负荷模型里是不是只写了稳定有功功率没有考虑电动机启动时的冲击功率。有些重要负荷的启动电流是额定电流的5到7倍MPS放电功率上限不足时即使模型选了它现场也会因为过流保护动作而送不上电。细节排查经验遇到这种情况我习惯把调度结果导成Gantt图横轴是时间纵轴是MPS颜色块表示接入节点。视觉上一眼就能看出“节点24被派了M2但M2根本没去”这类荒谬决策比对着数字表定位快得多。6. 验证与进阶用滚动时域闭环仿真验证你的动态调度方案6.1 从开环最优解到闭环仿真验证很多论文里的动态调度验证只做一件事给定故障场景跑一次MILP输出恢复电量。但实际项目里这个“一次求解”的结果没有说服力因为现场环境是时变的。我常用的验证办法是搭一个闭环仿真环境环境模拟故障演化、MPS移动到位过程、负荷恢复状态控制器每60分钟调用一次动态调度模型输入当前状态输出未来4小时调度计划只执行前1小时。闭环仿真跑完后把三个指标打出来对比总恢复电量、重要负荷累计停电时间、MPS平均利用率。我一般用这个验证过程来反推模型参数合不合理——如果MPS利用率低于40%说明模型给的调度方案过于保守如果高于90%说明MPS一直在满负荷跑故障蔓延时完全没有裕度。这两个极端都需要调参数。6.2 三个值得反复调的参数第一个是时间步长。步长1小时计算快但MPS在两节点间移动用时只有20分钟时1小时步长会让模型要么提前出发浪费供电时间要么延后到达导致负荷等待。建议移动时间不是步长整数倍的场景把步长设为15分钟。第二个是负荷权重的量级设置。我踩过的坑是把权重直接按“医院10、学校5、居民1”设表面合理实际量级差异不够目标函数里恢复电量kW·h本身是几百到几千的数量级权重差异被淹没。正确做法是把权重乘到负荷功率上让不同负荷的加权恢复电量有明显梯度差。第三个是故障场景集的生成方式。只在单一场景下验证很容易过拟合我一般从历史故障库抽样生成50个场景跑预配置加滚动时域调度取恢复电量的期望和90分位数来评估方案稳健性。6.3 落地前的一个验证技巧和一句忠告如果做的是科研复现有一个高效的验证技巧先拿一个已知最优解的小算例比如3节点、2台MPS、4个时段手算或穷举得到全局最优调度然后看模型能不能复现。这个小算例要包含一次“MPS移动后供电”的动作和一次“MPS转移”的动作两个动作都能复现再往大系统扩展就不容易出原则性错误。最后说个我自己的教训MPS动态调度模型本身并不难写难的是把配电网运行约束、交通约束和负荷时序约束放在同一个优化框架里不打架。我为此反复重写了三版数据接口最后发现与其追求模型“一步到位”不如从简化但闭环的版本跑通再加约束。希望这个“先跑通再加密”的思路能帮你在做配电网韧性提升和MPS动态调度时少走几段弯路。本文还有配套的精品资源点击获取