exponenta event banner

Создание многопериодной инвентарной модели в среде на основе проблем

В этом примере показано, как создать многопериодную модель инвентаризации в структуре на основе проблем. Проблема заключается в планировании производства смесей удобрений на определенный период времени с использованием различных ингредиентов, стоимость которых зависит от времени, предсказуемым образом. Предположим, что вы заранее знаете спрос на удобрения. Цель заключается в максимизации прибыли при одновременном удовлетворении спроса, когда затраты связаны с закупкой сырых ингредиентов и хранением удобрений с течением времени. Затраты можно определить заранее с помощью фьючерсов или других контрактов.

Удобрения и ингредиенты

Гранулированные удобрения содержат питательные вещества азот (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;

Количество, которое производится каждый месяц каждой смеси, является количеством используемого сырья. 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    

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

Связанные темы