Keyword: 飲料製造, 生産最適化, コスト最小化, 線形計画法, LP
飲料製造業分野の最適化問題:生産・物流コスト最小化
問題の概要
飲料製造業界では、多様な製品を複数の工場で生産し、様々な地域に配送する必要があります。この過程では、生産コストと物流コストの両方を考慮しながら、生産依頼を満たす最適な生産計画を立てることが重要です。本問題は、総コスト(生産コストと物流コスト)を最小化しつつ、各地域の生産依頼量を満たす生産計画を策定することを目的としています。
この最適化問題を解くことで、企業は以下のような利点を得ることができます: 1. コスト削減による利益の向上 2. 効率的な生産・物流体制の構築 3. 生産依頼の変動への柔軟な対応 4. 環境負荷の軽減(輸送距離の最適化による)
主要な制約条件として、各工場の生産能力、各地域の生産依頼量などを考慮する必要があります。
飲料生産・物流最適化問題(具体例)
あるソフトドリンク会社の生産・物流部門は、販売・在庫管理部門から週次の生産・配送依頼を受け取ります。この依頼は既に需要予測と在庫状況を考慮しています。生産・物流部門の課題は、3種類の飲料(コーラ、オレンジジュース、緑茶)を2つの工場で生産し、4つの地域に配送する最適な計画を立てることです。与えられた情報は以下の通りです:
- 各製品の各地域における週間生産依頼量
- 各工場の生産能力
- 製品ごとの工場別生産コスト
- 各工場から各地域への輸送コスト
目標は、総コスト(生産コスト+輸送コスト)を最小化しつつ、全ての生産依頼を満たすことです。
生産依頼データ(単位:本/週)
製品 | 地域1 | 地域2 | 地域3 | 地域4 |
---|---|---|---|---|
コーラ | 20,000 | 30,000 | 25,000 | 15,000 |
オレンジジュース | 15,000 | 25,000 | 20,000 | 10,000 |
緑茶 | 25,000 | 20,000 | 30,000 | 35,000 |
生産能力(単位:本/週)
工場 | 最大生産能力 |
---|---|
1 | 200,000 |
2 | 250,000 |
生産コスト(単位:円/本)
製品 | 工場1 | 工場2 |
---|---|---|
コーラ | 25 | 27 |
オレンジジュース | 20 | 18 |
緑茶 | 15 | 17 |
輸送コスト(単位:円/本)
工場 | 地域1 | 地域2 | 地域3 | 地域4 |
---|---|---|---|---|
1 | 2 | 3 | 4 | 5 |
2 | 3 | 2 | 4 | 3 |
問題の定式化
パラメータ
パラメータ | 説明 | 値 |
---|---|---|
製品 |
上記の生産依頼データ表参照 | |
工場 |
上記の生産コスト表参照 | |
工場 |
上記の輸送コスト表参照 | |
工場 |
上記の生産能力表参照 |
ここで、
決定変数
変数 | 説明 | 範囲 |
---|---|---|
工場 |
非負の実数 |
目的関数
目的 | 式 |
---|---|
総コスト最小化 |
この目的関数は、全ての生産コストと輸送コストの合計を最小化します。
制約条件
制約 | 式 | 説明 |
---|---|---|
生産依頼充足 | 各製品の各地域における生産依頼量を満たすこと | |
生産能力 | 各工場の生産量が最大生産能力を超えないこと | |
非負制約 | 生産量は非負であること |
これらの制約条件により、全ての生産依頼を満たしつつ、工場の生産能力を超えない実現可能な生産計画を保証します。
コード例
以下に、この線形計画問題を nAG Library for Python の スパース線形計画問題に適したソルバー関数 handle_solve_lp_ipm を用いて解くコード例を示します。
import numpy as np
from naginterfaces.library import opt
# PARAMETERS section
# 製品
= ['コーラ', 'オレンジジュース', '緑茶']
PRODUCTS # 地域
= ['1', '2', '3', '4']
REGIONS # 工場
= ['1', '2']
FACTORIES
# 需要データ(単位:本/週)
= {
demand 'コーラ': {'1': 20000, '2': 30000, '3': 25000, '4': 15000},
'オレンジジュース': {'1': 15000, '2': 25000, '3': 20000, '4': 10000},
'緑茶': {'1': 25000, '2': 20000, '3': 30000, '4': 35000}
}
# 生産能力(単位:本/週)
= {'1': 200000, '2': 250000}
capacity
# 生産コスト(単位:円/本)
= {
prod_cost 'コーラ': {'1': 25, '2': 27},
'オレンジジュース': {'1': 20, '2': 18},
'緑茶': {'1': 15, '2': 17}
}
# 輸送コスト(単位:円/本)
= {
trans_cost '1': {'1': 2, '2': 3, '3': 4, '4': 5},
'2': {'1': 3, '2': 2, '3': 4, '4': 3}
}
# 変数の数を計算
= len(PRODUCTS) * len(REGIONS) * len(FACTORIES)
nvar
# オプティマイザーの初期化
= opt.handle_init(nvar)
handle
# 目的関数の係数を設定
= []
cvec for p in PRODUCTS:
for r in REGIONS:
for f in FACTORIES:
+ trans_cost[f][r])
cvec.append(prod_cost[p][f]
opt.handle_set_linobj(handle, cvec)
# 変数の上下限を設定
= np.zeros(nvar)
bl = np.full(nvar, np.inf)
bu
opt.handle_set_simplebounds(handle, bl, bu)
# 制約条件の設定
# 需要充足制約
= []
demand_constraints for p in PRODUCTS:
for r in REGIONS:
= [0] * nvar
constraint for f in FACTORIES:
= PRODUCTS.index(p) * len(REGIONS) * len(FACTORIES) + REGIONS.index(r) * len(FACTORIES) + FACTORIES.index(f)
idx = 1
constraint[idx]
demand_constraints.append(constraint)
# 生産能力制約
= []
capacity_constraints for f in FACTORIES:
= [0] * nvar
constraint for p in PRODUCTS:
for r in REGIONS:
= PRODUCTS.index(p) * len(REGIONS) * len(FACTORIES) + REGIONS.index(r) * len(FACTORIES) + FACTORIES.index(f)
idx = 1
constraint[idx]
capacity_constraints.append(constraint)
# 全ての制約をまとめる
= demand_constraints + capacity_constraints
all_constraints
# 制約の上下限を設定
= []
bl_constraints = []
bu_constraints for p in PRODUCTS:
for r in REGIONS:
bl_constraints.append(demand[p][r])
bu_constraints.append(demand[p][r])for f in FACTORIES:
0)
bl_constraints.append(
bu_constraints.append(capacity[f])
# 制約の係数行列を設定
= []
irowb = []
icolb = []
b for i, constraint in enumerate(all_constraints):
for j, coeff in enumerate(constraint):
if coeff != 0:
+ 1)
irowb.append(i + 1)
icolb.append(j
b.append(coeff)
opt.handle_set_linconstr(handle, bl_constraints, bu_constraints, irowb, icolb, b)
# ソルバーオプションの設定
'Print Level = 0')
opt.handle_opt_set(handle,
# ソルバーの実行
= opt.handle_solve_lp_ipm(handle)
x, _, info, _
# 結果の表示
print("\n最適解:")
print("製品\t\t地域\t工場\t生産量")
for i, val in enumerate(x):
if val > 1e-3: # 小さな値は表示しない
= PRODUCTS[i // (len(REGIONS) * len(FACTORIES))]
p = REGIONS[(i % (len(REGIONS) * len(FACTORIES))) // len(FACTORIES)]
r = FACTORIES[i % len(FACTORIES)]
f print(f"{p}\t\t{r}\t{f}\t{val:.0f}")
print(f"\n目的関数値(総コスト): {info[1]:.0f}円")
# ハンドルの解放
opt.handle_free(handle)
コードの詳細説明
問題の初期化
nvar = len(PRODUCTS) * len(REGIONS) * len(FACTORIES)
handle = opt.handle_init(nvar)
handle_init
で最適化問題を初期化しています。ここでnvar
は決定変数の総数です。
目的関数の設定
cvec = [prod_cost[p][f] + trans_cost[f][r] for p in PRODUCTS for r in REGIONS for f in FACTORIES]
opt.handle_set_linobj(handle, cvec)
handle_set_linobj
で目的関数を設定しています。線形式を前提としているため、cvec
(係数ベクトル)のみで目的関数を完全に表現できます。
変数の上下限設定
bl = np.zeros(nvar)
bu = np.full(nvar, np.inf)
opt.handle_set_simplebounds(handle, bl, bu)
handle_set_simplebounds
で変数の上下限を設定しています。ここでは全変数が非負で、上限はない事を指定しています。
制約条件の設定
bl_constraints = [demand[p][r] for p in PRODUCTS for r in REGIONS] + [0 for _ in FACTORIES]
bu_constraints = [demand[p][r] for p in PRODUCTS for r in REGIONS] + [capacity[f] for f in FACTORIES]
irowb, icolb, b = [], [], []
for i, constraint in enumerate(all_constraints):
for j, coeff in enumerate(constraint):
if coeff != 0:
irowb.append(i + 1) # 1-based indexing
icolb.append(j + 1) # 1-based indexing
b.append(coeff)
opt.handle_set_linconstr(handle, bl_constraints, bu_constraints, irowb, icolb, b)
handle_set_linconstr
で線形制約を設定。この関数は以下の要素を組み合わせて完全な制約条件を表現します:
irowb
,icolb
,b
: 制約式の係数行列(スパース形式)irowb
: 制約式のインデックス(1-based)icolb
: 変数のインデックス(1-based)b
: 対応する係数値
bl_constraints
,bu_constraints
: 各制約式の下限と上限
これにより、等式制約、不等式制約など、様々な形式の制約を表現できます。
ソルバーの呼び出し
x, _, info, _ = opt.handle_solve_lp_ipm(handle)
handle_solve_lp_ipm
で線形計画問題のソルバーを呼び出しています。結果として、最適解(x
)及び目的関数値を含む各種情報(info
)が返されます。
結果の表示
print("\n最適解:")
print("製品\t\t地域\t工場\t生産量")
for i, val in enumerate(x):
if val > 1e-3: # 小さな値は表示しない
p = PRODUCTS[i // (len(REGIONS) * len(FACTORIES))]
r = REGIONS[(i % (len(REGIONS) * len(FACTORIES))) // len(FACTORIES)]
f = FACTORIES[i % len(FACTORIES)]
print(f"{p}\t\t{r}\t{f}\t{val:.0f}")
print(f"\n目的関数値(総コスト): {info[1]:.0f}円")
結果の表示では、生産量が微小(1e-3未満)の組み合わせを除外し、実質的な生産計画のみを表示しています。
リソースの解放
opt.handle_free(handle)
handle_free
でメモリを解放しています。
結果例
最適化問題を解いた結果、以下の生産・配送計画が得られました:
最適解:
製品 地域 工場 生産量
コーラ 1 1 20000
コーラ 2 1 30000
コーラ 3 1 25000
コーラ 4 1 6331
コーラ 4 2 8669
オレンジジュース 1 2 15000
オレンジジュース 2 2 25000
オレンジジュース 3 2 20000
オレンジジュース 4 2 10000
緑茶 1 1 25000
緑茶 2 1 20000
緑茶 3 1 30000
緑茶 4 1 12960
緑茶 4 2 22040
目的関数値(総コスト): 6075000円
この結果について、以下のように解釈できます:
生産計画: - コーラ: 主に工場1で生産。ただし、地域4向けは工場1と工場2で分割生産。 - オレンジジュース: 全て工場2で生産。 - 緑茶: 主に工場1で生産。ただし、地域4向けは工場1と工場2で分割生産。
需要充足: 各製品の各地域での生産量が需要と完全に一致しており、全ての需要が満たされています。
生産能力の利用: - 工場1: 生産能力200,000本/週をほぼフル活用(コーラ: 81,331本 + 緑茶: 87,960本 = 169,291本) - 工場2: 生産能力250,000本/週の一部を使用(オレンジジュース: 70,000本 + コーラ: 8,669本 + 緑茶: 22,040本 = 100,709本
生産の特徴: - オレンジジュースは完全に工場2で生産されています。これは、工場2でのオレンジジュースの生産コストが工場1よりも低いためです(18円 vs 20円)。 - コーラと緑茶は主に工場1で生産されていますが、地域4向けの生産では工場2も利用されています。これは、工場1の生産能力制約と輸送コストの最適化のバランスによるものと考えられます。
輸送コストの影響: - 地域4向けの生産が2つの工場に分割されているのは、輸送コストの差(工場1: 5円、工場2: 3円)が影響していると考えられます。 - 他の地域では、生産コストの差が輸送コストの差を上回るため、主に1つの工場で生産が行われています。
総コスト: 目的関数値6,075,000円は最小化された総コスト(生産コスト + 輸送コスト)を表しています。この値は、与えられた制約条件下で最も効率的な生産・配送計画を実現した場合のコストです。
まとめ
本問題では、飲料会社の生産・物流における短期的な最適化問題を、nAG Libraryの線形計画法ソルバーを用いて解決するアプローチを示しました。複数の製品、工場、地域を考慮しつつ、総コスト(生産コストと輸送コスト)を最小化する生産・配送計画を効率的に求めることができました。
この手法は、生産依頼の変動や生産能力の制約といった現実的な条件の下で最適解を導き出すことができます。さらに、CO2排出量の制約や、コスト最小化だけでなく在庫回転率の向上といった複数の目的を考慮するなど、より複雑で現実的なシナリオへの応用も可能です。