基于优化肥料生产实例的多阶段库存管理模型
导言
此示例演示如何使用AnyMath创建智能生产和库存计划模型。 有必要为未来一年做出战略决策。
让我们想象一下,我们经营一家生产两种类型肥料的工厂。 我们有几种类型的原材料(成分)可供使用,其价格根据众所周知的时间表(例如季节性)每月变化。 与此同时,我们确切地知道每个月客户将订购多少吨每种类型的肥料。 该
目标是实现利润最大化。 要做到这一点,你需要找到之间的完美平衡:
*生产完全一样多,你需要(或者你可以出售它盈利);
*在最便宜的月份购买原材料;
*在经济上可行的情况下将成品存放在仓库中。
对于这样的长期策略,您可以使用金融工具(例如,期货合约)来固定未来的商品价格并保护自己免受风险。 我们的数学模型将帮助您计算最有利可图的计划,考虑到所有这些因素。
我们将连接必要的库。
import Pkg
neededLibs = ["JuMP" ,"GLPK", "NamedArrays", "Measures" ]
for lib in neededLibs
try
eval(Meta.parse("using $lib"))
catch ex
Pkg.add(lib)
eval(Meta.parse("using $lib"))
end
end
肥料及其组成
颗粒肥料含有三种关键营养素:氮,磷和钾。 在工厂,我们不是从头开始生产它们,而是混合现成的原料,以获得具有正确成分的商业品牌肥料。
我们有几种"成分"-每种都有自己独特的营养素。 通过以正确的比例组合它们,我们创造了一个成品-一个平衡的肥料,我们的客户需要。
初始数据
我们将在此基础上确定优化生产的数据。:
*对肥料的需求量;
*一年中的月份列表;
*初始股票价值;
*每种肥料中养分的百分比;
*肥料订单的大小;
*肥料价格;
*储存容量;
*存储成本;
*生产能力;
*原材料成本;
*原料中营养成分的含量。
спрос_на_удобрения = DataFrame(
Сбалансированное = [750, 800, 900, 850, 700, 700, 700, 600, 600, 550, 550, 550],
Высокоазотное = [300, 310, 600, 400, 350, 300, 200, 200, 200, 200, 200, 200]
)
месяцы = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"]
начальные_запасы = DataFrame(
Сбалансированное = [200, 200],
Высокоазотное = [200, 200]
)
содержание_питательных_в_удобрениях = DataFrame(
Сбалансированное = [10, 10, 10],
Высокоазотное = [20, 10, 10]
)
заказы_на_удобрения = DataFrame(
Сбалансированное = [200, 400, 400, 400, 400, 400, 200, 0, 0, 0, 0, 0],
Высокоазотное = [0, 100, 200, 200, 200, 200, 200, 0, 0, 0, 0, 0]
)
цены_на_удобрения = DataFrame(
Сбалансированное = [400],
Высокоазотное = [550]
)
вместимость_склада = 1000
стоимость_хранения_единицы = 10
производственная_мощность = 1200
стоимость_сырья = DataFrame(
Моноаммонийфосфат = [350, 360, 350, 350, 320, 320, 320, 320, 320, 310, 310, 340],
Хлорид_калия = [610, 630, 630, 610, 600, 600, 600, 600, 600, 600, 600, 600],
Аммиачная_селитра = [300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300],
Сульфат_аммония = [135, 140, 135, 125, 125, 125, 125, 125, 125, 125, 125, 125],
Тройной_суперфосфат = [250, 275, 275, 250, 250, 250, 250, 240, 240, 240, 240, 240],
Песок = [80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80]
)
содержание_питательных_в_сырье = DataFrame(
Моноаммонийфосфат = [11, 48, 0],
Хлорид_калия = [0, 0, 60],
Аммиачная_селитра = [35, 0, 0],
Сульфат_аммония = [21, 0, 0],
Тройной_суперфосфат = [0, 46, 0],
Песок = [0, 0, 0]
)
виды_удобрений = names(спрос_на_удобрения)
вещества = ["Азот", "Фосфор", "Калий"]
виды_сырья = names(стоимость_сырья)
количество_видов_удобрений = length(виды_удобрений)
количество_видов_сырья = length(виды_сырья)
количество_питательных = length(вещества)
количество_месяцев = length(месяцы)
спрос_с_месяцами = copy(спрос_на_удобрения)
insertcols!(спрос_с_месяцами, 1, :Месяц => месяцы)
стоимость_сырья_с_месяцами = copy(стоимость_сырья)
insertcols!(стоимость_сырья_с_месяцами, 1, :Месяц => месяцы)
содержание_удобрений_с_названиями = copy(содержание_питательных_в_удобрениях)
insertcols!(содержание_удобрений_с_названиями, 1, :Питательное_вещество => вещества)
содержание_сырья_с_названиями = copy(содержание_питательных_в_сырье)
insertcols!(содержание_сырья_с_названиями, 1, :Питательное_вещество => вещества)
让我们定义一个以表格形式显示数据的函数。
function вывести_таблицу(df::DataFrame, заголовок::String="")
if !isempty(заголовок)
println("\n$заголовок:")
end
имена_столбцов = names(df)
ширина_столбцов = []
for столбец in имена_столбцов
ширина = length(string(столбец))
for i in 1:nrow(df)
значение = df[i, столбец]
if isa(значение, Number)
строка_значения = string(round(Int, значение))
else
строка_значения = string(значение)
end
ширина = max(ширина, length(строка_значения))
end
push!(ширина_столбцов, ширина + 2)
end
заголовок_таблицы = ""
for (i, столбец) in enumerate(имена_столбцов)
заголовок_таблицы *= rpad(столбец, ширина_столбцов[i])
end
println(заголовок_таблицы)
разделитель = ""
for ширина in ширина_столбцов
разделитель *= repeat("-", ширина)
end
println(разделитель)
for i in 1:nrow(df)
строка = ""
for (j, столбец) in enumerate(имена_столбцов)
значение = df[i, столбец]
if isa(значение, Number)
строка_значения = string(round(Int, значение))
else
строка_значения = string(значение)
end
строка *= rpad(строка_значения, ширина_столбцов[j])
end
println(строка)
end
println()
end
有两种类型的肥料可用:
*平衡-标准成分是10%氮,10%磷,10%钾。
*高氮-氮-增强版:20%氮,10%磷,10%钾。
唯一的区别是"高氮"中氮的比例增加了一倍,这使得生产更昂贵。
println("Содержание питательных веществ в удобрениях (в процентах):\n")
вывести_таблицу(содержание_удобрений_с_названиями)
原料及其营养成分(以重量百分比计):
println("Содержание питательных веществ в сырье (в процентах):\n")
вывести_таблицу(содержание_сырья_с_названиями)
砂是中性填料。 里面没有营养素。 它用作"稀释剂",以在活性成分过于浓缩时准确地保持成品混合物中所需的百分比。
化肥订单量和价格
在整个计划期间,我们知道每个品牌肥料的每月订单的确切数量。
println("订购量:")
вывести_таблицу(спрос_с_месяцами)
价格是固定的全年。 这意味着我们的收入只取决于销售量,而不是时间。 这种稳定性简化了规划,使您能够专注于成本优化。
println("化肥价格:")
вывести_таблицу(цены_на_удобрения)
原材料的成本
原材料价格是我们模型中的一个关键变量。 与成品的稳定价格不同,成分成本每个月都会发生变化(例如,由于季节性或市场条件)。
println("原材料的成本:")
вывести_таблицу(стоимость_сырья_с_месяцами)
生产及储存
我们将显示存储生产单位的成本,仓库容量和生产能力。
println("Стоимость хранения единицы: $стоимость_хранения_единицы")
println("Вместимость склада: $вместимость_склада")
println("Производственная мощность: $производственная_мощность")
如果我们无法在一个月内生产和运送全部订单,余额将不会转移到下个月。 这迫使模型始终在需求之前计划生产。 因此,该模型正在寻找一种平衡:生产足够的订单并达到目标余额,但不能生产太多,以免产生存储成本。
设置任务
该模型基于需要最大化的目标函数。 在我们的情况下,这是一个利润。
модель = Model(GLPK.Optimizer)
变量
任务中的变量是我们每个月生产和销售的肥料混合物的数量,以及用于制造它们的原材料。
@variable(модель, производить[1:количество_месяцев, 1:количество_видов_удобрений] >= 0)
@variable(модель, продавать[1:количество_месяцев, 1:количество_видов_удобрений] >= 0)
@variable(модель, использовать[1:количество_месяцев, 1:количество_видов_сырья, 1:количество_видов_удобрений] >= 0)
此外,我们将创建一个变量,表示每个时间点的库存量。
@variable(модель, 0 <= запасы[1:количество_месяцев, 1:количество_видов_удобрений] <= вместимость_склада)
销售上限是每个时间段和每个品牌肥料的需求量。
for i in 1:количество_месяцев, b in 1:количество_видов_удобрений
set_upper_bound(продавать[i, b], спрос_на_удобрения[i, b])
end
表达方式
要使用任务变量计算目标函数,您需要计算收入和成本。 收入是每种类型肥料的销售量乘以其价格,在所有时间段和所有类型的肥料中总结。 让我们定义一个收入表达式。
@expression(модель, выручка, sum(цены_на_удобрения[1, j] * sum(продавать[i, j] for i in 1:количество_месяцев) for j in 1:количество_видов_удобрений))
让我们定义一个原材料成本的表达式。 原材料成本是在每个时间点使用的每种成分的成本,在所有时期内总结。 由于每个时刻使用的原料体积除以每种肥料的使用量,因此求和也按肥料的类型进行。
@expression(модель, сырье_использовано[i=1:количество_месяцев, r=1:количество_видов_сырья], sum(использовать[i, r, b] for b in 1:количество_видов_удобрений))
@expression(модель, стоимость_сырья_общая, sum(стоимость_сырья[i, r] * сырье_использовано[i, r] for i in 1:количество_месяцев for r in 1:количество_видов_сырья))
让我们定义一个存储成本的表达式。 储存成本是在每个时间段内维持库存的成本,由时间和肥料类型总结。
@expression(модель, стоимость_хранения_общая, стоимость_хранения_единицы * sum(запасы))
目标功能
让我们定义目标函数。
@objective(модель, Max, выручка - стоимость_сырья_общая - стоимость_хранения_общая)
限制
模型必须符合关键生产规则。:
*库存余额:仓库中的余额是上个月余额与生产和销售的产品数量差异之和;
*目标股票:到年底,有必要达到设定的平衡水平;
*限制:仓库容量和工厂容量有限;
*物料平衡:成品量等于消耗的原料量;
*质量:每批次的化学成分必须严格符合配方。
所有这些规则都形式化为数学约束并加载到模型中以找到最优计划。
@constraint(модель, материальный_баланс1[i=2:количество_месяцев, b=1:количество_видов_удобрений],
запасы[i, b] == запасы[i-1, b] + производить[i, b] - продавать[i, b])
@constraint(модель, материальный_баланс2[b=1:количество_видов_удобрений],
запасы[1, b] == начальные_запасы[1, b] + производить[1, b] - продавать[1, b])
@constraint(модель, финальные_запасы[b=1:количество_видов_удобрений], запасы[количество_месяцев, b] == начальные_запасы[2, b])
@constraint(модель, ограничение_склада[i=1:количество_месяцев], sum(запасы[i, :]) <= вместимость_склада)
@constraint(модель, ограничение_мощности[i=1:количество_месяцев], sum(производить[i, :]) <= производственная_мощность)
@constraint(модель, использование_сырья[i=1:количество_месяцев, b=1:количество_видов_удобрений],
sum(использовать[i, r, b] for r in 1:количество_видов_сырья) == производить[i, b])
@constraint(модель, качество_удобрений[i=1:количество_месяцев, n=1:количество_питательных, b=1:количество_видов_удобрений],
sum(содержание_питательных_в_сырье[n, r] * использовать[i, r, b] for r in 1:количество_видов_сырья) == содержание_питательных_в_удобрениях[n, b] * производить[i, b])
解决和可视化结果
让我们开始解决优化问题。
optimize!(модель)
让我们将解决方案显示为表格(如果表格的宽度不适合,请在浏览器中缩小)。
статус = termination_status(модель);
if статус == MOI.OPTIMAL
прибыль = round(Int, objective_value(модель))
println("\nОптимальное решение найдено")
println("Прибыль: $прибыль")
значения_производить = value.(производить)
значения_продавать = value.(продавать)
значения_запасы = value.(запасы)
производить_округленные = round.(Int, значения_производить)
продавать_округленные = round.(Int, значения_продавать)
запасы_округленные = round.(Int, значения_запасы)
таблица_производства = DataFrame()
for b in 1:количество_видов_удобрений
таблица_производства[!, Symbol("произвести_" * replace(виды_удобрений[b], " " => "_"))] = производить_округленные[:, b]
end
таблица_продаж = DataFrame()
for b in 1:количество_видов_удобрений
таблица_продаж[!, Symbol("продать_" * replace(виды_удобрений[b], " " => "_"))] = продавать_округленные[:, b]
end
таблица_запасов = DataFrame()
for b in 1:количество_видов_удобрений
таблица_запасов[!, Symbol("запасы_" * replace(виды_удобрений[b], " " => "_"))] = запасы_округленные[:, b]
end
производственный_план = hcat(таблица_производства, таблица_продаж, таблица_запасов)
производственный_план[!, :Месяц] = месяцы
производственный_план = производственный_план[:, [end, 1:end-1...]]
println("\nПроизводственный план:")
вывести_таблицу(производственный_план)
end
производить_сбалансированное = производить_округленные[:, 1];
производить_высокоазотное = производить_округленные[:, 2];
продавать_сбалансированное = продавать_округленные[:, 1];
продавать_высокоазотное = продавать_округленные[:, 2];
запасы_сбалансированное = запасы_округленные[:, 1];
запасы_высокоазотное = запасы_округленные[:, 2];
我们将显示a平衡肥料的最佳生产,销售和库存的直方图。
p1 = bar(месяцы, производить_сбалансированное, title="制作人", xlabel="", color=:green, ylabel="数量")
p2 = bar(месяцы, продавать_сбалансированное, title="已售出", xlabel="", color=:green, ylabel="数量")
p3 = bar(месяцы, запасы_сбалансированное, title="股票", color=:green, ylabel="数量")
график1 = plot(p1, p2, p3, layout=(3, 1), size=(1000, 900), margin=10mm, legend=false)
display(график1)
我们将显示高氮肥料的最佳生产,销售和库存的直方图。
p1 = bar(месяцы, производить_высокоазотное, title="制作人", xlabel="", color=:orange, ylabel="数量")
p2 = bar(месяцы, продавать_высокоазотное, title="已售出", xlabel="", color=:orange, ylabel="数量")
p3 = bar(месяцы, запасы_высокоазотное, title="股票", color=:orange, ylabel="数量")
график2 = plot(p1, p2, p3, layout=(3, 1), size=(1000, 900), margin=10mm, legend=false)
display(график2)
结论
对多阶段生产任务进行的建模证明了优化方法在动态市场中的战略规划的有效性。 该解决方案提供了一个准确和实用的计划。
主要调查结果:
-
发布策略:该模型确定了在需求高峰期生产高利润产品的优先级,而基础产品支持整体产能利用率。 这与利润最大化的目标直接一致。
-
储备的动态作用:分析表明,储备不是作为储备,而是作为一个积极的缓冲,平滑可变需求和固定能力之间的不平衡。 采用计划积累和随后减少库存的策略来最大限度地减少总成本。
-
约束的影响:对产能和目标端平衡的严格约束形成计划的特定结构,导致生产尖峰满足边界条件。 这证实了系统对控制参数的高灵敏度。
因此,该模型充分地将复杂的规划任务形式化,并将其转化为优化任务。 其结果是一个战略地图,确定利润增长点和系统瓶颈,这使得这种方法成为证明管理决策合理的有价值的工具。
.png)
.png)