空调负荷看着不起眼,但把它纳进配电网的优化调度里,能挖出的潜力比你想象的大得多。这个课题我前后折腾了两版代码,从最初的懵懵懂懂到后来能独立跑通完整的日前优化,踩了不少坑,也积累了一些可以复制的方法。这篇文章就围绕“含可再生能源的配电网最佳空调负荷优化控制”这个典型研究方向,把建模思路、Matlab实现框架、求解器选型和调试心得一次说清楚。
1. 项目到底在做什么:三个核心问题拆解
先别急着看代码,这个课题的本质是三个问题叠在一起:配电网里有了可再生能源、负荷里有大量空调、我们要找到一个“最佳”的控制方案。三件事单独看都不复杂,合在一起就需要好好梳理。
1.1 可再生能源接入配电网的痛点
分布式光伏、小型风电大量接入配电网之后,带来的最大问题不是“电不够用”,而是“电不好管”。光伏出力中午高、晚上为零,跟居民负荷的“早高峰、晚高峰”错位明显,导致两个典型现象:白天光伏大发的时候,变压器倒送功率,电压越上限;傍晚光伏骤降,负荷迅速攀升,又得靠上级电网大功率购电。配电网运行人员最怕的就是这种“一会儿多一会儿少”的波动。
我在做仿真时常用的一组实测数据就很有代表性:某配电台区光伏装机容量的渗透率接近45%,晴天时中午11点到14点变压器关口功率接近零甚至短暂反转,而到了晚上18点到21点,负载率又冲到85%以上。这种场景下,如果只靠调节变压器分接头或者无功补偿,效果非常有限,因为问题出在“有功功率时序不匹配”上。
1.2 空调负荷为什么值得被“控制”
空调负荷在夏季尖峰时段往往能占到城市负荷的30%到50%,而且它有天然的热惯性——房间温度不是一个瞬时量,而是像水库一样,有“蓄水”能力。短时间调节空调功率,室温不会立刻越过舒适区间,这就给了调度人员一个宝贵的“时间窗口”。
我举个例子:一栋楼20台空调,每台额定功率1.5kW,如果允许每台在15分钟里降低30%的功率,那么可调节容量大约是9kW,持续15分钟。虽然单台没多少,聚合起来就是可观的调节资源,而且响应速度比机组调峰快得多。更关键的是,空调是“用户侧”设备,不需要新建储能或调峰电站,几乎零边际成本。
1.3 核心关键词:“最佳优化”
“最佳”这个词在数学上对应一个明确的含义:在满足电网安全约束和用户舒适度约束的前提下,让某个目标函数达到最优。目标函数可以是购电成本最小、弃光量最小、配电网网损最小,或者三者加权求和。
Matlab在这个课题里扮演的角色就是“建模+求解+分析”的一体化平台。我用Matlab的经验是:它的矩阵运算和内置优化工具箱特别适合这类中规模优化问题,而且画图方便,能快速把结果可视化出来。整个课题的核心工作,就是把物理问题翻译成数学问题,再用Matlab把它解出来。
2. 数学模型搭建:把空调控制变成可求解的优化命题
这一部分是整个项目的灵魂,Matlab代码只是把数学模型翻译成机器能懂的指令。模型建不好,调什么求解器都是白搭。
2.1 空调负荷的等值热力学参数模型(ETP)
空调负荷建模最常见的框架是等值热力学参数模型(Equivalent Thermal Parameter,ETP),它把一个房间的传热过程简化成一阶或二阶RC等效电路。一阶模型的形式是这样的:
% 室温动态方程(离散化形式) % T_in(t+1) = T_in(t) + dt/(R*C) * (T_out(t) - T_in(t)) % + dt/C * (eta * P_ac(t) * s_ac(t)) % 其中 R 是等效热阻,C 是等效热容,eta 是能效比,P_ac 是空调额定功率, % s_ac 是开关状态(0/1),T_out 是室外温度,T_in 是室内温度为了让非科班读者也容易理解,可以把R和C想象成一个“保温瓶”:C决定了保温瓶能装多少热量,R决定了瓶壁的隔热性能有多好。建筑围护结构的保温性越好,R越大,C越大,房间温度变化就越慢,可调节时间就越长。
实际仿真里,一阶ETP模型精度基本够用,但如果房间朝阳面积大、或者有人员频繁进出,误差会比较明显。这时候可以升级到二阶模型,把墙体温度和室内空气温度分开建模。代价是状态变量翻倍、参数辨识更麻烦,但精度会好一些。
2.2 目标函数设计:经济性还是新能源消纳
目标函数的设计决定了“最佳”的方向。我整理了三种最常见的做法,它们的侧重点完全不同:
| 目标函数 | 数学表达 | 适用场景 |
|---|---|---|
| 购电成本最小 | min Σ C_p(t) × P_grid(t) × Δt | 电力市场环境,分时电价下削峰填谷 |
| 弃光量/弃风量最小 | min Σ (P_re_newable_max - P_re_newable_use) × Δt | 可再生能源渗透率高、消纳压力大 |
| 网损最小 | min Σ I_line^2 × R_line | 网架结构薄弱、线损率高的台区 |
实际项目中更多是组合目标,比如带权重的加权求和:
% 组合目标函数示例 % f = lambda_1 * sum(C_p(1:T) .* P_grid(1:T)) ... % 购电成本 % + lambda_2 * sum((P_load(1:T) - P_ac_set(1:T) - P_base(1:T))) ... % 负荷跟踪偏差 % + lambda_3 * sum((U(1:T) - U_ref).^2); % 电压偏差惩罚权重系数的选择没有标准答案,我习惯先用等权重跑一遍,观察三个目标分量的数量级,再按数量级反推权系数。比如购电成本动辄几千块,电压偏差只有零点几,直接相加的话电压约束等于被无视了。归一化处理是必须的。
2.3 约束条件:电网与用户都不能“得罪”
约束条件大致分三类:电网安全约束、空调物理约束、用户舒适度约束。
电网侧最基础的是节点功率平衡约束,潮流方程的非线性版本在Matlab里可以用Matpower,但如果做优化调度,我们一般用DistFlow线性化或者直接忽略网络拓扑,只做单节点功率平衡——这取决于项目范围。单节点版本作为入门完全够用:
% 功率平衡约束 % P_grid(t) + P_pv(t) + P_wind(t) = P_base(t) + sum(P_ac(i,t)) % 也就是:上级电网购电 + 可再生能源出力 = 基础负荷 + 空调总功率空调物理约束包括每台空调的功率上下限,以及定频空调的启停次数限制。后者特别容易被忽略——如果优化结果是让空调每5分钟启停一次,压缩机很快就废了。实际中我一般限制每台空调15分钟内的开关切换次数不超过1次,或者对启停状态变量加变化率约束。
用户舒适度约束是优化模型里最敏感的部分。典型做法是把室内温度限制在一个死区内,比如制冷模式下设定24℃,允许偏差±1℃,那么室内温度必须在23℃到25℃之间。这个约束看着简单,但和室外温度、建筑参数耦合在一起,经常会成为模型不可行的导火索。后面调试章节我会详细讲怎么处理。
3. Matlab代码实操:从建模到求解的完整实现方法
数学模型的落地需要一套清晰的代码架构。我给这个课题的Matlab实现划分成五个模块:数据输入、参数初始化、模型构建、求解计算、结果可视化。每个模块各司其职,彼此之间用结构体传递数据。
3.1 代码整体框架设计
模块划分清晰的项目,后期扩展特别方便。我的建议结构是:
%% ==================== 主程序入口 ==================== % data_prepare.m -> 读取负荷、光伏、电价、温度等输入数据 % param_init.m -> 设置系统参数、空调参数、优化权重 % model_build.m -> 构建优化问题的目标函数和约束矩阵 % solver_run.m -> 调用求解器,并验证解可行性 % result_plot.m -> 输出控制前后对比曲线、统计指标主脚本只需要依次调用这五个函数,每个子函数用独立的.m文件保存。无论用YALMIP还是直接用MATLAB自带工具箱,这套架构都可以复用。
参数初始化部分,我列举一组比较典型的研究场景参数:
%% ==================== 参数设定示例 ==================== % 时间参数 T = 48; % 优化时段数(每30分钟一个点,共24小时) dt = 0.5; % 时间间隔,单位小时 % 空调参数(聚合等值,代表50台空调的集群) N_ac = 50; % 空调数量 P_ac_max = 1.5 * ones(N_ac,1); % 单台空调额定功率(kW) eta_ac = 3.0 * ones(N_ac,1); % 空调能效比EER R_ac = 0.08 * ones(N_ac,1); % 等效热阻(摄氏度/kW),单位做了归一化 C_ac = 0.45 * ones(N_ac,1); % 等效热容(kWh/摄氏度) % 室内温度约束 T_set = 24; % 设定温度 T_deadband = 1; % 舒适度死区 ±1℃ % 可再生能源出力序列(示例数据,实际读外部文件) P_pv_profile = [0, 0, 0, 0, 0, 0, 0.2, 18.5, 42.3, ...]; P_base_profile = [85, 82, 78, ...]; % 基础负荷(不含空调)这些参数不要写死在主脚本里,一定要抽出来集中管理。我见过不少同学把参数散落在各个循环里,后面想改一个空调数量,全局搜索花很长时间,朋友间交流代码时也容易闹误会。
3.2 优化模型求解的核心实现
我一直推荐用YALMIP作为建模层,再加上一个好的MILP求解器。YALMIP扮演的是“翻译官”角色:你把数学表达式告诉它,它自动翻译成求解器需要的标准形式。它的一个显著优势是可以用非常接近数学定义的方式写约束,不容易把矩阵维度搞错。
核心调用代码大致长这样:
%% ==================== YALMIP建模与求解 ==================== % 定义决策变量 P_ac = sdpvar(N_ac, T, 'full'); % 各时段空调功率(连续变量) s_ac = binvar(N_ac, T, 'full'); % 各时段空调开关状态(0/1变量) T_in = sdpvar(N_ac, T, 'full'); % 各时段室内温度 P_grid = sdpvar(1, T, 'full'); % 向上级电网购电功率 % 目标函数:购电成本最小 + 弃光惩罚 Objective = sum(C_p(1:T) .* P_grid(1:T) * dt) ... + 1000 * sum(P_pv_forecast(1:T) - P_pv_use(1:T)) * dt; % 约束条件 Constraints = []; % 功率平衡约束:向上级购电 + 新能源出力 = 基础负荷 + 空调总功率 for t = 1:T Constraints = [Constraints, P_grid(t) + P_pv_use(t) == ... P_base_load(t) + sum(P_ac(:, t))]; end % 室温动态方程(一阶ETP模型的线性化形式) for i = 1:N_ac for t = 1:T-1 Constraints = [Constraints, T_in(i,t+1) == T_in(i,t) + dt/(R_ac(i)*C_ac(i)) * ... (T_out(t) - T_in(i,t)) + dt/C_ac(i) * eta_ac(i) * P_ac(i,t)]; end end % 用户舒适度约束 for i = 1:N_ac for t = 1:T Constraints = [Constraints, T_set(i) - T_deadband <= T_in(i,t) <= T_set(i) + T_deadband]; Constraints = [Constraints, 0 <= P_ac(i,t) <= P_ac_max(i)]; end end % 求解 options = sdpsettings('solver', 'gurobi', 'verbose', 2, 'mipgap', 0.0001); optimize(Constraints, Objective, options);这里有几个关键点值得展开。
第一,空调功率和启停变量之间的关系。上面示例里没有显式写出P_ac和s_ac的关系,实际中需要补充0 ≤ P_ac(i,t) ≤ P_ac_max(i) × s_ac(i,t),这就把功率限制和开关状态绑定在一起。如果直接用P_ac连续变量且不设开关变量,那么温度约束会自然驱动空调功率变化,但对于定频空调,功率只有0和P_max两种状态,必须用二进制变量。
第二,双线性项的规避。1/2阶ETP模型里,如果同时引入开关状态s_ac和功率变量P_ac,而P_ac又乘以s_ac,那就会出现非凸的二次乘积项,MILP求解器处理不了。我这里把模型改写成直接用P_ac作为连续变量代入室温方程,用P_ac ≤ P_max * s_ac来体现开关逻辑,这样整个模型保持在混合整数线性规划(MILP)范畴内,几分钟就能求解。
% 功率-开关状态耦合约束,避免双线性项 for i = 1:N_ac for t = 1:T Constraints = [Constraints, P_ac(i,t) <= P_ac_max(i) * s_ac(i,t)]; end end第三,时间段内温度初值设定。首时段的T_in(i,1)应该由用户设定或情景预设,取值在舒适度区间内即可,后面时段就由动态方程推得。如果初值远超舒适度区间,模型一开始就不可行,这是常见错误。
3.3 求解器选型与参数设置
这个课题的模型规模决定了求解器的选择不会太难。典型场景是48个时间段、50台空调,变量规模大约在4000个左右,其中二进制变量几千个。这种中等规模MILP,Matlab自带的intlinprog也能跑,但速度明显不如专业商业求解器。
我测试对比过几组数据,给出一些实际体验:
| 工具组合 | 求解速度(48时段,50台) | 使用体验 | 备注 |
|---|---|---|---|
| MATLAB自带 intlinprog | 慢,可能数分钟到十几分钟 | 无需额外安装,简单直接 | 适合小规模验证 |
| YALMIP + Gurobi | 秒级到几十秒 | 建模快,求解加速明显 | 商业许可,学生可以申请学术版 |
| YALMIP + Cplex | 秒级到几十秒 | 稳定,文档丰富 | 商业许可,同样有学术版 |
| YALMIP + scip | 中等速度 | 免费开源,但安装配置略繁琐 | 预算有限时的选择 |
我的建议是:初学阶段先用intlinprog验证模型逻辑是否正确,确认无误后再换Gurobi跑大规模算例。别一上来就直奔商业求解器,那样模型出了问题你根本分不清是自己写错了还是求解器设置不对。
求解器参数方面,最常见的调节项是MIP Gap和Time Limit:
% Gurobi求解参数设置 options = sdpsettings('solver', 'gurobi', ... 'verbose', 2, ... 'gurobi.mipgap', 0.0005, ... 'gurobi.timelimit', 300);MIP Gap设得太小会无谓拉长求解时间,设得太大又影响解的精度。我一般在0.0001到0.005之间做梯度测试,找到一个平衡点。对于工程优化的实际需求,1%以内偏差已经非常可以接受了,成本差异几乎看不出来。
4. 常见问题与排查技巧实录
这个课题的调试过程比建模本身更考验耐心。我把实际操作中遇到的频率最高的几类问题整理成了一个速查表,希望能帮你少走弯路。
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 模型提示不可行 | 舒适度约束太紧/初温不合适 | 检查首时段初温;放宽温度死区到±2℃试跑 | 在目标函数中加入松弛变量+惩罚项 |
| 空调功率一直为0 | 目标函数缺电价信号或启动代价 | 检查成本系数C_p是否全为0 | 给定正确的分时电价序列 |
| 室温波形振荡剧烈 | 时间步长太大或死区过窄 | 缩小dt到0.25小时试算 | 增加启停次数约束或斜率限制 |
| 求解时间过长 | 整数变量过多/模型非凸 | 检查是否有双线性项;统计变量规模 | 聚合同类型空调,用等值参数代替逐台建模 |
| 结果控制前后无明显差异 | 目标函数权重不合理 | 对比目标分量数量级 | 归一化后再设置权重系数 |
4.1 模型不可行的排查思路
先看模型是不是真的没有可行解,很多时候只是约束打得太紧。最典型的例子是把温度死区设在±0.5℃,而室外温度高达38℃,空调容量又留了很大裕量,这种情况下制冷能力根本跟不上,模型自然会报错。
我推荐一个标准做法:在温度约束里引入松弛变量,并在目标函数中加上惩罚项:
% 松弛变量:允许温度越过舒适度死区,但付出代价 epsilon_T = sdpvar(N_ac, T, 'full'); Constraints = [Constraints, T_set - T_deadband - epsilon_T <= T_in <= T_set + T_deadband + epsilon_T]; Constraints = [Constraints, epsilon_T >= 0]; Objective = Objective + 10000 * sum(sum(epsilon_T));这样模型永远有“退路”,求解器不会直接给你一个“infeasible”然后罢工。目标函数里惩罚系数取足够大(比如比购电成本高两个数量级),就既能保证在不极端情况下不越限,又能在极端情况下给出一个可用的合理结果。这是工程上处理约束不可行的通用套路,跑模型必备。
4.2 控制效果评估:结果对比怎么看
优化完不是直接出图就完事了,核心是要和基准场景做对比。我是这样设计的:
基准场景设定为“无控制”——所有空调在白天温度越上限时全功率运行,达到设定温度后自然启停。优化场景则用我们求得的各时段空调功率序列。
画图时分四张子图,信息密度比较高:
- 子图1:光伏出力的预测值与实际消纳量,能看出弃光程度
- 子图2:优化前后购电功率曲线对比,能看出峰值削减和填谷效果
- 子图3:室内温度变化范围,能验证舒适度约束有没有被触发
- 子图4:所有空调的聚合功率时序,能看出负荷调节的节奏感
统计指标方面,我会算三个核心数字:购电成本下降百分比、峰谷差削减率、室温越限小时数。三个指标用一个表格输出,一目了然:
%% ==================== 统计指标计算 ==================== cost_base = sum(C_p(:) .* P_grid_base(:) * dt); % 基准场景购电成本 cost_opt = sum(C_p(:) .* P_grid_opt(:) * dt); % 优化场景购电成本 saving_pct = (cost_base - cost_opt) / cost_base * 100; peak_reduction = max(P_grid_base) - max(P_grid_opt); peak_avg_ratio = peak_reduction / max(P_grid_base) * 100;从我自己跑的算例来看,比较典型的结果是这样:空调集群在全天内的功率峰值削减了12%到18%,购电成本下降了8%到15%,而室温始终控制在24±1℃的舒适区里。说明用预冷、周期性轮停等手段,“既省了钱,又没让人热着”。
4.3 我的几点实操经验
最后说几个从失败中总结的细节,都是代码和模型层面容易忽略的地方。
所有用到YALMIP的变量,务必确认是sdpvar或binvar类型,然后你进行矩阵拼接时才能对齐维度。我犯过的低级错误是把一个普通Matlab数组直接和大规模sdpvar矩阵相加,导致维度全部错乱,排查了半天才找到原因。检验手段很简单——size()参数打印一遍,规格一目了然。
对聚合空调数量的选择。50台空调如果可以分类聚合就分类聚合,把参数相近的空调群组变成一个“等值空调”,约束和变量的数量可以砍掉一半甚至更多。初始建模用50台逐台跑,验证逻辑;扩展算例时按楼栋或按区域聚合成5到8个等效单元,计算效率提升非常明显。
注意数值量纲。这个课题里涉及的物理量有kW、kWh、℃,如果目标函数里既有购电成本又有温度偏差,量纲不统一的问题必然出现。我用的解法是给每个目标分量乘一个归一化因子,比如把购电成本除以基准成本、温度偏差除以温度死区范围,全部转成无量纲量再加权,这样权重系数直接可解释为“重视程度”,不用去猜。
模型测试递进策略很实用:先跑1台空调、24个时段的小模型,验证动态方程正确;再放大到5台、48时段,测试约束逻辑;最后才跑全规模。很多人上来直接跑大数据量算例,模型稍微有问题,连出错的位置都找不到。小模型跑通、大模型跑快,这个顺序不会错。
关于光伏预测数据的使用,我一般做两大类情景测试:晴天(光伏波动小)和阴天(光伏波动大)。阴天场景最容易暴露出模型在爬坡约束、储能协同上的缺陷,多跑几个恶劣场景对论文评估研究的鲁棒性很有说服力。如果只盯着晴天的漂亮曲线,审稿人或导师一个问题就能戳穿。
我个人的经验是,这类优化控制课题,真正花时间的地方70%在模型调参和结果解释上,只有30%在写代码本身。建议看到文章的你,拿到代码后先不急着换场景换数据,而是把基准场景完整复现一遍,确认每个输出曲线都符合物理直觉,再开始做自己的扩展。物理直觉很重要——优化结果里如果出现“中午温度还开到最大制冷”这种反直觉现象,大概率是目标函数权重或约束出了问题,而不是数据编得有创意。