做电力系统规划的朋友这几年应该都有同一个感受:新能源装机比例越拉越高,可电网对“可靠供电”这四个字的要求一点都没降。光伏和风电出力完全看天,中午光照强的时候光伏猛发,夜间负荷高峰风电又不一定给力,这种间歇性和波动性不是靠多装几块板子就能解决的。抽水蓄能是目前技术最成熟、容量最大的储能方式,正好可以充当风光和负荷之间的缓冲池。这篇文章要聊的,就是一套基于模拟退火算法SA的太阳能—风能—水力混合抽水蓄能系统优化研究,用Matlab代码实现容量配置和运行策略的联合寻优。整体思路不算复杂,但建模细节、算法参数和调试经验很多,适合正在做新能源规划、微电网优化方向的学生和工程师参考,也适合想对着SA算法快速上手实际问题的朋友拿来做模板。
我把问题简化成这样一个场景:某个区域电网里规划新建光伏电站、风电场和一座抽水蓄能电站,三者通过母线连在一起给负荷供电,同时保留从外部电网购电的能力。问题是,光、风、蓄分别该建多大容量,一年下来既能满足供电可靠性,总费用又不至于失控。这本质上是一个带约束的容量优化问题,目标函数非凸、还耦合着逐时段的运行模拟,传统梯度法基本使不上劲,所以我用模拟退火在外面搜容量组合,里面用一套运行规则模拟典型日24小时的功率分配,最后把总成本和惩罚项汇总回退火过程。下面把整个建模、算法设计、Matlab代码和调试过程完整拆开讲一遍。
1. 系统建模与优化目标:先把“优化什么”想清楚
1.1 风光水蓄混合系统的拓扑结构与运行规则
混合系统的基本拓扑并不复杂:光伏阵列、风电场、抽水蓄能电站共同接在一条母线上,母线再连接负荷和外部电网联络线。光伏和风电优先出力,剩余负荷由抽水蓄能和购电补充;反过来,如果风光出力大于负荷,富余电力就用来驱动抽水蓄能机组抽水,把电能转化为水的势能存起来。
抽水蓄能电站内部有上水库、下水库和可逆式机组。抽水工况下,电动机带动水泵把下水库的水抽到上水库,这个过程消耗电能;发电工况下,上水库的水放下推动水轮机发电。这里有一个关键的物理参数叫综合转换效率,抽水效率和发电效率都在90%左右,两者相乘后整套系统的往返效率大概在75%到80%之间。这个数字意味着蓄能电站放出的电量永远小于当初抽水消耗的电量,所以设计时必须校准能量平衡,不能想当然地认为存多少就能放多少。
实际运行时,我先根据净负荷曲线做功率分配:净负荷等于负荷减去风光出力。净负荷为负,说明风光有富余,优先抽水蓄能充电;净负荷为正,说明缺电,优先放水发电;水库容量不够或者水流不足时,再向外部电网购电。这种方法在工程上叫“规则调度”,虽然不是数学意义上的最优运行策略,但作为容量优化内层的运行模拟器已经够用,而且计算速度快、结果可解释性强。
1.2 决策变量与目标函数:容量怎么选、费用怎么算
决策变量选三个:光伏装机容量、风电装机容量、抽水蓄能装机容量,单位都是MW。这里没有把储能时长单独设成决策变量,而是用抽水蓄能的可储小时数固定配合,比如设定上水库能储存满发8小时的水量,这样问题规模小,收敛也快。
目标函数要体现“经济性”和“可靠性”两个方向。经济性主要看年化总费用,包含三块:建设投资的年化成本、运行维护费用、向电网购电的费用。可靠性用失负荷率LOLP来衡量,也就是一年中缺电电量占总负荷需求的比例,工程上通常要求控制在1%以内。因为失负荷率作为约束直接处理会让可行域变得很不规则,我把它放到目标函数里做惩罚项,超出1%的部分按缺电经济损失折算成费用加入总成本。
建设投资的年化成本用资金回收系数计算,折现率取6%,设备寿命按20年算,回收系数大概是0.087。光伏单位造价取3500元/kW,风电取6500元/kW,抽水蓄能取5500元/kW。这三个价格参考了主流工程项目的区间,不同地区有差异,但做方案比选时量级是够的。运行维护费率按建设投资的2%估算,购电电价按0.6元/kWh计算。目标函数写出来就是:
min F = CRF × (c_pv·P_pv + c_wt·P_wt + c_pump·P_pump) + 0.02×C_cap + C_purchase + C_penalty
其中CRF是资金回收系数,C_cap是建设总投资,C_penalty是失负荷惩罚。惩罚项我设定为失负荷电量乘以单位缺电成本再乘放大系数,单位缺电成本取8000元/MWh,放大系数取2到4倍,让算法优先保证可靠性。
1.3 为什么选模拟退火而不是梯度下降或线性规划
这个问题如果用梯度法解,会遇到两个麻烦。第一,目标函数内部嵌着24小时运行模拟,光伏、风电、蓄能、购电之间的切换逻辑让函数曲面非常不平滑,很多位置不存在连续导数。第二,容量配置和运行状态存在强耦合,光伏装多了可能午间大量弃光,蓄能装大了平摊年化成本很高,这些非线性关系会让梯度方向频繁跳变,最后卡在某个局部坑里出不来。
线性规划倒是能把部分问题线性化,但抽水蓄能机组的启停、库容的离散状态、可分段的风电出力曲线,都让模型变成混合整数规划,规模一大求解时间就失控。模拟退火的好处是基本不依赖函数形态,只要能把候选解输入进去算出一个目标值,它就能搜,而且通过Metropolis准则以一定概率接受差解,能跳出局部最优。代价是计算量大一点,但这套模型只有三个决策变量,SA跑几百代也就几分钟,完全在可接受范围内。
2. 模拟退火算法核心原理与参数整定
2.1 从爬山到随机跳出:SA为什么能逃出局部最优
模拟退火的灵感来自金属退火过程,金属加热到高温后缓慢冷却,原子在高温下剧烈运动,随着温度降低逐渐排列成低能稳态。映射到优化问题里,目标函数值就是“能量”,决策变量就是“原子位置”,温度就是控制搜索随机性的参数。
普通爬山法的问题是只接受更优解,遇到局部极小值就停住。SA在温度高的时候,除了接受更优解,还会以一定概率接受差解,这个概率由接受概率exp(-ΔE/T)决定,其中ΔE是候选解和当前解的目标值差,T是当前温度。温度高时,即使目标值变差很多,也有较大概率被接受,这样算法就能从一个局部坑里跳出来。随着温度逐渐降低,接受差解的概率越来越小,算法逐步收敛到一个稳定区域。
我用一个简单的例子说明这件事。假设目标函数有两个低谷,一个浅一个深,爬山法很可能一开始掉进浅谷就完了。SA在高温阶段频繁接受差解,等于可以翻过两个谷之间的山脊,等温度降下来后才锁定在深谷附近精细搜索。这个“先广后精”的节奏,正好匹配容量优化这种高维非线性问题。
2.2 参数整定经验:我试出来的SA参数组合
SA对参数敏感,但也没有想象中那么玄学。初温T0决定算法初期接受差解的能力,冷却系数α决定降温速度,内循环次数Lk决定每个温度下搜索的充分程度,终止条件决定整个算法何时收工。
初温我一般这样定:先对初始解做几十次邻域扰动,统计目标值变化的均方根,然后乘上5到10倍作为T0。这样能保证一开始的接受率在0.8以上,算法有足够的探索空间。如果接受率一开始就低于0.5,说明初温太低,算法还没开始就变成爬山法了。
冷却系数α我常用0.95,温度从100降到0.001大约需要200多代。α取0.9时收敛快但容易早熟,α取0.99效果好但计算时间翻好几倍。内循环次数取200,意思是每个温度下生成200个候选解。终止条件用两个:温度降到1e-3以下,或者连续300次迭代最优解没有任何改进就提前停机。
参数表整理如下:
| 参数 | 推荐值 | 作用 | 设置依据 |
|---|---|---|---|
| 初始温度T0 | 50~200 | 控制初期全局搜索强度 | 基于初始解邻域Δf的统计值放大5~10倍 |
| 冷却系数α | 0.90~0.98 | 控制降温速度 | 0.95平衡效果与耗时 |
| 内循环次数Lk | 100~300 | 每个温度下搜索的候选解数 | 200次足够覆盖3维邻域 |
| 终止温度T_end | 1e-3以下 | 判断是否停机 | 过低无意义,过高收敛不充分 |
| 邻域扰动幅度 | 0.05~0.15 | 控制候选解变化步长 | 容量跨度100MW级时取8%较合适 |
2.3 邻域解生成与约束处理
容量类决策变量都是连续量,邻域解生成最简单的方式是给当前解乘上一个随机扰动因子。我用的公式是x_new = x_cur .* (1 + 0.08*randn(1,3)),每个维度的扰动幅度略有差异,光伏变化可以稍大,蓄能变化稍小,这符合工程直觉。
容量边界是硬约束,直接用钳位处理:低于下限压回下限,高于上限压回上限。别的约束,比如功率平衡、库容边界、失负荷率,都在运行模拟和目标函数内部完成检查,有问题就以惩罚项的形式反映到目标值中。这种做法的好处是搜索过程不需要频繁判断可行性,算法始终在“计算目标值—比较接受”的循环里跑,效率高很多。
罚函数的系数设置有个经验值:先跑一次完全不含惩罚的优化,看看失负荷率会冲到多少,然后根据单位缺电成本放大2到4倍设定罚系数。罚系数太小,算法会给出不靠谱的低成本高失负荷方案;罚系数太大,目标函数曲面过于陡峭,搜索过程容易震荡。实际调试中我常用λ=5e6作为起点,效果不错。
3. Matlab实现与仿真分析
3.1 代码框架与核心函数
Matlab实现的代码结构我分成了四个文件:main.m负责SA主循环和结果输出,objective.m负责计算目标函数值,simulate_system.m负责24小时运行模拟并返回失负荷电量和购电电量,plot_result.m负责画收敛曲线和典型日运行曲线。
主循环的代码模型如下,这是整个搜索过程的核心:
% ========== 模拟退火主循环 ========== rng(42); % 固定随机种子,保证结果可复现 T0 = 100; alpha = 0.95; T_end = 1e-3; Lk = 200; % 每个温度下的内循环次数 x0 = [120, 80, 50]; % 初始解 [光伏MW, 风电MW, 抽蓄MW] x_lb = [20, 20, 10]; % 决策变量下限 x_ub = [300, 300, 200]; % 决策变量上限 x_best = x0; f_best = objective(x0); x_cur = x0; f_cur = f_best; T = T0; iter = 0; while T > T_end for k = 1:Lk % 生成邻域解 x_new = x_cur .* (1 + 0.08 * randn(1, 3)); x_new = max(x_new, x_lb); x_new = min(x_new, x_ub); % 计算目标函数值 f_new = objective(x_new); delta = f_new - f_cur; % Metropolis接受准则 if delta < 0 || exp(-delta / T) > rand x_cur = x_new; f_cur = f_new; if f_new < f_best x_best = x_new; f_best = f_new; end end end T = T * alpha; iter = iter + 1; fprintf('iter=%d, T=%.4f, f_best=%.4f\n', iter, T, f_best); endobjective.m里面先读入典型日的辐照度、风速和负荷曲线,然后调用simulate_system.m做时序模拟,最后汇总成本。光伏出力简化为辐照度与额定容量的线性关系,风电出力按标准分段函数处理,切入风速3m/s、额定风速12m/s、切出风速25m/s。抽水蓄能的充放电逻辑放在simulate_system.m里,代码片段如下:
function [lolp, purchase, discharge] = simulate_system(x, data) P_pv = x(1); P_wt = x(2); P_pump = x(3); G = data.G; v = data.v; L = data.L; T = length(L); % 光伏出力 P_pv_h = P_pv .* (G / 1000); P_pv_h = min(P_pv_h, P_pv); % 风电出力(分段函数) vci = 3; vr = 12; vco = 25; P_wt_h = zeros(T, 1); for t = 1:T if v(t) < vci || v(t) > vco P_wt_h(t) = 0; elseif v(t) < vr P_wt_h(t) = P_wt * (v(t) - vci) / (vr - vci); else P_wt_h(t) = P_wt; end end % 抽水蓄能运行模拟 E = 0.5 * P_pump * 8; % 初始库容,单位MWh Emin = 0.1 * P_pump * 8; Emax = 1.0 * P_pump * 8; eta_c = 0.9; % 抽水效率 eta_d = 0.9; % 发电效率 for t = 1:T net = L(t) - P_pv_h(t) - P_wt_h(t); if net > 0 % 缺电:先放水 P_dis = min(net, P_pump); P_dis = min(P_dis, (E - Emin) * eta_d); discharge(t) = P_dis; E = E - P_dis / eta_d; net = net - P_dis; else % 富余:抽水蓄能 P_ch = min(-net, P_pump); P_ch = min(P_ch, (Emax - E) / eta_c); E = E + P_ch * eta_c; net = net + P_ch; end if net > 0 purchase(t) = net; % 不足部分购电 loss(t) = 0; else purchase(t) = 0; loss(t) = -net; % 富余部分弃电 end end lolp = sum(loss) / sum(L); end这个模拟逻辑相当于把抽水蓄能当作“优先平衡工具”,风光和蓄能能就地消化的就不从电网买,实在平衡不了才动联络线。实际工程项目里这套规则很常用,改造成本低,也方便后续扩展新的控制策略。
3.2 输入数据准备与参数设定
典型日数据我用24小时步长,负荷曲线按区域电网特征手动构造:夜间低谷出现在凌晨4点左右,午间出现小高峰,晚高峰在19到21点。光伏出力曲线按夏季晴天设定,辐照度从早上6点开始爬升,中午12点达到峰值1000W/m2,傍晚18点降为零。风速曲线设定为白天中等、夜间偏强的形态,让风电和光伏形成一定互补。
需要说明的是,用典型日数据代替全年8760小时是一种工程近似,它可以快速给出容量配置的参考方向。如果要做可研级别的精确评估,应该把全年逐时数据都跑一遍,但这会让SA的计算量成倍增长。我建议先拿典型日调算法,确定参数后再切全年数据做精算,效率最高。
成本与系统参数汇总如下:
| 参数 | 数值 | 说明 |
|---|---|---|
| 光伏单位造价 | 3500元/kW | 含组件、支架、逆变器、安装 |
| 风电单位造价 | 6500元/kW | 含风机、基础、并网 |
| 抽蓄单位造价 | 5500元/kW | 含机组、水库、输水系统 |
| 资金回收系数 | 0.087 | 折现率6%、寿命20年 |
| 运维费率 | 2% | 按建设投资额计 |
| 购电电价 | 0.6元/kWh | 联络线购电 |
| 失负荷率上限 | 1% | 可靠性约束 |
3.3 结果分析与典型日运行过程
我用初始解光伏120MW、风电80MW、抽蓄50MW启动SA,跑完200多代退火后得到最优解光伏158MW、风电96MW、抽蓄62MW。总费用从初始的1.63亿元/年降到1.44亿元/年,其中初始解有0.42亿元的失负荷惩罚,优化解的失负荷率降到了0.7%,惩罚项基本归零。
收敛曲线的形态我比较满意:前80代成本下降很快,基本从1.6亿元级别一路降到1.45亿元,后面100多代只有小幅波动,说明算法在高位阶段探索充分,已经进入精细搜索状态。这个曲线形态是典型的SA健康收敛形态,如果曲线长时间一条直线没有波动,通常意味着初温太低或者邻域步长太小。
典型日运行图上,白天的净负荷为负,蓄能电站在电价低、光伏富余的时段大量抽水,库容逐步升高;傍晚光伏出力衰减,净负荷转正,蓄能开始放水发电,在晚高峰时段达到放电功率上限;夜间风速较高,风电接棒,蓄能库容缓慢下降。这种“白天抽水、晚上放水”的日循环模式,和抽水蓄能电站的实际运行规律完全吻合。
4. 常见问题与实战避坑指南
4.1 算法不收敛或者收敛太慢怎么办
最典型的现象是迭代日志里f_best长时间不变化。先检查初温是不是设低了,我调试时会加一段统计代码,在初始解附近随机采样20次,计算目标值变化的标准差,如果接受率一开始就低于0.5,直接把T0调大3到5倍。
还有一个容易被忽略的点是邻域生成步长。三个决策变量的量级都是几十到一两百MW,如果扰动系数设成0.01,每一步只移动1到2MW,整个搜索过程会非常缓慢。我常用0.08,也就是一步最多移动十几MW,这个量级对容量优化问题来说尺度合适。另外,内循环次数太少也会导致每个温度下没有充分搜索,温度降太快,算法来不及跳出局部坑。
4.2 约束不满足和罚函数失衡问题
用惩罚函数处理约束最怕罚系数没调好。我踩过的坑是刚开始把罚函数设得很小,结果SA给出一个光伏50MW、抽蓄10MW的方案,虽然年化成本低得惊人,但失负荷率冲到8%,完全不可用。后来把罚系数加大到缺电成本的3倍,算法才开始认真对待可靠性约束。
罚系数太大同样有问题,目标函数曲面会变得非常崎岖,搜索空间被强行切成可行和不可行两个区域,SA在边界附近反复震荡。我的建议是先用一个大罚系数保证解可行,跑通流程后逐渐降低,找到一个“可行域内还有优化空间”的平衡点。实际操作中,观察f_best的收敛轨迹如果出现阶梯状跳跃,多半就是罚系数过大的信号。
4.3 Matlab实现细节与性能优化
第一,一定要用rng固定随机种子。SA本身是随机算法,不固定种子每次结果都不一样,调试时很难判断参数改动到底有没有效果。第二,尽量向量化光伏和风电出力计算,24小时的数据量小看不出差别,但如果切到全年8760小时,循环和向量化的时间差距能达到几十倍。第三,fprintf打印迭代日志非常有用,可以实时看到温度、最优值的变化趋势,比跑完再plot更直观。
我还养成了一个习惯:SA跑完拿到最优解后,不直接收工,而是把这个解作为初值再喂给Matlab的fmincon做一轮局部精修。SA提供了一个很好的全局区域,fmincon负责在这个区域里做光滑搜索,经常能再挤出1%到2%的成本下降空间。这个“全局搜索+局部精修”的组合拳,在很多实际问题里都有效。
这个项目做完以后最深的体会是,算法不是越复杂越好,问题建模的合理性决定结果上限。模拟退火在容量优化这种中等规模、强非线性问题上表现出色,参数一旦调顺,整个搜索过程稳定可靠。后续想扩展的话,可以把单目标改成经济性和碳排放的双目标优化,或者把典型日换成全年时序数据做精算,也可以引入风光出力的不确定性场景做鲁棒优化,这些方向都是在现有代码框架上叠加新模块就能实现的。