Main Content

問題ベース フレームワークでの多期間にわたる在庫モデルの作成

この例では、問題ベース フレームワークで多期間にわたる在庫モデルを作成する方法を示します。問題は、経時的コスト変動が予測できるさまざまな原料を使用した、ある期間の配合肥料の生産スケジュールを設定することです。あらかじめ、肥料への需要はわかっているものとします。目的は、需要を満たしながら利益を最大化することです。コストは、原材料の購入と経時的な肥料の保管にかかります。将来またはその他の契約を使用して、あらかじめコストを求めることができます。

肥料および原料

粒状肥料には、窒素 (N)、リン (P)、カリウム (K) が栄養素として含まれています。次の原材料を配合して、必須栄養素を含む配合肥料を取得できます。

load fertilizer
blends = blendDemand.Properties.VariableNames % Fertilizers to produce
blends = 1x2 cell
    {'Balanced'}    {'HighN'}

nutrients = rawNutrients.Properties.RowNames
nutrients = 3x1 cell
    {'N'}
    {'P'}
    {'K'}

raws = rawNutrients.Properties.VariableNames % Raw materials
raws = 1x6 cell
    {'MAP'}    {'Potash'}    {'AN'}    {'AS'}    {'TSP'}    {'Sand'}

2 つの配合肥料は、"HighN" 配合で N が 10% 追加され合計 20% であることを除き、同じ栄養素要件 (10% N、10% P、10% K 重量比) です。

disp(blendNutrients) % Table is in percentage
         Balanced    HighN
         ________    _____

    N       10        20  
    P       10        10  
    K       10        10  

原材料の名前と栄養素の重量比は次のとおりです。

disp(rawNutrients) % Table is in percentage
         MAP    Potash    AN    AS    TSP    Sand
         ___    ______    __    __    ___    ____

    N    11        0      35    21     0      0  
    P    48        0       0     0    46      0  
    K     0       60       0     0     0      0  

原材料 Sand には栄養素はありません。Sand は、必要に応じて他の原料を希釈し、栄養素の必要な割合 (重量比) を実現します。

これらの各量の数を変数に格納します。

nBlends = length(blends);
nRaws = length(raws);
nNutrients = length(nutrients);

需要および収益の予測

あらかじめ、問題内の期間における 2 つの配合肥料の需要 (重量、トン) はわかっているものとします。

disp(blendDemand)
                 Balanced    HighN
                 ________    _____

    January        750        300 
    February       800        310 
    March          900        600 
    April          850        400 
    May            700        350 
    June           700        300 
    July           700        200 
    August         600        200 
    September      600        200 
    October        550        200 
    November       550        200 
    December       550        200 

配合肥料を販売する際のトン単位の価格はわかっています。これらのトン単位の価格は時間に左右されません。

disp(blendPrice)
    Balanced    HighN
    ________    _____

      400        550 

原材料の価格

あらかじめ、原材料のトン単位の価格はわかっているものとします。これらのトン単位の価格は、次の表に従い、時間によって異なります。

disp(rawCost)
                 MAP    Potash    AN     AS     TSP    Sand
                 ___    ______    ___    ___    ___    ____

    January      350     610      300    135    250     80 
    February     360     630      300    140    275     80 
    March        350     630      300    135    275     80 
    April        350     610      300    125    250     80 
    May          320     600      300    125    250     80 
    June         320     600      300    125    250     80 
    July         320     600      300    125    250     80 
    August       320     600      300    125    240     80 
    September    320     600      300    125    240     80 
    October      310     600      300    125    240     80 
    November     310     600      300    125    240     80 
    December     340     600      300    125    240     80 

保管コスト

配合した肥料を保管するためのコストは、トン単位と時間単位で適用されます。

disp(inventoryCost)
    10

容量の制約

どの期間でも、配合肥料は合計 inventoryCapacity トン以下しか保管できません。

disp(inventoryCapacity)
        1000

どの期間においても、合計 productionCapacity トン以下しか生産できません。

disp(productionCapacity)
        1200

生産、販売および在庫の関係

使用可能な配合肥料が一定量ある (在庫がある) 状態でスケジュールを開始します。最終期間にはこの在庫の特定のターゲットがあります。各期間で、配合肥料の量は直前の期間の終了時の量に生産量を加えたものから、販売量を引いた量です。つまり、1 より大きい期間について、

inventory(time,product) = inventory(time-1,product) + production(time,product) - sales(time,product)

この式は、在庫が期間の終わりにカウントされることを意味しています。問題の期間は次のとおりです。

months = blendDemand.Properties.RowNames;
nMonths = length(months);

次のように、初期在庫は期間 1 の在庫に影響します。

inventory(1,product) = initialInventory(product) + production(1,product) - sales(1,product)

初期在庫は、データ blendInventory{'Initial',:} にあります。最終在庫は、データ blendInventory{'Final',:} にあります。

満たされなかった需要は失われるものとします。つまり、期間内に注文を満たすことができない場合、超過分の注文は次の期間に繰り越されません。

最適化問題の定式化

この問題の目的関数は利益で、その最大化を目指します。そのため、問題ベース フレームワークで最大化問題を作成します。

inventoryProblem = optimproblem('ObjectiveSense','maximize');

問題の変数は、毎月生産および販売する配合肥料の数量と、それらの配合を生産するために使用する原材料です。sell の上限は、各期間の各配合肥料に対して、需要 blendDemand です。

make = optimvar('make',months,blends,'LowerBound',0);
sell = optimvar('sell',months,blends,'LowerBound',0,'UpperBound',blendDemand{months,blends});
use  = optimvar('use',months,raws,blends,'LowerBound',0);

さらに、各時点の在庫を表す変数を作成します。

