В этом примере показано, как создать модель многопериодного инвентаризации в основанной на проблеме среде. Задача состоит в том, чтобы планировать производство смесей удобрений в течение определенного периода времени с использованием различных ингредиентов, стоимость которых зависит от времени предсказуемым образом. Предположим, что вы заранее знаете спрос на удобрения. Цель состоит в том, чтобы максимизировать прибыль при удовлетворении спроса, где затраты на приобретение необработанных ингредиентов и хранение удобрений с течением времени. Можно определить затраты в усовершенствование с помощью фьючерсов или других контрактов.
Гранулированные удобрения содержат питательные вещества азот (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'}
Две смеси удобрений имеют одинаковые потребности в питательных веществах (10% N, 10% P и 10% K по массе), за исключением того, что смесь «HighN» имеет дополнительное 10% N в общей сложности 20% N.
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
не имеет питательного содержимого. При необходимости песок разбавляет другие ингредиенты, получая требуемый процент питательных веществ по весу.
Сохраните номера каждой из этих величин в переменных.
nBlends = length(blends); nRaws = length(raws); nNutrients = length(nutrients);
Предположим, что вы заранее знаете потребность в весе (тоннах) для двух смесей удобрений на периоды времени в задаче.
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;
Количество, которое вы производите каждый месяц каждой смеси, это количество сырья, которое вы используете. The squeeze
функция преобразует сумму из nmonths
-by-1-by- nblends
массив в nmonths
-by- 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: 162
constrviolation: 5.4570e-12
message: 'Optimal solution found.'
algorithm: 'dual-simplex'
firstorderopt: 6.5235e-12
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