hub-location-problem について
hub-location-problemは、輸送や物流などのハブ配置とハブ・アンド・スポーク網の設計を扱う最適化スキルです。複数のハブ配置問題を整理し、厳密解法、貪欲法、局所探索を紹介します。
主な機能
- 01ハブネットワークと問題種別の整理
- 02単一・複数割当pハブ中央値問題のモデル化
- 03貪欲法と集中度ヒューリスティックの紹介
- 04ハブ交換局所探索と焼きなまし法
- 05フロー、割引率、ハブ数の考慮
活用シーン
- 01貨物や郵便のハブネットワーク設計
- 02ODフローに基づくハブ位置の選定
- 03単一割当と複数割当の比較
- 04ハブ数別のネットワークコスト評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill hub-location-problemインストール後、Claude Code で/hub-location-problemを入力して読み込まれたか確認します。
詳細
ハブ配置問題
あなたは、ハブ配置問題とハブ・アンド・スポークネットワーク設計の専門家です。あなたの目標は、起点と終点のペア間のフローをハブ施設に集約し、輸送コストと経路コストの合計を最小化する、効率的なハブ・アンド・スポークネットワークの設計を支援することです。
初期評価
ハブ配置問題を解く前に、次の点を把握します。
-
ネットワークの種類
- ハブ・アンド・スポークネットワークですか?(ハブを経由して集約)
- 配置するハブの数はいくつですか?(p-ハブ問題)
- ハブカバリング問題ですか?(カバレッジ要件)
- ハブに階層はありますか?(多階層ハブ)
-
割り当ての種類
- 単一割り当てですか?(各ノードをちょうど1つのハブに割り当てる)
- 複数割り当てですか?(ノードが複数のハブに接続できる)
- r割り当てですか?(各ノードが最大r個のハブに接続する)
-
フローの特性
- 起点・終点(O-D)フローマトリックスはありますか?
- すべてのペア間に需要がありますか?
- フローの量やパターンはどうなっていますか?
- 方向性はありますか?(対称または非対称)
-
ハブの特性
- ハブに容量制約はありますか?
- ハブの設置に固定費がかかりますか?
- ハブの処理・取扱コストはありますか?
- ハブに規模の経済性はありますか?
- ハブ間接続の割引係数(α)はありますか?
-
ネットワークの目的
- 輸送コストの合計を最小化しますか?
- 最大移動時間を最小化しますか?(p-ハブセンター)
- ハブの数を最小化しますか?
- ハブの負荷を均衡させますか?
ハブ配置問題の枠組み
ハブ・アンド・スポークネットワークの概念
構造:
Origin → Access Hub → Hub-to-Hub → Egress Hub → Destination
Collection | Consolidation | Distribution
Leg | Leg | Leg
主な特徴:
- 集約:O-Dペア間のフローをハブ経由で経路設定します
- 規模の経済性:ハブ間輸送はより安価です(割引係数α)
- ハブ施設:仕分け・集約機能を備えた特別な施設です
- 接続の削減:すべてのノードが直接接続されるわけではありません
用途:
- 航空旅客ネットワーク
- 貨物輸送ネットワーク
- 郵便・小包配送
- 電気通信ネットワーク
- サプライチェーンの流通
- 公共交通機関
問題の分類
1. p-ハブメディアン問題(pHMP)
説明:
- ハブをちょうどp個配置します
- 輸送コストの合計を最小化します
- 最も一般的なハブ配置問題の派生形です
派生形:
- 単一割り当て(SA-pHMP):ハブ以外の各ノードを、ちょうど1つのハブに接続します
- 複数割り当て(MA-pHMP):ハブ以外のノードを、複数のハブに接続できます
特徴:
- ハブの数が固定されています(p)
- コストの最小化
- ネットワーク効率を重視します
2. ハブカバリング問題
説明:
- すべてのO-Dペアを、距離・時間のしきい値以内でカバーします
- 必要なハブの数を最小化します
用途:
- 緊急サービスネットワーク
- 時間制約のある配送
- サービス品質の保証
3. p-ハブセンター問題
説明:
- 最大O-D距離・コストを最小化します
- 公平性を重視する目的
- O-Dペアのコストが過度に高くならないようにします
用途:
- 緊急対応
- 均等なサービス品質
- 公平なネットワーク設計
4. ハブアーク配置問題
説明:
- どのハブ間接続を確立するかを決定します
- 必ずしもすべてのハブが接続されるわけではありません
- 接続性とコストのトレードオフを考慮します
5. 階層型ハブ配置
説明:
- 複数階層のハブ(地域、国内、国際)
- フローが階層に沿って流れます
- 複雑な多層ネットワーク
数学的定式化
単一割り当てp-ハブメディアン問題(SA-pHMP)
集合:
- N = {1, ..., n}:ノード(ハブ候補とハブ以外のノード)の集合
パラメーター:
- w_{ij}:ノードiからノードjへのフロー
- c_{ij}:ノードiからノードjへの単位コスト
- α:ハブ間割引係数(通常0.2~0.8)
- p:配置するハブの数
決定変数:
- y_k ∈ {0,1}:ノードkがハブの場合は1、それ以外は0
- x_{ik} ∈ {0,1}:ノードiがハブkに割り当てられる場合は1、それ以外は0
コスト構造:
For flow from i to j allocated to hubs k and m:
- Collection: c_{ik} (origin to hub)
- Transfer: α × c_{km} (hub to hub, discounted)
- Distribution: c_{mj} (hub to destination)
Total: c_{ik} + α × c_{km} + c_{mj}
目的関数:
Minimize: Σ_i Σ_j Σ_k Σ_m w_{ij} × (c_{ik} + α×c_{km} + c_{mj}) × x_{ik} × x_{jm}
制約条件:
1. Each node allocated to exactly one hub:
Σ_k x_{ik} = 1, ∀i ∈ N
2. Exactly p hubs located:
Σ_k y_k = p
3. Allocation only to hubs:
x_{ik} ≤ y_k, ∀i ∈ N, ∀k ∈ N
4. Hubs allocated to themselves:
x_{kk} = y_k, ∀k ∈ N
5. Binary variables:
y_k ∈ {0,1}, ∀k ∈ N
x_{ik} ∈ {0,1}, ∀i,k ∈ N
計算複雑性: NP困難
複数割り当て p-Hub Median 問題(MA-pHMP)
変更された変数:
- x_{ikm} ∈ [0,1]:ハブ k と m を経由する i から j へのフローの割合
目的関数:
Minimize: Σ_i Σ_j Σ_k Σ_m w_{ij} × (c_{ik} + α×c_{km} + c_{mj}) × x_{ikm}
主な制約:
1. Flow conservation:
Σ_k Σ_m x_{ikm} = 1, ∀i,j ∈ N
2. Exactly p hubs:
Σ_k y_k = p
3. Flow only through hubs:
x_{ikm} ≤ y_k, ∀i,j,k,m
x_{ikm} ≤ y_m, ∀i,j,k,m
厳密解法
1. 単一割り当て p-Hub Median(PuLP)
from pulp import *
import numpy as np
def solve_single_allocation_phub(flows, costs, p, alpha=0.75):
"""
Solve Single Allocation p-Hub Median Problem
Args:
flows: n x n matrix of flows between O-D pairs
costs: n x n matrix of unit transportation costs
p: number of hubs to locate
alpha: inter-hub discount factor (0 < α < 1)
Returns:
optimal hub location and allocation solution
"""
n = len(flows)
# Create problem
prob = LpProblem("Single_Allocation_pHub_Median", LpMinimize)
# Decision variables
# y[k] = 1 if node k is a hub
y = LpVariable.dicts("hub", range(n), cat='Binary')
# x[i,k] = 1 if node i is allocated to hub k
x = LpVariable.dicts("allocation",
[(i, k) for i in range(n) for k in range(n)],
cat='Binary')
# Objective: Minimize total weighted transportation cost
# Cost for flow from i to j through hubs k and m:
# c_ik + alpha*c_km + c_mj
objective = []
for i in range(n):
for j in range(n):
if i == j or flows[i][j] == 0:
continue
for k in range(n):
for m in range(n):
cost_ij_via_km = (costs[i][k] +
alpha * costs[k][m] +
costs[m][j])
objective.append(
flows[i][j] * cost_ij_via_km * x[i,k] * x[j,m]
)
prob += lpSum(objective), "Total_Transportation_Cost"
# Constraints
# 1. Each node allocated to exactly one hub
for i in range(n):
prob += (
lpSum([x[i,k] for k in range(n)]) == 1,
f"Allocation_{i}"
)
# 2. Exactly p hubs located
prob += (
lpSum([y[k] for k in range(n)]) == p,
"p_Hubs"
)
# 3. Can only allocate to hubs
for i in range(n):
for k in range(n):
prob += (
x[i,k] <= y[k],
f"Allocation_to_Hub_{i}_{k}"
)
# 4. Hubs must be allocated to themselves
for k in range(n):
prob += (
x[k,k] == y[k],
f"Hub_Self_Allocation_{k}"
)
# Solve
import time
start_time = time.time()
prob.solve(PULP_CBC_CMD(msg=1, timeLimit=600))
solve_time = time.time() - start_time
# Extract solution
if LpStatus[prob.status] in ['Optimal', 'Feasible']:
hubs = [k for k in range(n) if y[k].varValue > 0.5]
allocations = {}
for i in range(n):
for k in range(n):
if x[i,k].varValue > 0.5:
allocations[i] = k
break
# Calculate flows through each hub
hub_inflow = {k: 0 for k in hubs}
hub_outflow = {k: 0 for k in hubs}
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
hub_i = allocations[i]
hub_j = allocations[j]
hub_outflow[hub_i] += flows[i][j]
hub_inflow[hub_j] += flows[i][j]
return {
'status': LpStatus[prob.status],
'total_cost': value(prob.objective),
'hubs': hubs,
'num_hubs': len(hubs),
'allocations': allocations,
'hub_inflow': hub_inflow,
'hub_outflow': hub_outflow,
'solve_time': solve_time,
'alpha': alpha
}
else:
return {
'status': LpStatus[prob.status],
'solve_time': solve_time
}
# Example usage
if __name__ == "__main__":
np.random.seed(42)
# 10-node network
n = 10
# Generate random coordinates
coords = np.random.rand(n, 2) * 100
# Calculate Euclidean distance matrix
costs = np.zeros((n, n))
for i in range(n):
for j in range(n):
costs[i][j] = np.linalg.norm(coords[i] - coords[j])
# Generate flow matrix (random demands)
flows = np.random.uniform(10, 100, (n, n))
np.fill_diagonal(flows, 0) # No self-flow
# Parameters
p = 3 # Number of hubs
alpha = 0.75 # Inter-hub discount factor
print(f"{'='*70}")
print(f"SINGLE ALLOCATION p-HUB MEDIAN PROBLEM")
print(f"{'='*70}")
print(f"Nodes: {n}")
print(f"Hubs to locate: {p}")
print(f"Inter-hub discount factor: {alpha}")
print(f"Total O-D flow: {flows.sum():.2f}")
result = solve_single_allocation_phub(flows, costs, p, alpha)
print(f"\n{'='*70}")
print(f"SOLUTION")
print(f"{'='*70}")
print(f"Status: {result['status']}")
print(f"Total Cost: {result['total_cost']:,.2f}")
print(f"Hubs Located: {result['hubs']}")
print(f"Solve Time: {result['solve_time']:.2f} seconds")
print(f"\nNode Allocations:")
for node, hub in result['allocations'].items():
hub_indicator = " (HUB)" if node in result['hubs'] else ""
print(f" Node {node} → Hub {hub}{hub_indicator}")
print(f"\nHub Traffic:")
for hub in result['hubs']:
print(f" Hub {hub}:")
print(f" Inflow: {result['hub_inflow'][hub]:.2f}")
print(f" Outflow: {result['hub_outflow'][hub]:.2f}")
print(f" Total: {result['hub_inflow'][hub] + result['hub_outflow'][hub]:.2f}")
2. 複数割り当て p-Hub Median
def solve_multiple_allocation_phub(flows, costs, p, alpha=0.75):
"""
Solve Multiple Allocation p-Hub Median Problem
Allows each node to send/receive flows through multiple hubs
Args:
flows: n x n flow matrix
costs: n x n cost matrix
p: number of hubs
alpha: inter-hub discount factor
Returns:
optimal solution
"""
n = len(flows)
prob = LpProblem("Multiple_Allocation_pHub_Median", LpMinimize)
# Decision variables
y = LpVariable.dicts("hub", range(n), cat='Binary')
# x[i,j,k,m] = fraction of flow from i to j through hubs k and m
x = {}
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
for k in range(n):
for m in range(n):
x[i,j,k,m] = LpVariable(f"flow_{i}_{j}_{k}_{m}",
lowBound=0, upBound=1,
cat='Continuous')
# Objective: Minimize total cost
objective = []
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
for k in range(n):
for m in range(n):
cost_via_km = (costs[i][k] +
alpha * costs[k][m] +
costs[m][j])
objective.append(
flows[i][j] * cost_via_km * x[i,j,k,m]
)
prob += lpSum(objective), "Total_Cost"
# Constraints
# 1. Flow conservation: all flow from i to j routed through some hub pair
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
prob += (
lpSum([x[i,j,k,m] for k in range(n) for m in range(n)]) == 1,
f"Flow_{i}_{j}"
)
# 2. Exactly p hubs
prob += (
lpSum([y[k] for k in range(n)]) == p,
"p_Hubs"
)
# 3. Flow only through open hubs
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
for k in range(n):
for m in range(n):
prob += (
x[i,j,k,m] <= y[k],
f"Hub_k_{i}_{j}_{k}_{m}"
)
prob += (
x[i,j,k,m] <= y[m],
f"Hub_m_{i}_{j}_{k}_{m}"
)
# Solve
start_time = time.time()
prob.solve(PULP_CBC_CMD(msg=1, timeLimit=600))
solve_time = time.time() - start_time
if LpStatus[prob.status] in ['Optimal', 'Feasible']:
hubs = [k for k in range(n) if y[k].varValue > 0.5]
# Extract flow routing
flow_routing = {}
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
flow_routing[i,j] = []
for k in range(n):
for m in range(n):
if x[i,j,k,m].varValue > 0.01:
flow_routing[i,j].append({
'via_hubs': (k, m),
'fraction': x[i,j,k,m].varValue,
'flow': flows[i][j] * x[i,j,k,m].varValue
})
return {
'status': LpStatus[prob.status],
'total_cost': value(prob.objective),
'hubs': hubs,
'flow_routing': flow_routing,
'solve_time': solve_time,
'alpha': alpha
}
else:
return {'status': LpStatus[prob.status]}
# Example usage
result_ma = solve_multiple_allocation_phub(flows, costs, p=3, alpha=0.75)
print(f"\n{'='*70}")
print(f"MULTIPLE ALLOCATION p-HUB MEDIAN SOLUTION")
print(f"{'='*70}")
print(f"Status: {result_ma['status']}")
print(f"Total Cost: {result_ma['total_cost']:,.2f}")
print(f"Hubs: {result_ma['hubs']}")
print(f"\nSample Flow Routing (first 5 O-D pairs):")
count = 0
for (i, j), routing in result_ma['flow_routing'].items():
if count >= 5:
break
print(f" Flow {i}→{j} (total={flows[i][j]:.2f}):")
for route in routing:
via_k, via_m = route['via_hubs']
print(f" via hubs ({via_k}, {via_m}): "
f"{route['fraction']*100:.1f}% ({route['flow']:.2f} units)")
count += 1
貪欲法ヒューリスティック
1. 貪欲ハブ選択
def greedy_hub_selection(flows, costs, p, alpha=0.75):
"""
Greedy heuristic for p-Hub Median
Iteratively select hub that gives maximum cost reduction
Args:
flows: flow matrix
costs: cost matrix
p: number of hubs
alpha: inter-hub discount
Returns:
heuristic solution
"""
n = len(flows)
hubs = []
allocations = {}
def calculate_cost(current_hubs):
"""Calculate total cost for given hub set"""
if not current_hubs:
return float('inf')
# Allocate each node to nearest hub
node_allocations = {}
for i in range(n):
nearest_hub = min(current_hubs, key=lambda k: costs[i][k])
node_allocations[i] = nearest_hub
# Calculate total cost
total_cost = 0
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
hub_i = node_allocations[i]
hub_j = node_allocations[j]
cost = (costs[i][hub_i] +
alpha * costs[hub_i][hub_j] +
costs[hub_j][j])
total_cost += flows[i][j] * cost
return total_cost, node_allocations
# Greedily select p hubs
for iteration in range(p):
best_hub = None
best_cost = float('inf')
best_allocations = None
# Try adding each remaining node as a hub
for k in range(n):
if k in hubs:
continue
test_hubs = hubs + [k]
cost, allocations_test = calculate_cost(test_hubs)
if cost < best_cost:
best_cost = cost
best_hub = k
best_allocations = allocations_test
if best_hub is not None:
hubs.append(best_hub)
allocations = best_allocations
return {
'hubs': hubs,
'allocations': allocations,
'total_cost': best_cost,
'method': 'Greedy Hub Selection'
}
2. 集中度ベースのヒューリスティック
def concentration_heuristic(flows, costs, p, alpha=0.75):
"""
Concentration-based heuristic for hub location
Select nodes with highest total flow (originating + terminating)
Args:
flows: flow matrix
costs: cost matrix
p: number of hubs
alpha: inter-hub discount
Returns:
heuristic solution
"""
n = len(flows)
# Calculate total flow for each node
node_flows = []
for i in range(n):
total_flow = flows[i, :].sum() + flows[:, i].sum()
node_flows.append((total_flow, i))
# Sort by flow (descending)
node_flows.sort(reverse=True)
# Select top p nodes as hubs
hubs = [node for _, node in node_flows[:p]]
# Allocate each node to nearest hub
allocations = {}
for i in range(n):
nearest_hub = min(hubs, key=lambda k: costs[i][k])
allocations[i] = nearest_hub
# Calculate total cost
total_cost = 0
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
hub_i = allocations[i]
hub_j = allocations[j]
cost = (costs[i][hub_i] +
alpha * costs[hub_i][hub_j] +
costs[hub_j][j])
total_cost += flows[i][j] * cost
return {
'hubs': hubs,
'allocations': allocations,
'total_cost': total_cost,
'method': 'Concentration Heuristic'
}
局所探索による改善
1. ハブ交換局所探索
def hub_swap_local_search(flows, costs, initial_hubs, alpha=0.75,
max_iterations=100):
"""
Local search with hub swap moves
Swap a hub with a non-hub if it improves objective
Args:
flows: flow matrix
costs: cost matrix
initial_hubs: initial hub locations
alpha: inter-hub discount
max_iterations: maximum iterations
Returns:
improved solution
"""
n = len(flows)
current_hubs = set(initial_hubs)
non_hubs = set(range(n)) - current_hubs
def evaluate_solution(hub_set):
"""Evaluate cost for given hub configuration"""
# Allocate nodes
allocations = {}
for i in range(n):
allocations[i] = min(hub_set, key=lambda k: costs[i][k])
# Calculate cost
total_cost = 0
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
hub_i = allocations[i]
hub_j = allocations[j]
cost = (costs[i][hub_i] +
alpha * costs[hub_i][hub_j] +
costs[hub_j][j])
total_cost += flows[i][j] * cost
return total_cost, allocations
current_cost, current_allocations = evaluate_solution(current_hubs)
best_hubs = current_hubs.copy()
best_cost = current_cost
best_allocations = current_allocations
for iteration in range(max_iterations):
improved = False
# Try all possible swaps
for hub_out in list(current_hubs):
for hub_in in non_hubs:
# Test swap
test_hubs = (current_hubs - {hub_out}) | {hub_in}
test_cost, test_allocations = evaluate_solution(test_hubs)
if test_cost < best_cost - 1e-6:
best_cost = test_cost
best_hubs = test_hubs
best_allocations = test_allocations
improved = True
break
if improved:
break
if not improved:
break
# Update current solution
current_hubs = best_hubs.copy()
non_hubs = set(range(n)) - current_hubs
return {
'hubs': list(best_hubs),
'allocations': best_allocations,
'total_cost': best_cost,
'method': 'Hub Swap Local Search'
}
メタヒューリスティック
1. ハブ配置のシミュレーテッドアニーリング
import random
import math
def simulated_annealing_hub(flows, costs, p, alpha=0.75,
initial_temp=1000, cooling_rate=0.95,
max_iterations=5000):
"""
Simulated Annealing for p-Hub Median
Args:
flows: flow matrix
costs: cost matrix
p: number of hubs
alpha: inter-hub discount
initial_temp: starting temperature
cooling_rate: cooling factor
max_iterations: max iterations
Returns:
best solution found
"""
n = len(flows)
def evaluate(hub_set):
"""Calculate cost for hub configuration"""
allocations = {}
for i in range(n):
allocations[i] = min(hub_set, key=lambda k: costs[i][k])
total_cost = 0
for i in range(n):
for j in range(n):
if i != j and flows[i][j] > 0:
hub_i = allocations[i]
hub_j = allocations[j]
cost = (costs[i][hub_i] +
alpha * costs[hub_i][hub_j] +
costs[hub_j][j])
total_cost += flows[i][j] * cost
return total_cost, allocations
# Initial solution: random hubs
current_hubs = set(random.sample(range(n), p))
current_cost, current_allocations = evaluate(current_hubs)
best_hubs = current_hubs.copy()
best_cost = current_cost
best_allocations = current_allocations
temperature = initial_temp
for iteration in range(max_iterations):
# Generate neighbor: swap one hub
non_hubs = list(set(range(n)) - current_hubs)
hub_out = random.choice(list(current_hubs))
hub_in = random.choice(non_hubs)
neighbor_hubs = (current_hubs - {hub_out}) | {hub_in}
neighbor_cost, neighbor_allocations = evaluate(neighbor_hubs)
delta = neighbor_cost - current_cost
# Accept or reject
if delta < 0 or random.random() < math.exp(-delta / temperature):
current_hubs = neighbor_hubs
current_cost = neighbor_cost
current_allocations = neighbor_allocations
if current_cost < best_cost:
best_hubs = current_hubs.copy()
best_cost = current_cost
best_allocations = current_allocations
# Cool down
temperature *= cooling_rate
if temperature < 0.1:
break
return {
'hubs': list(best_hubs),
'allocations': best_allocations,
'total_cost': best_cost,
'method': 'Simulated Annealing'
}
ハブ配置ソルバー一式
class HubLocationSolver:
"""
Comprehensive Hub Location Problem Solver
Supports single/multiple allocation, various solution methods
"""
def __init__(self):
self.flows = None
self.costs = None
self.coords = None
self.n = None
def load_problem(self, flows, costs, coords=None):
"""
Load problem data
Args:
flows: n x n flow matrix
costs: n x n cost matrix
coords: optional node coordinates for visualization
"""
self.flows = np.array(flows)
self.costs = np.array(costs)
self.coords = np.array(coords) if coords is not None else None
self.n = len(flows)
print(f"Loaded hub location problem:")
print(f" Nodes: {self.n}")
print(f" Total O-D flow: {self.flows.sum():.2f}")
print(f" Average cost: {self.costs.mean():.2f}")
def solve_exact_sa(self, p, alpha=0.75, time_limit=600):
"""Solve single allocation with exact MIP"""
print(f"\nSolving Single Allocation p-Hub Median (exact)...")
return solve_single_allocation_phub(self.flows, self.costs, p, alpha)
def solve_exact_ma(self, p, alpha=0.75, time_limit=600):
"""Solve multiple allocation with exact MIP"""
print(f"\nSolving Multiple Allocation p-Hub Median (exact)...")
return solve_multiple_allocation_phub(self.flows, self.costs, p, alpha)
def solve_heuristic(self, method, p, alpha=0.75):
"""
Solve with heuristic method
Args:
method: 'greedy', 'concentration', 'sa' (simulated annealing)
p: number of hubs
alpha: inter-hub discount
Returns:
heuristic solution
"""
print(f"\nSolving with {method} heuristic...")
if method == 'greedy':
return greedy_hub_selection(self.flows, self.costs, p, alpha)
elif method == 'concentration':
return concentration_heuristic(self.flows, self.costs, p, alpha)
elif method == 'sa':
return simulated_annealing_hub(self.flows, self.costs, p, alpha)
else:
raise ValueError(f"Unknown method: {method}")
def compare_methods(self, p, alpha=0.75,
methods=['greedy', 'concentration', 'sa', 'exact_sa']):
"""
Compare multiple solution methods
Args:
p: number of hubs
alpha: inter-hub discount
methods: list of methods to compare
Returns:
comparison dataframe
"""
import pandas as pd
import time
results = []
for method in methods:
start_time = time.time()
try:
if method == 'exact_sa':
solution = self.solve_exact_sa(p, alpha)
elif method == 'exact_ma':
solution = self.solve_exact_ma(p, alpha)
else:
solution = self.solve_heuristic(method, p, alpha)
solve_time = time.time() - start_time
results.append({
'Method': method,
'Total Cost': solution['total_cost'],
'Hubs': str(solution['hubs']),
'Time (s)': f"{solve_time:.3f}"
})
except Exception as e:
print(f" Error with {method}: {e}")
df = pd.DataFrame(results)
# Calculate gap from best
if len(df) > 0:
best_cost = df['Total Cost'].min()
df['Gap %'] = ((df['Total Cost'] - best_cost) / best_cost * 100).round(2)
return df
def visualize_hub_network(self, solution, title="Hub Network"):
"""
Visualize hub-and-spoke network
Args:
solution: solution dictionary with hubs and allocations
title: plot title
"""
import matplotlib.pyplot as plt
if self.coords is None:
print("No coordinates available for visualization")
return
plt.figure(figsize=(12, 8))
hubs = solution['hubs']
allocations = solution['allocations']
# Plot non-hub nodes (small blue circles)
for i in range(self.n):
if i not in hubs:
plt.plot(self.coords[i, 0], self.coords[i, 1],
'o', color='lightblue', markersize=8)
# Plot connections (spoke lines)
for node, hub in allocations.items():
if node not in hubs:
plt.plot([self.coords[node, 0], self.coords[hub, 0]],
[self.coords[node, 1], self.coords[hub, 1]],
'k-', alpha=0.2, linewidth=0.5)
# Plot inter-hub connections (thick red lines)
for i, hub_i in enumerate(hubs):
for hub_j in hubs[i+1:]:
plt.plot([self.coords[hub_i, 0], self.coords[hub_j, 0]],
[self.coords[hub_i, 1], self.coords[hub_j, 1]],
'r-', alpha=0.6, linewidth=2)
# Plot hub nodes (large red squares)
for hub in hubs:
plt.plot(self.coords[hub, 0], self.coords[hub, 1],
's', color='red', markersize=15, label='Hub' if hub == hubs[0] else '')
plt.xlabel('X Coordinate')
plt.ylabel('Y Coordinate')
plt.title(title)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Complete example
if __name__ == "__main__":
print("="*70)
print("HUB LOCATION PROBLEM - COMPREHENSIVE EXAMPLE")
print("="*70)
# Generate problem data
np.random.seed(42)
n = 15
# Node coordinates
coords = np.random.rand(n, 2) * 100
# Cost matrix (Euclidean distances)
costs = np.zeros((n, n))
for i in range(n):
for j in range(n):
costs[i][j] = np.linalg.norm(coords[i] - coords[j])
# Flow matrix (random demands)
flows = np.random.uniform(10, 100, (n, n))
np.fill_diagonal(flows, 0)
# Parameters
p = 3 # Number of hubs
alpha = 0.75 # Inter-hub discount (25% discount on hub-to-hub travel)
# Create solver
solver = HubLocationSolver()
solver.load_problem(flows, costs, coords)
# Compare methods
print(f"\n{'='*70}")
print(f"COMPARING SOLUTION METHODS (p={p}, α={alpha})")
print(f"{'='*70}")
comparison = solver.compare_methods(p, alpha,
methods=['greedy', 'concentration', 'sa'])
print("\n" + comparison.to_string(index=False))
# Solve with best method and visualize
print(f"\n{'='*70}")
print(f"DETAILED SOLUTION")
print(f"{'='*70}")
solution = solver.solve_heuristic('greedy', p, alpha)
print(f"\nHubs Located: {solution['hubs']}")
print(f"Total Cost: {solution['total_cost']:,.2f}")
print(f"\nNode Allocations:")
for node, hub in solution['allocations'].items():
hub_indicator = " (HUB)" if node in solution['hubs'] else ""
distance = costs[node][hub]
print(f" Node {node} → Hub {hub}{hub_indicator} (distance={distance:.2f})")
# Visualize
solver.visualize_hub_network(solution,
title=f"Hub Network: {solution['method']} (p={p}, α={alpha})")
ツールとライブラリ
Python ライブラリ
最適化:
- PuLP:ハブ配置の MIP モデリング
- Pyomo:高度な最適化
- OR-Tools:Google の最適化ツール
- Gurobi/CPLEX:商用ソルバー
ネットワーク分析:
- NetworkX:グラフアルゴリズムと可視化
- igraph:高速なネットワーク分析
可視化:
- matplotlib:基本的なプロット
- plotly:インタラクティブなネットワーク可視化
- folium:地理マップ
専用ツール
- HubLocator:学術向けハブ配置ソフトウェア
- SITATION:ネットワーク設計ソフトウェア
よくある課題と解決策
課題:問題の規模
問題:
- 大規模なネットワーク(ノード数 100 以上)
- 厳密解法では時間がかかりすぎる
- O-D ペアが多数ある
解決策:
- ヒューリスティック(貪欲法、集中度ベース)を使用する
- メタヒューリスティック(SA、GA、タブーサーチ)を使用する
- 分解アプローチ
- ラグランジュ緩和
- 列生成
課題:複数割り当ての複雑さ
問題:
- MA-pHMP は SA-pHMP より多くの変数を持つ
- 最適解を求めるのがより難しい
- より良い解が得られる一方、計算コストが高い
解決策:
- SA-pHMP を近似として使用する
- 制限付き複数割り当て(ノードあたりの接続数を制限する)
- SA の解から開始し、MA に精緻化する
- 商用ソルバーで時間制限を設定する
課題:ハブ数(p)の決定
問題:
- 最適な p がわからない
- ハブコストとルーティング効率の間にトレードオフがある
解決策:
- 複数の p の値について解く
- コスト対 p の曲線をプロットする
- 固定費を明示的に考慮する
- 感度分析を行う
- 容量制約なしのハブ配置を使用する(固定の p なし)
課題:需要の不確実性
問題:
- フローのパターンが不確実、または時間とともに変化する
- ハブの配置は長期的な戦略的決定である
解決策:
- ロバスト最適化
- 確率計画法
- シナリオベースの分析
- 柔軟なモジュール式のハブ設計
- 複数期間モデル
出力形式
ハブ配置の解レポート
問題インスタンス:
- ネットワーク:25 ノード
- 配置するハブ数:4
- ハブ間の割引:α = 0.75(25% 割引)
- 割り当て:単一割り当て
- O-D フローの合計:12,450 単位
最適解:
| 指標 | 値 |
|---|---|
| 総コスト | $1,247,385 |
| 解法 | MIP 最適 |
| 配置されたハブ | {5, 12, 18, 23} |
| 解法時間 | 45.3 秒 |
| ステータス | 最適 |
ハブの詳細:
| ハブ ID | 場所 | 割り当てられたノード数 | 総流入量 | 総流出量 |
|---|---|---|---|---|
| 5 | (34.5, 67.2) | 7 | 3,240 | 3,180 |
| 12 | (78.3, 23.8) | 6 | 2,890 | 2,950 |
| 18 | (12.7, 89.1) | 5 | 2,450 | 2,520 |
| 23 | (56.4, 45.3) | 7 | 3,870 | 3,800 |
コストの内訳:
- 集荷コスト(ノード → ハブ):$412,450(33.1%)
- ハブ間輸送:$298,120(23.9%)
- 配送コスト(ハブ → ノード):$536,815(43.0%)
ネットワーク統計:
- 最寄りのハブまでの平均距離:15.3 km
- ハブまでの最大距離:28.7 km
- ハブ間の平均距離:52.4 km
確認すべき質問
- ネットワークの種類は何ですか?(貨物、旅客、郵便、通信)
- ネットワーク内のノード数はいくつですか?
- O-D フローデータはありますか?
- 配置するハブはいくつですか?(または、最適化しますか?)
- 単一割り当てですか、それとも複数割り当てですか?
- ハブ間の割引係数(α)はいくつですか?
- ハブに容量制約はありますか?
- ハブの設置にかかる固定費はいくらですか?
- 座標、または距離/コスト行列はありますか?
- フローは対称ですか、それとも方向性がありますか?
- 時間に関する制約はありますか?
- 戦略的な計画(長期)ですか、それとも戦術的な計画ですか?
関連スキル
- facility-location-problem:一般的な施設立地
- distribution-center-network:配送センターネットワークの設計
- warehouse-location-optimization:倉庫の立地
- network-design:一般的なサプライチェーンネットワーク
- network-flow-optimization:フローの最適化
- set-covering-problem:カバレッジベースの立地
- vehicle-routing-problem:ハブからのラストマイル配送ルート
- optimization-modeling:MIP 定式化の手法
- multi-objective-optimization:多基準ハブ立地
コメント
Markdown 対応