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