ドキュメンテーション

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

カッティング ストック問題: 問題ベース

この例は、整数線形計画法のサブルーチンと共に線形計画法を使用して、カッティング ストック問題を解く方法を説明します。この例では、問題ベースの最適化の設定のアプローチを使用します。ソルバーベースのアプローチについては、カッティング ストック問題: ソルバーベースを参照してください。

問題概要

製材所は、木から固定長の木材を切り出すことから始めます。木材の固定長を指定します。

logLength = 40;

その後、以降の処理に適した固定長に木材を切ります。問題は、製材所が最小限の木材で一連の注文に対応できるように、どのように木材を切り出すかということです。

これらの固定長と長さごとの注文の数量を指定します。

lengthlist = [8; 12; 16; 20];
quantity = [90; 111; 55; 30];
nLengths = length(lengthlist);

切り出す際に素材の無駄は生じず、カッティングのコストもかからないものとします。

線形計画法の定式化

Ford と Fulkerson [1] および Gilmore と Gomory [2] を含む複数の著者が以下の手順を提案しています。これを次のセクションで実装します。カッティング パターンとは 1 本の木材から切り出すことができる長さのセットです。

すべての考えられるカッティング パターンを生成するより、部分問題の解としてカッティング パターンを生成する方が効率的です。カッティング パターンの基本セットから始め、既存のパターンを使用したカッティングで要求を満たすという制約の下で、使用する木材数を最小限にする線形計画問題を解きます。

その問題を解いた後に、整数線形計画法の部分問題を解いて新しいパターンを生成します。この部分問題では最適な新しいパターン、つまり、考えられる長さ合計の logLength を総和が上回らないように、lengthlist の各長さで切り出せる木材の数を求めます。最適化するための数量は、新しいパターンの被約費用であり、現在の解のラグランジュ乗数と新しいカッティング パターンの積の和を、1 から引いたものです。この数量が負の場合、そのパターンを線形計画問題に持ち込むと、その目的関数が改善されます。それ以外の場合は、より良いカッティング パターンは存在せず、今までに使用されたパターンが最適な線形計画問題の解を与えます。この結論の理由は、主シンプレックス法 (被約費用が負になる変数がないときに停止する方法) を停止する場合の理由と正確に対応します。この例の問題は、負の被約費用のパターンがないときに終了します。詳細と例については、列生成アルゴリズムとその参考文献を参照してください。

この方法で線形計画問題を解くと、非整数解が得られることがあります。したがって、生成されたパターンを使用し、変数が整数値を持つという制約の下で、もう一度問題を解きます。

MATLAB 問題ベースでの定式化

この定式化では、パターンは lengthlist の各長さのカット (切り出した木材) 数を含む整数のベクトルです。パターンを保存するための行列 patterns を準備します。行列の各列が 1 つのパターンを表します。たとえば、

patterns=[20020110]

最初のパターン (列) は長さ 8 の 2 本のカットと長さ 20 の 1 本のカットを表します。2 番目のパターンは、長さ 12 の 2 本のカットと長さ 16 の 1 本のカットを表します。それぞれは、カットの長さの合計が logLength = 40 を上回らないため、実行可能なパターンです。

この定式化では、x が、各パターンの使用回数を含む整数の列ベクトルである場合、patterns*x は各タイプのカット数を示す列ベクトルとなります。要求を満たすための制約は、patterns*x >= quantity です。たとえば、前出の行列 patterns を使用して、x=[4556] であると仮定します (この x は 101 本の木材を使用します)。この場合

patterns*x=[901125645]

これは要求を上回っているため、実行可能な解を表しています。

quantity=[901115530]

初期の実行可能なカッティング パターンを得るため、カットの長さが 1 つだけの最も簡単なパターンを使用します。その長さで、木材に対して実行可能なだけ多くのカット数を使用します。

patterns = diag(floor(logLength./lengthlist));
nPatterns = size(patterns,2);

現在のラグランジュ乗数に基づいて既存のパターンから新しいパターンを生成するには、部分問題を解きます。それ以上の改善が見つからなくなるまで、ループ内で部分問題を呼び出してパターンを生成します。部分問題の目的関数は、現在のラグランジュ乗数のみに依存します。変数は、各長さのカット数を表す非負の整数です。唯一の制約は、1 つのパターン内のカットの長さの合計が木材の長さを超えないことです。

