Lotsizing Optimzation System

簡易システムの紹介ビデオ

ロットサイズ最適化システム OptLot

はじめに

ここで論じるのは,需要量が期によって変動するときの 各期の生産量(もしくは発注量)ならびに在庫量を決定するためのモデル(動的ロットサイズ決定モデル)である. 発注量を決める古典モデルである経済発注量モデルは,サプライ・チェイン基本分析システム SCBASの,安全在庫、ロットサイズ、目標在庫(基在庫レベル)の設定関数 inventory_analysisで計算できる. ここで考えるモデルは,経済発注量モデルにおける需要が一定という仮定を拡張し,期によって変動することを許したものである.

計画期間は有限であるとし,発注を行う際の段取り費用(もしくは生産費用)と 在庫費用のトレードオフをとることがモデルの主目的になる.

ロットサイズ決定は,タクティカルレベルの意思決定モデルであり, 与えられた資源(機械や人)の下で,活動をどの程度まとめて行うかを決定する.

一般に,生産や輸送は規模の経済性をもつ. これをモデル化する際には,生産や輸送のための諸活動を行うためには「段取り」とよばれる準備活動が必要になると考える. ロットサイズ決定とは,段取り活動を行う期を決定し, 生産・輸送を表す諸活動をまとめて行うときの「量」を決定するモデルである.

ロットサイズ決定問題は,古くから多くの研究が行われている問題であるが, 国内での(特に実務家の間での)認知度は今ひとつのようである. 適用可能な実務は,ERP(Enterprise Resource Planning)やAPS(Advanced Planning and Scheduling)などの処理的情報技術を導入しており,かつ 段取りの意思決定が比較的重要な分野である. そのような分野においては, ERPやAPSで単純なルールで自動化されていた部分に最適化を持ち込むことによって, より現実的かつ効率的な解を得ることができる. 特に,装置産業においては,ロットサイズ決定モデルは,生産計画の中核を担う.

現在の我が国で販売されている生産スケジューラは,すべてルールに基づいたヒューリスティクスであり,いわゆる処理的情報技術に過ぎない. ロットまとめの意思決定はタクティカルレベルであり,オペレーショナルレベルのスケジューリング最適化と同時に行うべきではない. 個々の意思決定レベルの最適化に基づき,情報のやりとりによって全体最適化を目指す必要があり,そのためには,広範囲の問題が解けるロットサイズ決定モデルが必須である.

データ

読み込みと生成

prod_df = pd.read_csv(folder + "lotprod.csv",index_col=0)
prod_df.set_index("name", inplace=True)
production_df = pd.read_csv(folder + "production.csv",index_col=0)
bom_df = pd.read_csv(folder + "bomodel.csv", index_col =0)
resource_df = pd.read_csv(folder + "resource.csv", index_col=0)
plnt_demand_df = pd.read_csv(folder+"plnt-demand.csv")
demand = pd.pivot_table(plnt_demand_df, index= "prod", columns ="period",  values="demand", aggfunc=sum)

品目データ

製品や半製品や部品や原材料をあわせて品目とよぶ. データ項目は以下の通り.

  • name: 名称
  • inv_cost: (工場における)在庫費用
  • safety_inventory: 安全在庫量(在庫量の下限値)
  • initial_inventory: 初期在庫量
  • target_inventory: 目標在庫量(在庫量の上限値)
prod_df
inv_cost safety_inventory initial_inventory target_inventory
name
A 0.005769 38.636429 401.0 763.398459
B 0.005769 37.812572 395.0 753.503381
C 0.005769 22.971393 373.0 723.295128
D 0.005769 17.463825 323.0 630.222614
E 0.005769 200.161342 934.0 1668.384758
F 0.005769 33.532552 385.0 737.729494
G 0.005769 41.214374 450.0 859.171178
H 0.005769 96.322978 498.0 901.348857
I 0.005769 20.535360 333.0 645.835288
J 0.005769 133.584150 654.0 1175.499053
ABCDE 0.002000 317.045562 2426.0 4538.804340
FGHIJ 0.002000 325.189413 2320.0 4319.583871

