はじめに
ロジスティクス・ネットワークが供給地点から需要地点への,一方向的な「もの」の流れを扱うのに対して, サービス・ネットワークでは,発地と着地の間に輸送される,多対多の「もの」の流れを扱う. ロジスティクス・ネットワークにおいては,顧客需要の不確実性やサービス・レベルの要求などの条件を満たすために, ネットワーク内の地点で在庫を適切に管理することが重要になる. それに対して,サービス・ネットワークにおいては,基本的には途中で在庫することなく, 発地から着地へ「もの」が流れていく. (ただし,輸送のタイミングをとるために,一時保管することは許される.)
サービス・ネットワーク設計問題(consolidation transportation network design problem)は, しばしば混載ネットワーク設計問題ともよばれる. しかし,我が国における実務家の間では,「混載」という用語が「異なる種類の製品の積み合わせ」という意味で用いられ,本モデルでは,同じ種類の製品の積み合わせが主な応用であるため, 混乱を避ける意味でサービス・ネットワーク設計問題とよぶことにする. ここで考えるモデルでは, 主に(郵便物や宅配便のように)発地・着地が異なる同じ種類の製品の積み合わせを対象とする 場合が多いが,異なる種類の製品の積み合わせ(いわゆる業界用語での混載)への適用も可能である.
配送計画は,比較的短距離の輸送を計画するためのモデルであるが, サービス・ネットワーク設計では比較的長距離の輸送を対象とする. そのため,途中での積み替えを考慮する必要が出てくる. これが,ここで考えるサービス・ネットワーク設計が, 配送計画と異なったアプローチを必要とする理由である. 配送計画においては,積み替えを考慮する必要がなかったので,運搬車の移動経路を求めれば, 荷(品目)の移動は自動的に定まった.一方,サービス・ネットワーク設計においては, 積み替えを考慮する必要があるので,運搬車の流れのみならず,荷の流れが意思決定項目となり, これによって問題の難しさが増大する.
ここで考えるモデルは,実務から生まれたものであり,以下の仮定に基づく.
荷(load)とは,地点間を移動させる「モノ」の総称である.ネットワーク理論では品種(commodity) とよばれるが,ここでは現実問題を想定していることを強調するために荷とよぶことにする. 荷は,その移動によって利益を生む資源の総称である. 荷が最初に出発する(最後に到着する)地点を荷の発地(着地)とよび,あらかじめ決められている. ネットワーク理論では,発地(source)は始点もしくは発生点,着地(sink, terminal)は終点もしくは集中点とよばれるが,以下では,発地,着地とよぶものとする.
荷は途中で分岐してはならない.言い換えれば,荷の発地から着地までの移動経路は,1本のパスでなければならない.
宅配便や郵便事業などへの応用では,さらに荷の移動に入木条件とよばれる制約が付加される. 入木条件とは,着地が同じ荷のパスが合流したら,その後のパスは同じ経路にならなければならないことを規定する. これは,集荷した荷物を積み替え(ソーティング)する際に,現場でのオペレーションの簡便性のため, 着地の情報だけを利用するためである. 実際に,東京行きの荷物という荷の集まりは,様々な発地からの荷が集約されたものであり,これを発地別に分けて 異なる方面行きの運搬車に積み替えることは (たとえそれによって費用が減少する可能性があっても)現実には行われないのである.
運搬車(トラック)の積載容量は既知であり,積載される荷量の合計は積載容量を超えない.
地点間の運搬車の移動費用は既知である.
中継地点での積替え費用は荷量によって定まり,既知である.
運搬車は出発地点に戻ってくる必要はない.(この仮定を緩めるのは比較的容易である.)
上のサービス・ネットワーク設計モデルに対して,数理最適化(ならびに制約最適化)ソルバーを利用した専用解法を構築する.
od_df = pd.read_csv(folder +"od.csv", index_col=0)
Demand = od_df.values
#Demand
dc_df = pd.read_csv(folder+"DC.csv", index_col=0)
n = len(dc_df)
dc_df.head()
trace1 = go.Histogram(
x= [ Demand[i,j] for i in range(n) for j in range(n) ],
opacity=0.5,
name="OD間需要"
)
data = [trace1]
layout = go.Layout(
title='OD間需要量',
xaxis=dict(
title="需要量"
),
yaxis=dict(
title='件数'
)
)
fig = go.Figure(data=data, layout=layout)
plotly.offline.plot(fig);
サービスネットワーク設計問題を解くための関数 solve_sndp
引数:
- dc_df: 倉庫データフレーム(緯度・経度情報,容量,変動費用(積替え費用)を利用)
- od_df: 発地・着地間の需要量を保管したデータフレーム
- cost_per_dis: 1kmあたりの運搬車の変動費用(人件費を含まず)
- cost_per_time: 1時間あたりの運搬車の変動費用
- capacity: 運搬車の積載容量
- max_cpu: 最大CPU時間
- scaling: 勾配スケーリング法を用いるとき True
- k: 最短路の本数
- alpha: 勾配スケーリング法のパラメータ
- max_iter: 勾配スケーリング法の最大反復回数
- osrm: 地図 (OSRM) を利用するとき True
返値:
- path_df: 解のパス情報を保管したデータフレーム
- vehicle_df: 解の運搬車情報を保管したデータフレーム
- cost_df: 費用の内訳を表すデータフレーム
- fig: 図オブジェクト
dc_df = pd.read_csv(folder + "DC.csv", index_col=0) #ub =base capacity, vc = transfer cost
od_df = pd.read_csv(folder + "od.csv", index_col=0)
# 問題のサイズの縮小
n= 10
dc_df = dc_df.iloc[:n,:]
od_df = od_df.iloc[:n,:n]
try:
# just MIP
#path_df, vehicle_df, cost_df, fig = solve_sndp(dc_df, od_df, cost_per_dis=20, cost_per_time=8000, capacity=1000., max_cpu = 10, scaling=False, k=10, alpha=0.9, max_iter = 10, osrm = False)
# slope scaling + column generation
path_df, vehicle_df, cost_df, fig = solve_sndp(dc_df, od_df, cost_per_dis=20, cost_per_time=8000, capacity=1000., max_cpu = 10, scaling=True, k=10, alpha=0.5, max_iter = 10, osrm = False)
except SolverError as e:
print(e)
Demand = od_df.values
od_df
vehicle_df.head()
cost_df