简介:这份资源是一份基于粒子群算法的配电网日前优化调度仿真包,面向电气工程、电力系统优化运行方向的研究者与学习者。它以IEEE33节点配电网为对象,搭建了包含风电、光伏、储能、柴油发电机与燃气轮机的经济调度模型,以运行成本与环境成本最小为目标,并考虑储能、潮流等约束条件,利用粒子群算法求解得到各电源的逐小时出力计划。资源包共8个文件,其中4个.m文件涵盖主程序与核心函数,3个.mat文件提供风、光及负荷等输入数据,1个txt文件用于辅助配置,整体仅11KB,轻量便携。目前已有144人学习下载,适合作为理解智能优化算法在配电网调度中应用的教学示例或课题参考。通过该资源可完整查看建模、目标函数设置、约束处理与PSO求解流程,便于在此基础上扩展改进或开展对比实验。
1. 配电网经济优化:一个 zip 包背后的“算钱”问题
一张配电线路的月度电费单上,最刺眼的往往不是终端电器的耗电,而是线损、负载不均衡和新能源反送带来的隐性成本。配电网经济优化要做的,就是把这张“看不见的账”显性化:给定网络拓扑、负荷曲线、分时电价和分布式电源出力,在电压、潮流、设备容量等物理约束下,算出 24 时段最优的购电、储能充放电和 DG 出力计划。标题里的 .zip 正是这类工作最常见的交付物——拓扑数据、参数表、求解脚本打成一个压缩包,拿到就能复现算例。适合配网调度、园区微网、光储充项目的工程师,以及电力方向研究生阅读。先抛一个反直觉结论:这包里的难点从来不在读数据,而在“经济性”和“可行性”打架时怎么权衡,所以值得花一整篇把模型和坑都拆清楚。
2. 拆开 zip 看家底:配电网经济优化包的典型文件结构与数据格式
2.1 别急着解压运行,先看目录长什么样
拿到配电网经济优化类的 zip 包,第一反应不应该是双击解压后直接点 run,而是先把它当成一个方案评审对象:目录怎么组织,数据从哪来,模型入口在哪。我经手的大多数这类工程包,即使来自不同团队,目录结构都出奇地一致,无非是 data、model、solve、output 四个区域的不同组合。下面是我常用于“摸底”的目录速览:
grid-economic-optimization/ ├── data/ # 所有输入数据都放这里 │ ├── bus.csv # 节点表:编号 / 有功负荷 / 无功负荷 / 基准电压 │ ├── branch.csv # 支路表:首端节点 / 末端节点 / 电阻 / 电抗 │ ├── dg.csv # 分布式电源表:接入节点 / 有功上限 / 单位发电成本 │ ├── storage.csv # 储能表:接入节点 / 容量 / 功率上限 / 充放电效率 │ └── price.csv # 分时电价表:24 行,每行对应一个时段 ├── model/ │ └── opt_model.py # 目标函数与约束的建模文件,读懂它等于读懂整个包 ├── solve/ │ └── run_case.py # 主脚本:读 data 里的表,调 model,求解,落盘结果 └── output/ # 结果输出目录:目标值、时段计划、电压与网损这段 tree 不是某个项目的私有配置,而是配电网经济优化里最常见的基础布局。对照它能快速判断一个包能不能跑:data 里有没有价格表、model 里是不是把潮流约束写全、solve 里有没有留结果出口。缺 price.csv 或让程序硬编码电价,这类包往往“能跑但没法改参数”,后续做敏感性分析时会很痛苦。
2.2 从 33 节点数据开始读表
配电网经济优化的标准“练手网”是 IEEE 33 节点系统,一个 12.66 kV 的辐射网。它的拓扑参数是公开的,所以大多数 zip 包里装的都是这 33 个节点的数据,只是列名和单位略有差异。读表时我一般先看两列:电压初值和负荷单位。如果 voltage 列全是 1.0,说明数据打算用标幺值;如果出现 12660,那就得仔细确认是不是 kV 和 V 混用。
import pandas as pd # 读节点表:编号、有功负荷(kW)、无功负荷(kvar)、电压初值(p.u.) bus = pd.read_csv('data/bus.csv') print(bus.head()) # 期望看到 bus_id 从 1 到 33,p_load 是正值,v_init 全是 1.0 # 读支路表:from_node / to_node / r(ohm) / x(ohm) branch = pd.read_csv('data/branch.csv') # 分支表里电阻电抗的单位必须与负荷单位匹配: # 如果负荷用 kW,阻抗用 ohm,那么潮流计算前要统一到标幺值; # 这一步不做,后面所有结果都会偏离实际。节点表的 p_load 是每天 24 个时段的序列还是单一峰值?这决定数据装载方式。典型做法是 bus.csv 只放拓扑参数,负荷曲线单放在 load_profile.csv 或用 DataFrame 的列表示时段。统一单位在流程上不麻烦,麻烦的是“每张表单位不同”的包——曾有团队把 DG 出力写成 MW、负荷写成 kW,目标函数算出来的购电成本直接大了 1000 倍,一度被误认为优化算法发现了“套利”机会。血泪经验:读表时顺手 print 出每列的 max/min,比任何调试都省时间。
2.3 电价、DG、储能:经济性的三张关键表
经济优化与单纯降网损的最大区别,在于引入了“钱”的系数:电价、发电成本、惩罚因子。这三样数据通常分布在三张表里。price.csv 是 24 时段分时电价,峰谷比通常在 3:1 到 5:1 之间,这是储能套利的经济基础。dg.csv 记录光伏或风机的有功上限和单位成本——光伏边际成本接近 0,但弃光惩罚必须写清楚。storage.csv 记录储能位置、额定容量、功率上限、充放电效率,而 SOC 上下限往往写在建模文件里而不是数据表里。
| 表 | 关键字段 | 易错点 |
|---|---|---|
| price.csv | 时段索引、购电价、售电价 | 时段对齐:1 对应 00:00 还是 08:00 |
| dg.csv | 接入节点、出力上限、成本 | kW/MW 混用 |
| storage.csv | 容量、功率、效率 | 效率是往返效率还是单程效率 |
| load_profile.csv | 节点、时段、有功/无功 | 缺无功负荷会高估电压水平 |
很多包把“弃光惩罚”放在 model 里当常量,改数据不动 model 就不会发现它影响多大。建议拿到 zip 后第一件事是把三个数值找出来:分时电价数组、储能 SOC 上下限、弃光惩罚系数。搞清楚它们,等于看懂了包主人的优化思路。这三张表是后续所有参数调整的入口,也是判断一个包“能不能用于实际项目”的分水岭。
3. 把“省钱”写成数学:目标函数与约束的建模要点
3.1 目标函数里的三笔账:购电、网损、弃光惩罚
配电网经济优化之所以叫“经济”,因为目标函数写的是钱。以日前调度为例,典型目标函数是:
$$\min \sum_{t \in T} \left[ c_{buy}(t) P_{buy}(t) + c_{loss} P_{loss}(t) + c_{curtail} P_{curtail}(t) \right] \Delta t$$
其中 T 是调度时段集合(通常 24 点),P_buy 是从上级电网购电的有功功率,P_loss 是网络损耗,P_curtail 是弃光或弃风功率。三个系数的数量级关系往往决定优化结果的风格:如果 c_loss 设得比 c_buy 还高,优化器会为了降几百瓦网损牺牲正常的购电策略,产出的计划一样能跑,但经济性不对。
弃光惩罚是配网经济优化和输电网经济调度差异最大的一处。分布式光伏在中午大发时,配网潮流倒送会导致根节点电压抬升甚至超过上限。面对这种情况,优化器有两种选择:让储能充电把多余电量吃掉,或者直接切掉一部分光伏出力。前者花钱但保电量,后者省钱但有惩罚。c_curtail 设得太小,结果会“合理”地丢弃大量光伏;设得太大,又可能逼着储能过充。常见做法是把弃光惩罚设为峰值电价的 1.5~2 倍,让优化器只有在电压越限时才去弃光。
从求解角度看,目标函数本身是线性的,难点全在约束。配电网经济优化本质是一个非线性、非凸的优化问题,正是约束这一层把“算钱”变成了“算不算得动”的问题。
3.2 DistFlow 潮流约束与二阶锥松弛
配电网的一大特点是 R/X 比值高,不能像输电网那样直接用直流潮流假设。支路潮流模型 DistFlow 是配电网里最常用的建模骨架,它用支路功率和节点电压幅值描述潮流分布:
$$ \begin{aligned} P_{ij} - r_{ij} l_{ij} &= \sum_{k: (j,k)} P_{jk} + P_{load,j} - P_{gen,j} \ Q_{ij} - x_{ij} l_{ij} &= \sum_{k: (j,k)} Q_{jk} + Q_{load,j} - Q_{gen,j} \ v_j &= v_i - 2(r_{ij} P_{ij} + x_{ij} Q_{ij}) + (r_{ij}^2 + x_{ij}^2) l_{ij} \ | (2P_{ij}, 2Q_{ij}, l_{ij} - v_i) |2 &\le l{ij} + v_i \end{aligned} $$
这里 v_i 是节点电压幅值平方,l_ij 是支路电流幅值平方。在 cvxpy 里,这段约束的写法如下——以 33 节点算例的示例代码片段演示:
import cvxpy as cp # 变量定义 P = cp.Variable((n_branch, T)) # 支路有功,形状 (支路数, 时段数) Q = cp.Variable((n_branch, T)) # 支路无功 v = cp.Variable((n_bus, T)) # 节点电压幅值平方 l = cp.Variable((n_branch, T)) # 支路电流幅值平方 # 潮流约束可写成矩阵形式: # A_p @ P + B_p @ v == C_p (节点功率平衡) # ||cp.hstack([2*P[:, t], 2*Q[:, t], l[:, t] - v_from])|| <= l[:, t] + v_from逐支路循环在 33 节点上能跑,但换到几百上千节点的配网会慢到不可接受。更常见的做法是构造关联矩阵(incidence matrix),把支路-节点的关系写成矩阵乘法,cvxpy 可以利用稀疏矩阵做向量化计算。
二阶锥松弛为何可行?关键是把原非凸的等号约束“l_ij · v_i = P_ij² + Q_ij²”放松成凸的二阶锥不等式。历史经验表明,在辐射状配网和合理的负荷范围内,松弛通常是“紧”的——最优解处等号成立,因此松弛不改变最优值。但“通常紧”不等于“一定紧”,这也是工程里最容易出黑匣子问题的地方:结果看起来在优化,实际可能在利用松弛度钻空子。所以正式项目里应在求解完成后拿回代潮流做校验。
3.3 储能 SOC 跨时段约束:求解器会“偷鸡”
储能建模是配电网经济优化里最容易写错的一环。SOC(荷电状态)的递推关系如下:
$$\text{SOC}{t+1} = \text{SOC}t + \frac{\eta{ch} P{ch,t} \Delta t}{E} - \frac{P_{dis,t} \Delta t}{E \eta_{dis}}$$
这里 SOC 是连续变量,P_ch 和 P_dis 是两个非负连续变量。如果只写 SOC 上下限和功率上限,不处理“同一时段同时充放电”,优化器会钻空子:同时充放,看似功率平衡,实则白白消耗效率损失,但目标函数里只有购电成本,没有惩罚这种行为的项,求解器就会造出同时充放电的解来满足约束。
处理方案有二:一是引入二进制变量 y,P_ch ≤ y·M,P_dis ≤ (1-y)·M,M 取功率上限的 1.1~1.2 倍。这个 M 不能取太大,太大会让混合整数问题的连续松弛性能变差。二是不用二进制,在目标函数里对 P_ch + P_dis 加一个小惩罚,迫使优化器避免同时充放。我在工程里偏好第一种,因为更严格、不依赖惩罚系数调;在纯连续问题、追求快速求解时,第二种也能接受。
初值 SOC_0 也影响结果。如果做日前调度,通常假设初始 SOC 已知(比如 0.2 或 0.5),而末尾 SOC_T 往往要求回到初值附近,否则“日复一日”的调度计划会在边界上失去可持续性——这也是一个常见的翻车点。
4. 从解压到跑通:配电网经济优化的实操流程与参数调整
4.1 解压不代表即点即用:先搭环境再管求解器
配电网经济优化的 zip 包和软件绿色版 zip 是两回事。这类包解压之后,第一件事是建虚拟环境、装依赖、安装求解器。常见依赖是 numpy、pandas、cvxpy(或 MATLAB 里的 Yalmip)。免费求解器推荐 Clarabel 或 SCS;商业项目用 Gurobi 或 Mosek,它们在 SOCP 的数值稳定性上比开源求解器更可靠,且能直接给出对偶间隙。
# 建议在项目根目录下创建一个干净的虚拟环境 python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate # 安装核心依赖 pip install numpy pandas cvxpy clarabel # Gurobi 需要单独安装并配置 license,学术版本可申请免费许可 # pip install gurobipy # 验证求解器是否就绪 python -c "import cvxpy; print(cvxpy.installed_solvers())"提示:cvxpy 装好了不代表有可用的求解器。installed_solvers 列表里没有 CLARABEL 或 GUROBI,后面建模写得再漂亮也是白搭。这一步是新手最常见的卡点,也是排错时最先要看的地方。
最后一行输出极其关键。如果列表是空的,就回头检查求解器安装;如果列表里有 CLARABEL,但求解规模超大时收敛慢,再考虑换 Mosek 或 Gurobi。
4.2 数据装载与预处理:单位、初值、时段对齐
读数据时我会强制做几件“护士发药”式的检查。推荐的步骤是:统一单位;打印每列 max/min;把价格表与负荷曲线按同一时间轴对齐。先看 3 张核心数据表的装载与校验代码:
import pandas as pd import numpy as np # 基准值(以 33 节点典型系统为例) S_base = 100.0 # kVA,基准功率 V_base = 12.66 # kV,基准电压 # 读节点表 bus = pd.read_csv('data/bus.csv') branch = pd.read_csv('data/branch.csv') price = pd.read_csv('data/price.csv', header=None, names=['hour', 'buy_price', 'sell_price']) # 单位对齐:把 kW 转成标幺值 bus['p_load_pu'] = bus['p_load_kw'] / S_base bus['q_load_pu'] = bus['q_load_kvar'] / S_base # 校验:负荷必须是正数、电压初值应在 0.9~1.1 p.u. 范围内 assert (bus['p_load_pu'] > 0).all(), "负荷不能为负" assert (bus['v_init'] >= 0.9).all() and (bus['v_init'] <= 1.1).all(), "电压初值异常" # 时段对齐:价格表必须有 24 行,且价格严格为正 assert len(price) == 24, "分时电价必须覆盖 24 个时段" print(price.describe())时段时间对齐的问题往往藏在“索引错位”里:有的包第一个时段从 00:00 开始,有的从 08:00 开始。如果光伏出力曲线与价格时段对不上,储能会在电价的低谷时段“白天充电、夜里放电”,出来一条完全错误的曲线。
4.3 搭建带储能与 DG 的 24 时段优化并求解
主脚本中把数据与模型拼起来,核心套路是:先构造变量,然后加潮流约束与边界约束,最后设置目标函数并求解。
import cvxpy as cp import numpy as np T = 24 n_bus = len(bus) n_branch = len(branch) n_dg = 3 # 假设 3 台 DG n_sto = 1 # 假设 1 套储能 # 决策变量 p_buy = cp.Variable(T, nonneg=True) # 上级购电有功 p_dg = cp.Variable((n_dg, T), nonneg=True) # DG 出力 p_curtail = cp.Variable((n_dg, T), nonneg=True) # 弃光功率 p_ch = cp.Variable((n_sto, T), nonneg=True) # 储能充电功率 p_dis = cp.Variable((n_sto, T), nonneg=True) # 储能放电功率 soc = cp.Variable((n_sto, T + 1)) # SOC 轨迹,T+1 是因为含初始时刻 constraints = [] # 功率平衡约束(简化写法示意:正式实现需配合 DistFlow 潮流变量) # 这里用“根节点购电 + DG + 储能放电 - 储能充电 >= 总负荷”体现功率平衡逻辑 for t in range(T): constraints.append( p_buy[t] + cp.sum(p_dg[:, t]) - cp.sum(p_curtail[:, t]) + cp.sum(p_dis[:, t]) - cp.sum(p_ch[:, t]) >= bus_p_load_total[t] ) # 储能 SOC 递推与边界:容量 500kWh、功率上限 100kW、SOC 0.1~0.9 E_cap = 500.0 # kWh eta_ch = 0.95 # 充电效率 eta_dis = 0.95 # 放电效率 for idx in range(n_sto): constraints.append(soc[idx, 0] == 0.2) # 初始 SOC for t in range(T): constraints.append(soc[idx, t+1] == soc[idx, t] + (eta_ch * p_ch[idx, t] - p_dis[idx, t] / eta_dis) / E_cap) constraints.append(0.1 <= soc[idx, t+1]) constraints.append(soc[idx, t+1] <= 0.9) constraints.append(p_ch[idx, t] <= 100.0) constraints.append(p_dis[idx, t] <= 100.0) # 目标函数:购电成本 + 弃光惩罚 c_buy = price['buy_price'].values purchase_cost = c_buy @ p_buy # 逐时段购电成本之和 c_curtail = 1.8 * c_buy.max() # 弃光惩罚设为峰值电价的 1.8 倍 curtail_cost = c_curtail * cp.sum(p_curtail) obj = cp.Minimize(purchase_cost + curtail_cost) prob = cp.Problem(obj, constraints) prob.solve(solver='CLARABEL', verbose=True) print("最优目标值(购电+弃光惩罚):", prob.value)代码里的关键参数集中在三处:E_cap 决定储能“挪电”的规模;SOC 上下限 0.1/0.9 是安全边界;η 决定每一度电经过储能后的损耗。调参方向很明确——储能容量越大,削峰填谷能力越强,但 SOC 上下限过窄会让大容量形同虚设;η 过低时优化器会减少储能的充放电频次。
| 参数 | 典型值 | 调整方向 |
|---|---|---|
| E_cap 储能容量 | 500 kWh | 增大则削峰能力增强,但投资成本上升 |
| SOC 上下限 | 0.1~0.9 | 收窄则保护寿命,但可调度区间变小 |
| 充放电效率 η | 0.95 | 效率越低,优化器越少动用储能 |
| 弃光惩罚 | 1.8×峰值电价 | 过低会弃光,过高会逼储能过充 |
| 分时电价 | 峰谷比 3:1~5:1 | 峰谷差越大,储能套利空间越大 |
4.4 求解器返回异常时先查这三个位置
如果 prob.value 异常小甚至为负,不是“挖到金矿”,多半是模型写错了。我一般依次查三个点:一是弃光惩罚系数是不是被设成 0,二是储能有没有同时充放约束,三是价格表是否瞬时归零。三者都会骗过目标函数,做出“负成本”的假象。求解时报 infeasible 时,优先看约束集合的数值缩放:单位没统一时,功率平衡约束的数值可能差出几个数量级,对求解器数值稳定性是毁灭性的。把 kW 级物理量换算成标幺值或统一到 MW,再跑一遍,大概率恢复正常。
5. 配电网经济优化的高频坑与排查清单
5.1 潮流不收敛:先怀疑初值而不是模型
现象:求解直接报 infeasible 或数值错误,第一轮迭代就发散。
原因:最常见的是电压初值给了 0(标幺值体系下绝对不可行),或负荷表里混进了负值,导致潮流方程无解。极少数情况是 DistFlow 里支路方向搞反了,首末节点颠倒,电流平方变量变负。
解决:先跑一个“只做潮流、不做优化”的测试,把 DG 和储能置零,仅保留根节点供电,看潮流约束能否满足。若这都过不了,问题肯定在数据层;若能过,再逐步加入 DG 和储能。这条隔离思路能减掉 90% 的排查时间。
5.2 二阶锥松弛不紧:对偶间隙下不来
现象:优化结果看起来“太完美”,但用潮流验证时电压越限或功率不平衡。求解器日志里 conic residual 一直偏高。
原因:SOCR 在高负荷、重无功的场景下会不“紧”,即最优解处锥约束没有取等号,松弛后的可行域比真实潮流大,让优化器“占便宜”。另外,如果 M 值取得过大,混合整数变量会在连续松弛时形成巨大可行域,也会诱导松弛不紧。
解决:先检查目标函数里网损权重是否过高;再看支路无功 Q 的量级。若 Q 比 P 大一个数量级,先回头查无功负荷数据是不是 kvar/kVAr 之类的单位问题。工程里我习惯在 SOCP 求解完成后,把得到的 v 和 P/Q 反带回原始潮流方程做一次校验,用残差判断松弛紧度。这是一道必做的“后悔药”,别省。
5.3 储能 SOC 越界:跨时段的状态传错了
现象:SOC 曲线在各时段之间直上直下,甚至出现负值;或连续几天调度计划无法闭环。
原因:递推式 SOC_{t+1} = SOC_t + ... 在读取结果时没按顺序累加,或约束里漏了 SOC_{t+1} 的上下限(只约束了 SOC_t)。另一个常见元凶是 Δt 不匹配:24 时段里第一个时段跨 2 小时,后面跨 1 小时,而代码统一用 1.0 乘。
解决:第一步确认 Δt 数组与 price 表逐时段对应;第二步把 SOC 上下限同时作用于 t 和 t+1;第三步在结果输出里打印 SOC 的 min/max,若越界,直接在约束里给决策变量本身加 bound。SOC 是配电网经济优化里最容易出问题的变量,曲线画得再顺也要做交叉验证。
5.4 弃光惩罚设太低:光伏全被“合理”丢弃
现象:光伏大发的午间时段,优化结果选择弃掉大部分光伏,购电曲线却在抬升。
原因:弃光惩罚 c_curtail 被设成 0,优化器发现“不发电还能少承担网损”,就会主动弃光。这在数学上没错,在工程上很荒谬。
解决:把 c_curtail 设为峰时购电价的 1.5~2 倍。单价建议从 model 里抽出来,改成从数据表读取,方便日后做不同场景的参数扫描。另一种工程做法是给 DG 增加最低出力比例约束(比如午间不低于 70%),但那是另一种建模思路,不如调惩罚系数来得直接。
5.5 单位混用:kW 与 MW 的经典血泪
现象:目标函数值忽大忽小,或者与手算的购电成本差 3 个量级。
原因:一张表用 kW,一张表用 MW,还有一张用 p.u.,分布在不同人维护的数据集里,不显眼但致命。
解决:在所有数据装载代码后面加一条“单位强制检查”:
def check_units(df, name, expected_max, expected_min=0): lo, hi = df.min().min(), df.max().max() assert lo >= expected_min and hi <= expected_max, \ f"{name}: 数值越界 {lo} ~ {hi},请检查单位" print(f"{name}: {lo:.4g} ~ {hi:.4g}, OK") check_units(price['buy_price'], '分时电价', 2.0) # 元/kWh check_units(bus['p_load_kw'], '负荷', 4000.0) # 单节点 kW check_units(p_dg.value, 'DG出力', 1500.0) # 单台 DG 上限这个函数花十分钟写,能救回整个下午。单位混用属于“不翻车则已,一翻车就是大翻车”的坑,值得单独设一道关卡。
6. 配电网经济优化结果可信吗:4 个检查点与敏感性测试
跑完不是终点,输出一套 24 时段的充放电计划后,还要过 4 个检查点。第一是功率平衡校验:把根节点购电加 DG 出力,减去负荷、储能净充电和网损,余量应当小于设定容差。第二是 SOC 首尾闭环:检查末时段 SOC 是否回到初始值附近,回不去的话,第二天调度计划会从错误状态起步。第三是锥松弛紧度:用一套潮流验证工具把优化出的注入功率回代,比对支路功率残差;残差超标说明 SOCR 没抱紧,结果不能直接用于现场。第四是成本构成校验:分时段列出购电成本与网损成本占比,看看是否出现某个时段成本突然为 0 之类的怪相。
对经济优化还需要做敏感性测试:把 c_buy 和 c_curtail 各偏移 ±30%,重跑同一模型,看最优解是否会剧烈跳变。如果 10% 的参数扰动让储能策略完全反转,说明目标函数在最优解附近过“平”,此时应检查是否漏了约束(比如购电上限)。这类验证我一般写在 run_case.py 末尾,把结果导出成 CSV 后再做一次人工审核。
我个人经历里印象最深的一次翻车:SOC 初值设成了 0.9,优化器一天都没有给储能充电,结果第二天一开跑就触发出力不足。从那以后,我在任何调度包里都会强制打印首末时段 SOC 值。这种包好不好用,往往就体现在这种细节上。希望这份拆解能帮你少走一段弯路。
本文还有配套的精品资源,点击获取