生産データ

工場における各品目の生産データを保管する. データ項目は以下の通り.

  • name: 品目名
  • ProdTime: 品目1単位の生産時間
  • SetupTime: 品目の段取り時間
  • ProdCost: 品目1単位の生産費用
  • SetupCost: 品目の段取り費用
production_df
name ProdTime SetupTime ProdCost SetupCost
0 A 1 4845 269 15700
1 B 1 7092 175 13354
2 C 1 3803 169 16694
3 D 1 5837 294 12870
4 E 1 6887 249 18291
5 F 1 7130 174 11046
6 G 1 6964 232 11428
7 H 1 6294 183 18378
8 I 1 6045 244 15215
9 J 1 6229 153 19880
10 ABCDE 1 3849 191 13508
11 FGHIJ 1 4256 187 18245

部品展開表(枝)データ

どの品目(親品目)がどの品目(子品目)から製造されるかを表すデータ.データ項目は,以下の通り.

  • child: 子品目(枝の出発点)
  • parent: 親品目(枝の到着点)
  • units: 親品目1単位を製造するために必要な子品目の数
bom_df
child parent units
0 ABCDE A 1
1 ABCDE B 1
2 ABCDE C 1
3 ABCDE D 1
4 ABCDE E 1
5 FGHIJ F 1
6 FGHIJ G 1
7 FGHIJ H 1
8 FGHIJ I 1
9 FGHIJ J 1

需要データ

各(最終)品目の期ごとの需要量を保管する.

demand
period 0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23
prod
A 153 105 81 81 115 95 156 86 93 106 ... 153 104 124 162 207 214 187 136 70 140
B 101 119 136 117 133 65 125 109 104 122 ... 140 201 167 205 167 167 128 141 208 180
C 83 87 75 45 90 57 82 70 78 70 ... 111 96 87 124 102 110 48 116 106 124
D 67 55 59 57 58 63 78 56 66 69 ... 75 66 110 99 90 82 105 79 118 78
E 175 364 249 300 269 340 331 318 235 489 ... 267 694 624 469 343 329 749 704 343 584
F 47 82 90 95 93 96 74 86 83 101 ... 175 134 149 144 132 170 151 117 133 140
G 101 47 78 137 99 125 113 88 99 141 ... 182 160 200 175 121 154 132 169 132 139
H 147 258 165 184 173 248 200 103 15 126 ... 230 249 253 187 198 252 365 252 213 160
I 65 80 72 46 81 73 84 47 63 57 ... 90 128 97 102 83 117 112 76 116 84
J 276 271 325 308 413 247 179 355 323 280 ... 265 471 384 400 362 399 429 519 489 253

10 rows × 24 columns

資源データ

資源の使用可能量上限(容量;使用可能時間)を規定する.データ項目は,以下の通り.

  • name: 資源名
  • period: 期
  • capacity: 使用可能量上限
resource_df.head()
name period capacity
0 Res0 0 9848.9375
1 Res0 1 9848.9375
2 Res0 2 9848.9375
3 Res0 3 9848.9375
4 Res0 4 9848.9375
T = 24 
yearly_ratio = [1.0 + np.sin(i)*0.5 for i in range(13)] 
dem, prod, month = [], [], [] 
try:
    prod_df.reset_index(inplace=True)
except:
    pass
