第一次接触非线性规划时,我被它强大的实用性震撼到了。相比线性规划,非线性规划能更好地描述现实世界中的复杂问题,比如机械臂的轨迹优化、电力系统的负荷分配、甚至是投资组合的收益最大化。Matlab中的fmincon()函数就像一把瑞士军刀,能帮我们解决这类问题。
fmincon()的基本语法是这样的:
matlab复制x = fmincon('fun',x0,A,b,Aeq,beq,lb,ub,'nonlcon',options)
看起来参数很多,但其实很好理解。'fun'就是我们的目标函数,可以用匿名函数定义;x0是初始猜测值,就像扔飞镖前要先瞄准一样;A,b,Aeq,beq处理线性约束;lb和ub是变量的上下界;'nonlcon'专门处理非线性约束;options可以调整算法参数。
我刚开始用的时候经常搞混参数顺序,后来发现一个记忆技巧:先目标函数和初始值,再线性不等式约束,接着线性等式约束,然后是边界约束,最后才是非线性约束和选项。这样记就不会乱了。
让我们从一个经典问题开始:Rosenbrock香蕉函数优化。这个函数长这样:
matlab复制fun = @(x)100*(x(2)-x(1)^2)^2 + (1-x(1))^2;
假设我们有个线性约束x₁ + 2x₂ ≤ 1,代码实现很简单:
matlab复制x0 = [0,0];
A = [1,2];
b = 1;
[x,fval] = fmincon(fun,x0,A,b)
运行后会得到最优解x = [0.5022, 0.2489],此时函数值fval = 0.2489。这个例子虽然简单,但包含了fmincon()的核心用法。
如果我们再加个等式约束2x₁ + x₂ = 1,代码只需要稍作修改:
matlab复制Aeq = [2,1];
beq = 1;
[x,fval] = fmincon(fun,x0,A,b,Aeq,beq)
这时候解变成了x = [0.4149, 0.1701],fval = 0.3427。等式约束让解空间变小了,所以最优值比之前要大,这很合理。
现实问题中更常见的是非线性约束。比如我们要最小化f(x) = x₁² + x₂² + x₃² + 8,但有以下约束:
这时候需要写两个函数文件:
matlab复制function f = objfun(x)
f = sum(x.^2) + 8;
end
function [c,ceq] = nonlcon(x)
c = [-x(1)^2 + x(2) - x(3)^2; % 非线性不等式约束
x(1) + x(2)^2 + x(3)^3 - 20];
ceq = [-x(1) - x(2)^2 + 2; % 非线性等式约束
x(2) + 2*x(3)^2 - 3*x(1)];
end
调用方式:
matlab复制x0 = rand(3,1);
lb = zeros(3,1);
[x,fval] = fmincon(@objfun,x0,[],[],[],[],lb,[],@nonlcon)
有时候问题会有圆形约束区域。比如要求在圆(x₁-1/3)² + (x₂-1/3)² ≤ (1/3)²内最小化Rosenbrock函数,同时0≤x₁≤0.5,0.2≤x₂≤0.8。
非线性约束函数这样写:
matlab复制function [c,ceq] = circlecon(x)
c = (x(1)-1/3)^2 + (x(2)-1/3)^2 - 1/9;
ceq = [];
end
调用时指定边界:
matlab复制lb = [0; 0.2];
ub = [0.5; 0.8];
[x,fval] = fmincon(@rosen,x0,[],[],[],[],lb,ub,@circlecon)
假设要设计一个悬臂梁,在满足强度条件下最小化重量。设设计变量为截面宽b和高h,目标函数是截面面积A = b×h,约束条件是最大应力不超过许用应力。
数学模型可以表示为:
matlab复制function f = beamWeight(x)
b = x(1); h = x(2);
f = b * h; % 最小化截面面积
end
function [c,ceq] = beamConstraints(x)
b = x(1); h = x(2);
M = 1000; % 弯矩(N·m)
sigma_max = M * h / (2 * (b*h^3)/12);
sigma_allow = 200e6; % 许用应力(Pa)
c = sigma_max - sigma_allow; % 应力约束
ceq = [];
end
优化调用:
matlab复制x0 = [0.1, 0.2]; % 初始猜测(m)
lb = [0.05, 0.1]; % 最小尺寸(m)
ub = [0.2, 0.3]; % 最大尺寸(m)
options = optimoptions('fmincon','Display','iter');
[x_opt, f_opt] = fmincon(@beamWeight,x0,[],[],[],[],lb,ub,@beamConstraints,options)
在PID控制器设计中,我们经常需要优化Kp、Ki、Kd参数。以ISE(积分平方误差)为目标函数:
matlab复制function J = pidCost(x)
Kp = x(1); Ki = x(2); Kd = x(3);
% 这里是仿真代码,实际使用时替换为你的系统模型
simOut = sim('pid_model.slx');
J = sum(simOut.error.^2);
end
约束条件可能包括稳定性裕度、超调量等:
matlab复制function [c,ceq] = pidConstraints(x)
Kp = x(1); Ki = x(2); Kd = x(3);
% 计算相位裕度和增益裕度
[Gm,Pm] = margin(sys_with_pid);
c = [40 - Pm; % 要求相位裕度>40度
2 - Gm]; % 要求增益裕度>2
ceq = [];
end
优化调用:
matlab复制x0 = [1, 0.1, 0.01]; % 初始PID参数
lb = [0, 0, 0]; % 参数下限
ub = [100, 10, 1]; % 参数上限
options = optimoptions('fmincon','Algorithm','sqp');
[x_opt, J_min] = fmincon(@pidCost,x0,[],[],[],[],lb,ub,@pidConstraints,options)
fmincon()有几种算法可选:
设置方法:
matlab复制options = optimoptions('fmincon','Algorithm','sqp',...
'MaxIterations',1000,...
'StepTolerance',1e-6);
当优化不收敛时,可以尝试:
我曾经遇到一个问题,优化总是失败,后来发现是约束函数在某点出现了NaN值。添加了输入检查后问题就解决了:
matlab复制function [c,ceq] = safeConstraints(x)
if any(x <= 0) % 防止对数函数的负输入
c = inf; ceq = [];
return
end
% 正常的约束计算...
end
对于耗时长的目标函数,可以启用并行计算:
matlab复制options = optimoptions('fmincon','UseParallel',true);
前提是要先打开并行池:
matlab复制if isempty(gcp('nocreate'))
parpool;
end
在工厂做参数优化时,我发现直接使用fmincon()有时会得到理论最优但实际不可行的解。后来我总结了几条经验:
一个典型的多初始点优化代码:
matlab复制nRuns = 10;
X0 = lhsdesign(nRuns,2); % 拉丁超立方采样
X0 = lb + (ub-lb).*X0; % 缩放至变量范围
bestX = []; bestF = inf;
for i = 1:nRuns
[x,fval] = fmincon(@objfun,X0(i,:),A,b,Aeq,beq,lb,ub,@nonlcon,options);
if fval < bestF
bestF = fval;
bestX = x;
end
end