
并网模式下的微电网经济调度往小了说是做优化算法往大了其实是分布式能源怎么跟大电网和谐相处的问题。这几年光伏、风电、储能越装越多微电网早就不是实验室里的概念了但真正落地的时候大家发现最难的不是硬件而是“怎么用最省钱的方式把电发出来、调出去”。这个问题的核心就是一个带约束的非线性优化问题而粒子群算法在这个场景里几乎是出场率最高的解法——参数少、好上手、不用求导迭代几十轮就能看到成本曲线往下掉,调参的空间也大很适合做入门到实战的过渡。我在帮几个项目做微电网仿真时试过遗传算法、模拟退火也试过把约束写进目标函数的罚函数法最后在工程上报得最久的还是粒子群。这篇内容就围绕并网模式下微电网经济调度展开从问题建模、粒子群原理、代码实现到调参经验一次说透。适合刚接触微电网优化调度、或者想搞懂粒子群算法怎么落地到电力系统场景的同学参考。1. 并网模式下的微电网经济调度到底在解什么题1.1 微电网里的“经济调度”不是简单的最小成本微电网通常包含分布式光伏、风电、柴油发电机、储能电池还有可控负荷并网模式下又多了大电网这个“无限大功率源”。经济调度要回答的问题很直接在满足负荷需求的前提下未来一个小时甚至一天内每台机组发多少电、储能充放多少、从大电网买多少电才能把总运行成本压到最低。但注意这里说的“成本”不是单纯的煤耗或者购电电价。实际工程里至少包含三块燃料成本柴油发电机发电的燃油消耗成本通常用二次函数拟合即Cost_fuel aP^2 bP c其中a、b、c是机组的耗量特性系数P是出力。运行维护成本设备启停损耗、维护折算常简化为与出力相关的比例项。购电成本并网模式下从大电网购入电量的费用分时电价下各时段不同如果微网有富余电力还可以反向售电对应售电收益。目标函数就是这三块的代数和取最小化。单看这个目标好像就是个典型优化问题但真正让问题变复杂的是约束条件。1.2 并网模式带来的机会和约束并网模式最大的特点是微电网与大电网之间有功率交换通道这意味着缺电时可以买多电时可以卖。从调度角度看相当于增加了一个边界无穷大的“虚拟机组”但这个虚拟机组不是免费的它的价格是分时电价而且功率交换通常有上限变压器容量或联络线容量不可能无限买电。此外并网模式下的约束比孤岛模式更多一层功率平衡约束所有发电出力、储能充放电、购电售电与负荷之间必须每时每刻平衡。机组出力上下限约束分布式电源和柴发的出力范围。储能SOC约束荷电状态不能越界充放电功率也不能超限。联络线功率约束与大电网交换的功率要在允许范围内。爬坡约束柴油发电机的出力变化速率限制这一条很多初学者会漏掉但实际运行中不满足爬坡约束的调度方案根本没法执行。所以并网模式下经济调度其实是一个多变量、多约束、分段非线性的优化问题。普通线性规划很难直接处理因为目标函数里含二次项约束又不全平滑。粒子群算法的优势就在这里它不要求目标函数可导不需要知道梯度只要能把解表示成粒子位置能把约束处理成罚函数或修复策略就能迭代逼近最优解。1.3 调度周期与决策变量的选取通常按小时级做排列组合。假设调度周期是24小时步长为1小时决策变量包括各柴油发电机每个小时的出力 P_gen[t]储能每小时充放电功率 P_bat[t]正为放电负为充电与大电网交换功率 P_grid[t]正为购电负为售电可平移负荷的启停状态如果有需求响应决策变量总数 机组数 × 时段数 储能时段数 联络线时段数。一个典型的微电网例如2台柴发、1台储能、1条联络线、24个时段决策变量就是 2×24 24 24 96 维。粒子群算法处理96维问题其实很轻松粒子数设置几十个就够了几秒钟就能迭代完成。2. 粒子群算法原理从鸟群觅食到微电网寻优2.1 粒子的“飞行”逻辑粒子群算法是受鸟群觅食行为启发的群体智能算法。每只“鸟”粒子代表一个候选解在多维空间中飞行飞行的方向由两个因素决定粒子自己的历史最优位置pbest和整个群体的历史最优位置gbest。用公式来表达就是v[i] w * v[i] c1 * r1 * (pbest[i] - x[i]) c2 * r2 * (gbest - x[i]) x[i] x[i] v[i]其中 w 是惯性权重v是速度x是位置c1、c2是学习因子r1、r2是[0,1]随机数。解释得直白一点粒子下一时刻的速度等于“保持原来惯性”加上“朝自己见过的最好方向修正”加上“朝大家共认的最好方向修正”。三个分量的权重对应了探索和开发的权衡。在微电网调度场景里粒子的位置就是一个调度方案所有维度的数值就是各时段各设备的出力。适应度函数就是总运行成本加上罚函数成本越低粒子位置越优。2.2 为什么粒子群拈合经济调度很顺手我在做算法对比时发现粒子群在微电网经济调度里受欢迎不只是因为它简单。关键在于不需要梯度信息目标函数即使分段、带罚函数、非光滑粒子群照样迭代不像牛顿法那样要求连续可导。天然适合并行计算多个粒子互不依赖适应度计算可以并行工程上用多进程或者向量化编码都能加速。对初值不敏感随机初始化也能收敛到不错的解不像某些启发式算法对初值要求极高。实现成本低核心代码不到一百行改造成本低适合快速验证。当然它也有缺点比如容易早熟收敛、局部搜索能力弱。但这个可以通过参数调整、变异策略和局部搜索来弥补后面讲调参时会专门说。2.3 标准PSO的缺陷和应对纯标准PSO在调度问题上最常见的症状是迭代前期成本降得很快但后期在最优解附近震荡始终压不到理论最优点。这是因为粒子群体多样性下降所有粒子都挤到gbest附近搜索范围缩死了。常用的应对策略有三种惯性权重递减w从0.9线性降到0.4前期全局探索、后期局部开发。速度边界限制限制v_max避免粒子飞出搜索空间。变异/扰动对gbest随机扰动或者每迭代几轮重置部分粒子的位置维持多样性。这些对策都是工程上验证有效的后面代码里会一并体现。3. 经济调度建模目标函数、约束与罚函数设计3.1 目标函数的工程写法为了方便用粒子群求解目标函数写成可计算的最小化形式。假设微电网内包含柴油发电机、储能电池和与大电网的交互目标函数可以写成min F Σ [ FuelCost(P_gen[t]) OM(P_gen[t]) P_bat[t]*C_bat P_grid[t]*C_buy[t] - P_sell[t]*C_sell[t] ]其中 C_buy[t] 是分时购电价C_sell[t] 是售电价P_grid[t]大于0为购电小于0为售电。储能充放电的单位成本常数 C_bat 用于折算电池损耗。实际项目里我一般会把售电收益单独拆开避免把负功率直接乘购电价产生歧义代码里用条件判断分情况计算。3.2 约束条件的处理方式粒子群算法本身不直接处理约束需要把约束变成可量化惩罚或可行解修复。我的经验是功率平衡这类的等式约束用修复策略解决设备上下限这类不等式约束用位置裁剪解决SOC约束用罚函数加裁剪结合处理。具体来说设备出力上下限粒子编码时直接限制位置范围每一维都限制在对应的 [min, max] 区间内超界就裁剪回边界。功率平衡约束计算出各机组出力和负荷后剩余的功率缺口/盈余由大电网承担即P_grid[t] Load[t] - ΣP_gen[t] - P_bat[t] - P_pv[t] - P_wind[t]然后把 P_grid[t] 限制到联络线上下限内。这样等式约束天然满足。储能SOC约束SOC递推公式为SOC[t] SOC[t-1] - P_bat[t] * Δt / CapacitySOC[t]必须在[0.2, 0.9]内。这里用罚函数对越界的时段加高昂惩罚成本迫使粒子往不越界的区域飞。爬坡约束实际中用相邻时段出力差的罚函数因为直接修复会破坏粒子结构。罚函数项通常设置为Penalty λ1 * Σ max(SOC_min - SOC[t], 0)^2 λ2 * Σ max(SOC[t] - SOC_max, 0)^2 λ3 * Σ max(0, |P_gen[t] - P_gen[t-1]| - ramp_limit)^2λ取值需要调试太小约束形同虚设太大又会让算法只顾罚函数而忽略真实成本一般先设几千微调。3.3 决策变量编码方式编码方式直接影响粒子群的搜索效率。我常用两种方式方式一直接编码各设备出力。粒子维度 时段数 × 设备数优点是直观缺点是变量多搜索空间大。方式二编码柴发出力和储能功率联络线功率按功率平衡计算。维数减少搜索空间缩小收敛更快。实际案例中我推荐方式二。因为联络线功率本身是一个“余量”变量由其他决策组合决定强行作为独立变量容易破坏功率平衡还得修复绕一圈不如直接算。下面用一个具体案例展示建模和求解过程。4. 基于Python的粒子群经济调度实现4.1 案例场景数据假设一个园区微电网包含1台柴油发电机额定功率500kW运行范围150kW~500kW耗量系数 a0.0008, b0.2, c15爬坡速率100kW/h光伏阵列峰值功率300kW某典型日光伏出力已知储能电池容量600kWh最大充放电功率150kWSOC运行范围20%~90%初始SOC 50%联络线最大交换功率300kW分时购电价峰时1.2元/kWh、平时0.8元/kWh、谷时0.4元/kWh售电价分别为0.9、0.6、0.3元/kWh负荷24小时典型日负荷曲线调度周期24小时时间步长1小时。柴油发电机组燃料成本即目标函数中的二次函数储能损耗成本取0.05元/kWh。4.2 基础代码结构先定义一个适应度计算函数输入是粒子位置柴发出力序列P_gen和储能功率序列P_bat输出是总成本。粒子位置是一个48维向量前24维是柴发各时段出力后24维是储能各时段功率正为放电负为充电。import numpy as np def objective(x): P_gen x[:24] # 柴发出力 P_bat x[24:] # 储能功率 P_load load_curve # 24h负荷 P_pv pv_curve # 24h光伏 P_grid P_load - P_gen - P_bat - P_pv # 联络线上下限裁剪多余功率无法售出缺口只能切负荷或购电上限 P_grid np.clip(P_grid, -300, 300) fuel_cost np.sum(a * P_gen**2 b * P_gen c) om_cost 0.02 * np.sum(P_gen) bat_cost 0.05 * np.sum(np.abs(P_bat)) buy_cost 0.0 sell_income 0.0 for t in range(24): if P_grid[t] 0: buy_cost P_grid[t] * C_buy[t] else: sell_income (-P_grid[t]) * C_sell[t] # SOC约束 soc 0.5 soc_penalty 0 for t in range(24): soc soc - P_bat[t] / capacity if soc 0.2: soc_penalty (0.2 - soc) ** 2 * 5000 if soc 0.9: soc_penalty (soc - 0.9) ** 2 * 5000 # 爬坡约束 ramp_penalty 0 for t in range(1, 24): diff abs(P_gen[t] - P_gen[t-1]) if diff 100: ramp_penalty (diff - 100) ** 2 * 300 total fuel_cost om_cost bat_cost buy_cost - sell_income soc_penalty ramp_penalty return total注意这里用的是电量纲简化实际程序里要统一单位比如功率kW乘以1小时就是kWh成本以元为单位。4.3 粒子群主循环粒子群的核心循环可以封装成一个类或者函数。为了工程可读性我拆成初始化、速度更新、位置更新三块class PSO: def __init__(self, dim, pop_size, max_iter, w, c1, c2, bounds): self.dim dim self.pop_size pop_size self.max_iter max_iter self.w w self.c1 c1 self.c2 c2 self.bounds bounds self.x np.random.uniform(lowbounds[:, 0], highbounds[:, 1], size(pop_size, dim)) self.v np.random.uniform(low-1, high1, size(pop_size, dim)) * 0.1 self.pbest self.x.copy() self.pbest_score np.array([objective(x) for x in self.x]) self.gbest self.pbest[np.argmin(self.pbest_score)] self.gbest_score np.min(self.pbest_score) def run(self): for it in range(self.max_iter): w_cur 0.9 - (0.9 - 0.4) * it / self.max_iter # 权重递减 r1 np.random.rand(self.pop_size, self.dim) r2 np.random.rand(self.pop_size, self.dim) self.v w_cur * self.v self.c1 * r1 * (self.pbest - self.x) self.c2 * r2 * (self.gbest - self.x) self.x self.x self.v for d in range(self.dim): self.x[:, d] np.clip(self.x[:, d], self.bounds[d, 0], self.bounds[d, 1]) scores np.array([objective(x) for x in self.x]) better scores self.pbest_score self.pbest[better] self.x[better] self.pbest_score[better] scores[better] best_idx np.argmin(scores) if scores[best_idx] self.gbest_score: self.gbest_score scores[best_idx] self.gbest self.x[best_idx].copy() return self.gbest, self.gbest_score这个版本用了线性递减惯性权重速度更新时保留了随机因子每次迭代后会裁剪位置到边界内。实际测试中迭代300次左右收敛曲线已经很平。4.4 边界设置与初始化技巧边界设置要根据设备参数来bounds数组的每个维度对应一个决策变量柴发前24维[150, 500]储能后24维[-150, 150]负值代表充电初始化时不需要特意保证可行性只要在范围内随机就行。因为SOC约束有罚函数初值即使让SOC越界迭代几轮后粒子也会被罚函数拉回可行域。但有一个技巧是在初始化时把储能功率序列设成“先放电后充电”或者“平滑曲线”收敛速度会明显加快。我推荐一个实操技巧随机初始化后手动计算一次每个粒子的SOC序列把明显会越界的粒子重新初始化。这个“预筛选”能减少约20%的迭代次数。5. 仿真结果分析与典型数据曲线5.1 收敛过程观察用上面的代码跑一次典型日粒子数30迭代300次。成本下降曲线大致是第0代初始随机方案的成本可能在2万元左右迭代50代后降到1.5万元附近100代后降到1.45万元150代后基本稳定在1.43万元。前期下降快是因为粒子从随机状态快速修正后期下降慢是因为大家聚集在最优区域只能用局部搜索慢慢磨。如果你看到成本曲线后期呈锯齿状波动大概率是罚函数系数太大或者速度更新时随机扰动太强。前者会让粒子在可行域边界反复震荡后者会让粒子在最优解附近“刹车不住”。5.2 并网模式下的典型调度策略从仿真结果看并网模式下粒子群给出的调度策略很有规律谷时段电价低储能大概率处于充电状态柴发出力压到下限负荷尽量从大电网买电。峰时段电价高储能放电顶高峰柴发接近满发必要时微网向大电网售电获利。平时段电价中等根据负荷曲线和光伏出力灵活切换。这个结果其实符合经济学直觉粒子群并没有“理解”分时电价但通过适应度函数的引导它自动学习到了“低买高放”的策略。这也是我为什么愿意在项目里用粒子群的原因——不用显式写业务规则把成本函数写好算法自己就能挖出不少令人意外的调度规律。5.3 与线性规划结果的对比我用同样的边界数据跑了Python的线性规划求解器做对比发现在不考虑非线性成本项的理想情况下线性规划求得的成本比粒子群低大约1%~2%。但一旦加入爬坡约束、SOC罚函数、分段电价这些实际约束线性规划模型表达起来很麻烦甚至要引入大量辅助变量性能优势反而没了。粒子群的优势不在精度而在建模灵活性和实现成本。工程项目里成本最优解往往只是一个参考调度方案还要考虑机组启停次数、设备寿命、操作可行性粒子群这种灵活框架能轻松加自定义约束这个优势更大。代码跑完可以输出一张各时段出力堆叠图横轴是时间纵轴是功率堆叠柴发、光伏、储能放电和购电功率。做汇报时这张图比任何表格都直观。6. 粒子群调参与避坑实录6.1 关键参数怎么定粒子群参数虽少但每个都影响巨大我直接把常用经验值列成表参数常用范围影响方向我的建议粒子数 pop_size20~60越大搜索越充分但计算量线性增长变量维数48时取30~40足够最大迭代次数200~500太少不收敛太多浪费时间300是甜点跑完看曲线再决定惯性权重 w0.4~1.0越大越倾向全局探索越小越局部开发线性递减0.9→0.4最稳学习因子 c1/c21.0~2.5c1大个体探索强c2大群体收敛快c1c22.0经典配置实际也够用最大速度 v_max边界宽度的10%~20%限制粒子飞行跨度储能维度设30柴发维度设50一个非常常见的错误是把所有维度的v_max设成同一个值。柴发出力范围和储能功率范围不同速度限幅也应该分开。不然粒子在储能维度飞得太快SOC很容易在几轮迭代内剧烈震荡罚函数项爆发适应度数值会变得很大整个收敛过程就毁了。6.2 罚函数系数调参的实战建议罚函数系数λ的调节是比较折磨人的环节。实用经验是先设一个较大的罚函数系数比如5000跑一次看约束违反量是否趋近于0。如果约束违反量趋近0但成本曲线出现的“平台期”很早说明罚函数系数过大粒子被罚得太狠不敢靠近边界搜索空间被压缩了改为2000~3000再跑。如果约束违反量一直不收敛说明罚函数系数太小约束形同虚设加大到8000以上。另一个更省事的做法是动态惩罚迭代初期λ小让粒子自由探索迭代后期λ增大强迫粒子进入可行域。但这个做法实现稍复杂基础版本用固定系数就能处理大部分场景。6.3 多次运行同一组数据结果不一样怎么办粒子群随机初始化导致每次运行结果有微小差异这是正常现象不是bug。我自己在项目交付时有个标准流程同一场景至少跑10次取最优或者平均结果。看每次运行的成本标准差标准差小于1%说明算法稳定如果标准差很大优先检查是否陷入局部最优这时需要加大粒子数或者提高惯性权重前期值。把每次运行的最优解对应的调度曲线画出来人工目测是否合理。算法认为最优但曲线乱跳的方案往往含有隐性不合理调度需要结合人工经验判断。有一次我跑了20轮每轮成本都差不多但有一轮的储能SOC曲线在凌晨时段异常上冲到92%虽然罚函数把它拉回了90%但说明算法在边界附近找到了一个“钻空子”的方案。后来把SOC上限罚函数加到了20000这个现象才消失。所以仿真结果不能只看总成本数字一定要配合设备功率曲线、SOC曲线一起看。6.4 收敛速度慢可以尝试的加速技巧如果你发现迭代300次还明显没收敛除了调参还可以从三个方向加速第一缩小搜索空间。把柴发出力边界从[150,500]收缩到日常预测运行区间比如[200,450]粒子初始位置就在一个比较合理的区域省去大量无效飞行。第二把已知的一个可行调度方案作为初始粒子之一放进去。比如把“柴发恒功率、储能不动作、电网全额买电”方案作为初始pbest这相当于给粒子群一个“合理的锚点”能加速收敛。第三用向量化计算替代循环。Python里objective函数如果逐时段循环300次迭代乘以30个粒子总计算量不小用numpy数组运算能快3~5倍。7. 实操心得总结我在多个微电网算例里用过粒子群算法之后最大的感受是这个算法不是靠“聪明”取胜而是靠“宽容”取胜。它不要求目标函数光滑、不要求约束线性、不要求初始解可行只要你愿意把实际约束写成罚函数它就能给你一个工程上能用的好方案。比起那些求解一阶必要条件、强依赖凸性的数学优化方法粒子群在微电网这样强非线性、多约束场景里往往更实用。还有一个容易忽略的点算法写完之后一定要花时间把输入数据看清楚。分布式光伏出力曲线、负荷曲线到底代表的是预测值还是实测值分时电价有没有包含输配电价这些数据层面的小细节对调度结果的影响远大于算法上的某个参数调整。数据不对再好的粒子群也白搭。如果后续你想往深走可以在这个基础上加多目标优化比如同时最小化成本和碳排放或者加入需求响应模型。粒子群改造成多目标版本也不难加一个非支配排序就行。这个方向我后面会单独写一篇先把单目标调度跑通跑透再往复杂里扩展。