for row in prod_df.itertuples():
    mu, sigma  = row.average_demand, row.standard_deviation  #monthly demand and std (original average is weekly) 
    for t in range(T):
        dem.append( int(yearly_ratio[t//12]* random.gauss(mu,sigma)) )
        prod.append(row.name)
        month.append(t)
plnt_demand_df = pd.DataFrame({"prod":prod, "period":month, "demand": dem})
plnt_demand_df.to_csv(folder+"plnt-demand.csv")
plnt_demand_df.head()
#print("T=",T)
prod period demand
0 A 0 12607
1 A 1 9700
2 A 2 10743
3 A 3 12361
4 A 4 10971
try:
    prod_df.set_index("name",inplace=True)
except:
    pass

#2段階モデルを仮定;各段階には1つの資源制約と仮定
num_stages = 2
maintenance_cycle = 100 #資源が使用不可になる周期;この数で割り切れると休止
#num_resources = [2,3]
#assert num_stages == len(num_resources) #段階数と資源数のリストの長さは同じであることの確認

prod_time_bound = (1,1)
setup_time_bound =(3600,7200)
prod_cost_bound = (100,300)
setup_cost_bound =(10000,20000)

units_bound =(1,1)

num_parents = 5  # 1つの子品目から生成される親品目の数;distribution型の生産工程を仮定
products = list(prod_df.index)
num_prod = len(products)

#原材料リストを生成
raw_material_name=""
raw_materials = [] 
for i,p in enumerate(prod_df.index):
    raw_material_name += str(p)
    if (i+1)%num_parents == 0 or i==len(prod_df)-1:
        #print(i,p, raw_material_name)
        raw_materials.append(raw_material_name)
        raw_material_name =""
#親子関係を定義
parent = defaultdict(list)
child = {}
for r in raw_materials:
    parent[r] = list(r)
    for p in parent[r]:
        child[p] = r

#生産工程の容量を計算
average_demand = plnt_demand_df.demand.sum()/T
print(average_demand)
186479.70833333334
cycle_time= 4
capacity = (average_demand*prod_time_bound[1]+setup_time_bound[1]*num_prod/cycle_time)

print("capacity=",capacity)
#資源データフレーム
#期ごとに容量が変化するモデル
#print(capacity)
name_, period_, capacity_ = [], [], []
for s in range(num_stages):
    for t in range(T):
        name_.append( f"Res{s}")
        period_.append(t)
        if (t+1) % maintenance_cycle == 0:
            capacity_.append( 0 )
        else:
            if s ==0:
                capacity_.append( capacity/2 )
            else:
                capacity_.append( capacity)
resource_df = pd.DataFrame(data={"name": name_, "period": period_, "capacity": capacity_})

#部品展開表のデータフレーム生成
bom_df = pd.DataFrame(data={"child": [child[p] for p in products],
                         "parent": products,
                         "units": [random.randint(units_bound[0], units_bound[1]) for i in range(num_prod)]
                        })

#生産情報データフレーム生成
items = products+raw_materials
num_item = len(items)
production_df = pd.DataFrame(data={"name": items,
                             "ProdTime": [random.randint(prod_time_bound[0], prod_time_bound[1]) for i in range(num_item)],
                             "SetupTime": [random.randint(setup_time_bound[0], setup_time_bound[1]) for i in range(num_item)],
                             "ProdCost": [random.randint(prod_cost_bound[0], prod_cost_bound[1]) for i in range(num_item)],
                             "SetupCost": [random.randint(setup_cost_bound[0], setup_cost_bound[1]) for i in range(num_item)]
                             })
production_df.set_index("name", inplace=True)
production_df.reset_index(inplace=True)
production_df.to_csv(folder+"production.csv")
bom_df.to_csv(folder+"bomodel.csv")
resource_df.to_csv(folder+"resource.csv")
capacity= 204479.70833333334
#inv_cost: Optional[float] = Field(description="工場における在庫費用")
#safety_inventory: Optional[float] = Field(description="安全在庫量(最終期の目標在庫量)")
#initial_inventory: Optional[float] = Field(description="初期在庫量")
#target_inventory

prod_df.reset_index(inplace=True)
name = prod_df.name.to_list()
inv_cost = prod_df.inv_cost.to_list()
safety_inventory = prod_df.safety_inventory.to_list()
initial_inventory = prod_df.initial_inventory.to_list()
target_inventory = prod_df.target_inventory.to_list()

name.extend(raw_materials)
inv_cost.extend( [0.002 for i in range(len(raw_materials))])
safety_inventory.extend([sum(safety_inventory[:num_parents]),sum(safety_inventory[num_parents:])] )
initial_inventory.extend([sum(initial_inventory[:num_parents]),sum(initial_inventory[num_parents:])] )
target_inventory.extend([sum(target_inventory[:num_parents]),sum(target_inventory[num_parents:])] )

prod_df = pd.DataFrame({"name":name, "inv_cost":inv_cost,"safety_inventory":safety_inventory,"initial_inventory":initial_inventory,"target_inventory":target_inventory})
prod_df.to_csv(folder+"lotprod.csv")

ロットサイズ決定問題を解く関数 lotsizing

2段階モデルを仮定し、最初の段階(原材料生成工程)における資源名を Res0、次の段階(完成品生産工程)における資源名をRes1とする。

原材料の在庫は許さないものとする。

親品目は子品目1単位から生成される。

引数:

  • prod_df : 品目データフレーム
  • production_df : 生産情報データフレーム
  • bom_df : 部品展開表データフレーム
  • demand : (期別・品目別の)需要を入れた配列(行が品目,列が期)
  • resource_df : 資源データフレーム

返値:

  • model : ロットサイズ決定モデルのオブジェクト(最適解の情報も mode.__data に含む); 最適化の状態は model.Status
  • T : 計画期間数

lotsizing[source]

lotsizing(prod_df, production_df, bom_df, demand, resource_df, max_cpu=10, solver='CBC')

ロットサイズ決定問題を解く関数

lotsizing関数の使用例

prod_df = pd.read_csv(folder + "lotprod.csv",index_col=0)
prod_df.set_index("name", inplace=True)
production_df = pd.read_csv(folder + "production.csv",index_col="name")
bom_df = pd.read_csv(folder + "bom.csv")

#需要量の計算
#demand = demand_df.iloc[:,3:].values
plnt_demand_df = pd.read_csv(folder+"plnt-demand.csv")

demand_ = pd.pivot_table(plnt_demand_df, index= "prod", columns ="period",  values="demand", aggfunc=sum)
demand = demand_.values
_, T = demand.shape 
resource_df = pd.read_csv(folder + "resource.csv")

model, T = lotsizing(prod_df, production_df, bom_df, demand = demand, resource_df=resource_df, max_cpu= 10, solver="SCIP")

最適化結果から図とデータフレームを生成する関数 show_result_for_lotsizing

引数:

  • model : ロットサイズ決定モデル
  • T : 計画期間
  • production_df : 生産情報データフレーム
  • bom_df : 部品展開表データフレーム
  • resource_df : 資源データフレーム

返値:

  • violated : 需要満足条件を逸脱した品目と期と逸脱量を保存したデータフレーム
  • production : 生産量を保管したデータフレーム
  • inventory : 在庫量を保管したデータフレーム
  • fig_inv : 在庫量の推移を表した図オブジェクト
  • fig_capacity : 容量制約を表した図オブジェクト

show_result_for_lotsizing[source]

show_result_for_lotsizing(model, T, prod_df, production_df, bom_df, resource_df)

最適化結果から図とデータフレームを生成する関数

violated, production, inventory, fig_inv, fig_capacity = show_result_for_lotsizing(model, T, prod_df, production_df, bom_df, resource_df=resource_df)
plotly.offline.plot(fig_inv);
plotly.offline.plot(fig_capacity);
production.columns = [str(i) for i in list(production.columns)]
production
0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23
FGHIJ 0.000000 0.00000 6282.38280 0.00000 0.00000 0.00000 0.00000 7298.43600 0.0 5242.57507 ... 0.0000 5787.49000 0.000000 5515.05310 0.00000 5059.50944 0.0000 5928.4375 0.0 4581.18941
B 0.000000 0.00000 0.00000 0.00000 0.00000 0.00000 7857.47797 0.00000 0.0 0.00000 ... 0.0000 0.00000 0.000000 0.00000 8014.69081 0.00000 0.0000 0.0000 0.0 0.00000
I 0.000000 0.00000 6713.83529 0.00000 0.00000 0.00000 0.00000 0.00000 0.0 6792.29993 ... 0.0000 6871.29993 0.000000 0.00000 0.00000 0.00000 0.0000 0.0000 6433.0 0.00000
ABCDE 0.000000 0.00000 0.00000 0.00000 4456.00576 5295.47240 5963.55650 0.00000 0.0 0.00000 ... 6325.9182 0.00000 0.000000 0.00000 0.00000 0.00000 7208.4375 0.0000 0.0 4166.04556
C 0.000000 0.00000 0.00000 0.00000 4533.29513 0.00000 0.00000 0.00000 0.0 0.00000 ... 0.0000 0.00000 0.000000 4533.00000 0.00000 0.00000 0.0000 0.0000 0.0 0.00000
E 0.000000 0.00000 0.00000 0.00000 0.00000 7964.16130 0.00000 0.00000 0.0 0.00000 ... 0.0000 0.00000 0.000000 0.00000 0.00000 0.00000 0.0000 0.0000 0.0 0.00000
A 0.000000 5666.50830 5173.89016 5053.15945 0.00000 0.00000 5876.84050 5162.23797 0.0 0.00000 ... 5500.0000 0.00000 6163.000000 0.00000 5261.23797 0.00000 6298.0000 0.0000 5772.0 0.00000
F 0.000000 0.00000 0.00000 7796.72949 0.00000 0.00000 0.00000 0.00000 0.0 7663.00000 ... 0.0000 0.00000 0.000000 7978.19694 0.00000 7268.80306 0.0000 0.0000 0.0 0.00000
J 6851.499050 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 7015.0 0.00000 ... 0.0000 7039.08510 0.000000 0.00000 0.00000 0.00000 0.0000 0.0000 6500.0 0.00000
D 5873.027089 6513.19552 0.00000 0.00000 0.00000 6438.24121 0.00000 0.00000 0.0 0.00000 ... 0.0000 0.00000 6473.831800 0.00000 5966.97191 0.00000 6191.4375 6462.0000 0.0 0.00000
G 0.000000 7518.17118 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.0 0.00000 ... 7871.9568 0.00000 7061.043196 0.00000 0.00000 0.00000 0.0000 0.0000 0.0 0.00000
H 6973.348860 0.00000 0.00000 6847.98605 6894.81287 0.00000 0.00000 7237.20107 0.0 0.00000 ... 0.0000 0.00000 0.000000 0.00000 0.00000 7369.56250 0.0000 7307.4375 0.0 0.00000

12 rows × 24 columns

production.head()
0 1 2 3 4 5 6 7 8 9 ... 14 15 16 17 18 19 20 21 22 23
D 0.0 0.0 5915.46383 0.00000 6511.0 0.000000 0.00000 0.0 0.0 0.0 ... 0.0 6448.0 0.0 0.0 0.0 6501.0 0.0 0.0 0.0 0.0
F 0.0 0.0 0.00000 7668.53255 0.0 0.000000 0.00000 0.0 0.0 0.0 ... 0.0 0.0 7532.0 0.0 0.0 0.0 7567.0 0.0 0.0 0.0
H 0.0 0.0 0.00000 0.00000 0.0 4007.322978 4807.00000 0.0 0.0 0.0 ... 0.0 0.0 4840.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
G 0.0 0.0 0.00000 0.00000 0.0 3841.000000 4537.21437 0.0 0.0 0.0 ... 0.0 0.0 4598.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
I 0.0 0.0 0.00000 7388.53536 0.0 0.000000 0.00000 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 7410.0 0.0 0.0 0.0

5 rows × 24 columns

一般的なモデルのExcelデータテンプレートの生成

単に最適化を行うだけでなく,実際の運用で使うためのWebアプリとExcel連携

Webアプリで以下のものを入力

  • 単位期間: 日や週や月などの計画単位期間を事前に決める.
  • 計画期間の開始日と終了日

品目データ(マスタ)生成関数 generate_item_master

generate_item_master[source]

generate_item_master(wb)

wb = Workbook()
ws = wb.active
wb.remove(ws)
wb = generate_item_master(wb)                

工程データ(マスタ) 生成関数 generate_process_master

生産モードの概念を導入し,子品目数と資源数を入力する. このデータを元に部品展開表テンプレートと,資源必要量テンプレートを生成する.

generate_process_master[source]

generate_process_master(wb)

ロットサイズ決定システムのマスターExcel Workbookを生成する関数 generate_lotsize_master

品目と工程シートの他に, 資源名と基本容量を入れたシートをもつWorkbookを生成する. これがロットサイズ決定システムのマスタデータになる.

generate_lotsize_master[source]

generate_lotsize_master()

wb = generate_lotsize_master()
wb.save("optlot-master.xlsx")

部品展開表と資源必要量のシートを追加する関数 add_bom_resource_sheets

引数:

  • wb: シート追加前のWorkbook; 工程シートに必要な子品目数と資源数を入れておく.

返り値:

  • wb: 部品展開表と必要資源量のシートを追加したWorkbook

add_bom_resource_sheets[source]

add_bom_resource_sheets(wb)

wb = load_workbook("lotsize-ex1.xlsx")
wb = add_bom_resource_sheets(wb)
#wb.save("lotsize-ex1.xlsx")

資源データ入力Excelテンプレートの生成関数

基本データをマスタで入力した後, Webアプリで開始日と終了日と期間(pandasの頻度(freq)パラメータ)を入力し,生成した期別の入力シートをダウンロードしてもらい, 期別の容量(稼働時間上限)をExcelで入力し,アップロードする.これが, オペレーショナルに用いる資源データになる.

引数:

  • wb: ロットサイズ決定のためのExcel Workbook
  • start: 計画期間の開始日(日付時刻を表す文字列もしくは日付時刻型)
  • finish: 計画期間の終了日(日付時刻を表す文字列もしくは日付時刻型)
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.

返値:

  • wb: 資源の期別の容量(稼働時間上限)のシートを追加したWorkbook; シート名は「期別上限 開始日 終了日」となる.

add_detailed_resource_sheet[source]

add_detailed_resource_sheet(wb, start, finish, period=1, period_unit='日')

wb = load_workbook("lotsize-sample-new.xlsx")
start = '2021-01-01' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
wb = add_detailed_resource_sheet(wb, start, finish, period, period_unit)
wb.save("lotsize-sample-new.xlsx")

注文データ(品目の納期と数量) generate_order_excel

注文データ(品目の納期と数量)のテンプレートExcel Workbookを生成する. これは,オペレーショナルデータである. これから需要量データを生成する.

generate_order_master[source]

generate_order_master()

wb = generate_order_master()
wb.save("order.xlsx")

Excelブックの読み込みとデータ変換のための関数 read_dfs_from_excel_lot

引数

  • wb: Excel Workbook

返値:

  • item_df: 品目データフレーム
  • process_df: 工程データフレーム
  • resource_df: 資源データフレーム
  • bom_df: 部品展開表データフレーム
  • usage_df: 資源使用量データフレーム

read_dfs_from_excel_lot[source]

read_dfs_from_excel_lot(wb)

wb = load_workbook("lotsize-new.xlsx")
item_df, process_df, resource_df, bom_df, usage_df = read_dfs_from_excel_lot(wb)
#print(item_df)
#print(process_df)
#print(resource_df)

注文Workbookから需要を生成する関数 generate_demand_from_order

引数

  • wb: 注文情報を入れたExcel Workbook(シート名は注文)
  • start: 開始日
  • finish: 終了日
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.

返値

  • demand: 品目 $p$ の期 $t$ の需要量 demand[t,p] を入れた辞書
  • T: 計画期間数

generate_demand_from_order[source]

generate_demand_from_order(wb, start, finish, period=1, period_unit='日')

wb = load_workbook("order-sample.xlsx")
#開始日を設定し,需要を生成
start = '2021-01-10' #開始日
finish = '2021-01-20'
period=1
period_unit="日"
demand, T = generate_demand_from_order(wb, start, finish, period, period_unit)
demand
defaultdict(float,
            {(0, 'A1'): 100.0,
             (1, 'A1'): 0.0,
             (2, 'A1'): 0.0,
             (3, 'A1'): 0.0,
             (4, 'A1'): 0.0,
             (5, 'A1'): 200.0,
             (6, 'A1'): 0.0,
             (7, 'A1'): 0.0,
             (8, 'A1'): 400.0,
             (9, 'A1'): 0.0,
             (10, 'A1'): 300.0,
             (0, 'B1'): 100.0})

期別資源使用量上限をもつWorkbookから情報を抽出する関数 get_resource_ub

引数

  • wb: 注文情報を入れたExcel Workbook(シート名は注文)
  • start: 開始日
  • finish: 終了日
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.

返値:

  • M: 期別の資源量上限を表す辞書; 資源 $r$ の期 $t$ の上限が M[t,r] になる.

get_resource_ub[source]

get_resource_ub(wb, start, finish, period=1, period_unit='日')

start = '2021-01-01' #開始日
finish = '2021-01-25'
period=1
period_unit="日"
wb = load_workbook("lotsize-new.xlsx")
M = get_resource_ub(wb, start, finish, period, period_unit)

多モードロットサイズ決定問題を解く関数 multi_mode_lotsizing

引数:

  • item_df: 品目データフレーム
  • process_df: 工程データフレーム
  • resource_df: 資源データフレーム
  • bom_df: 部品展開表データフレーム
  • usage_df: 資源使用量データフレーム
  • demand: 需要量を入れた辞書
  • capacity: 資源量上限を入れた辞書
  • T: 計画期間数
  • fix_x: 変数固定情報を入れた辞書

返値:

  • model: モデルオブジェクト

multi_mode_lotsizing[source]

multi_mode_lotsizing(item_df, resource_df, process_df, bom_df, usage_df, demand, capacity, T=1, fix_x=None)

order_wb = load_workbook("optlot-order-ex1.xlsx")
# start = '2021-01-01' #開始日
# finish = '2021-01-25'
start = '2021-01-10' #開始日
finish = '2021-01-20'
start = '2021-01-1' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
demand, T = generate_demand_from_order(order_wb, start, finish, period, period_unit)

#資源量設定
wb = load_workbook("optlot-master2-ex1.xlsx")
item_df, process_df, resource_df, bom_df, usage_df = read_dfs_from_excel_lot(wb)
M = get_resource_ub(wb, start, finish, period, period_unit)

#変数固定情報
result_wb = load_workbook("lotsize-out.xlsx")
fix_x = extract_fix_info(result_wb, start, finish, period=1, period_unit="日")

model = multi_mode_lotsizing(item_df, resource_df, process_df, bom_df, usage_df, demand, M, T, fix_x)
solver = PULP_CBC_CMD(timeLimit=100, presolve=True)
model.optimize(solver)
print(model.Status, model.ObjVal)
#     class Status:
#         OPTIMAL = 2
#         INFEASIBLE = 3
#         INF_OR_UNBD = 4
#         UNBOUNDED = 5
#         UNDEFINED = None
2 527090.0
x, I, y, slack, surplus, inv_slack, inv_surplus, cost, items, modes, item_modes, setup_time, prod_time, parent, resources = model.__data
for v in y:
    if y[v].X is not None and y[v].X > 0:
        print(v,y[v].X, x[v].X)
for i in cost:
    print(cost[i].X)
(2, 'M2', 'Bottle6') 1.0 150.0
(3, 'M2', 'Bottle6') 1.0 430.0
(0, 'M1', 'Bottle6') 1.0 150.0
(1, 'M1', 'Bottle6') 1.0 300.0
(0, 'M1', 'Can6') 1.0 250.0
(2, 'M1', 'Can6') 1.0 340.0
(4, 'M1', 'Can6') 1.0 200.0
(0, 'M1', 'Bottle1') 1.0 900.0
(1, 'M1', 'Bottle1') 1.0 2700.0
(3, 'M1', 'Bottle1') 1.0 2580.0
(0, 'M1', 'Can1') 1.0 1500.0
(2, 'M1', 'Can1') 1.0 2040.0
(4, 'M1', 'Can1') 1.0 1200.0
(0, 'M0', 'Apple') 1.0 5400.0
(1, 'M0', 'Apple') 1.0 2700.0
(2, 'M0', 'Apple') 1.0 6120.0
(3, 'M0', 'Apple') 1.0 2580.0
(4, 'M0', 'Apple') 1.0 3600.0
(0, 'M0', 'Watermelon') 1.0 3300.0
(1, 'M0', 'Watermelon') 1.0 5400.0
(2, 'M0', 'Watermelon') 1.0 2040.0
(3, 'M0', 'Watermelon') 1.0 5160.0
(4, 'M0', 'Watermelon') 1.0 1200.0
0.0
0.0
344000.0
136750.0
46340.0

最終期の在庫量を最適化するための方法

将来の不確実性を考慮するために,過去の需要量からブートストラップ(繰り返しを許したサンプリング)して未来の需要のシナリオを生成する. シナリオごとに確定的な最適化を行い,それらの多数決もしくは期待値で行動を決める.

費用内訳のデータフレームを生成する関数 make_cost_df

make_cost_df[source]

make_cost_df(cost)

最適化結果のExcelファイルを出力する関数 lot_output_excel

在庫・生産,需要量とシミュレーション用の関数を埋め込み

次の計画の初期在庫量は,このテンプレートの該当列になる. ローリングホライズンで行うために,以前のWorkbookを入れてそれに追加

引数

  • model: ロットサイズ最適化のモデルオブジェクト
  • start: 開始日
  • finish: 終了日
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.
  • demand: 需要を入れた辞書
  • cost: 費用の変数を入れた辞書

返値:

  • wb: 品目別の在庫量・生産量・需要量を入れたExcel Workbook

lot_output_excel[source]

lot_output_excel(model, start, finish, period, period_unit, demand, cost_df)

# finish = '2021-01-25'
start = '2021-01-10' #開始日
finish = '2021-01-20'
start = '2021-01-1' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
cost_df = make_cost_df(cost)
wb = lot_output_excel(model, start, finish, period, period_unit, demand, cost_df)
wb.save("lotsize-out.xlsx")

多モードモデルの最適化結果の図を生成する関数 show_result_for_multimode_lotsizing

引数

  • model: ロットサイズ最適化のモデルオブジェクト
  • start: 開始日
  • finish: 終了日
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.
  • capacity: 資源量上限を入れた辞書

返値:

  • fig_inv : 在庫量の推移を表した図オブジェクト
  • fig_capacity : 容量制約を表した図オブジェクト

show_result_for_multimode_lotsizing[source]

show_result_for_multimode_lotsizing(model, start, finish, period, period_unit, capacity)

show_result_for_multimode_lotsizing関数の使用例

# finish = '2021-01-25'
start = '2021-01-10' #開始日
finish = '2021-01-20'
start = '2021-01-1' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
fig_inv, fig = show_result_for_multimode_lotsizing(model, start, finish, period, period_unit, M)

plotly.offline.plot(fig);
plotly.offline.plot(fig_inv);

最適化結果Workbookの色情報を元に変数の固定情報を抽出する関数 extract_fix_info

出力のExcelシートに色を塗った箇所の生産(発注)量を固定する情報を抽出する.

引数

  • wb: 最適化結果を入れた Excel Workbook
  • start: 開始日
  • finish: 終了日
  • period: 期を構成する単位期間の数;既定値は $1$
  • period_unit: 期の単位 (時,日,週,月から選択; 既定値は日; periodとあわせて期間の生成に用いる. たとえば,既定値だと1日が1期となる.

返値:

  • fix_x: 変数 $x$ の固定情報;キーは「期番号,モード,アイテム」で値は固定したい数値を入れた辞書

extract_fix_info[source]

extract_fix_info(wb, start, finish, period=1, period_unit='日')

extract_fix_info関数の使用例

wb = load_workbook("lotsize-out.xlsx")
start = '2021-01-01' #開始日
finish = '2021-01-25'
period=1
period_unit="日"
fix_x = extract_fix_info(wb, start, finish, period=1, period_unit="日")
fix_x
{(0, 'M1', 'Bottle6'): 150.0,
 (1, 'M1', 'Bottle6'): 450.0,
 (0, 'M1', 'Can6'): 250.0,
 (1, 'M1', 'Can6'): 0.0}