基于分时电价的家庭能量管理模型:MATLAB与CPLEX求解MILP负荷优化策略
发布时间:2026/10/2 23:06:30来源:尧图网络
做家庭能量管理模型也有一阵子了最近把分时电价条件下的家庭负荷优化调度又完整跑了一遍用MATLAB搭主程序调CPLEX解混合整数线性规划MILP整体效果挺理想。今天把这套策略的核心思路、数学建模、代码实现和踩坑记录都摊开聊一聊希望对正在做家庭能量管理模型、分时电价、可平移负荷相关课题的同学或者同行有点帮助。这套东西解决什么问题简单说就是家庭里各种电器不是都要立刻用冰箱、路由器这类不能挪但洗衣机、洗碗机、电动车充电这类是可以挑时段的。在分时电价下每度电在不同时间价格不一样如果能把可动的负荷尽量挪到便宜时段同时家里又有光伏或者储能还能进一步优化。整个过程用MATLAB建模把负荷调度写成优化问题交给CPLEX求解得到每个时间点各个设备的开关状态或功率指令。1. 项目整体逻辑分时电价引导下的负荷时间平移1.1 家庭能量管理到底在管什么家庭能量管理不是简单把总用电量降下来它最核心的目标是降低电费同时尽量不影响生活舒适度。家里用电设备大致分三类一类是固定负荷比如冰箱、路由器、油烟机、电视你没法随意挪一类是可平移负荷比如洗衣机、洗碗机、烘干机运行时长固定、功率固定但是可以整体挪到另一个时段还有一类是可转移负荷比如电动汽车充电它总充电量要求满足但充电功率可以在时间上灵活分配。分时电价把一天分成峰、平、谷若干时段谷段电价可能只有峰段的三分之一。这时候把可平移负荷挪到谷段运行电费肉眼可见地省下来。但是如果所有家庭都在谷段集中用电电网侧又可能出现新的负荷尖峰。所以现在很多研究会在模型里加一个“削峰填谷”目标或者在约束里限制定时段的最大总负荷。我这里先以“用户电费最小化”为目标再在扩展部分讲怎么考虑对电网友好的策略。1.2 为什么用分时电价作为触发信号分时电价是目前需求侧响应里最直观、最容易让用户理解的一种机制。它不是实时电价那种每分钟跳动的信号而是提前公布好的全天价格曲线用户可以根据价格安排用电计划。我接触过的很多实际项目里居民用户并不会实时盯着电价看他们需要的是“一键优化”或者“自动调度”。这就是家庭能量管理模型存在的意义把电价曲线读进来把负荷参数填进去自动给出第二天24小时每台设备的动作时间表。从数学角度看分时电价是一个分段常数函数非常适合作混合整数线性规划。因为目标函数是价格乘以功率再积分离散化后就是线性表达式设备启停又天然是0/1整数变量所以整个问题就是一个标准的MILPCPLEX这类商业求解器处理起来很快。1.3 可平移负荷建模的三个关键特征可平移负荷是这套模型里最容易出问题的地方也是很多论文里讲得模棱两可的部分。它有三个关键特征一是启动时间灵活但一旦启动就必须连续运行二是运行时长固定不是想开多久就开多久三是额定功率固定运行期间功率不变。举个例子洗衣机额定功率1.5千瓦程序周期2小时那么它在任意时间点开始未来两小时都会以1.5千瓦运行中间不允许断电。这个特性决定了它不能被拆成“0到1之间可调”的连续变量必须引入一个“开始时刻”的0/1整数变量。很多初学者会直接用连续功率变量表示洗衣机这样求解器会把洗衣机变成“功率可随时调”的假设备出来的结果完全不能用。1.4 整体技术路线与求解器选择我的方案分成四步第一步把一天按1小时间隔离散成24个时点录入分时电价曲线和三种负荷参数 第二步搭建目标函数即总购电成本在含储能时还要考虑充放电损耗 第三步写出所有约束包括功率平衡、设备运行逻辑、储能SOC递推、可平移负荷的连续运行约束 第四步调用CPLEX求解MILP问题输出每个时段各设备的运行状态和总功率曲线并对比优化前后的电费。求解器选CPLEX是因为这类问题的整数变量数量不多但0/1变量和连续变量混在一起MATLAB自带的linprog只能处理纯线性规划对整数变量无能为力。优化工具箱里的intlinprog也能解MILP但遇到设备种类多、时间窗更长的问题求解速度和稳定性不如CPLEX。而且CPLEX的MATLAB接口可以直接传矩阵和向量不需要额外写文件调试起来非常方便。2. 数学模型把“省钱”翻译成求解器听得懂的语言2.1 目标函数设计详解以24小时为一个优化周期时间步长取1小时定义时刻集合 T {1, 2, ..., 24}。目标函数为总购电成本最小化min sum( p_t * c_t )其中 p_t 是第 t 个时段家庭与电网交换的总功率正为购电负为反送电c_t 是该时段电价。在纯用电场景下p_t 恒正如果加了光伏和储能p_t 可能为负也就是卖电收益。目标函数看起来简单但这里有个隐含细节如果不加约束求解器会把所有负荷一股脑地同时塞进最便宜的时段然后就会出现一个超大的功率尖峰。实际家庭进线容量有限所以必须加一个总功率限值约束例如p_t P_limit这一条非常关键。很多论文只说“电费最小化”但如果没有功率限制优化结果就是凌晨3点所有家电同时启动现实中根本不可能。2.2 固定负荷与可平移负荷的数学表达固定负荷在每个时段的功率是已知常数直接用数组 F_t 表示比如冰箱功率100瓦全天24个时段都是0.1千瓦照明则在晚上几个时段有值。可平移负荷用二进制变量表示启动时刻。设设备 i 的额定功率为 P_i运行时长为 D_i 小时那么在1到T-D_i1个时点中只能选择一个时点启动因此有约束sum_{s1}^{T-D_i1} start_{i,s} 1其中 start_{i,s} 是0/1变量取1表示设备 i 在时段 s 开始运行。设备 i 在第 t 时段的运行功率可以表示为p_{i,t} P_i * sum_{smax(1,t-D_i1)}^{min(t,T-D_i1)} start_{i,s}这个式子的实际含义是如果设备在 t 时刻运行那么它的开始时间 s 一定满足 t-D_i s t也就是从当前时刻往前推 D_i 个时段内的某个开始时点启动了。由于 start 变量只有一个为1所以 p_{i,t} 不是0就是P_i。这种写法比直接用“运行状态”二进制变量更严谨。如果用运行状态变量 on_{i,t}那么约束“连续运行D_i小时”需要写成一长串不等式容易写错而且求解效率更低。我这里直接用“开始变量”逻辑直观CPLEX处理起来也顺手。2.3 可转移负荷电动汽车充电的两种常见模型电动汽车充电和洗衣机不一样它的功率可以连续调节而且不需要连续运行一整段。一般有两种建模方式。第一种是功率可调模型充电功率在0到最大功率之间连续变化只需要满足全天累计充电量达到目标值0 p_ev,t P_ev_max sum_t p_ev,t E_ev_target这种模型适合慢充、交流充电桩充电功率可以按1小时粒度调节。第二种是“充电时段寻优”模型充电功率要么不充要么以额定功率充引入0/1变量适合直流快充。我这里主要用第一种因为它更贴合普通家庭夜间慢充场景而且能天然避开可平移负荷的整数复杂性之外让模型里既有整数变量又有连续变量。2.4 加入储能后的SOC递推与损耗如果家庭装了储能电池状态变量是荷电状态 SOC_t第 t 时段结束时电池的剩余电量比例SOC_t SOC_{t-1} - (p_ch,t * eta_ch - p_dis,t / eta_dis) * delta_t / B_cap其中 p_ch 和 p_dis 分别是充放电功率eta 是效率B_cap 是电池容量delta_t 是1小时。充放电不能同时进行所以需要二进制变量引入互斥约束p_ch,t M * y_t p_dis,t M * (1 - y_t)这里 M 是一个足够大的数比如电池最大功率的1.5倍。储能加入后模型会利用分时电价进行“低充高放”比如谷段充电、峰段放电。这时候总购电功率 p_t 固定负荷 可平移负荷 可转移负荷 充电功率 - 放电功率。由于充放电功率是连续变量加上SOC递推模型仍然保持MILP结构CPLEX能稳定求解。3. MATLAB环境搭建与CPLEX联合配置3.1 MATLAB版本与CPLEX安装注意事项先说明我的环境Windows 11MATLAB R2024aCPLEX 12.10用官方提供的MATLAB接口。如果你用的是MATLAB R2026b或R2025一定要注意CPLEX版本是否匹配不同版本的CPLEX在MATLAB路径设置上略有差异。CPLEX安装建议下载IBM ILOG CPLEX Optimization Studio安装时选择完整版。安装完成后在MATLAB命令行执行addpath(C:\Program Files\IBM\ILOG\CPLEX_Studio1210\cplex\matlab\x64_win64)然后运行Cplex Cplex(); Cplex.DisplayFunc [];如果没报错说明接口配置成功。如果报错找不到dll文件先检查是否把对应文件夹添加到了MATLAB路径另外注意MATLAB必须是64位CPLEX也是64位位数不一致几乎必挂。3.2 参数初始化与数据结构组织写这套程序时建议把所有参数写在一个结构体里方便后面改场景跑对比。我一般这样组织param.T 24; param.price [0.3 0.3 ... 1.2 ...]; % 24个时段电价单位元/kWh param.p_fixed [0.1 0.1 ...]; % 固定负荷功率 param.dishwasher struct(P, 1.2, D, 3); % 洗碗机 param.washer struct(P, 1.5, D, 2); % 洗衣机 param.ev struct(Pmax, 7, Etarget, 10); % 电动汽车这种数据组织方式最关键的好处是当你想测试不同家庭规模时只需要改结构体里的值不需要动后面所有代码。3.3 决策变量与约束矩阵生成的核心代码直接写Cplex的模型接口时需要把目标函数和约束都转成矩阵形式。这一步最容易出错我的习惯是先定义好变量顺序再用循环把每类约束一行一行加进模型。变量顺序我通常设为可平移负荷启动变量 start_wash、start_dish电动汽车充电功率 ev_power储能充放电变量和SOC变量最后是总购电功率 p_grid。每个变量都有一个全局编号 index可以用一个计数器逐渐累加。生成启动变量的核心代码如下numVar 0; % 洗衣机启动变量22个待选开始时段 num_wash_start param.T - param.washer.D 1; startIndex.wash (1:num_wash_start); numVar numVar num_wash_start; % 洗碗机启动变量 num_dish_start param.T - param.dishwasher.D 1; startIndex.dish (1:num_dish_start) numVar; numVar numVar num_dish_start;注意这里的 startIndex 保存了每个变量在全局变量数组中的位置后面构造约束都会用到。约束矩阵用稀疏矩阵 S 表示右端项向量 rhs约束方向 sense。对于“只能启动一次”的约束每一行就是对应设备所有启动变量的系数1for i 1:num_wash_start S(end1, startIndex.wash(i)) 1; end rhs(end1) 1; sense(end1) E; % equality可平移负荷运行功率的构造要小心它不是一个简单变量而是通过启动变量的“历史窗口”组合出来的。例如洗衣机在 t 时段是否运行取决于 start_wash 在 t-D1 到 t 之间是否有一个位置为1。构造 p_wash_t 这个中间变量时理想做法是把它定义为一个连续变量并加上约束让它等于启动变量窗口和乘以额定功率p_wash_t P_wash * sum_{smax(1,t-D1)}^{min(t,T-D1)} start_wash_s这里的 p_wash_t 不需要整数因为等号右侧已经由0/1变量驱动所以它自动是0或 P_wash。把这个关系作为等式约束写进去% 添加 p_wash_t 变量 numVar numVar 1; pWashIndex(t) numVar; % 添加等式约束 S(end1, pWashIndex(t)) 1; for s max(1,t-D1):min(t,T-D1) S(end1, startIndex.wash(s)) -P_wash; end rhs(end1) 0; sense(end1) E;同理为洗碗机、电动汽车充电功率都生成变量后在功率平衡约束里把它们加起来p_grid_t p_fixed_t p_wash_t p_dish_t p_ev_t这个等式约束中 p_grid_t 也是决策变量用来计算目标函数。最终目标函数系数 vector 中只有 p_grid_t 的系数为非零等于电价。3.4 调用CPLEX求解并读取结果构造完 Cplex 对象后设置目标函数和约束Cplex.Model.obj f; % 目标函数系数向量 Cplex.Model.A S; % 稀疏约束矩阵 Cplex.Model.lhs lb; Cplex.Model.rhs ub; Cplex.Model.sense sense; Cplex.Model.lb lb; % 变量下界 Cplex.Model.ub ub; % 变量上界 Cplex.Model.ctype ctype; % I表示整数C表示连续然后调用Cplex.solve();求解完成后结果保存在 Cplex.Solution.x 中。我在读取结果时会先做个校验否则经常出现“解出来了但变量错位”的情况。我的校验方法很简单拿结果回代所有约束看最大残差是否小于1e-6。这个步骤看似多余实际能挡住大多数下标的低级错误。x Cplex.Solution.x; optCost Cplex.Solution.objval; % 回代校验 maxResidual max(abs(S * x - rhs)); if maxResidual 1e-6 warning(约束残差过大请检查变量顺序); end4. 实操中踩过的坑与排查方法4.1 CPLEX接口的常见坑变量顺序错乱用Cplex的MATLAB接口最怕的一件事是变量编号和约束行顺序对不上。我在初写代码时因为把“洗衣机启动变量”和“洗碗机启动变量”的索引用错了结果求解器给出的方案竟然是“一天启动30次洗衣机”。这种错误不是不好找而是肉眼很难看出来因为目标函数值看起来还挺合理。排查方法调试时先固定所有可平移负荷的启动变量把模型退化成纯线性规划跟手算结果对比。比如把洗衣机强制安排在早上8点启动洗碗机安排在20点启动看看生成的功率曲线是否符合逻辑。确认无误后再放开整数变量这样能快速定位问题是不是出在约束拼接上。4.2 可平移负荷的“连续运行”约束写错另一种常见问题是把可平移负荷直接写成“运行时段的0/1变量”再额外加“相邻时段状态相同”的约束。比如约束 on_t on_{t1} 这种写法会导致洗衣机和洗碗机被强行绑定在一起无法独立选择开始时间。最稳妥的还是我前面那种“启动变量窗口求和”的写法。如果你坚持用状态变量请保证这样的约束组on_t - on_{t1} 1 - start_{t1} start_t sum_{jt1}^{tD-1} start_j 1这一套虽然也能做对但明显不如启动变量清晰。我在实地项目里因为要维护可读性最终还是全改成启动变量。4.3 模型报“infeasible”怎么办求解器返回 infeasible 时大概率不是CPLEX的问题而是约束自相矛盾。最常见的原因是电动汽车充电目标值太高而夜间时段又限制了最大功率。比如电池要求一晚充30千瓦时但家庭进线功率限值只有10千瓦可充电时间窗口只有4小时那一定无解。我的处理流程是第一步把硬约束里最可疑的约束逐条放宽。先将 EV 的充电目标从30降到20如果可行说明充电目标过紧 第二步去掉进线功率限值再跑一次如果可行说明功率上限约束与其他约束冲突 第三步加入松弛变量给每个约束一个惩罚成本用CPLEX求解最小化违规量的模型它能帮你指出哪些约束被违反得最严重。第三步其实很有用。我一般会预定义一组松弛变量 r把关键约束改成原约束左边 原约束右边 r目标函数里加上 sum(w * r)w 给一个相对大的权值比如10000。解出来后看 r 的取值哪条约束对应的 r 非零哪条就是瓶颈。4.4 求解结果振荡或不合常理的处理有时候目标函数正常但结果里洗衣机在半夜2点启动电是便宜了可用户体验很差。这说明模型缺少“舒适度约束”。我后来加了一个“最晚开始时间”参数比如洗衣机必须在晚上10点到次日早上7点之间开始洗碗机必须在白天运行。这个约束很简单就是在启动变量里把允许启动的窗口缩小sum_{sallowed} start_s 1其中 allowed 集合是用户自定义的可运行时段。这比在目标函数里加“不舒适惩罚”更容易实现也更好解释给业主听。另外还有一种不合理情况是储能频繁充放电一会充一会放对电池寿命损伤很大。这时候需要加一个最小充放电时间约束或者干脆对开关次数做限制。我试过在目标函数里加一个很小的切换惩罚系数比如0.01乘以开关动作次数就能有效防止抖动。要注意惩罚系数不能太大否则优化结果会牺牲电费收益。5. 从仿真到更贴近真实家庭系统的扩展5.1 加入光伏出力曲线如果家里有光伏只需要把光伏出力序列 pv_t 作为已知参数加到功率平衡约束里p_grid_t p_fixed_t p_wash_t p_dish_t p_ev_t - pv_t如果允许余电上网p_grid_t 可以为负目标函数变成min sum(p_grid_t * price_t - max(p_grid_t,0) * feed_t)其中 feed_t 是上网电价通常低于购电价。注意 max(p_grid_t,0) 是个非线性项在MILP里需要引入一个辅助二进制变量来区分购电和售电状态否则模型不再是线性。我这里在扩展版里单独加入了两个非负变量 p_buy_t 和 p_sell_t并约定它们不能同时大于0p_grid_t p_buy_t - p_sell_t 0 p_buy_t M * z_t 0 p_sell_t M * (1 - z_t)这样就把非线性成本转化成了线性约束CPLEX依然能稳定求解。5.2 把电动汽车充电做成联合优化家庭里的电动车充电完全可以和储能协同调度。比如晚上谷电时段电价低储能和电动车可以同时充电白天峰电时段电动车不充电反而由储能放电给空调和冰箱。这样做的前提是储能电池容量够大、功率够充足而且变压器容量没有卡住。实际项目里我还会加入“电动车第二天早上必须满电”的约束这个其实就是充电目标约束非常简单但能防止优化结果把电动车充电量压低到舒适阈值以下。5.3 考虑不确定性的两种实用做法光伏出力和用电习惯都不可能完全预测准确。两种实用做法第一种是滚动时域优化把24小时问题切成每1小时滚动一次每次只执行第一个调度指令然后更新预测重新求解。这种做法对家庭场景特别友好因为实时电价或者负荷预测数据不断更新时滚动规划能及时响应。第二种是鲁棒优化在约束里把光伏出力最坏情况考虑进去例如用光伏出力的下限参与约束这样即使明天阴天结果也不会崩溃。鲁棒优化的代价是结果偏保守电费节省会少一些但在工程上很实用。我之前在某个项目里对比过鲁棒模型比确定性模型的电费只高约8%但方案可执行性高了很多。5.4 代码复用与后续开发建议这套MATLABCPLEX的代码结构完全可以把可平移负荷的个数从2个扩到10个只需要维护好变量编号。我的建议是封装成一个函数function schedule householdOptimize(param)输入是参数结构体输出是各类设备的功率曲线和启动时间。这样做的好处是后续无论做GUI界面还是接入真实家庭网关都只需要调用这个函数就行。如果你以后要把模型部署到嵌入式设备MATLAB代码并不适合直接跑建议先用该模型离线验证调度策略再改写为C版本或者使用MATLAB Compiler生成独立exe。这个流程是目前行业内比较标准的做法。最后提醒几个实操中让我印象很深的细节第一分时电价数据别写死最好单独放一个Excel或CSV文件每次读取。电价一变程序不用改。我吃过亏为了图方便把电价写进主程序后来当地电价政策调整不得不一个个改常量极其痛苦。第二CPLEX的求解时间虽然短但模型规模变大时比如储能SOC、EV充电功率步步细化到15分钟粒度变量数和约束数会翻好几倍。这时尽量使用稀疏矩阵形式构造约束不要用MATLAB全矩阵否则内存容易爆炸。第三优化前先画电价曲线和负荷总需求曲线。我曾经遇到一个结果电费确实降了但可平移负荷被安排到早上6点家里没人洗衣机开着其实很不方便。后来我意识到模型没有“人的行为约束”于是加上“仅在用户在家时允许洗衣机启动”的限制这样才是真正能落地的家庭能量管理方案。这套模型我前后跑了好几个月从最初的纯线性规划到后来加入储能、光伏、电动汽车再到滚动时域每一步都是踩坑踩出来的。你如果准备复现我建议先从最基础的“固定负荷一台洗衣机分时电价”起步跑通之后再慢慢加复杂度。这样万一出问题你能明确知道是哪一块模型引入的。希望这些经验能帮你少走点弯路。
网站建设高端定制企业官网