ドキュメンテーション

最新のリリースでは、このページがまだ翻訳されていません。 このページの最新版は英語でご覧になれます。

同じ関数における目的と非線形制約

この例では、ソルバーベースのアプローチを使用して、目的関数と制約の両方に対して値を計算する場合に関数を 2 回呼び出すことを回避する方法を説明します。問題ベースのアプローチを使用して関数を 2 回呼び出すことを回避する方法については、共通の関数を持つ目的関数と制約の逐次評価または並列評価、問題ベースを参照してください。

このような関数は一般にシミュレーションで使用されます。fmincon などのソルバーでは、目的関数と非線形制約関数が別々に評価されます。両方の結果に対して同じ計算を使用する場合は、この評価は無駄なものです。

時間を無駄にしないためには、計算で入れ子関数を使用し、時間のかかる計算の値を保持して、目的関数と制約関数を必要な場合にのみ評価するようにします。入れ子関数を使用すると、グローバル変数を使用しなくても、中間結果を保持して目的関数と制約関数で共有できます。

メモ

ga が非線形制約関数を呼び出す方法のため、この例の手法では通常、目的関数または制約関数の呼び出し回数が少なくなりません。

手順 1: 目的と制約を計算する関数

たとえば、関数computeall は計算量が多く (時間がかかり)、目的関数と非線形制約関数の両方によって呼び出されるとします。また、fmincon をオプティマイザーとして使用するとします。

Rosenbrock 関数 f1 の一部を計算する関数と、原点を中心とする半径 1 の円板内に解を維持する非線形制約 c1 を作成します。Rosenbrock 関数は次のようになります。

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

これは、(1,1) で一意的な最小値 0 をもちます。詳細は、制約付き非線形問題の解法、ソルバーベースを参照してください。

この例では非線形等式制約がないため、ceq1 = [] になります。時間のかかる計算をシミュレーションするためにステートメント pause(1) を追加します。

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

MATLAB® パス上に computeall.m をファイルとして保存します。

手順 2: 最新の値を保持する入れ子関数への関数の組み込み

次のような目的関数について考えます。

y = 100(x2 – x12)2 + (1 – x1)2
+ 20*(x3 – x42)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 functions
        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 203.797275 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

tictoc で呼び出しの時間を測定しながら、fmincon を実行します。

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

目的と制約を別々に評価しなければならないため、ソルバーの実行時間はこの場合 2 倍になります。

手順 5: 並列計算による計算時間の短縮

Parallel Computing Toolbox™ のライセンスをお持ちの場合は、UseParallel オプションを true に設定すると、計算時間をさらに短縮できます。

parpool
Starting parallel pool (parpool) using the 'local' profile ... connected to 4 workers.

ans = 

 Pool with properties: 

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

この場合は、並列計算を有効にすると計算時間が半分になります。

入れ子関数を使用する場合と使用しない場合で、並列計算の実行時間を比較します。

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

この例では、入れ子関数を使用しないで並列計算を行った場合と、入れ子関数を使用して並列計算を行わなかった場合の実行時間はほぼ同じになります。入れ子関数と並列計算の両方を使用すると、いずれか一方を使用した場合と比べて実行時間は半分になります。

関連するトピック