交直流混合微网程序这个方向,我在课题组里前前后后折腾了小半年。最开始接到任务时,以为只是把交流微网和直流微网拼在一起,跑个潮流、做个优化就完事,真正动手才发现坑比想象中多:风光出力的随机性怎么描述,场景怎么生成才靠谱,缩减到什么程度计算量才扛得住,粒子群算法又该怎么编码约束才不崩。今天把整套程序从设计思路到具体实现掰开揉碎讲一遍,给正在做微网调度、或者想用场景法处理不确定性的朋友一个能直接抄作业的参考。
这套程序的核心链路很清晰:用拉丁超立方抽样生成大量风光出力场景,再通过多场景缩减得到少数典型场景,最后用粒子群算法在典型场景下做交直流混合微网的日前优化调度。它解决的核心问题是“风光出力不确定条件下,混合微网怎么安排各分布式电源、储能和换流器的出力,让总运行成本最低”。适合电力系统方向的研究生、做微网能量管理的工程师,以及刚开始接触场景优化和群智能算法的同学拿来练手和拓展。
1. 程序整体设计与建模思路
1.1 交直流混合微网为什么难算
传统的交流微网研究已经够成熟了,直流微网这几年也火,但交直流混合微网是把两条母线放在一个系统里,通过AC/DC换流器耦合。交流侧挂风电、柴油机、交流负荷,直流侧挂光伏、储能、直流负荷,两侧之间用双向换流器交换功率。
这个结构带来的麻烦是:潮流计算要从纯交流的牛拉法,变成交直流交替迭代;优化调度时又多了一个换流器传输功率的决策变量,而且换流器还有容量限制和损耗;两侧功率平衡需要耦合方程来约束,不能各自为政。我在程序里用的是简化的稳态模型,即认为换流器可以双向传输功率,传输效率恒定(比如取95%),容量限制作为不等式约束,这样既保留了交直流耦合的关键特性,又不至于把程序复杂到没法跑。
建模前先把系统参数列清楚,这一步很重要。交流母线电压、直流母线电压、各分布式电源的容量和爬坡率、储能额定容量和SOC上下限、换流器额定容量、联络线功率上限、分时电价,这些参数不准备好,后面写约束就是空中楼阁。我习惯用结构体或MATLAB的表格统一存,避免后面改参数时到处翻代码。
1.2 用场景法处理风光不确定性的完整链路
风光出力的随机性本质上是一个连续的概率过程。处理不确定性的主流方法有三类:鲁棒优化、机会约束规划、场景法。
鲁棒优化最保守,要保证最恶劣情况下也满足约束,结果往往过于悲观;机会约束允许以一定概率不满足约束,但对随机变量的分布假设要求高;场景法最直观,用大量离散场景去近似连续不确定性,然后对场景做期望意义上的优化,既保留了随机性信息,计算复杂度又在可控范围内。
这套程序采用场景法,完整链路是:概率分布建模 → 拉丁超立方抽样 → 多场景缩减 → 典型场景下的确定性优化。先说概率分布建模,风电出力通常先对风速做Weibull分布建模,再用风速-功率曲线折算成出力;光伏出力则用Beta分布描述光照强度,再结合光伏板参数计算输出功率。实际写程序时,如果拿不到实测历史数据,就用这两个常用分布生成风速和光照样本,再按公式转换成功率。
抽样完成后,初始场景数量很大(通常500到2000个),每个场景都做优化调度不现实,所以必须先缩减到5到10个典型场景,并保留每个场景的概率,最后目标函数变成“场景概率×场景成本”的加权和。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 拉丁超立方抽样原理与MATLAB实现
2.1 LHS的核心思想:分层比随机更重要
拉丁超立方抽样(Latin Hypercube Sampling,LHS)和蒙特卡洛抽样最大的区别在于“分层”两个字。蒙特卡洛抽样是完全随机的,样本落在概率密度高的区域就多,落在尾部就少,可能出现局部聚集,导致样本对分布的代表性不稳定。
LHS的做法是:把每个变量的累积概率分布[0,1]区间均匀分成N层(N为抽样规模),每一层里随机抽取一个概率值,再通过逆累积分布函数映射回实际变量取值。这样保证每个变量在整个取值范围内都被均匀覆盖,尤其是分布的尾部。
打个生活化的比方:蒙特卡洛抽样像是闭着眼睛往靶子上扔飞镖,可能全扎在左上角;LHS则像是先在靶子上画好网格,保证每个格里都有飞镖,只是每一格内的落点随机。因此LHS在相同抽样数下,覆盖性远好于蒙特卡洛,对后续场景缩减和优化也更有优势。
在风光不确定性建模中,风速和光照都有明显的偏态分布和尾部特征,用LHS能更好地保留极端阴天、大风天这类小概率但高影响的场景。
| 对比项 | 蒙特卡洛抽样 | 拉丁超立方抽样 |
|---|---|---|
| 抽样原理 | 完全随机 | 分层随机,每层一样本 |
| 分布覆盖性 | 可能出现聚集,尾部覆盖差 | 全区间均匀覆盖 |
| 达到同等精度的样本数 | 多 | 通常少30%-50% |
| 程序复杂度 | 低 | 中,需处理分层和打乱 |
2.2 采样代码一步一步写
MATLAB实现LHS其实不复杂,最通用的写法是分层均匀抽样加乱序排列。假设我们要生成N个风速场景,每个场景有T个时段,风速服从形状参数k、尺度参数c的Weibull分布:
matlab复制% 参数设置
N = 1000; % 初始场景数
T = 24; % 调度时段数(小时)
k = 2.1; c = 8.5; % Weibull 形状参数和尺度参数
% 拉丁超立方抽样
u = lhsdesign(N, T); % 生成 N 行 T 列,每列都是 [0,1] 上的分层均匀随机数
wind_speed = wblinv(u, c, k); % 通过逆累积分布函数转换为风速
% 风速转功率(分段线性功率曲线)
v_in = 3; v_out = 25; v_rated = 12; P_rated = 0.6; % 单位 MW
wind_power = zeros(N, T);
for t = 1:T
v = wind_speed(:, t);
p = zeros(N, 1);
p(v >= v_in & v < v_rated) = P_rated .* (v(v >= v_in & v < v_rated) - v_in) / (v_rated - v_in);
p(v >= v_rated & v < v_out) = P_rated;
wind_power(:, t) = p;
end
MATLAB的lhsdesign函数很省心,它默认输出每列都是分层均匀的,并且各列之间的相关性已经做了随机化处理。如果要用更多控制,可以自己写分层抽样:u = (rand(N,1) + (0:N-1)') / N,再打乱行的顺序。但实测下来lhsdesign够用,而且速度很快。
光伏光照强度的Beta分布抽样原理类似,先用betainv把分层均匀数映射到Beta分布,再通过光照-功率模型计算光伏出力。有一点要提醒:Beta分布的参数需要根据实际光照均值和方差来估计,如果手里没有统计资料,也可以简化为正态分布,不过要注意截断处理,避免出现负光照。
2.3 抽样规模和时段相关性怎么定
初始场景数N怎么选,直接影响后面计算量。N太小,分布拟合精度差,缩减后的典型场景失真;N太大,抽样和聚类耗时大幅增加。根据我的实验,当N取500到1000时,缩减后的5个典型场景基本能稳定复现原始分布的均值和方差;N超过1500后,精度提升非常有限,但耗时可能翻倍。
时段相关性方面要注意,上面代码里每一列独立抽样,等于假设每个时段的风速互不相关,这在24小时尺度上不够精确。严格做法是先对风速做时间序列建模(比如ARMA模型),再对残差用LHS抽样。但很多程序为了简化,会直接对24时段独立LHS,或者先抽日平均风速,再用一个日内波动系数生成24小时曲线。前者简单,后者对风光出力的日内形态刻画更好,我实际用的是后者:先抽样得到日平均风速和日平均光照,再叠加时段波动模式生成24h场景。
3. 多场景缩减的原理与程序实现
3.1 为什么非减不可
假设抽了1000个场景,每个场景是24时段的风光出力序列,直接全部带入PSO优化:每个场景都要做一次潮流和功率平衡校验,而PSO一次迭代有几十上百个粒子,每个粒子对应一组调度方案,每个方案又要对所有场景做校验,计算量直接爆炸,个人电脑根本扛不住。
场景缩减的目标是:从初始场景集里挑出少数典型场景,并重新分配概率,使得缩减前后的场景集合在概率分布意义上最接近。衡量“最接近”的数学工具是Wasserstein距离(也叫推土机距离),通俗理解就是把一个概率分布“搬运”成另一个概率分布所需的最小代价。
对做工程的人来说,不必从头推导最优传输理论,直接掌握两种实现方式即可:K-means聚类和同步回代消除法。
3.2 K-means聚类缩减法
K-means天然适合场景缩减:把N个场景看作N个样本点,每个样本是T维向量,聚类成K个簇(K就是要保留的典型场景数),每个簇的质心就是典型场景,簇内样本数占比就是该场景的概率。
matlab复制% scenarios_matrix: N 行 T 列,每行是一个场景的全时段出力
K = 5; % 缩减后场景数
[idx, centroid] = kmeans(scenarios_matrix, K, 'Distance', 'sqeuclidean', 'Replicates', 10);
% 计算各场景概率
prob = histcounts(idx, K) / N;
% 输出典型场景和对应概率
typical_scenarios = centroid; % K 行 T 列
scenario_prob = prob; % K 行 1 列,注意归一化
Replicates设为10是为了让K-means多次随机初始化选取最优聚类结果,避免陷入局部最优。Distance用平方欧氏距离比较合适,因为场景缩减更关注数值差异,而不是方向差异。
聚类后建议检查一下结果的合理性:把所有初始场景和典型场景放在同一张图上画出来,直观看看典型场景是否覆盖了高概率区域,而不是几个极端场景的拼接。如果典型场景明显集中在某一类,说明K值太大或初始抽样分布参数有问题。
3.3 同步回代消除法
另一种常用方法是同步回代消除(也被称为后向削减)。它的思想是:每次迭代找出“最不重要的”场景删除,并把它的概率加到离它最近的场景上,直到剩K个。
算法步骤如下:
- 计算所有场景两两之间的距离矩阵
D(i,j) = ||x_i - x_j||_2(或加权欧氏距离)。 - 对每个场景i,找出与它距离最小的另一个场景j,记录最小距离
minD(i)。 - 找到
minD最小的场景i*,删除它,并将它的概率叠加到最近邻居j*上。 - 重复步骤2-3,直到剩余场景数为K。
这个方法的优势是每一步都有明确的概率传递逻辑,保留下来的是“代表性”而非“平均性”的场景,极端情况不容易被平均掉。我在程序中两种方法都实现了,默认用K-means,因为它速度快、一步到位;但遇到场景差异很大、K-means聚类不稳定时,会切到同步回代消除做交叉验证。
3.4 缩减效果怎么量化验证
场景缩减不是减完就完事,要验证缩减后还能不能代表原始不确定性。我常用的三个指标:
- 场景概率之和是否为1(不归一化后面算权重时会出错)
- 缩减前后风速/光照/出力的总体均值偏差是否在可接受范围(通常控制在5%以内)
- 缩减前后累计概率分布的Wasserstein距离是否足够小
matlab复制% 验证:比较缩减前后出力均值
mean_original = mean(wind_power, 1); % 1 行 T 列
mean_reduced = sum(typical_wind .* scenario_prob, 1); % 加权重构均值
bias = max(abs(mean_reduced - mean_original) ./ (mean_original + eps));
disp(['最大相对偏差: ', num2str(bias*100), '%']);
这个验证步骤看起来很基础,但很多人会跳过,结果优化了半天也不知道场景缩减有没有引入太大偏差。
4. 粒子群算法求解交直流混合微网调度
4.1 粒子群算法的核心逻辑
粒子群算法(Particle Swarm Optimization,PSO)模拟鸟群觅食行为,每个“粒子”代表优化问题的一个候选解,粒子在解空间里飞行,速度决定下一步移动的方向和幅度,位置表示当前的解。
速度更新公式是经典的两行代码:
matlab复制v = w * v + c1 * rand * (pbest - x) + c2 * rand * (gbest - x);
x = x + v;
其中pbest是粒子自身历史最优,gbest是全局最优,w是惯性权重,c1和c2分别是自我认知和社会认知加速系数,rand是[0,1]均匀分布随机数。
这个算法在微网调度里好用有两个原因:一是决策变量是连续量(功率值),PSO天然擅长连续空间寻优,不需要像遗传算法那样做编码解码;二是目标函数和约束关系复杂,有大量非线性环节,PSO不依赖梯度信息,只要能把适应度算出来就能迭代。
当然PSO也有软肋,最典型的是高维问题容易早熟,收敛到局部最优。24个时段、每时段又有储能和换流器等多个决策变量,总维度轻松上百,这时候需要精心设计参数和约束处理,我在下一节细说。
4.2 优化变量编码与约束处理
这一步是整套程序的重中之重。先看决策变量的编码方式。以24小时日前调度为例,我选取的决策变量包括:
- 储能充放电功率
P_b(t),共24个变量,正值充电、负值放电 - 换流器传输功率
P_acdc(t),共24个变量,正值交流到直流,负值直流到交流 - 联络线功率
P_grid(t),共24个变量,正值购电、负值售电
如果系统里还有柴油机,也可以把柴油机出力P_dg(t)作为决策变量,但要注意柴油机的启动成本和爬坡约束会让问题复杂不少,新手建议先把储能、换流器、联络线三类变量做通,再加柴油机。
这样粒子维度是3 * T = 72,种群规模取50到100,迭代100到200次。每个粒子就是一组长度为72的实数向量,解算时把它reshape成三个24维向量,分别代表三个决策序列。
约束处理用的是罚函数法。功率平衡这类等式约束、储能SOC范围这种不等式约束,如果直接作为硬约束施加,粒子飞行的可行域会被切割得很碎,PSO容易找不到可行解。我的做法是:
matlab复制% 以交流母线功率平衡为例,写成罚函数
P_balance_ac = P_wind + P_dg + P_grid - P_load_ac - P_acdc; % 不平衡功率
penalty = lambda * sum(max(P_balance_ac - tol, 0).^2); % 超出容差部分平方惩罚
惩罚系数lambda的选取有讲究。太小,约束几乎不生效;太大,粒子一旦越界就寸步难行,容易陷入局部极小。我的经验是:先算一次不带惩罚的适应度范围,把罚函数量级调到目标函数正常值的10到100倍,这样粒子既能探索边界,又不敢乱越界。
储能SOC的动态约束是另一个大坑。SOC更新方程SOC(t+1) = SOC(t) + P_b(t) * dt / E_rated虽然简单,但要保证全天结束时SOC回到初始值(比如0.5),否则优化结果会“把储能用到光”,没有实际意义。这个约束我建议用等式罚函数处理,惩罚系数取大一些,保证最终解能回到初值。
4.3 适应度函数的组成
粒子群算法的“好”与“坏”,完全由适应度函数定义。这个程序的目标是总运行成本最小,成本组成至少包括以下几块:
- 购电成本:
sum(P_grid .* price * dt),price是分时电价,峰谷差异可以很大 - 柴油机燃料成本:
sum(alpha * P_dg + beta),系数由柴油机特性决定 - 储能充放电损耗成本:可以用一个和充放电功率成正比的小惩罚项来近似
- 弃风弃光惩罚:如果某时段风光出力用不完必须弃掉,要加惩罚防止浪费
这里有个值得注意的细节:弃风弃光惩罚不能设太低,否则优化程序会“偷懒”选择弃掉所有波动大的风光出力,而实际上并网发电是有收益的。我的做法是让弃风弃光惩罚略高于购电价均值,这样既鼓励消纳可再生能源,又允许极端情况下弃一部分。
目标函数最终写成:
matlab复制% 对所有场景加权求和
total_cost = 0;
for s = 1:K
scenario_cost = ...; % 该场景下按调度方案计算的运行成本
total_cost = total_cost + scenario_prob(s) * scenario_cost;
end
fitness = total_cost + constraints_penalty; % PSO 最小化 fitness
4.4 PSO参数设置与迭代策略
实际调试中,PSO参数直接影响能否收敛到好解。我最常用的一组参数:
| 参数 | 数值 | 说明 |
|---|---|---|
| 种群规模 | 80 | 维度72时够用,太大耗时 |
| 最大迭代次数 | 150 | 配合惯性权重递减 |
| 惯性权重 w_start | 0.9 | 初期侧重全局搜索 |
| 惯性权重 w_end | 0.4 | 后期侧重局部精细搜索 |
| c1 | 2.0 | 自我认知学习因子 |
| c2 | 2.0 | 社会认知学习因子 |
| 速度限制 | ±0.2倍变量范围 | 防止粒子飞出合理区间 |
惯性权重线性递减非常重要,它能平衡全局探索和局部开发。我见过很多人把w设成固定值,结果要么前期收敛太快陷入局部最优,要么后期振荡不收敛。线性递减的写法很成熟:
matlab复制w = w_start - (w_start - w_end) * iter / max_iter;
还需要对每个粒子的位置即决策变量做上下限约束。储能充放电功率不能超过换流器或PCS的额定功率,换流器传输功率不能超过其容量,联络线功率有购售电上限。最简单的方法是在更新位置后做截断:
matlab复制x = min(max(x, lb), ub);
截断看起来粗暴但有效,能保证粒子始终在物理可行域内,大幅减少不必要的罚函数震荡。
4.5 核心优化循环代码框架
把所有逻辑整合起来,PSO主循环的骨架如下:
matlab复制% 初始化和迭代求解
for iter = 1:max_iter
w = w_start - (w_start - w_end) * iter / max_iter;
for i = 1:pop_size
% 更新速度和位置
velocity(i,:) = w * velocity(i,:) ...
+ c1 * rand * (pbest(i,:) - position(i,:)) ...
+ c2 * rand * (gbest - position(i,:));
% 速度限幅
velocity(i,:) = min(max(velocity(i,:), -v_max), v_max);
% 更新位置并做边界截断
position(i,:) = position(i,:) + velocity(i,:);
position(i,:) = min(max(position(i,:), lb), ub);
% 计算适应度
fitness(i) = calc_fitness(position(i,:), typical_scenarios, scenario_prob, sys_params);
% 更新个体最优
if fitness(i) < fitness_pbest(i)
pbest(i,:) = position(i,:);
fitness_pbest(i) = fitness(i);
end
end
% 更新全局最优
[best_val, best_idx] = min(fitness_pbest);
if best_val < fitness_gbest
gbest = pbest(best_idx,:);
fitness_gbest = best_val;
end
% 记录收敛曲线
convergence(iter) = fitness_gbest;
end
适应度函数calc_fitness里就是把粒子拆成三组决策变量,做功率平衡、SOC更新、成本计算和罚函数累加。这个函数是性能瓶颈所在,建议向量化写法,避免在每个粒子内部再套一层时段循环,否则80个粒子乘150次迭代,MATLAB跑起来会相当吃力。
5. 常见问题与排查技巧实录
5.1 场景抽样结果失真,典型场景完全不典型
现象:缩减出的典型场景和原始场景分布明显不符,均值偏差超过10%。
排查路径:先看初始抽样是否覆盖了完整分布范围。可以画出风速的LHS样本直方图和理论Weibull概率密度曲线做对比,如果直方图明显缺了尾部,说明lhsdesign的分层数不够或者分布参数设置错误。再看缩减时K值是否过小,我做过对比,风力场景K取3时很难兼顾全天候形态,K取5到7比较稳。
经验:一定要在缩减前保留一版原始抽样结果文件,便于回溯。不要一边调分布参数,一边调聚类参数,否则问题叠在一起很难定位。
5.2 PSO早熟收敛,每次结果差异巨大
现象:多次运行程序,优化结果波动很大,有时候成本很低,有时候高得离谱。
这是PSO在72维空间里常见的早熟问题。可能原因有两个:一是惯性权重衰减太快,前期还没探索完整个空间,后期已经失去全局搜索能力;二是速度限制v_max设得太小,粒子活动范围受限。
解决方案:把惯性权重初始值提到0.95,结束值提到0.45,衰减曲线换成非线性递减(可以先快后慢),v_max设为变量范围的30%左右。再有就是增加种群规模到100,同时多跑几次取最优。实测下来,配合线性递减惯性权重和边界截断,变异率不调问题也不大。
5.3 功率平衡约束总是不满足,罚函数失效
现象:优化结束后,检查各个场景的功率平衡,发现交流或直流母线不平衡量很大,但适应度值看起来正常。
问题出在罚系数上。如果lambda设得太小,粒子越界后受到的惩罚比正常成本还低,算法就“乐于”越界换取更低成本。这时候你会看到目标函数值一直下降,但约束根本不受控。
我自己的调试方法是:先只运行一个粒子、一次迭代,手动检查某几个随机初始解的约束越界量和罚函数数值,确保罚数量级已经在正常成本的10倍以上。如果还不够,就直接把等式约束的罚系数拉高到100倍量级,宁可让寻优过程保守一些,先保证可行性,再优化经济性。
5.4 参数速查表
| 问题 | 排查方向 | 推荐参数/做法 |
|---|---|---|
| 初始场景数 | 分布覆盖 vs 计算量 | 500-1000 |
| 缩减后场景数 | 精度 vs 速度 | 5-7个 |
| 惩罚系数 | 约束满足度 | 目标成本量级10-100倍 |
| 惯性权重 | 收敛全局性 | 0.9→0.4 线性递减 |
| 种群规模 | 高维搜索能力 | 维度×1.5,72维用80-100 |
| 速度限制 | 搜索范围 | 变量范围的20%-30% |
这套程序跑通之后,后续可以扩展的方向很多,比如把单目标改成考虑碳排放的多目标优化,或者把场景法的离散场景换成分布鲁棒优化。我个人的体会是,交直流混合微网调度的难点从来不在某一项技术上,而在把所有环节串起来时,参数、数据、约束之间相互牵连造成的“整体调试困局”。拉通一次之后,再回去看相关论文会顺畅很多。
