В этом примере показано, как создать многопериодную модель инвентаризации в структуре на основе проблем. Проблема заключается в планировании производства смесей удобрений на определенный период времени с использованием различных ингредиентов, стоимость которых зависит от времени, предсказуемым образом. Предположим, что вы заранее знаете спрос на удобрения. Цель заключается в максимизации прибыли при одновременном удовлетворении спроса, когда затраты связаны с закупкой сырых ингредиентов и хранением удобрений с течением времени. Затраты можно определить заранее с помощью фьючерсов или других контрактов.
Гранулированные удобрения содержат питательные вещества азот (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 materialsraws = 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;
Количество, которое производится каждый месяц каждой смеси, является количеством используемого сырья. squeeze функция преобразует сумму из nmonths-by-1-by- 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: 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