subproblem = optimproblem();
cuts = optimvar('cuts', nLengths, 1, 'Type','integer','LowerBound',zeros(nLengths,1));
subproblem.Constraints = dot(lengthlist,cuts) <= logLength;

ソルバーからの不要なフィードバックを避けるために、外側のループと内側の部分問題の解の両方に対して Display オプションを 'off' に設定します。

lpopts = optimoptions('linprog','Display','off');
ipopts = optimoptions('intlinprog',lpopts);

ループの変数を初期化します。

reducedCost = -inf;
reducedCostTolerance = -0.0001;
exitflag = 1;

ループを呼び出します。

while reducedCost < reducedCostTolerance && exitflag > 0
    logprob = optimproblem('Description','Cut Logs');     
    % Create variables representing the number of each pattern used
    x = optimvar('x', nPatterns, 1, 'LowerBound', 0);
    % The objective is the number of logs used
    logprob.Objective.logsUsed = sum(x);
    % The constraint is that the cuts satisfy the demand
    logprob.Constraints.Demand = patterns*x >= quantity;
    
    [values,nLogs,exitflag,~,lambda] = solve(logprob,'options',lpopts);
    
    if exitflag > 0
        fprintf('Using %g logs\n',nLogs);
        % Now generate a new pattern, if possible
        subproblem.Objective = 1.0 - dot(lambda.Constraints.Demand,cuts);
        [values,reducedCost,pexitflag] = solve(subproblem,'options',ipopts);
        newpattern = round(values.cuts);
        if double(pexitflag) > 0 && reducedCost < reducedCostTolerance
            patterns = [patterns newpattern];
            nPatterns = nPatterns + 1;
        end
    end
end
Using 97.5 logs
Using 92 logs
Using 89.9167 logs
Using 88.3 logs

これで、線形計画問題の解が得られます。解を完全なものにするために、最終パターンでもう一度、解の変数 x を整数型に変えて問題を解きます。また、各パターンと問題全体について、使用されない木材の数量 (フィート単位) として無駄を計算します。

if exitflag <= 0 
    disp('Error in column generation phase')
else
    x.Type = 'integer';
    [values,logsUsed,exitflag] = solve(logprob,'options',ipopts);
    if double(exitflag) > 0
        values.x = round(values.x); % in case some values were not exactly integers
        logsUsed = sum(values.x);
        fprintf('Optimal solution uses %g logs\n', logsUsed);
        totalwaste = sum((patterns*values.x - quantity).*lengthlist); % waste due to overproduction
        for j = 1:size(values.x)
            if values.x(j) > 0
                fprintf('Cut %g logs with pattern\n',values.x(j));
                for w = 1:size(patterns,1)
                    if patterns(w,j) > 0
                        fprintf('    %g cut(s) of length %d\n', patterns(w,j),lengthlist(w));
                    end
                end
                wastej = logLength - dot(patterns(:,j),lengthlist); % waste due to pattern inefficiency
                totalwaste = totalwaste + wastej;
            fprintf('    Waste of this pattern is %g\n',wastej);
            end
        end
        fprintf('Total waste in this problem is %g.\n',totalwaste);
    else 
        disp('Error in final optimization')
    end
end
Optimal solution uses 89 logs
Cut 1 logs with pattern
    3 cut(s) of length 12
    Waste of this pattern is 4
Cut 15 logs with pattern
    2 cut(s) of length 20
    Waste of this pattern is 0
Cut 18 logs with pattern
    1 cut(s) of length 8
    2 cut(s) of length 16
    Waste of this pattern is 0
Cut 36 logs with pattern
    2 cut(s) of length 8
    2 cut(s) of length 12
    Waste of this pattern is 0
Cut 19 logs with pattern
    2 cut(s) of length 12
    1 cut(s) of length 16
    Waste of this pattern is 0
Total waste in this problem is 28.

無駄の一部は過剰生産が原因です。製材所は 1 本の木材から 3 本の 12 フィートの木材を切り出しますが、1 本しか使用しないためです。無駄の一部はパターンの非効率性が原因です。3 本の 12 フィートの木材は、全長の 40 フィートより 4 フィート短いためです。

参考文献

[1] Ford, L. R., Jr. and D. R. Fulkerson.A Suggested Computation for Maximal Multi-Commodity Network Flows.Management Science 5, 1958, pp. 97-101.

[2] Gilmore, P. C., and R. E. Gomory.A Linear Programming Approach to the Cutting Stock Problem--Part II.Operations Research 11, No. 6, 1963, pp. 863-888.

関連するトピック