首页 / 资讯中心 / 文章详情

Matlab调用Cplex求解激励型需求响应负荷转移策略

Matlab调用Cplex求解激励型需求响应负荷转移策略 ★ FEATURED ARTICLE
做电力系统优化的人都知道需求响应这几个字看着热闹真正落地的时候最头疼的不是算法而是怎么把用户侧那点“弹性”变成可计算的数学模型。前两天做激励型需求响应下的负荷转移策略用Matlab把场景搭好再用Cplex求解混合整数规划整个过程踩了不少坑也把原本零散的思路理顺了。这篇就把这套方法完整拆开讲一遍从业务逻辑、数学模型到Matlab调用Cplex的代码实现、调试经验适合正在做需求响应仿真、或者准备用Matlab接Cplex做优化调度的同学参考。你会看到一个负荷转移策略从“想法”到“代码”再到“结果曲线”的全过程不绕弯子。1. 激励型需求响应与负荷转移的业务逻辑1.1 激励型响应和价格型响应的关键区别需求响应Demand ResponseDR大体分两条路线价格型需求响应和激励型需求响应。价格型比较“间接”靠峰谷电价、实时电价去引导用户自发调整用电行为激励型则比较“直接”是电网公司、售电公司或者负荷聚合商和用户提前签订协议约定在某些时段由调度方发出信号用户按指令削减或者转移负荷事后按响应量给补偿。两种模式在优化建模上的差异非常明显。价格型响应模型里电价是输入参数用户负荷弹性系数是核心建模重点在价格弹性矩阵而激励型响应模型里补偿单价是显式的成本项决策模型里多了一笔“补偿支出”还会出现“用户到底有没有参与响应”这种逻辑状态也就是0-1变量。说得直白点激励型响应天然适合用整数规划来表达因为你不仅要算“转多少负荷”还得算“哪些用户、哪些时段参与”。从工程角度看激励型响应的典型玩法包括直接负荷控制DLC、可中断负荷IL、需求侧竞价DSB等。这套机制的设计初衷是在系统峰值负荷压力较大时用确定的补偿合同换确定的负荷削减量比价格传导更加可靠。对优化调度人员来说用户侧的“可转移负荷”就是一组可以重新排布的资源——就像快递站点的包裹高峰期送不完的只要用户同意就可以挪到闲时再配送。1.2 负荷转移本质上是在时间轴上做“搬运”负荷转移这个词听起来简单拆开看其实是两个动作的组合在高峰时段把可推迟的负荷搬出去在低谷时段把负荷搬回来。典型的可转移负荷包括居民家庭的热水器、空调预冷、电动汽车充电、工业流程里非连续运行的粉碎机、清洗机等。真正建模的时候我需要把一天切成若干个时段一般直接用24小时。每个用户在每个时段有一个原始负荷削峰时段的负荷减少量就是“转移出去”的功率填谷时段的负荷增加量就是“转移进来”的功率。关键约束是同一个用户在调度周期内转出去的累计电量和转进来的累计电量必须相等。也就是说总的用电量不变只是时间位置变了。别小看这条守恒约束没有它求解器会跑出一堆“凭空产生电量”或“凭空消灭电量”的荒谬结果。还有一个容易被忽略的工程问题转移不是免费的也不可能是无限量的。每个用户受设备特性和使用习惯限制可转移比例一般只有总负荷的10%到40%左右。比如工业用户的大型间歇设备单台设备启动后不能随便频繁启停所以还要限制一个用户在一天内最多参与几个时段。这些放在优化模型里就是容量约束和响应次数约束。模型如果缺少这些约束求出来的“最优策略”看起来很美实际调度时根本执行不下去。2. 为什么偏偏是Matlab加Cplex这套组合2.1 负荷转移模型里真正难啃的是那堆0-1变量坦白讲负荷转移策略的数学模型如果全部使用连续变量用Excel自带的求解器都能做一部分。但一旦加入“用户是否参与响应”的0-1变量或者加入“响应次数不超过K次”这类组合约束问题就变成了混合整数线性规划MILP。MILP的求解难度比线性规划高一个数量级因为求解器需要在离散变量的组合空间里搜索可行域不是连续的凸集而是零零散散的可行整数点。Cplex最擅长的正是这一类问题。它对MILP的处理方式是分支定界加上各种切割平面技术能够解决电力系统中常见的数千甚至数万个变量规模的调度问题。对于负荷转移策略这种中规模优化问题Cplex在可接受时间内找到全局最优解或者有界次优解通常没有压力。我实测过几百个用户、24个时段、加上响应状态变量后总变量数在两万以内的模型Cplex求解时间基本在几秒到一两分钟之间这个性能表现对仿真研究和离线调度都够用。相比之下如果只用连续松弛模型去近似0-1变量全被当成[0,1]区间内的连续量求解速度快很多但解出来的“用户响应状态”很可能是0.6这种不伦不类的值。要是拿这样的结果去做实际调度要么没法执行要么执行结果和模型预期完全对不上。所以这类问题该用MILP就老老实实用MILP不要贪图省事去做线性松弛否则后面返工的代价更大。2.2 Matlab负责场景和矩阵Cplex负责搜索和求解Matlab和Cplex的分工其实很明确。Matlab承担三件事生成模拟负荷数据、把优化问题组织成标准矩阵形式、对求解结果做可视化和统计分析。Cplex承担一件事在给定的约束矩阵和目标函数下高效搜索可行整数解。有人可能会问既然Matlab里有intlinprog为什么还要额外装一个Cplex我的经验是Cplex的求解性能在中等规模MILP上通常优于Matlab默认求解器而且电力系统领域很多学术代码、历史项目都是基于Cplex写的兼容性更省事。再加上Cplex对大模型的内存管理、数值稳定性处理都比较成熟遇到病态矩阵或大规模问题时更容易收敛。IBM还提供了免费的Community Edition可求解变量规模和求解时间都受到限制但对教学、验证算法做小规模仿真完全够用。等模型真的需要大规模计算再考虑学术版或企业版授权。还有人问用Yalmip包装Cplex不是更省事吗Yalmip确实能简化建模把约束写成类数学表达式的形式我平时做算法原型也用它。但如果你要集成到工程系统中或者要对约束矩阵做逐行细粒度调试直接用Matlab原生API加Cplex工具箱其实更可控。Yalmip中间层会引入矩阵变换报错的时候定位问题会绕一些。这篇文章里的代码不使用Yalmip全部直接用矩阵形式构造模型这样每一行约束对应什么物理含义都清清楚楚。3. 负荷转移策略的数学模型搭建3.1 场景假设与参数说明我这里构建一个典型的仿真场景一个负荷聚合商管理若干用户需要用激励手段让用户在次日24小时内调整用电曲线目标是压低系统最大峰值并控制总成本。用户侧的负荷数据、可转移比例、补偿价格都是已知输入。用到的参数如下表参数含义典型取值U用户数量5到200均可T调度时段数24每小时一个时段D(u,t)用户u在时段t的原始负荷单位kWhα(u)用户u最大可转移负荷比例0.1~0.4c_down(u)用户u削减负荷的补偿单价元/kWhr_up(u)用户u增加负荷的收益单价元/kWhλ峰值成本系数元/kWK单个用户最多参与响应时段数2~6需要解释一下c_down和r_up这两个价格参数的含义。激励型响应中用户被要求削减负荷聚合商需要支付削减补偿而当用户在低谷时段增加用电本质上是帮助聚合商消化了低谷电量、增厚了售电收益所以模型中把它记为负成本也就是收益项。两套价格的取值可以不一致通常削减补偿比填谷奖励更高因为削减用电给用户带来的舒适度损失和生产经营损失更大。3.2 决策变量与目标函数决策变量一共四组再加一个辅助变量x_down(u,t)用户u在时段t的削峰转移量连续变量大于等于0。x_up(u,t)用户u在时段t的填谷转移量连续变量大于等于0。b_down(u,t)0-1变量取1表示用户u在时段t参与了削减响应。b_up(u,t)0-1变量取1表示用户u在时段t参与了填谷响应。M调度周期内系统最大峰值负荷辅助变量。目标函数我写成$$ \min \quad \lambda \cdot M \sum_{u1}^{U}\sum_{t1}^{T} c_{\text{down}}(u) \cdot x_{\text{down}}(u,t) - \sum_{u1}^{U}\sum_{t1}^{T} r_{\text{up}}(u) \cdot x_{\text{up}}(u,t) $$这个表达式其实是两个目标的加权复合。第一项λ乘以M是在惩罚峰值促使模型把负荷从高峰时段挪走。第二项是削减补偿成本第三项是填谷收益。因为λ的取值通常远大于单位电量的补偿单价模型会在经济性和削峰效果之间做权衡。假如λ设得太小模型会倾向于“少动用户”峰值压不下来假如设得太大模型会拼命让用户填谷产生不合理的负荷曲线畸变。后面做灵敏度分析时λ就是一个核心待调参数。3.3 约束条件的工程含义第一组约束是电量守恒这是负荷转移模型不可动摇的底线$$ \sum_{u}\sum_{t} x_{\text{down}}(u,t) \sum_{u}\sum_{t} x_{\text{up}}(u,t) $$这保证转移前后总用电量完全一致。从数学上理解这是一个等式约束对应Cplex里的Aeq矩阵。第二组约束定义系统峰值。任意时段的系统总负荷都不能超过M$$ \sum_{u} D(u,t) - \sum_{u} x_{\text{down}}(u,t) \sum_{u} x_{\text{up}}(u,t) \le M, \quad \forall t $$这个约束的意思是每个时段“原始负荷减去削减量加上填谷量”之后的值都不能大于M。由于目标函数里在最小化M求解器会自动把M压到所有时段负荷的最大值。这正是峰值负荷建模的标准技巧用辅助变量加一组不等式代替复杂的max函数。第三组约束是响应能力限制。转移量不能超过用户可转移比例$$ x_{\text{down}}(u,t) \le \alpha(u) \cdot D(u,t) \cdot b_{\text{down}}(u,t) $$$$ x_{\text{up}}(u,t) \le \alpha(u) \cdot D(u,t) \cdot b_{\text{up}}(u,t) $$这里的0-1变量起到“开关”的作用。当b_down等于0时右边的上界就是0x_down被迫等于0当b_down等于1时x_down最多取到α乘以原始负荷。这组约束把连续变量和离散变量绑定在一起正是整数规划的核心表达方式。第四组约束限制用户参与次数防止模型把优化压力集中在少数几个用户上$$ \sum_{t} b_{\text{down}}(u,t) \le K, \quad \forall u $$$$ \sum_{t} b_{\text{up}}(u,t) \le K, \quad \forall u $$这样处理之后每个用户的削减和填谷行为都被限制在K个时段以内实际调度中更具可操作性。工业用户如果一天被要求频繁调整生产节奏根本无法接受居民用户如果频繁被打扰也会流失参与意愿。这组约束更多是从工程实际和用户体验出发设置的。4. Matlab调用Cplex的完整代码实现4.1 环境准备与工具箱检查首先确认Cplex能正常被Matlab识别。安装好IBM ILOG CPLEX之后需要把Cplex安装目录下cplex/matlab文件夹添加到Matlab路径。这一步经常被忽略不少人明明装好了Cplex却在Matlab里运行cplexmilp时报“未定义函数或变量”。添加路径的方式有两种。一种是在Matlab界面当前文件夹浏览到Cplex的matlab目录右键“添加到路径”另一种是在代码里用addpath(genpath(C:\Program Files\IBM\ILOG\CPLEX_Studio2210\cplex\matlab))来加。注意版本号要对应你自己的安装目录。添加完成后运行ver(cplex)如果能显示Cplex工具箱版本号说明环境正常。这时工作区里调用cplexmilp就不会再报找不到函数了。4.2 构造优化模型的标准Aineq和Aeq矩阵Cplex的Matlab接口要求用户自己把目标函数、不等式约束、等式约束组织成标准形式。核心就是三块目标向量f、不等式矩阵Aineq和右端项bineq、等式矩阵Aeq和右端项beq。变量顺序我规定如下1到U*T个变量x_down(u,t)UT1到2U*T个变量x_up(u,t)2UT1到3UT个变量b_down(u,t)3UT1到4UT个变量b_up(u,t)最后1个变量M先构造模拟负荷数据和基础参数clear; clc; rng(42); U 5; % 用户数量 T 24; % 时段数量 D 80 60*rand(U,T); % 基础负荷 for t 1:T if t8 t18 D(:,t) D(:,t) 40*sin((t-8)/10*pi); % 人为制造峰时段 elseif t23 || t6 D(:,t) D(:,t) * 0.6; % 谷时段 end end alpha 0.2 * ones(U,1); % 可转移比例 c_down_u 0.8 * ones(U,1); % 削减补偿 r_up_u 0.5 * ones(U,1); % 填谷收益 lambda 30; % 峰值成本系数 K 3; % 最多参与响应时段数这里我手动制造了一个接近实际电网的峰谷形态白天上午8点到下午18点负荷偏高深夜和凌晨负荷偏低。数据不是一个平滑的数学函数而是带随机性的仿真数据这样后面的结果图看起来更有说服力。接着建立变量索引n_down U*T; n_up U*T; n_bdown U*T; n_bup U*T; nVar n_down n_up n_bdown n_bup 1; idx_down reshape(1:n_down, U, T); idx_up reshape(n_down (1:n_up), U, T); idx_bdown reshape(n_down n_up (1:n_bdown), U, T); idx_bup reshape(n_down n_up n_bdown (1:n_bup), U, T); idx_M nVar;构造目标函数向量ff zeros(nVar,1); for u 1:U f(idx_down(u,:)) c_down_u(u); f(idx_up(u,:)) -r_up_u(u); end f(idx_M) lambda;注意这里b_down和b_up本身没有直接的目标系数它们通过上边界x_down ≤ αDb_down间接影响目标。如果希望模型“少用”某个用户的响应次数也可以在b_down、b_up上加上很小的惩罚系数例如0.001起到正则化作用。构造不等式约束。先把响应能力约束和响应次数约束组织成交错的行Aineq sparse(T U U 2*U*T, nVar); bineq zeros(T U U 2*U*T, 1); row 0; % 1) 峰值约束 for t 1:T row row 1; Aineq(row, idx_down(:,t)) -1; Aineq(row, idx_up(:,t)) 1; Aineq(row, idx_M) -1; bineq(row) -sum(D(:,t)); end % 2) 每个用户最多参与K个削减时段 for u 1:U row row 1; Aineq(row, idx_bdown(u,:)) 1; bineq(row) K; end % 3) 每个用户最多参与K个填谷时段 for u 1:U row row 1; Aineq(row, idx_bup(u,:)) 1; bineq(row) K; end % 4) 转移量上限与0-1变量的绑定约束 for u 1:U for t 1:T row row 1; Aineq(row, idx_down(u,t)) 1; Aineq(row, idx_bdown(u,t)) -alpha(u)*D(u,t); bineq(row) 0; row row 1; Aineq(row, idx_up(u,t)) 1; Aineq(row, idx_bup(u,t)) -alpha(u)*D(u,t); bineq(row) 0; end end这样写循环在U和T较小的时候完全够用。如果用户数量涨到上千U*T规模很大建议把循环改成向量化批量构造否则生成矩阵本身就会成为性能瓶颈。构造等式约束即总转移电量守恒Aeq sparse(1, nVar); Aeq(1, idx_down(:)) 1; Aeq(1, idx_up(:)) -1; beq 0;设置变量上下界。连续变量的下界全部为0上界中x_down和x_up设置为可转移上限b系列变量上界为1M的上界设为无穷大lb zeros(nVar,1); ub inf(nVar,1); ub(idx_down(:)) alpha(:) .* D(:); ub(idx_up(:)) alpha(:) .* D(:); ub(idx_bdown(:)) 1; ub(idx_bup(:)) 1;4.3 求解与结果提取调用cplexmilp函数。这里要指定连续变量和整数变量的类型xint [idx_bdown(:); idx_bup(:)]; % 整数变量位置 ctype char(C * ones(1, nVar)); ctype(xint) I; options cplexoptimset(Display, on, MIPInterval, 1); [x, fval, exitflag, output] cplexmilp(f, Aineq, bineq, Aeq, beq, ... [], [], [], lb, ub, ctype, xint, options);几个容易出错的地方值得提一下。第一xint必须是行向量而且索引对应的是整数变量在完整变量向量中的位置。第二cplexmilp的签名在不同版本略有差别我列出的这个签名包含了SOS约束的空位参数如果你的版本弹出“参数数目不正确”的提示就用doc cplexmilp查一下当前版本的实际签名通常把空位参数删掉或补齐就行。第三ctype里大写字母C代表连续变量I代表整数变量别把小写的c和i当成有效值。求解完成后把结果重新组织回用户-时段矩阵x_down reshape(x(idx_down), U, T); x_up reshape(x(idx_up), U, T); b_down reshape(x(idx_bdown), U, T); b_up reshape(x(idx_bup), U, T); M_opt x(idx_M); load_before sum(D, 1); load_after load_before - sum(x_down, 1) sum(x_up, 1);这里顺手计算了转移前后的系统总负荷曲线后面画图会用到。写一个简单的绘图脚本把优化结果呈现出来figure; subplot(2,1,1); stairs(1:T, load_before, LineWidth, 1.5); hold on; stairs(1:T, load_after, LineWidth, 1.5); plot([1 T], [M_opt M_opt], k--); legend(原始负荷, 转移后负荷, 优化峰值); xlabel(时段); ylabel(负荷/kW); grid on; subplot(2,1,2); bar(1:T, sum(x_down,1), r); hold on; bar(1:T, -sum(x_up,1), b); legend(削峰转移量, 填谷转移量); xlabel(时段); ylabel(转移量/kWh); grid on;在我的测试里目标函数值和负荷曲线都能正常输出。转移后的负荷曲线明显比原始曲线平滑峰值被压了下来低谷时段有了额外的负荷填充整体呈现出“削峰填谷”的效果。这里要特别核对一下电量守恒是否满足转移后总电量应该和转移前完全一致用sum(load_before) - sum(load_after)验证数值应该接近机器精度。5. 调试记录与常见问题排查5.1 无可行解问题做优化最怕的就是求解器返回“无可行解”。我自己在调模型时遇到无可行解有九成是因为上下界或约束写矛盾了。最常见的坑是等式约束和上界冲突。比如电量守恒要求sum(x_down) sum(x_up)但如果每个用户的可转移量上限设得太低可能出现所有用户削峰总量小于强制填谷需求量的情况导致没有可行解。排查方法是把等式约束去掉再解一次看看约束矩阵本身是否兼容。或者把上界放大先跑出一个解再逐步收紧上界观察可行域变化。另一种典型情况是响应次数约束和转移量绑定约束冲突。假如K设成了1而模型又要求同一个用户在多个时段参与削峰才能满足峰值约束那就会出现无解。这时把K调大一点或者放宽alpha参数通常能解决。建议调试时把约束矩阵梳理成一个清单逐条对照物理意义。我习惯在构造完Aineq之后写一段校验代码assert(size(Aineq,2) nVar, Aineq列数不匹配); assert(size(Aeq,2) nVar, Aeq列数不匹配); assert(all(size(ub) [nVar 1]), ub维度错误);这些小断言在模型变量数量变大时特别管用能及时发现变量索引错位的问题。5.2 求解时间过长中规模MILP在绝大多数情况下不会很慢但如果你的用户数量大、时段拉长到日内96点或者K允许每个用户参与很多时段组合空间就会急剧膨胀。我实测下来几个有效的加速手段按性价比排序是这样的第一给MIP设置一个合理的相对gap。Cplex默认追求严格最优仿真研究不需要非要证明0.00%的gap。设置options.mip.tolerances.mipgap 0.001允许0.1%的次优性求解时间可能压缩到原来的十分之一。第二给整数变量提供初始可行解。如果你有启发式算法能先给出一个可行的调度方案就把它传给Cplex的x0参数分支定界可以从一个高质量起点开始搜索显著加快收敛。工程上常见的做法是先用连续松弛解一个近似解然后把接近0或1的变量直接取整作为初始解喂给MILP。第三检查约束矩阵是否存在大量非零元。如果矩阵规模很大务必用稀疏矩阵存储。我在代码里使用了sparse构造Aineq这一行改动在用户数超过100时效果显著否则内存占用和求解器预处理都会变得很慢。5.3 许可证与版本兼容问题Cplex Community Edition对求解问题规模有限制变量数、约束数、求解时间都受限。如果你的模型规模比较大跑一会儿就报许可证限制错误先别急着怀疑代码查一下是不是已经超过免费版上限。此时可以考虑缩减对应用户数或者购买授权。另一个常见问题是Matlab版本和Cplex版本不匹配。Cplex的Matlab接口本质上是编译好的MEX文件不同版本的Matlab对MEX文件格式有兼容性要求。如果装完Cplex后调用cplexmilp报“找不到符号”或“MEX文件无效”通常就是版本不匹配。解决办法是重新下载对应你Matlab版本号的Cplex版本或者安装完整版的Cplex Studio时选择兼容的组件。装完之后务必重启Matlab让环境变量生效。6. 实操心得与进阶建议6.1 参数灵敏度测试怎么做模型跑通只是第一步真正有价值的是了解参数如何影响结果。我强烈建议把λ、α、K、补偿价格这几个参数做一轮网格扫描。以峰值成本系数λ为例把λ从1逐步调到100每次重新求解记录优化后的峰值负荷M、补偿总成本、填谷总收益、以及每个用户的实际响应次数。画成曲线之后你会看到一个明显的规律λ很小时模型几乎不愿意动用户M接近原始峰值随着λ增大M快速下降但补偿成本也在上升当λ超过某个阈值后M的变化幅度变缓说明已经接近转移能力的上限。这个拐点对应的λ就是工程上最合适的取值。这样的灵敏度分析还有一个额外好处它帮你确认模型对参数的依赖性是否合理。如果λ微小变化导致结果剧烈翻转说明模型可能存在数值稳定性问题。这时候优先检查矩阵条件数和约束量纲比如把负荷单位统一到MW把补偿价格统一到元/MWh避免数值差距过大影响求解器判断。6.2 从单日调度扩展到多日协同和实时控制这套模型本质上做的是“日前调度”层面的负荷转移策略它假设用户响应行为完全可以预测。实际项目中还有两个扩展方向很常见。第一个方向是多日协同。很多可转移负荷并不是必须在当天内平衡比如电动汽车充电可以今天少充、明天多充。把模型的时间窗口从24小时扩展到168小时电量守恒约束从用户日内守恒变成跨日守恒模型结构和代码改动都不大但能反映更真实的用户行为。代价是变量数量和约束数量翻好几倍求解性能需要重新评估。第二个方向是滚动优化。日内如果出现预测误差比如某时段实际负荷比预测高很多需要在线重新计算负荷转移指令。可以把当前时段的负荷实测数据当作已知量只对未来几个时段重新求解模型。这种策略叫模型预测控制和本文的静态优化结合后需求响应方案就从“静态计划”变成了“动态调度”离实际工程又近了一步。我在实际使用中发现这份代码里最有价值的部分不是求解器调用那几行而是约束矩阵的组织方式。把电量守恒、峰值定义、响应绑定、次数限制这套逻辑理清楚无论后面换Gurobi、换成线性规划还是换成随机优化模型的骨架都是通用的。你的代码可以换掉求解器但物理约束一个都少不了。也希望这篇文章能帮你少走弯路快速把负荷转移策略做出来。
阅读完成 · 觉得有帮助?
咨询建站