inventory = optimvar('inventory',months,blends,'LowerBound',0,'UpperBound',inventoryCapacity);

問題変数に関して目的関数を計算するには、収益とコストを計算します。収益は、各配合肥料の販売量に価格を乗算し、それをすべての期間と配合について合算した金額です。

revenue = sum(blendPrice{1,:}.*sum(sell(months,blends),1));

原料のコストは、各時点で使用された各原料のコストを、すべての期間について合算した値です。各期間で使用された金額は、配合ごとに使用された金額に分けられるため、これも配合全体で合算します。

blendsUsed = sum(use(months,raws,blends),3);
ingredientCost = sum(sum(rawCost{months,raws}.*blendsUsed));

保管コストは、各期間で在庫の保管にかかったコストを、期間と配合について合算した値です。

storageCost = inventoryCost*sum(inventory(:));

ここで、ドット表記を使用して、問題の Objective プロパティに目的関数を配置します。

inventoryProblem.Objective = revenue - ingredientCost - storageCost;

問題の制約

問題にはいくつかの制約があります。最初に、問題変数の一連の制約として在庫の式を表します。

materialBalance = optimconstr(months,blends);
timeAbove1 = months(2:end);
previousTime = months(1:end-1);
materialBalance(timeAbove1,:) = inventory(timeAbove1,:) == inventory(previousTime,:) +...
    make(timeAbove1,:) - sell(timeAbove1,:);
materialBalance(1,:) = inventory(1,:) == blendInventory{'Initial',:} +...
    make(1,:) - sell(1,:);

同様に、最終的な在庫が固定であるという制約を表します。

finalC = inventory(end,:) == blendInventory{'Final',:};

各時点の在庫の合計は制限されています。

boundedInv = sum(inventory,2) <= inventoryCapacity;

各期間には制限された量まで生産できます。

processLimit = sum(make,2) <= productionCapacity;

配合ごとに毎月生産される量は、使用する原材料の量です。関数 squeeze は、合計を nmonths x 1 x nblends の配列からnmonthsnblends 列の配列に変換します。

rawMaterialUse = squeeze(sum(use(months,raws,blends),2)) == make(months,blends);

各配合の栄養素は、必要な値をもたなければなりません。次の内部ステートメントでは、乗算 rawNutrients{n,raws}*use(m,raws,b)' が使用された原材料について各時点の栄養素の値を合算しています。

blendNutrientsQuality = optimconstr(months,nutrients,blends);
for m = 1:nMonths
    for b = 1:nBlends
        for n = 1:nNutrients
            blendNutrientsQuality(m,n,b) = rawNutrients{n,raws}*use(m,raws,b)' == blendNutrients{n,b}*make(m,b);
        end
    end
end

制約を問題に配置します。

inventoryProblem.Constraints.materialBalance = materialBalance;
inventoryProblem.Constraints.finalC = finalC;
inventoryProblem.Constraints.boundedInv = boundedInv;
inventoryProblem.Constraints.processLimit = processLimit;
inventoryProblem.Constraints.rawMaterialUse = rawMaterialUse;
inventoryProblem.Constraints.blendNutrientsQuality = blendNutrientsQuality;

問題を解く

問題の定式化は完了です。問題を解きます。

[sol,fval,exitflag,output] = solve(inventoryProblem)
Solving problem using linprog.

Optimal solution found.
sol = struct with fields:
    inventory: [12x2 double]
         make: [12x2 double]
         sell: [12x2 double]
          use: [12x6x2 double]

fval = 
2.2474e+06
exitflag = 
    OptimalSolution

output = struct with fields:
         iterations: 110
          algorithm: 'dual-simplex-highs'
    constrviolation: 9.0949e-13
            message: 'Optimal solution found.'
      firstorderopt: 1.1369e-13
             solver: 'linprog'

結果を表形式とグラフ形式で表示します。

if exitflag > 0
    fprintf('Profit: %g\n',fval);
    makeT = array2table(sol.make,'RowNames',months,'VariableNames',strcat('make',blends));
    sellT = array2table(sol.sell,'RowNames',months,'VariableNames',strcat('sell',blends));
    storeT = array2table(sol.inventory,'RowNames',months,'VariableNames',strcat('store',blends));
    productionPlanT = [makeT sellT storeT]
    figure
    subplot(3,1,1)
    bar(sol.make)
    legend('Balanced','HighN','Location','eastoutside')
    title('Amount Made')
    subplot(3,1,2)
    bar(sol.sell)
    legend('Balanced','HighN','Location','eastoutside')
    title('Amount Sold')
    subplot(3,1,3)
    bar(sol.inventory)
    legend('Balanced','HighN','Location','eastoutside')
    title('Amount Stored')
    xlabel('Time')
end
Profit: 2.24739e+06
productionPlanT=12×6 table
                 makeBalanced    makeHighN    sellBalanced    sellHighN    storeBalanced    storeHighN
                 ____________    _________    ____________    _________    _____________    __________

    January          1100           100           750            300            550              0    
    February          600           310           800            310            350              0    
    March             550           650           900            600              0             50    
    April             850           350           850            400              0              0    
    May               700           350           700            350              0              0    
    June              700           300           700            300              0              0    
    July              700           200           700            200              0              0    
    August            600           200           600            200              0              0    
    September         600           200           600            200              0              0    
    October           550           200           550            200              0              0    
    November          550           200           550            200              0              0    
    December          750           400           550            200            200            200    

Figure contains 3 axes objects. Axes object 1 with title Amount Made contains 2 objects of type bar. These objects represent Balanced, HighN. Axes object 2 with title Amount Sold contains 2 objects of type bar. These objects represent Balanced, HighN. Axes object 3 with title Amount Stored, xlabel Time contains 2 objects of type bar. These objects represent Balanced, HighN.

関連するトピック