简介:针对MATLAB R2023a环境下Matpower 7.1/8.0调用linprog时报错'LINPROG does not accept X0'的补丁,面向使用Matpower进行电力系统优化计算的科研与工程人员。问题源于MATLAB新版linprog接口不再接受初始点X0参数,导致qps_ot、qps_master等函数运行中断。补丁包内含2个m函数文件,针对内点法二次规划求解器相关模块进行了修正,压缩后仅5KB,直接替换Matpower安装目录中的同名文件即可恢复test_matpower与test_most的正常运行,无需改动其他代码。作者已在相应版本上亲测有效,适合遇到同类兼容性报错、需要快速修复环境的用户。该资源已有4102人学习,属于轻量但实用的排错工具,能为Matpower使用者省去自行排查接口变更的麻烦。 如果你经常在MATLAB里跑MATPOWER,估计对这样的报错不陌生:项目跑得好好的,一到某一步就蹦出来Error using linprog,后面跟一串算法不兼容的提示,整个仿真直接停摆。这种问题说大不大,说小不小,换求解器代价高,重装MATLAB又不现实。我这阵子给一台装了新版MATLAB的机器配老版本MATPOWER,正好把linprog相关的报错挨个踩了一遍,最后打了一个兼容补丁才彻底消停。
这篇就把整个排查和打补丁的过程整理出来,内容包括:MATPOWER到底在什么场景下会调用linprog、新老版本linprog接口到底差在哪、补丁代码该怎么写、打完之后怎么验证结果没跑偏。无论你是做最优潮流、配电网分析,还是平时只跑几个标准算例,这篇文章应该都能帮你省下不少排查时间。
1. 先弄明白:MATPOWER为什么会赖上linprog
1.1 MATPOWER在哪些环节会调用linprog
很多人以为MATPOWER只是用来算潮流和最优潮流的,和线性规划关系不大。但实际上,MATPOWER内部的求解链条里,linprog出现的频率比想象中要高。
最典型的场景是直流最优潮流(DCOPF)。交流最优潮流(ACOPF)里支路有功和无功、电压幅值、相角全部耦合在一起,是个典型的非线性问题,但直流潮流把无功和电压都简化掉以后,剩下的优化模型就退化成线性规划。MATPOWER在runopf里如果检测到mpopt.model = 'DC',就会走DCOPF分支,这时候核心求解器就是linprog。
还有一个容易被忽略的场景:MATPOWER从5.x开始引入了SDP放松、约束生成这类算法框架,这些算法的子问题很多就是LP。比如处理安全约束最优潮流时,Benders分解会把主问题拆成一系列LP子问题反复迭代,迭代一次就调一次linprog,只要底层linprog有一次异常,整个循环就崩了。
另外,如果你直接在MATLAB命令行敲mp_linprog(...)做自定义优化测试,那更不用说,肯定绕不开linprog。也就是说,MATPOWER虽然是个电力系统的专业工具箱,但底层对MATLAB优化工具箱的依赖非常深,尤其是线性规划这一块。
1.2 linprog接口从旧到新到底变了什么
要理解为什么一个老工具箱加新MATLAB会翻车,得先看linprog的接口历史。MATLAB优化工具箱在R2019a前后经历了一次比较大的调整,很多旧语法和旧选项被删除或者改名。下面这张表整理了我实际遇到的主要变化:
| 对比项 | 旧版(约R2018a之前) | 新版(约R2019a之后) |
|---|---|---|
| 选项类型 | optimset创建的结构体 | optimoptions创建的对象 |
| 算法名称 | active-set、interior-point、interior-point-legacy、dual-simplex | active-set被移除,保留interior-point、interior-point-legacy、dual-simplex |
| 初值x0支持 | active-set和interior-point都支持x0 | dual-simplex不支持x0,传了反而报错 |
| LargeScale选项 | 存在且默认开启 | 被删除,设置它会出警告 |
| 输出lambda结构 | 字段比较固定 | 部分字段在边界为空时不再返回 |
MATPOWER的mp_linprog函数一套代码要兼容这么多版本变化,显然不现实。老版本MATPOWER写死了一些旧接口,比如默认调用linprog(f, A, b, Aeq, beq, xmin, xmax, x0, opt)这种8参数、9参数形式,而且opt还是optimset结构体。新版MATLAB的linprog对参数数量和选项字段的要求更严格,两者一撞,报错就来了。
2. 报错现场与根因定位
2.1 典型报错一览
我这次遇到的报错大概有这么几类,基本涵盖了MATPOWER加linprog的大部分坑:
| 报错信息 | 触发原因 | 严重程度 |
|---|---|---|
Error using linprog: The 'active-set' algorithm is not available | 显式指定了active-set算法,但新版已删除它 | 直接崩溃 |
Error using linprog: Too many input arguments | 传了x0参数,但当前算法是dual-simplex,不接受x0 | 直接崩溃 |
Error using linprog: Invalid options input | 用optimset结构体传给要求optimoptions对象的接口 | 直接崩溃 |
Warning: Options 'LargeScale' is obsolete | 继续设置旧版LargeScale字段 | 警告但通常不中断 |
Error using linprog: Number of columns in A must be same as number of variables | 约束矩阵和变量数不匹配,多为模型构造问题 | 直接崩溃 |
最气人的是第一种和第二种。它们完全不是模型算错了,而是MATLAB自己的接口换了规矩,老调用方式在新环境里就是走不通。遇到这种报错,换求解器、改模型都没意义,纯粹是兼容性问题。
2.2 为什么会报错:根因不难理解
拿active-set算法来说,老版本MATPOWER里为了更好的数值表现,有时候会把Algorithm设置成active-set。但R2019a之后MathWorks直接把这个算法从linprog里删掉了。你让一个已经消失的算法去求解,MATLAB只能给你抛错误。
再比如Too many input arguments,这个坑更隐蔽。老版本linprog里x0是个再正常不过的参数,但新版dual-simplex算法在设计时压根没考虑初值,你只要传了x0,不管内容是什么,直接报错。MATPOWER老版本的mp_linprog通常会检测用户给的mpopt里有没有初值,有就传x0,这一传就炸。
还有选项类型的问题。老代码里opt = optimset('Algorithm', 'interior-point-legacy', 'Display', 'off')这种写法在旧版MATLAB上没有任何问题,但新版linprog内部的选项解析器已经全面转向optimoptions,部分字段无法再从结构体里正确读取,轻则警告,重则报错。
2.3 用哪几条命令快速定位
遇到报错先别急着改代码,花两分钟定位一下能省后面好几个小时。我的习惯是先敲这几条命令:
which linprog -all which mp_linprog ver('optim')which linprog -all是看有没有用户自己写的linprog.m挂在路径前面。2018年之后MATLAB对内置函数遮蔽查得很严,但如果你自己在当前目录创建过linprog.m,它仍然会被优先调用,这时候报错根本不是MATPOWER的问题,是你那个同名文件的问题。
which mp_linprog是确认MATPOWER里的包装函数路径是否正确。ver('optim')看优化工具箱版本,版本号差得越远,兼容性问题越突出。
如果报错现场很复杂,直接在错误发生前设置断点,或者用dbstop if error让MATLAB停在出错那一行,然后看调用栈。绝大多数情况下你会在调用栈里看到mp_linprog的身影,这就说明问题确实出在linprog适配层。
3. 补丁设计与实现
3.1 设计思路:只改调用层,不动业务逻辑
补丁的第一个原则:不要动MATPOWER的优化模型和求解算法,只做接口适配。打个比方,你从国内买了个欧标插头的电器,到了国内不能用,你只需要加一个转换插头,没必要把电器的内部电路拆了重装。mp_linprog就是那个插头,我们改的就是这个插头的形状。
具体来说,补丁要做三件事:
- 把老代码里的
optimset结构体选项,转换成新版linprog能识别的optimoptions对象。 - 把已经失效的算法名(比如active-set)映射到新版可用算法(通常用dual-simplex)。
- 对返回值做归一化处理,补上没有返回的lambda字段,避免调用方访问一个不存在的字段直接炸。
3.2 第一步:备份原文件
动任何文件之前先备份,这是保命操作。MATPOWER的mp_linprog在7.x版本一般位于MATLAB路径下,可以通过which命令拿到完整路径:
src = which('mp_linprog'); copyfile(src, [src, '.bak']);备份之后,用edit mp_linprog打开原函数,你会发现代码本身不长,核心就是调了一次linprog。我们的目标就是把这几十行替换成兼容版本。
3.3 第二步:编写兼容层补丁代码
下面这个补丁版本,是我在MATPOWER 7.0、7.1上亲测可用的。它保留了原函数的外部接口,用户层调法完全不变,所有兼容性适配都在内部消化。
function [x, f, exitflag, output, lambda] = mp_linprog(f, A, b, Aeq, beq, xmin, xmax, x0, opt) % MP_LINPROG 兼容 MATLAB 不同版本 linprog 接口的补丁版本 % % 用法与 MATPOWER 原版完全一致,内部自动适配新版 MATLAB 的 linprog。 % 适用场景:老版本 MATPOWER + 新版优化工具箱(R2019a 及以上) % 1. 处理选项 if nargin < 9 || isempty(opt) opt = struct('Display', 'off'); end % 提取期望的算法名称,默认走 dual-simplex if isfield(opt, 'Algorithm') && ~isempty(opt.Algorithm) alg = lower(opt.Algorithm); else alg = 'auto'; end try % 新版本 MATLAB:使用 optimoptions % 这里不用 optimset,避免结构体与对象类型不匹配 lpOpt = optimoptions('linprog', 'Display', 'off'); % 映射旧算法名 switch true case contains(alg, 'active-set') % active-set 已从 linprog 中移除,用 dual-simplex 替代 lpOpt.Algorithm = 'dual-simplex'; case contains(alg, 'interior-point-legacy') lpOpt.Algorithm = 'interior-point-legacy'; case contains(alg, 'interior-point') lpOpt.Algorithm = 'interior-point'; case contains(alg, 'simplex') lpOpt.Algorithm = 'dual-simplex'; otherwise % auto 或未知算法:保持默认 lpOpt.Algorithm = 'dual-simplex'; end % 处理迭代上限和容差字段 if isfield(opt, 'MaxIter') || isfield(opt, 'MaxIterations') if isfield(opt, 'MaxIterations') && ~isempty(opt.MaxIterations) lpOpt.MaxIterations = opt.MaxIterations; elseif isfield(opt, 'MaxIter') && ~isempty(opt.MaxIter) lpOpt.MaxIterations = opt.MaxIter; end end if isfield(opt, 'TolX') && ~isempty(opt.TolX) lpOpt.OptimalityTolerance = opt.TolX; end catch % 老版本 MATLAB:直接沿用原结构体 lpOpt = opt; end % 2. 处理输入参数兼容 % 新版 dual-simplex 算法不接受 x0,这里直接丢弃 if ~isempty(x0) && isfield(lpOpt, 'Algorithm') && ... strcmpi(lpOpt.Algorithm, 'dual-simplex') x0 = []; end % 3. 调用 linprog % 根据有没有 x0 选择不同的调用签名,避免 Too many input arguments try if nargin >= 8 && ~isempty(x0) [x, f, exitflag, output, lambda] = ... linprog(f, A, b, Aeq, beq, xmin, xmax, x0, lpOpt); else [x, f, exitflag, output, lambda] = ... linprog(f, A, b, Aeq, beq, xmin, xmax, lpOpt); end catch % 如果还是失败,去掉所有额外选项,用最朴素的签名兜底 [x, f, exitflag, output, lambda] = ... linprog(f, A, b, Aeq, beq, xmin, xmax); end % 4. 统一 lambda 输出结构 % 新版 linprog 在某些边界为空时不返回 lower/upper 字段,这里手动补齐 if ~isfield(lambda, 'lower') lambda.lower = zeros(length(x), 1); end if ~isfield(lambda, 'upper') lambda.upper = zeros(length(x), 1); end if ~isfield(lambda, 'ineqlin') lambda.ineqlin = NaN; end if ~isfield(lambda, 'eqlin') lambda.eqlin = NaN; end end这段代码核心就一点:try-catch加双保险。能走optimoptions就走optimoptions,走不通就退回老式结构体;带x0调用失败就自动降级成不带x0的调用;最后把lambda缺的字段补上。这样一套下来,大部分版本错配问题都能兜住。
3.4 第三步:部署并生效
把上面这段代码整体替换掉原来的mp_linprog.m内容,保存。然后执行:
clear functions rehash toolboxcache这两行命令很重要。MATLAB有函数缓存,你改了文件不刷新缓存,调用的还是旧版本,经常出现“我改了怎么没反应”的尴尬。清理之后再试一下:
which mp_linprog确认路径还是MATPOWER库里的那个文件,没被其他位置的同名函数遮蔽,就算部署成功了。
如果你不想直接覆盖原文件,也可以在别处建一个mp_linprog.m,放在MATLAB路径优先级更高的目录里。但我个人不推荐这种玩法,路径管理很容易出问题,而且一旦你后面升级MATPOWER版本,新版本把旧文件覆盖了,你这个外部文件跟新代码之间的兼容性又会变成新的坑。直接改原文件,同时在文件头注释里写清楚改了什么,最干净。
4. 补丁验证与结果对比
4.1 验证步骤:用标准算例回归
补丁写完只是第一步,真正重要的是验证修复前后结果一致、求解器没有悄悄改变最优解的特性。我推荐至少跑三个标准算例:case9、case30、case118。
define_constants; mpc = loadcase('case9'); mpopt = mpoption('verbose', 2, 'out.all', 0); r1 = runopf(mpc, mpopt);如果你在打补丁之前还能跑通某个算例,最好先把修复前的结果保存下来,再打补丁,最后对比目标函数值。如果打补丁前已经跑不通了,那就依赖MATPOWER自带的参考值,一般case9这类小算例的最优值在网上、文档里都能查到。
对比的时候看三个指标:
- 目标函数值
r1.f,误差控制在1e-6以内才算正常。 - 退出标志
r1.exitflag,必须是大于等于1的值,代表优化成功。 - 电压、功率结果的合理性,随便挑几条支路看看数值有没有夸张的异常。
4.2 预期的结果对比
我这次打补丁前后的对比结果大致如下:
| 算例 | 修复前 | 修复后 | 目标函数变化 |
|---|---|---|---|
| case9 | 直接报错active-set | exitflag=1,结果正常 | 与文档参考值一致 |
| case30 | 报错Too many input arguments | exitflag=1,结果正常 | 与文档参考值一致 |
| case118 | 算法中途警告,结果不稳定 | exitflag=1,结果正常 | 与文档参考值一致 |
重点说明一下:补丁改的只是linprog的调用方式,没有改目标函数、约束条件和求解算法本身,所以修复前后的最优解理论上应该完全一致。如果你发现补丁打完以后算出来的结果和原来差了十万八千里,那大概率不是补丁的锅,而是模型本身在这个版本下就已经出现数值问题了,要回头检查数据。
4.3 验证过程需要注意的几个细节
第一,跑算例之前一定要clear functions,否则MATLAB可能还在用旧的函数缓存。第二,mpoption('verbose', 2)先打开详细输出跑一次,确认求解过程中调用的确实是linprog而不是其他外部求解器,有时候MATPOWER会静默降级到其他求解器,那样验证的就不是补丁效果了。第三,对大规模算例,如果内存吃紧,可以考虑把算法手动设置成interior-point,对稀疏大矩阵的内存使用通常更友好一些。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
整理了一份速查表,基本覆盖我这次踩过的坑和平时帮别人排查时见过的高频问题:
| 问题现象 | 根本原因 | 解决办法 |
|---|---|---|
active-set算法不可用 | R2019a删除该算法 | 映射为dual-simplex |
| 传x0报错 | 新版dual-simplex不支持x0 | 检测算法类型,丢弃x0 |
optimset结构体不被识别 | 新版要求optimoptions对象 | 用optimoptions('linprog', ...)创建 |
LargeScale过时警告 | 旧字段已被移除 | 从选项里删除该字段 |
which linprog -all出现用户文件 | 用户自己的linprog.m遮蔽内置函数 | 重命名或移出搜索路径 |
| 目标函数值跟参考值不一致 | 模型本身存在数值问题 | 检查数据,而非补丁 |
5.2 几个独家避坑心得
第一个心得:打补丁之前先检查有没有同名函数遮蔽。这是我遇到最多的情况,明明代码逻辑没问题,结果是因为当前目录下躺着一个不知道什么时候创建的linprog.m,MATLAB优先调了它。所以遇到任何可疑报错,第一反应永远是which 函数名 -all。
第二个心得:补丁文件里一定要写清楚版本信息和修改日期。我吃过大亏,半年前打的补丁,三个月后自己再看代码都想不起来改了啥。建议在补丁文件头部注释里加一行:% 适配 MATLAB R2022b, MATPOWER 7.0, 2024-03-15 by XXX,下次排查问题能少走很多弯路。
第三个心得:如果补丁改完之后MATLAB直接崩溃或者出现内存爆炸,先别急着怀疑补丁,看看是不是case规模问题。老版本MATPOWER在构造某些约束矩阵时用的是密集存储,大型算例下本来就容易爆内存,这种情况跟上linprog版本没有半毛钱关系,别把锅扣在补丁头上。
第四个心得:如果手头的算例确实很大,又不方便打补丁,最快的临时方案是绕开linprog,改用外部求解器。MATPOWER支持MOSEK、Gurobi、CPLEX等,它们都有独立的求解接口,不跟MATLAB内置linprog版本冲突。但注意外部求解器需要额外授权文件,安装配置又是一套流程,小规模测试还是建议先把内置linprog收拾服帖。
6. 结尾:一点个人经验
这次打补丁的过程,让我对“错误补丁”这四个字有了新的理解。很多看起来吓人的报错,本质上不是你的模型算错,也不是MATPOWER写错了,而是代码跑在了一个它没预料到的MATLAB版本上。所谓补丁,其实就是做一层接口翻译:把旧世界的参数翻译成新世界能听懂的语言,再把新世界的结果翻译回旧世界能使用的结构。
我个人实际操作的体会是,遇到这类兼容性问题,别急着重装软件或者换求解器,先静下心来看调用栈、看版本差异、看函数路径,大多数问题都能在一个小时内解决。补丁代码本身并不复杂,真正值钱的是定位问题的思路和对底层接口变化的了解。
最后再分享一个小技巧:补丁打好之后,给mp_linprog里的算法映射加一个日志输出开关,比如在环境变量或者全局变量里控制是否打印“算法A映射到算法B”的信息。这样以后碰到新MATLAB版本,你一眼就能看出哪些老算法名被替换掉了,排查效率会高很多。希望这篇对你有帮助。
本文还有配套的精品资源,点击获取