Main Content

同一函数中的目标和非线性约束

此示例显示了当使用基于求解器的方法计算目标和约束的值时如何避免两次调用函数。为了避免使用基于问题的方法两次调用函数,请参阅 具有串行或并行共同函数的目标和约束,基于问题

您通常在模拟中使用这样的函数。诸如 fmincon 之类的求解器分别评估目标和非线性约束函数。当对两个结果使用相同的计算时,这种评估是浪费的。

为了避免浪费时间,仅在需要时使用嵌套函数来评估目标和约束函数,并保留耗时计算的值。这种方法避免使用全局变量,同时保留中间结果并在目标函数和约束函数之间共享它们。

注意

由于 ga (Global Optimization Toolbox) 调用非线性约束函数的方式,本例中的技术通常不会减少对目标或约束函数的调用次数。

步骤 1.编写一个计算目标和约束的函数。

例如,假设 computeall 是目标函数和非线性约束函数都调用的昂贵(耗时)函数。假设您想使用 fmincon 作为优化器。

编写一个函数,计算罗森布洛克函数 f1 的一部分,并包含一个非线性约束 c1,使解保持在以原点为中心的半径为 1 的圆盘中。罗森布洛克函数是

f(x)=100(x2x12)2+(1x1)2,

在 (1,1) 处具有唯一的最小值 0。请参阅使用优化实时编辑器任务或求解器的有约束非线性问题

这个示例没有非线性等式约束,所以 ceq1 = []。添加 pause(1) 语句来模拟昂贵的计算。

function [f1,c1,ceq1] = computeall(x)
    ceq1 = [];
    c1 = x(1)^2 + x(2)^2 - 1;
    f1 = 100*(x(2) - x(1)^2)^2 + (1-x(1))^2;
    pause(1) % Simulate expensive computation
end

computeall.m 保存为 MATLAB® 路径上的文件。

步骤 2.将该函数嵌入保留最新值的嵌套函数中。

假设目标函数是

y = 100(x2x12)2 + (1 – x1)2
+ 20*(x3x42)2 + 5*(1 – x4)2

computeall 返回目标函数的第一部分。将对 computeall 的调用嵌入到嵌套函数中:

function [x,f,eflag,outpt] = runobjconstr(x0,opts)

if nargin == 1 % No options supplied
    opts = [];
end

xLast = []; % Last place computeall was called
myf = []; % Use for objective at xLast
myc = []; % Use for nonlinear inequality constraint
myceq = []; % Use for nonlinear equality constraint

fun = @objfun; % The objective function, nested below
cfun = @constr; % The constraint function, nested below

% Call fmincon
[x,f,eflag,outpt] = fmincon(fun,x0,[],[],[],[],[],[],cfun,opts);

    function y = objfun(x)
        if ~isequal(x,xLast) % Check if computation is necessary
            [myf,myc,myceq] = computeall(x);
            xLast = x;
        end
        % Now compute objective function
        y = myf + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2;
    end

    function [c,ceq] = constr(x)
        if ~isequal(x,xLast) % Check if computation is necessary
            [myf,myc,myceq] = computeall(x);
            xLast = x;
        end
        % Now compute constraint function
        c = myc; % In this case, the computation is trivial
        ceq = myceq;
    end

end

将嵌套函数保存为 MATLAB 路径上名为 runobjconstr.m 的文件。

步骤 3.使用嵌套函数确定运行时间。

运行该函数,并使用 tictoc 对调用进行计时。

opts = optimoptions(@fmincon,'Algorithm','interior-point','Display','off');
x0 = [-1,1,1,2];
tic
[x,fval,exitflag,output] = runobjconstr(x0,opts);
toc
Elapsed time is 259.364090 seconds.

步骤 4.确定不使用嵌套函数的运行时间。

比较使用和不使用嵌套函数时求解器的运行时间。对于没有嵌套函数的运行,将 myrosen2.m 保存为目标函数文件,将 constr.m 保存为约束。

function y = myrosen2(x)
    f1 = computeall(x); % Get first part of objective
    y = f1 + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2;
end

function [c,ceq] = constr(x)
    [~,c,ceq] = computeall(x);
end

运行 fmincon,并使用 tictoc 来计时调用。

tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
                   [],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 518.364770 seconds.

求解器花费的时间是以前的两倍,因为它分别评估目标和约束。

步骤 5.通过并行计算节省计算时间。

如果您拥有 Parallel Computing Toolbox™ 许可证,则可以通过将 UseParallel 选项设置为 true 来节省更多时间。

parpool
Starting parallel pool (parpool) using the 'local' profile ...
Connected to the parallel pool (number of workers: 6).

ans = 

 ProcessPool with properties: 

            Connected: true
           NumWorkers: 6
              Cluster: local
        AttachedFiles: {}
    AutoAddClientPath: true
          IdleTimeout: 30 minutes (30 minutes remaining)
          SpmdEnabled: true
opts = optimoptions(opts,'UseParallel',true);
tic
[x,fval,exitflag,output] = runobjconstr(x0,opts);
toc
Elapsed time is 121.151203 seconds.

在这种情况下,与使用嵌套函数的串行运行相比,启用并行计算可将计算时间减少一半。

比较使用和不使用嵌套函数的并行计算的运行情况:

tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
                   [],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 235.914597 seconds.

在这个示例中,并行计算但不嵌套计算所花费的时间与嵌套计算但不并行计算所花费的时间大约相同。嵌套计算和并行计算所花的时间只是单独使用其中任一种的一半。

相关主题