十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

建筑光储系统容量配置与运行调度联合优化的Matlab实现

建筑光储系统容量配置与运行调度联合优化的Matlab实现 简介本资源是面向能源系统规划与优化方向研究生、电力系统科研人员及Matlab建模仿真实践者的专业代码包聚焦建筑集成光伏-储能BIPVESS系统的容量配置与运行调度协同优化问题。基于《中国电机工程学报》2023年发表的双层耦合优化模型外层以全寿命周期投资回收期最短为目标优化光伏与储能容量组合内层在给定容量下求解经济性最优的日能量调度策略实现规划与运行的深度耦合。压缩包共6个文件5个核心m文件1份PDF说明涵盖主程序、粒子群算法框架、适应度函数、参数配置及结果可视化模块总大小1.46MB结构清晰、注释完整便于理解双层迭代逻辑与工程经济性建模细节。目前已有484人学习下载提供从文献复现到代码调试的完整闭环特别适合开展光储系统多目标优化、低碳建筑能源管理或课程设计仿真实践。 直接分享一个我最近做完的活儿建筑集成光伏储能系统的容量配置与运行调度联合优化整套模型用Matlab实现能一次性算出“光伏装多少千瓦、储能建多大容量、每时段怎么充放电、向电网买多少电”的全局最优方案。这个项目折腾了我将近三周踩了不少坑也沉淀出一套可以直接拿来改改就用的代码结构。如果你正在做建筑光储系统设计、园区微网规划或者论文里恰好涉及光储容量优化这篇内容应该能帮你少走不少弯路。先说一个常被新手忽略的关键点建筑光储系统的“规划”和“运行”是强耦合的光伏装多了储能却配小了或者储能容量够了但充放电策略不对整体经济性都会大打折扣。所以项目一开始我就定了目标——不是分开算光伏容量和储能容量而是把规划层和运行层塞进同一个优化模型里用统一的数学模型同时决策。1. 项目到底在优化什么目标拆解和建模逻辑1.1 建筑光储系统的核心经济矛盾做建筑光伏储能优化本质上是在回答三个问题光伏板铺多少电池装多大每天怎么充放电这三个问题互相牵制而且最终要落到钱上。我在项目里使用的核心目标函数是“年化总成本最小”包括四个方面光伏和储能的投资成本折算成年值每年的设备运维成本从电网购电的费用向电网售电的收益作为负成本。有意思的是很多初学者会把目标函数写成“运行成本最小”然后单独校核投资回报率。但从数学优化的角度这是典型的“近视”做法。如果你先固定了光伏和储能容量再优化运行策略那本质上等于你在一个次优的配置基础上做运维优化永远找不到全局最优。我在这个项目中坚持把容量变量连续决策变量和逐时运行变量连续二进制变量放进同一个模型用混合整数线性规划统一求解后面会详细展开。1.2 为什么规划与运行必须联合优化我做一个简单的量化对比你就明白了。假设某建筑年用电量100万kWh屋顶可装光伏面积有限储能电池价格大约是1400元/kWh光伏系统造价3200元/kWp。如果你先凭经验定一个配置比如光伏200kWp、储能300kWh/150kW再跑运行优化可能最后年化成本是85万。但你把容量变量放开让模型自己去权衡光伏出力、储能套利和购电成本它可能会给出光伏180kWp、储能200kWh/100kW的方案年化总成本降到78万。差的这7万块钱就是“分步优化”的代价。之所以会有这种差异核心在于光伏出力曲线和建筑负荷曲线的匹配程度。光伏午间出力大但如果建筑午间用电少多余的光伏电要么低价上网要么存进电池晚上用。储能在中间扮演的是“能量搬运工”它的大小直接改变了光伏的消纳率和峰谷套利空间。这个互馈关系只有放进同一个目标函数里才能被正确捕捉。1.3 这个模型和代码适合谁来用如果你属于下面几类人这套思路和代码可以直接参考做建筑光伏储能可行性研究或初步设计的工程师需要快速给出配置方案和经济性评估做园区微网、光储充系统规划的科研人员需要一套可复现的Matlab优化模板电气、能源系统工程相关专业的同学写毕业论文或竞赛项目时需要光储容量优化和调度策略的完整代码框架。项目代码使用Matlab YALMIP工具箱搭建优化模型求解器默认调用intlinprogMatlab自带也可以换成Gurobi、Mosek等高性能求解器。这套组合在学术界和工程界都非常常见你拿到代码后改改数据就能迁移到自己的场景里。2. 数学建模目标函数、约束条件和变量设计2.1 目标函数的完整表达式与量纲处理我在模型里使用了“年化总成本”作为目标函数公式如下min Cost_total C_inv_pv C_inv_bat C_om C_grid_buy - C_grid_sell其中每一项的展开逻辑光伏投资年值C_inv_pv r_pv × P_pv × c_pv其中r_pv是资金回收系数P_pv是光伏安装容量(kWp)c_pv是单位造价(元/kWp)储能投资年值C_inv_bat r_bat × (E_bat × c_e P_bat × c_p)储能成本要拆成能量容量(元/kWh)和功率容量(元/kW)两部分因为电池和PCS的价格是分开的年运维成本C_om k_om_pv × P_pv k_om_bat × E_bat按照年运维费率估算年购电成本根据典型日加权C_grid_buy sum_d days_d × sum_t price_buy(t) × P_buy(d,t) × dt年售电收益C_grid_sell sum_d days_d × sum_t price_sell(t) × P_sell(d,t) × dt。资金回收系数r i × (1i)^n / ((1i)^n - 1)其中i是折现率n是设备寿命。这里有个技术细节必须提一下光伏寿命一般是25年储能寿命则是10年左右两者不能用同一个n工程上通常把储能做一次替换成本纳入计算或者按各自寿命分别折算。我在代码里分别计算两个设备的资金回收系数避免估值偏差。2.2 决策变量分类与含义整个优化模型共有四类变量连续变量规划层P_pv光伏安装容量单位kWpE_bat储能额定能量容量单位kWhP_bat储能额定功率单位kW。连续变量运行层P_pv_out(d,t)光伏各时段实际出力(kW)P_bat_c(d,t)、P_bat_d(d,t)储能充、放电功率(kW)SOC(d,t)储能荷电状态(kWh或百分比)P_buy(d,t)、P_sell(d,t)建筑从电网购电、向电网售电功率(kW)。二进制变量运行层u_bat_c(d,t)、u_bat_d(d,t)储能充放电状态防止同时充放电u_grid_buy(d,t)、u_grid_sell(d,t)购售电状态防止同时买卖。这里我用了多个典型日来代表全年比如典型工作日、典型周末、典型节假日每个典型日有24个时段再乘以天数权重days_d。这样做比全年8760小时全建模高效得多同时又能较好地保留分时电价和负荷曲线的特征。2.3 约束条件从物理规律到数学表达约束条件是最容易出错的地方我把每一类约束的物理含义列一下电功率平衡约束是模型的“骨架”P_load(d,t) P_pv_out(d,t) P_bat_d(d,t) × η_d - P_bat_c(d,t) / η_c P_buy(d,t) - P_sell(d,t)这个等式强行保证了每一时刻建筑负荷都由光伏、储能、电网三者共同满足。这里特别容易犯的错误是充放电效率的写法。储能电池充电时电网或光伏输入电量电池实际存入的电量要乘以充电效率η_c放电时电池放出电量乘以放电效率η_d才是实际供给负荷的电量。方向不要搞反。光伏出力约束P_pv_out(d,t) ≤ P_pv_ava(d,t)其中P_pv_ava(d,t) P_pv × G(d,t) / 1000。也就是实际出力不能超过该时刻光伏理论出力而理论出力等于安装容量乘以辐照度比例。如果考虑逆变器限功率可以再加一个上限系数但一般不用。储能SOC递推约束SOC(d,t1) SOC(d,t) (P_bat_c(d,t) × η_c - P_bat_d(d,t) / η_d) × dt / E_bat。这里要注意E_bat是变量不是常数所以这个约束是非线性的。为了让模型保持线性我在代码里做了一步变化用能量的绝对值表示SOC变化量即定义E_soc(d,t) SOC × E_bat这样递推式就变成E_soc(d,t1) E_soc(d,t) P_bat_c × η_c × dt - P_bat_d / η_d × dt完全线性。这是MILP建模里很经典的一个技巧项目代码里我会写清楚。储能容量与功率限制0 ≤ P_bat_c(d,t) ≤ P_bat × u_bat_c(d,t)0 ≤ P_bat_d(d,t) ≤ P_bat × u_bat_d(d,t)u_bat_c(d,t) u_bat_d(d,t) ≤ 1SOC_min × E_bat ≤ E_soc(d,t) ≤ SOC_max × E_bat。购售电互斥约束P_buy(d,t) ≤ M × u_grid_buy(d,t)P_sell(d,t) ≤ M × u_grid_sell(d,t)u_grid_buy u_grid_sell ≤ 1。M是一个足够大的常数通常取建筑最大负荷的5倍即可。储能初值终值约束E_soc(d,1) E_soc_initial × E_batE_soc(d,24) E_soc_initial × E_bat。这个约束保证储能一个周期内能量守恒否则模型会“作弊”——把所有能量在最后一个时段一次性放完得到一个虚假的最优解。2.4 为什么选择MILP而不是启发式算法我见过很多人一上来就写粒子群算法、遗传算法做光储容量优化但在这个问题上我强烈建议先用MILP。原因很简单光储系统规划运行模型在数学上是相当规整的线性混合整数结构MILP求解器能保证全局最优解而且求解速度快。启发式算法虽然“显得高级”但既不能保证最优调参又费劲收敛性还不稳定。我之前在一个项目里对比过同样一套数据用遗传算法跑30分钟得到一个次优解用Gurobi求解MILP模型5秒找到全局最优。这个差距是碾压级的。当然如果你要加非线性约束比如电池寿命衰减模型、光伏出力非线性效率曲线那就需要MINLP或者启发式算法这个另当别论。对大多数工程初设阶段的需求来说MILP足够了。3. Matlab代码实现从数据准备到求解器调用3.1 输入数据准备参数全部结构体化这是工程经验里很实用的一点——把参数用结构体打包而不是写成几十个散落的全局变量。我的代码里输入数据块是这样的% 系统参数 param.pv_cost 3200; % 光伏单位造价 元/kWp param.pv_life 25; % 光伏寿命 年 param.pv_om 15; % 光伏年运维单价 元/kWp/年 param.bat_energy_cost 1400; % 储能能量成本 元/kWh param.bat_power_cost 800; % 储能功率成本 元/kW param.bat_life 10; % 储能寿命 年 param.bat_om 30; % 储能年运维单价 元/kWh/年 param.rate 0.06; % 折现率 param.eta_ch 0.95; % 充电效率 param.eta_dis 0.95; % 放电效率 param.soc_min 0.1; % 储能SOC下限 param.soc_max 0.9; % 储能SOC上限 param.soc_init 0.5; % 储能SOC初值/终值3.2 典型日数据与时间序列处理输入数据包括三类时间序列光伏归一化出力基于典型气象年数据、建筑逐时负荷、分时电价。我用的数据结构是一个三维数组第一维是典型日编号第二维是时间1到24第三维是数据类型。这里分享一个处理光伏出力的经验不要直接用原始辐照度数据先做归一化处理除以一个参考辐照度比如1000W/m²这样P_pv_out的计算就是光伏容量乘以归一化出力物理量纲一目了然。同时需要注意的是负荷数据要区分工作日和周末因为建筑负荷曲线差异很大只取平均会丢失峰谷特征。3.3 YALMIP变量定义与约束构建YALMIP是个好东西它把建模和求解器解耦了代码写起来很像数学公式可读性极高。变量定义如下% 规划层连续变量 P_pv sdpvar(1,1); % 光伏额定容量 E_bat sdpvar(1,1); % 储能额定电能量 P_bat sdpvar(1,1); % 储能额定功率 % 运行层连续变量 P_pv_out sdpvar(Tf, n_d); % 各典型日逐时光伏出力 P_bat_ch sdpvar(Tf, n_d); % 各典型日逐时充电功率 P_bat_dis sdpvar(Tf, n_d); % 各典型日逐时放电功率 E_soc sdpvar(Tf, n_d); % 各典型日逐时储能电量 P_grid_buy sdpvar(Tf, n_d);% 各典型日逐时购电功率 P_grid_sell sdpvar(Tf, n_d);% 各典型日逐时售电功率 % 二进制变量 u_ch binvar(Tf, n_d); % 充电状态 u_dis binvar(Tf, n_d); % 放电状态 u_buy binvar(Tf, n_d); % 购电状态 u_sell binvar(Tf, n_d); % 售电状态变量定义之后最主要的功夫在约束构建。我把功率平衡约束、SOC递推约束、储能运行约束、购售电互斥约束都写成了循环每种约束只用一个循环块清清楚楚Constraints []; for d 1:n_d % 1. 功率平衡负荷 光伏 储能放电 - 储能充电 购电 - 售电 Constraints [Constraints, ... P_load(:,d) P_pv_out(:,d) P_bat_dis(:,d) - P_bat_ch(:,d) ... P_grid_buy(:,d) - P_grid_sell(:,d)]; % 2. 光伏出力上限 Constraints [Constraints, ... P_pv_out(:,d) P_pv * pv_profile_norm(:,d)]; % 3. SOC递推用能量绝对值表示 Constraints [Constraints, ... E_soc(2:end,d) E_soc(1:end-1,d) ... P_bat_ch(1:end-1,d) * param.eta_ch * dt ... - P_bat_dis(1:end-1,d) / param.eta_dis * dt]; % 4. 储能充放电功率限制与状态互斥 Constraints [Constraints, ... P_bat_ch(:,d) P_bat .* u_ch(:,d)]; Constraints [Constraints, ... P_bat_dis(:,d) P_bat .* u_dis(:,d)]; Constraints [Constraints, ... u_ch(:,d) u_dis(:,d) 1]; % 5. SOC上下限 Constraints [Constraints, ... E_soc(:,d) param.soc_min * E_bat]; Constraints [Constraints, ... E_soc(:,d) param.soc_max * E_bat]; % 6. 初值终值相等 Constraints [Constraints, ... E_soc(1,d) param.soc_init * E_bat]; Constraints [Constraints, ... E_soc(end,d) param.soc_init * E_bat]; % 7. 购售电互斥 Constraints [Constraints, ... P_grid_buy(:,d) bigM .* u_buy(:,d)]; Constraints [Constraints, ... P_grid_sell(:,d) bigM .* u_sell(:,d)]; Constraints [Constraints, ... u_buy(:,d) u_sell(:,d) 1]; end注意我在变量注释里用了Tf表示24用n_d表示典型日数量。这样代码的可读性和可维护性都很好。3.4 目标函数构建与求解目标函数在YALMIP里写起来也非常直白投资成本部分用年值系数折算% 资金回收系数 r_pv param.rate * (1 param.rate)^param.pv_life / ((1 param.rate)^param.pv_life - 1); r_bat param.rate * (1 param.rate)^param.bat_life / ((1 param.rate)^param.bat_life - 1); % 投资年值 Cost_inv r_pv * param.pv_cost * P_pv ... r_bat * (param.bat_energy_cost * E_bat param.bat_power_cost * P_bat); % 年运维成本 Cost_om param.pv_om * P_pv param.bat_om * E_bat; % 运行成本典型日加权求和 Cost_grid 0; for d 1:n_d Cost_grid Cost_grid days_weight(d) * ... (sum(price_buy(:,d) .* P_grid_buy(:,d)) ... - sum(price_sell(:,d) .* P_grid_sell(:,d))) * dt; end Objective Cost_inv Cost_om Cost_grid;求解调用ops sdpsettings(solver, gurobi, verbose, 2, showprogress, 1); % 如果没装gurobi可以改成solver,intlinprog sol optimize(Constraints, Objective, ops);求解完成后取值并做后处理if sol.problem 0 pv_capacity value(P_pv); bat_energy value(E_bat); bat_power value(P_bat); pv_out_matrix value(P_pv_out); soc_matrix value(E_soc) / value(E_bat); else disp(求解失败请检查约束或求解器配置); end这一套代码从数据输入到结果输出的流程整体运行时间在Gurobi下不超过10秒即使是Matlab自带的intlinprog也就一两分钟。3.5 我踩过的一个大坑M取值写约束时用大M法处理购售电互斥这个M的取值很有讲究。取太小会错误地限制购售电功率上限取太大会导致数值稳定性问题求解器收敛变慢甚至报错。我一般取建筑最大峰值负荷的3倍左右既能保证约束松弛又不至于破坏矩阵条件数。4. 典型结果分析与综合优化价值量化4.1 一个实际案例的配置结果用一个真实项目的数据来做示例——某办公建筑年用电量约120万kWh峰值负荷380kW屋顶可用面积约2000平方米典型分时电价峰平谷分别为1.1元、0.7元、0.35元光伏上网电价0.4元/kWh。运行优化模型后得到的结果大概是这样的方案光伏容量(kWp)储能容量(kWh)储能功率(kW)年化总成本(万元)光伏自消纳率仅光伏优化1800082.632%光储联合优化16022011074.368%这个对比很能说明问题。加入储能之后光伏最优容量反而减小了因为储能承担了部分“移峰填谷”职能光伏不必过度安装来满足所有高峰负荷。同时光伏自消纳率从32%提升到68%说明储能把原本低价上网的“垃圾电”转运到了晚上自用。4.2 运行调度策略长什么样看储能调度曲线会发现一个规律低谷电价时段比如凌晨1点到6点储能充电上午负荷爬坡时段储能放电削峰午间光伏大发时如果建筑负荷低储能充电存掉多余光伏晚高峰时再放电避免从电网高价购电。整个过程完全由模型自动优化出来没有人为设定规则这正是优化调度的价值所在。4.3 联合优化相比分步优化的收益有多大为了单独量化“联合优化”的数学价值我专门做了一个对照实验第一步只优化光伏容量第二步在光伏容量固定的基础上优化储能容量和运行策略。结果分步优化的年化成本是78.9万元联合优化是74.3万元差额4.6万元/年大约是总成本的6%。这6%就是只看局部不看全局的代价。所以我特别建议你在写论文或做报告时一定要把“联合优化 vs 分步优化”作为一个对比维度。这不仅能证明你方法的优越性数值结果也很有说服力。5. 常见问题与排查技巧实录5.1 储能SOC约束报错或结果跳变我写代码初期遇到过SOC曲线不连续的情况后来排查发现是SOC递推约束里漏了dt。如果你的时间步长是1小时dt1那这个问题暂时不会暴露但如果改成15分钟一个时段dt0.25漏掉dt就会导致SOC变化量被放大4倍结果完全乱套。建议在代码里统一用变量dt表示时间步长而不是写死1。5.2 求解器报“infeasible problem”模型无解通常来自几个地方SOC初值和上下限冲突比如soc_min0.8但soc_init0.5功率平衡约束太紧比如某个时段负荷必须满足但光伏、储能、电网总可用容量不够二进制变量互斥约束和功率上限约束组合导致可行域为空。排查手段是先用一个极小规模算例调试比如24个时段、1个典型日然后逐条注释掉约束看哪条导致无解。YALMIP有个命令“diagnostics”可以直接输出冲突约束信息我调试时基本靠它。5.3 储能容量结果出现奇怪的“零”如果模型算出来储能容量为0但直觉上应该配储能先别急着怀疑模型错误。大概率是分时电价价差不够覆盖储能度电成本。储能充放一次的度电成本大约是电池单位能量成本除以寿命循环次数再除以往返效率。如果峰谷价差小于这个值最优解自然不装储能。这个是模型在说真话不是bug。如果想强制配储能可以加一个最小容量约束或者把储能成本改成更实际的数值。5.4 求解时间过长怎么办MILP的求解时间对二进制变量数量极其敏感。如果你有365个典型日每个典型日24个时段4个二进制状态变量那就是35040个二进制变量intlinprog会跑得很痛苦。解决办法有三个用聚类算法把365天压缩成3-6个典型日这是最有效的降维手段减少二进制变量数量比如购售电互斥在实际场景中可以用价格机制天然保证只要你设置购电价恒高于售电价模型就不会同时买卖那对互斥约束可以去掉一组换用Gurobi或者Cplex这类商用求解器对MILP的branch and bound实现远好于intlinprog。5.5 Matlab版本与求解器兼容性坑我最早用Matlab R2020a自带intlinprog跑后来换到R2022b发现YALMIP版本和求解器接口有小变化。建议YALMIP直接从GitHub拉最新版不要用Matlab Add-on里的旧版。另外装Gurobi时要注意Matlab要能从路径里找到gurobi的mex文件运行“gurobi_setup”把路径加进去否则YALMIP会报“No solver found”。6. 代码结构扩展思路这套代码的框架可以轻松扩展到更多场景。比如你要在模型里加“需量电费”只需要在目标函数里增加一项基于月峰值负荷的惩罚项并添加峰值负荷约束变量即可你要考虑储能寿命衰减可以把电池循环次数作为状态变量加一个寿命惩罚项到目标函数里。甚至你可以把电动汽车充电桩的负荷也纳入模型形成一个光储充一体化优化问题核心逻辑不变只是变量和约束数量增多。最后再分享一个我非常受用的经验任何优化模型动手写代码之前先把数学模型在纸上完整列出来变量、目标函数、约束条件、量纲全部手写一遍再开始写Matlab。这样能减少至少一半的调试时间。光储系统优化看起来复杂但一旦你把物理问题翻译成数学表达式剩下的工作就像搬砖一块一块码清楚就能跑出结果。希望这篇内容对你的项目有实实在在的帮助。本文还有配套的精品资源点击获取
返回列表