first upload
This commit is contained in:
+460
@@ -0,0 +1,460 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from collections import Counter
|
||||
import math
|
||||
import openjij as oj
|
||||
from itertools import permutations
|
||||
|
||||
# ============================
|
||||
# 預設距離矩陣:10-city TSP
|
||||
# ============================
|
||||
D = np.array([
|
||||
[0, 2, 8, 5, 7, 6, 3, 9, 4, 2],
|
||||
[2, 0, 5, 7, 3, 8, 4, 6, 9, 1],
|
||||
[8, 5, 0, 2, 6, 7, 3, 4, 1, 5],
|
||||
[5, 7, 2, 0, 4, 3, 8, 6, 2, 7],
|
||||
[7, 3, 6, 4, 0, 5, 9, 2, 7, 3],
|
||||
[6, 8, 7, 3, 5, 0, 4, 7, 9, 6],
|
||||
[3, 4, 3, 8, 9, 4, 0, 5, 6, 2],
|
||||
[9, 6, 4, 6, 2, 7, 5, 0, 8, 3],
|
||||
[4, 9, 1, 2, 7, 9, 6, 8, 0, 5],
|
||||
[2, 1, 5, 7, 3, 6, 2, 3, 5, 0]
|
||||
])
|
||||
|
||||
N = D.shape[0]
|
||||
penalty = 20
|
||||
|
||||
def idx(i, t, N):
|
||||
return i * N + t
|
||||
|
||||
# ============================
|
||||
# KDE 函數
|
||||
# ============================
|
||||
def kde_1d(samples, num_points=200):
|
||||
if len(samples) == 0:
|
||||
return np.array([]), np.array([])
|
||||
|
||||
xs = np.linspace(min(samples), max(samples), num_points)
|
||||
n = len(samples)
|
||||
if n < 2:
|
||||
return xs, np.zeros_like(xs)
|
||||
std = np.std(samples)
|
||||
if std == 0:
|
||||
std = 1.0
|
||||
h = 1.06 * std * (n ** (-1/5))
|
||||
if h == 0:
|
||||
h = 1.0
|
||||
ys = []
|
||||
inv_sqrt_2pi = 1.0 / math.sqrt(2.0 * math.pi)
|
||||
for x in xs:
|
||||
s = 0.0
|
||||
for xi in samples:
|
||||
z = (x - xi) / h
|
||||
s += math.exp(-0.5 * z * z) * inv_sqrt_2pi
|
||||
ys.append(s / (n * h))
|
||||
return xs, np.array(ys)
|
||||
|
||||
# ============================
|
||||
# Route diversity matrix 函數
|
||||
# ============================
|
||||
def route_diversity_matrix(routes, N):
|
||||
freq = np.zeros((N, N), dtype=float)
|
||||
valid_routes = 0
|
||||
for route in routes:
|
||||
if len(route) == N and all(0 <= city < N for city in route):
|
||||
for t, city in enumerate(route):
|
||||
if 0 <= city < N: # 確保city有效
|
||||
freq[city, t] += 1
|
||||
valid_routes += 1
|
||||
if valid_routes > 0:
|
||||
freq /= valid_routes
|
||||
return freq
|
||||
|
||||
# ============================
|
||||
# 建立 QUBO:位置編碼 + 固定起點 0
|
||||
# ============================
|
||||
def build_qubo(D, penalty, fix_start=True):
|
||||
N = D.shape[0]
|
||||
Q = {}
|
||||
|
||||
# 距離項
|
||||
for t in range(N):
|
||||
t2 = (t + 1) % N
|
||||
for i in range(N):
|
||||
for j in range(N):
|
||||
if i != j:
|
||||
u = idx(i, t, N)
|
||||
v = idx(j, t2, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + D[i, j]
|
||||
|
||||
# 約束A: 每個 time slot 有且只有一個城市
|
||||
for t in range(N):
|
||||
for i in range(N):
|
||||
u = idx(i, t, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) - penalty
|
||||
for j in range(i + 1, N):
|
||||
v = idx(j, t, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + 2 * penalty
|
||||
|
||||
# 約束B: 每個城市被訪問一次
|
||||
for i in range(N):
|
||||
for t in range(N):
|
||||
u = idx(i, t, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) - penalty
|
||||
for t2 in range(t + 1, N):
|
||||
v = idx(i, t2, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + 2 * penalty
|
||||
|
||||
# 固定起點:t=0 必須是 city 0
|
||||
if fix_start:
|
||||
big = 9999.0
|
||||
# 禁止 i!=0 在 t=0
|
||||
for i in range(1, N):
|
||||
u = idx(i, 0, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) + big
|
||||
# 獎勵 city 0 在 t=0
|
||||
u0 = idx(0, 0, N)
|
||||
Q[(u0, u0)] = Q.get((u0, u0), 0) - big
|
||||
|
||||
return Q, N
|
||||
|
||||
# ============================
|
||||
# Route decode & cost
|
||||
# ============================
|
||||
def decode_route(sample, N):
|
||||
route = []
|
||||
for t in range(N):
|
||||
city_for_t = None
|
||||
for i in range(N):
|
||||
if sample.get(idx(i, t, N), 0) == 1:
|
||||
city_for_t = i
|
||||
break
|
||||
if city_for_t is None:
|
||||
city_for_t = -1
|
||||
route.append(city_for_t)
|
||||
return route
|
||||
|
||||
def compute_cost(route, D):
|
||||
if -1 in route:
|
||||
return 99999 # 無效路徑懲罰
|
||||
N = len(route)
|
||||
total = 0
|
||||
for k in range(N):
|
||||
a = route[k]
|
||||
b = route[(k + 1) % N]
|
||||
total += D[a, b]
|
||||
return total
|
||||
|
||||
# ============================
|
||||
# 兩台UAV TSP分配策略
|
||||
# ============================
|
||||
def split_route_for_two_uavs(route):
|
||||
"""
|
||||
將單一TSP路徑分配給兩台UAV
|
||||
策略:交替分配城市,確保負載平衡
|
||||
"""
|
||||
if len(route) == 0:
|
||||
return [], []
|
||||
|
||||
# 移除無效城市
|
||||
valid_route = [city for city in route if city >= 0 and city < N]
|
||||
|
||||
if len(valid_route) == 0:
|
||||
return [], []
|
||||
|
||||
# 確保起點是0
|
||||
if valid_route[0] != 0:
|
||||
if 0 in valid_route:
|
||||
valid_route.remove(0)
|
||||
valid_route = [0] + valid_route
|
||||
else:
|
||||
valid_route = [0] + valid_route
|
||||
|
||||
# 策略1: 奇偶分配
|
||||
uav1_path = []
|
||||
uav2_path = []
|
||||
|
||||
for i, city in enumerate(valid_route):
|
||||
if i % 2 == 0:
|
||||
uav1_path.append(city)
|
||||
else:
|
||||
uav2_path.append(city)
|
||||
|
||||
return uav1_path, uav2_path
|
||||
|
||||
def compute_two_uav_cost(uav1_path, uav2_path, D):
|
||||
"""計算兩台UAV的總成本"""
|
||||
def path_cost(path):
|
||||
if len(path) <= 1:
|
||||
return 0
|
||||
cost = 0
|
||||
for i in range(len(path)):
|
||||
current = path[i]
|
||||
next_city = path[(i + 1) % len(path)]
|
||||
cost += D[current, next_city]
|
||||
return cost
|
||||
|
||||
cost1 = path_cost(uav1_path)
|
||||
cost2 = path_cost(uav2_path)
|
||||
return cost1 + cost2, cost1, cost2
|
||||
|
||||
# ============================
|
||||
# 經典 TSP 解法:暴力枚舉 (限制小規模)
|
||||
# ============================
|
||||
def classic_tsp_two_uav(D, max_permutations=1000):
|
||||
"""
|
||||
經典TSP求解並分配給兩台UAV
|
||||
由於10城市的排列數太大(9!=362880),限制搜索數量
|
||||
"""
|
||||
cities = list(range(1, len(D))) # 排除起點0
|
||||
best_total_cost = float('inf')
|
||||
best_route = None
|
||||
best_uav1 = None
|
||||
best_uav2 = None
|
||||
|
||||
count = 0
|
||||
for perm in permutations(cities):
|
||||
if count >= max_permutations:
|
||||
break
|
||||
count += 1
|
||||
|
||||
route = [0] + list(perm) # 固定0為起點
|
||||
|
||||
# 分配給兩台UAV
|
||||
uav1_path, uav2_path = split_route_for_two_uavs(route)
|
||||
total_cost, cost1, cost2 = compute_two_uav_cost(uav1_path, uav2_path, D)
|
||||
|
||||
if total_cost < best_total_cost:
|
||||
best_total_cost = total_cost
|
||||
best_route = route
|
||||
best_uav1 = uav1_path
|
||||
best_uav2 = uav2_path
|
||||
|
||||
return best_uav1, best_uav2, best_total_cost, best_route
|
||||
|
||||
# ============================
|
||||
# 跑 SA or SQA 多次
|
||||
# ============================
|
||||
def run_algorithm(name, sampler, Q, D, N, num_runs=20, num_reads=20):
|
||||
all_costs = []
|
||||
all_routes = []
|
||||
all_uav1_paths = []
|
||||
all_uav2_paths = []
|
||||
all_uav_costs = []
|
||||
|
||||
print(f"\n=== Running {name} ===")
|
||||
for r in range(num_runs):
|
||||
result = sampler.sample_qubo(Q, num_reads=num_reads)
|
||||
best = result.first.sample
|
||||
route = decode_route(best, N)
|
||||
cost = compute_cost(route, D)
|
||||
|
||||
# 分配給兩台UAV
|
||||
uav1_path, uav2_path = split_route_for_two_uavs(route)
|
||||
total_uav_cost, cost1, cost2 = compute_two_uav_cost(uav1_path, uav2_path, D)
|
||||
|
||||
all_costs.append(cost)
|
||||
all_routes.append(tuple(route))
|
||||
all_uav1_paths.append(tuple(uav1_path))
|
||||
all_uav2_paths.append(tuple(uav2_path))
|
||||
all_uav_costs.append(total_uav_cost)
|
||||
|
||||
print(f"{name} Run {r+1:02d}: Route={route}")
|
||||
print(f" TSP Cost={cost}, UAV1={uav1_path}(cost={cost1}), UAV2={uav2_path}(cost={cost2}), Total={total_uav_cost}")
|
||||
|
||||
return all_costs, all_routes, all_uav1_paths, all_uav2_paths, all_uav_costs
|
||||
|
||||
# ============================
|
||||
# 創建綜合結果圖表
|
||||
# ============================
|
||||
def create_comprehensive_charts(sa_results, sqa_results, classic_results):
|
||||
"""創建綜合分析圖表"""
|
||||
fig = plt.figure(figsize=(18, 12))
|
||||
|
||||
# 解包結果
|
||||
sa_costs, sa_routes, sa_uav1, sa_uav2, sa_uav_costs = sa_results
|
||||
sqa_costs, sqa_routes, sqa_uav1, sqa_uav2, sqa_uav_costs = sqa_results
|
||||
classic_uav1, classic_uav2, classic_cost, classic_route = classic_results
|
||||
|
||||
# 1. TSP成本分布比較 (左上)
|
||||
ax1 = plt.subplot(2, 3, 1)
|
||||
bins = range(min(sa_costs + sqa_costs), max(sa_costs + sqa_costs) + 2)
|
||||
ax1.hist(sa_costs, bins=bins, alpha=0.6, label="SA", density=True, color='lightblue')
|
||||
ax1.hist(sqa_costs, bins=bins, alpha=0.6, label="SQA", density=True, color='lightcoral')
|
||||
|
||||
# 添加KDE
|
||||
if len(sa_costs) > 1:
|
||||
xs_sa, ys_sa = kde_1d(sa_costs)
|
||||
ax1.plot(xs_sa, ys_sa, label="SA KDE", color='blue', linewidth=2)
|
||||
if len(sqa_costs) > 1:
|
||||
xs_sqa, ys_sqa = kde_1d(sqa_costs)
|
||||
ax1.plot(xs_sqa, ys_sqa, label="SQA KDE", color='red', linewidth=2)
|
||||
|
||||
ax1.axvline(x=classic_cost, color='green', linestyle='--',
|
||||
label=f'Classic: {classic_cost}', linewidth=2)
|
||||
ax1.set_title("TSP Cost Distribution")
|
||||
ax1.set_xlabel("Tour Cost")
|
||||
ax1.set_ylabel("Density")
|
||||
ax1.legend()
|
||||
ax1.grid(True, alpha=0.3)
|
||||
|
||||
# 2. UAV總成本分布比較 (右上)
|
||||
ax2 = plt.subplot(2, 3, 2)
|
||||
uav_bins = range(min(sa_uav_costs + sqa_uav_costs), max(sa_uav_costs + sqa_uav_costs) + 2)
|
||||
ax2.hist(sa_uav_costs, bins=uav_bins, alpha=0.6, label="SA UAV", density=True, color='lightblue')
|
||||
ax2.hist(sqa_uav_costs, bins=uav_bins, alpha=0.6, label="SQA UAV", density=True, color='lightcoral')
|
||||
|
||||
classic_uav_total, _, _ = compute_two_uav_cost(classic_uav1, classic_uav2, D)
|
||||
ax2.axvline(x=classic_uav_total, color='green', linestyle='--',
|
||||
label=f'Classic UAV: {classic_uav_total}', linewidth=2)
|
||||
ax2.set_title("Two-UAV Total Cost Distribution")
|
||||
ax2.set_xlabel("Total UAV Cost")
|
||||
ax2.set_ylabel("Density")
|
||||
ax2.legend()
|
||||
ax2.grid(True, alpha=0.3)
|
||||
|
||||
# 3. 箱型圖比較 (中上)
|
||||
ax3 = plt.subplot(2, 3, 3)
|
||||
box_data = [sa_costs, sqa_costs, sa_uav_costs, sqa_uav_costs]
|
||||
bp = ax3.boxplot(box_data, labels=["SA\nTSP", "SQA\nTSP", "SA\nUAV", "SQA\nUAV"],
|
||||
patch_artist=True, showmeans=True)
|
||||
|
||||
colors = ['lightblue', 'lightcoral', 'skyblue', 'salmon']
|
||||
for patch, color in zip(bp['boxes'], colors):
|
||||
patch.set_facecolor(color)
|
||||
|
||||
ax3.set_title("Cost Comparison Box Plot")
|
||||
ax3.set_ylabel("Cost")
|
||||
ax3.grid(True, alpha=0.3)
|
||||
|
||||
# 4. Route diversity heatmap - SA (左下)
|
||||
ax4 = plt.subplot(2, 3, 4)
|
||||
sa_mat = route_diversity_matrix(sa_routes, N)
|
||||
im1 = ax4.imshow(sa_mat, aspect='auto', origin='lower', cmap='viridis')
|
||||
ax4.set_title("SA Route Diversity")
|
||||
ax4.set_xlabel("Position")
|
||||
ax4.set_ylabel("City")
|
||||
plt.colorbar(im1, ax=ax4, shrink=0.8)
|
||||
|
||||
# 5. Route diversity heatmap - SQA (右下)
|
||||
ax5 = plt.subplot(2, 3, 5)
|
||||
sqa_mat = route_diversity_matrix(sqa_routes, N)
|
||||
im2 = ax5.imshow(sqa_mat, aspect='auto', origin='lower', cmap='viridis')
|
||||
ax5.set_title("SQA Route Diversity")
|
||||
ax5.set_xlabel("Position")
|
||||
ax5.set_ylabel("City")
|
||||
plt.colorbar(im2, ax=ax5, shrink=0.8)
|
||||
|
||||
# 6. 最佳路徑可視化 (中下)
|
||||
ax6 = plt.subplot(2, 3, 6)
|
||||
|
||||
# 找出最佳量子解
|
||||
best_sa_idx = sa_costs.index(min(sa_costs))
|
||||
best_sqa_idx = sqa_costs.index(min(sqa_costs))
|
||||
best_sa_route = list(sa_routes[best_sa_idx])
|
||||
best_sqa_route = list(sqa_routes[best_sqa_idx])
|
||||
|
||||
# 簡單的圓形佈局
|
||||
angles = np.linspace(0, 2*np.pi, N, endpoint=False)
|
||||
x_pos = np.cos(angles)
|
||||
y_pos = np.sin(angles)
|
||||
|
||||
# 繪製城市
|
||||
ax6.scatter(x_pos, y_pos, s=200, c='red', zorder=5)
|
||||
for i, (x, y) in enumerate(zip(x_pos, y_pos)):
|
||||
ax6.annotate(str(i), (x, y), ha='center', va='center',
|
||||
fontsize=10, fontweight='bold', color='white', zorder=6)
|
||||
|
||||
# 選擇更好的路徑來顯示
|
||||
if min(sa_costs) <= min(sqa_costs):
|
||||
display_route = best_sa_route
|
||||
route_name = "SA"
|
||||
route_cost = min(sa_costs)
|
||||
else:
|
||||
display_route = best_sqa_route
|
||||
route_name = "SQA"
|
||||
route_cost = min(sqa_costs)
|
||||
|
||||
# 繪製路徑
|
||||
if all(0 <= city < N for city in display_route):
|
||||
route_x = [x_pos[city] for city in display_route] + [x_pos[display_route[0]]]
|
||||
route_y = [y_pos[city] for city in display_route] + [y_pos[display_route[0]]]
|
||||
ax6.plot(route_x, route_y, 'b-', linewidth=2, alpha=0.8, zorder=3)
|
||||
|
||||
ax6.set_title(f"Best {route_name} Route\nCost: {route_cost}")
|
||||
ax6.set_xlim(-1.3, 1.3)
|
||||
ax6.set_ylim(-1.3, 1.3)
|
||||
ax6.set_aspect('equal')
|
||||
ax6.axis('off')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig("tsp_two_uav_comprehensive_analysis.png", dpi=300, bbox_inches='tight')
|
||||
print(f"\n📊 綜合分析圖表已保存: tsp_two_uav_comprehensive_analysis.png")
|
||||
plt.show()
|
||||
|
||||
# ============================
|
||||
# Main
|
||||
# ============================
|
||||
if __name__ == "__main__":
|
||||
print("🚀 開始10城市兩台UAV TSP分析...")
|
||||
print(f"Distance Matrix ({N}x{N}):")
|
||||
print(D)
|
||||
|
||||
Q, N = build_qubo(D, penalty, fix_start=True)
|
||||
|
||||
# Samplers
|
||||
sqa_sampler = oj.SQASampler()
|
||||
sa_sampler = oj.SASampler()
|
||||
|
||||
# 參數設定
|
||||
NUM_RUNS = 15
|
||||
NUM_READS = 20
|
||||
|
||||
# 運行量子退火算法
|
||||
print("🔥 Running Quantum Annealing Algorithms...")
|
||||
sa_results = run_algorithm("SA", sa_sampler, Q, D, N, NUM_RUNS, NUM_READS)
|
||||
sqa_results = run_algorithm("SQA", sqa_sampler, Q, D, N, NUM_RUNS, NUM_READS)
|
||||
|
||||
# 運行經典TSP解法
|
||||
print("\n🎯 Running Classic TSP (limited search)...")
|
||||
classic_results = classic_tsp_two_uav(D, max_permutations=5000)
|
||||
classic_uav1, classic_uav2, classic_total_cost, classic_route = classic_results
|
||||
|
||||
print(f"Classic TSP Results:")
|
||||
print(f" Best Route: {classic_route}")
|
||||
print(f" UAV1 Path: {classic_uav1}")
|
||||
print(f" UAV2 Path: {classic_uav2}")
|
||||
print(f" Total Cost: {classic_total_cost}")
|
||||
|
||||
# 統計分析
|
||||
sa_costs, sa_routes, sa_uav1, sa_uav2, sa_uav_costs = sa_results
|
||||
sqa_costs, sqa_routes, sqa_uav1, sqa_uav2, sqa_uav_costs = sqa_results
|
||||
|
||||
print(f"\n📊 Algorithm Performance Summary:")
|
||||
print(f"{'Algorithm':<15} {'TSP Cost':<20} {'UAV Total Cost':<20}")
|
||||
print(f"{'-'*55}")
|
||||
print(f"{'SA':<15} min={min(sa_costs):<3} mean={np.mean(sa_costs):<6.1f} "
|
||||
f"min={min(sa_uav_costs):<3} mean={np.mean(sa_uav_costs):<6.1f}")
|
||||
print(f"{'SQA':<15} min={min(sqa_costs):<3} mean={np.mean(sqa_costs):<6.1f} "
|
||||
f"min={min(sqa_uav_costs):<3} mean={np.mean(sqa_uav_costs):<6.1f}")
|
||||
print(f"{'Classic':<15} {classic_total_cost:<23} {classic_total_cost:<20}")
|
||||
|
||||
# 創建綜合圖表
|
||||
print("\n🎨 Creating comprehensive analysis charts...")
|
||||
create_comprehensive_charts(sa_results, sqa_results, classic_results)
|
||||
|
||||
# 最終比較
|
||||
best_sa_uav = min(sa_uav_costs)
|
||||
best_sqa_uav = min(sqa_uav_costs)
|
||||
|
||||
print(f"\n🏆 Final Two-UAV TSP Results:")
|
||||
print(f"SA best UAV cost: {best_sa_uav}")
|
||||
print(f"SQA best UAV cost: {best_sqa_uav}")
|
||||
print(f"Classic UAV cost: {classic_total_cost}")
|
||||
|
||||
if best_sa_uav <= best_sqa_uav and best_sa_uav <= classic_total_cost:
|
||||
print("🥇 Winner: SA")
|
||||
elif best_sqa_uav <= best_sa_uav and best_sqa_uav <= classic_total_cost:
|
||||
print("🥇 Winner: SQA")
|
||||
else:
|
||||
print("🥇 Winner: Classic Algorithm")
|
||||
+587
@@ -0,0 +1,587 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from itertools import permutations
|
||||
import math
|
||||
import openjij as oj
|
||||
import random
|
||||
|
||||
# ============================
|
||||
# 1. KDE 函數
|
||||
# ============================
|
||||
def kde_1d(samples, num_points=200):
|
||||
if len(samples) == 0:
|
||||
return np.array([]), np.array([])
|
||||
|
||||
xs = np.linspace(min(samples), max(samples), num_points)
|
||||
n = len(samples)
|
||||
if n < 2:
|
||||
return xs, np.zeros_like(xs)
|
||||
|
||||
std = np.std(samples)
|
||||
if std == 0:
|
||||
std = 1.0
|
||||
|
||||
h = 1.06 * std * (n ** (-1/5))
|
||||
ys = []
|
||||
inv_sqrt_2pi = 1.0 / math.sqrt(2 * math.pi)
|
||||
|
||||
for x in xs:
|
||||
s = 0.0
|
||||
for xi in samples:
|
||||
z = (x - xi) / h
|
||||
s += math.exp(-0.5 * z * z) * inv_sqrt_2pi
|
||||
ys.append(s / (n * h))
|
||||
|
||||
return xs, np.array(ys)
|
||||
|
||||
|
||||
# ============================
|
||||
# 2. route diversity matrix(支援可變長度路徑)
|
||||
# ============================
|
||||
def route_diversity_matrix(routes, N):
|
||||
"""
|
||||
routes: list of routes,每條 route 是城市序列,例如 [0, 2, 5, 1, 9]
|
||||
我們只用前 N 個位置來統計(超過 N 的部分忽略)
|
||||
"""
|
||||
freq = np.zeros((N, N), dtype=float)
|
||||
valid_routes = 0
|
||||
|
||||
for route in routes:
|
||||
if len(route) == 0:
|
||||
continue
|
||||
valid_routes += 1
|
||||
for t in range(min(len(route), N)):
|
||||
city = route[t]
|
||||
if 0 <= city < N:
|
||||
freq[city, t] += 1
|
||||
|
||||
if valid_routes > 0:
|
||||
freq /= valid_routes
|
||||
|
||||
return freq
|
||||
|
||||
|
||||
# ============================
|
||||
# 3. MTSP 變數編碼:x[k,i,p] → index
|
||||
# ============================
|
||||
def idx_mtsp(k, i, p, N):
|
||||
""" x[k,i,p] → 1D index """
|
||||
return k * (N * N) + i * N + p
|
||||
|
||||
|
||||
# ============================
|
||||
# 4. 建立 2-UAV M-TSP QUBO(城市數不固定)
|
||||
# ============================
|
||||
def build_mtsp_qubo_variable(D, penalty=20.0, big_penalty=9999.0):
|
||||
"""
|
||||
- 城市: 0..N-1,其中 0 為 depot
|
||||
- 兩台 UAV: k = 0,1
|
||||
- 每台 UAV 有 N 個 slot: p = 0..N-1
|
||||
- 變數: x_{k,i,p}
|
||||
QUBO包含:
|
||||
距離成本 + 位置約束 + 城市約束 + 起點約束
|
||||
"""
|
||||
D = np.asarray(D)
|
||||
N = D.shape[0]
|
||||
Q = {}
|
||||
|
||||
def addQ(u, v, w):
|
||||
if w == 0:
|
||||
return
|
||||
if u > v:
|
||||
u, v = v, u
|
||||
Q[(u, v)] = Q.get((u, v), 0.0) + w
|
||||
|
||||
# ---- 距離成本 ----
|
||||
for k in range(2):
|
||||
for p in range(N):
|
||||
q = (p + 1) % N
|
||||
for i in range(N):
|
||||
for j in range(N):
|
||||
dij = D[i, j]
|
||||
if dij == 0:
|
||||
continue
|
||||
u = idx_mtsp(k, i, p, N)
|
||||
v = idx_mtsp(k, j, q, N)
|
||||
addQ(u, v, dij)
|
||||
|
||||
# ---- 位置約束:每 slot 只能一個城市 ----
|
||||
for k in range(2):
|
||||
for p in range(N):
|
||||
vars_pos = [idx_mtsp(k, i, p, N) for i in range(N)]
|
||||
|
||||
for u in vars_pos:
|
||||
addQ(u, u, -penalty)
|
||||
|
||||
for a in range(N):
|
||||
for b in range(a+1, N):
|
||||
addQ(vars_pos[a], vars_pos[b], 2*penalty)
|
||||
|
||||
# ---- 城市約束:1..N-1 每城恰好一次 ----
|
||||
for i in range(1, N):
|
||||
vars_city = []
|
||||
for k in range(2):
|
||||
for p in range(N):
|
||||
vars_city.append(idx_mtsp(k, i, p, N))
|
||||
|
||||
for u in vars_city:
|
||||
addQ(u, u, -penalty)
|
||||
|
||||
L = len(vars_city)
|
||||
for a in range(L):
|
||||
for b in range(a+1, L):
|
||||
addQ(vars_city[a], vars_city[b], 2*penalty)
|
||||
|
||||
# ---- 起點約束:每台 UAV 的 p=0 必須是 city 0 ----
|
||||
for k in range(2):
|
||||
for i in range(1, N):
|
||||
addQ(idx_mtsp(k, i, 0, N), idx_mtsp(k, i, 0, N), big_penalty)
|
||||
addQ(idx_mtsp(k, 0, 0, N), idx_mtsp(k, 0, 0, N), -big_penalty)
|
||||
|
||||
return Q, N
|
||||
|
||||
|
||||
# ============================
|
||||
# 5. 解碼 slot 序列(不修剪)
|
||||
# ============================
|
||||
def decode_slots(sample, N):
|
||||
"""
|
||||
回傳:
|
||||
u1_slots, u2_slots
|
||||
各自長度 N,裡面是城市 index(可能重複,也可能很多 0)
|
||||
"""
|
||||
slots = []
|
||||
for k in range(2):
|
||||
row = []
|
||||
for p in range(N):
|
||||
chosen = 0 # 預設當作待在 depot
|
||||
for i in range(N):
|
||||
if sample.get(idx_mtsp(k, i, p, N), 0) == 1:
|
||||
chosen = i
|
||||
break
|
||||
row.append(chosen)
|
||||
slots.append(row)
|
||||
return slots[0], slots[1]
|
||||
|
||||
|
||||
# ============================
|
||||
# 6. 根據 slots 做 repair → 產生合法 uav1 / uav2 path
|
||||
# ============================
|
||||
def repair_routes_from_slots(u1_slots, u2_slots, N):
|
||||
"""
|
||||
依據 slots 傾向資訊,把城市分配給兩台 UAV,
|
||||
並用暴力 TSP 在各自子集合裡找最短路徑,確保:
|
||||
- 每個城市 1..N-1 被恰好一台 UAV 拜訪
|
||||
- 兩台 UAV 路徑皆為 0 -> ... -> 0(成本計算時處理)
|
||||
"""
|
||||
|
||||
# 統計每個城市在兩台 UAV slots 中出現次數
|
||||
count1 = [0]*N
|
||||
count2 = [0]*N
|
||||
for c in u1_slots:
|
||||
if 0 <= c < N:
|
||||
count1[c] += 1
|
||||
for c in u2_slots:
|
||||
if 0 <= c < N:
|
||||
count2[c] += 1
|
||||
|
||||
# 分配城市:看誰出現次數多,平手則讓城市較少的一邊拿
|
||||
assign1 = []
|
||||
assign2 = []
|
||||
for city in range(1, N): # city 0 = depot 不分配
|
||||
if count1[city] > count2[city]:
|
||||
assign1.append(city)
|
||||
elif count2[city] > count1[city]:
|
||||
assign2.append(city)
|
||||
else:
|
||||
# 平手 → 給目前負載較少的 UAV
|
||||
if len(assign1) <= len(assign2):
|
||||
assign1.append(city)
|
||||
else:
|
||||
assign2.append(city)
|
||||
|
||||
return sorted(assign1), sorted(assign2)
|
||||
|
||||
|
||||
# ============================
|
||||
# 7. 單 UAV 成本 & 子集合最佳排列
|
||||
# ============================
|
||||
def uav_cost(path, D):
|
||||
if not path:
|
||||
return 0.0
|
||||
path = [c for c in path if 0 <= c < len(D)]
|
||||
if len(path) == 0:
|
||||
return 0.0
|
||||
|
||||
cost = D[0, path[0]]
|
||||
for i in range(len(path)-1):
|
||||
cost += D[path[i], path[i+1]]
|
||||
cost += D[path[-1], 0]
|
||||
return float(cost)
|
||||
|
||||
|
||||
def best_order_for_cities(cities, D):
|
||||
"""
|
||||
對給定 city set,暴力搜尋最短 0 -> perm -> 0 的路徑
|
||||
cities: list of city index (1..N-1)
|
||||
"""
|
||||
if len(cities) <= 1:
|
||||
return list(cities), uav_cost(list(cities), D)
|
||||
|
||||
best_perm = None
|
||||
best_cost = float('inf')
|
||||
for perm in permutations(cities):
|
||||
c = uav_cost(list(perm), D)
|
||||
if c < best_cost:
|
||||
best_cost = c
|
||||
best_perm = list(perm)
|
||||
return best_perm, best_cost
|
||||
|
||||
|
||||
# ============================
|
||||
# 8. 利用 QUBO 取樣 + repair 得到合法解
|
||||
# ============================
|
||||
def sample_and_repair(sampler, Q, N, D):
|
||||
"""
|
||||
一次取樣 + repair,即可保證得到合法 2-UAV M-TSP 解。
|
||||
不需要反覆重試。
|
||||
"""
|
||||
result = sampler.sample_qubo(Q, num_reads=30)
|
||||
sample = result.first.sample
|
||||
|
||||
# 1) 先解出 slots
|
||||
u1_slots, u2_slots = decode_slots(sample, N)
|
||||
|
||||
# 2) 依 slots 傾向分配城市給兩台 UAV
|
||||
assign1, assign2 = repair_routes_from_slots(u1_slots, u2_slots, N)
|
||||
|
||||
# 3) 各自子集合內找最短路徑
|
||||
u1_path, c1 = best_order_for_cities(assign1, D)
|
||||
u2_path, c2 = best_order_for_cities(assign2, D)
|
||||
|
||||
total = c1 + c2
|
||||
|
||||
return u1_path, u2_path, c1, c2, total
|
||||
|
||||
|
||||
|
||||
# ============================
|
||||
# 9. Classic 2-UAV MTSP 暴力搜尋(全域最佳)
|
||||
# ============================
|
||||
def classic_mtsp_two_uav_bruteforce(D):
|
||||
N = D.shape[0]
|
||||
cities = list(range(1, N))
|
||||
best_total = float('inf')
|
||||
best_u1, best_u2 = None, None
|
||||
best_route = None
|
||||
|
||||
def cost_of(path):
|
||||
return uav_cost(path, D)
|
||||
|
||||
for perm in permutations(cities):
|
||||
for cut in range(len(perm)+1):
|
||||
u1 = list(perm[:cut])
|
||||
u2 = list(perm[cut:])
|
||||
total = cost_of(u1) + cost_of(u2)
|
||||
if total < best_total:
|
||||
best_total = total
|
||||
best_u1 = u1
|
||||
best_u2 = u2
|
||||
best_route = [0] + list(perm)
|
||||
|
||||
return best_u1, best_u2, best_total, best_route
|
||||
|
||||
|
||||
# ============================
|
||||
# 10. 共用:計算兩 UAV 總成本
|
||||
# ============================
|
||||
def compute_two_uav_cost(uav1_path, uav2_path, D):
|
||||
c1 = uav_cost(uav1_path, D)
|
||||
c2 = uav_cost(uav2_path, D)
|
||||
return c1 + c2, c1, c2
|
||||
|
||||
|
||||
# ============================
|
||||
# 11. SA & SQA:每 run 使用 sample_and_repair
|
||||
# ============================
|
||||
def run_mtsp_sa_sqa(Q, N, D, NUM_RUNS=10):
|
||||
sa_costs = []
|
||||
sa_routes = []
|
||||
sa_u1_list = []
|
||||
sa_u2_list = []
|
||||
sa_uav_costs = [] # store tuple (c1, c2)
|
||||
|
||||
sqa_costs = []
|
||||
sqa_routes = []
|
||||
sqa_u1_list = []
|
||||
sqa_u2_list = []
|
||||
sqa_uav_costs = [] # store tuple (c1, c2)
|
||||
|
||||
sa = oj.SASampler()
|
||||
sqa = oj.SQASampler()
|
||||
|
||||
print("\n🔥 Running SA (with repair, always feasible)...")
|
||||
for r in range(NUM_RUNS):
|
||||
u1, u2, c1, c2, total = sample_and_repair(sa, Q, N, D)
|
||||
|
||||
full_route = [0] + u1 + u2
|
||||
|
||||
print(f"SA Run {r+1:02d}: UAV1={u1}, UAV2={u2}, "
|
||||
f"UAV1_cost={c1:.2f}, UAV2_cost={c2:.2f}, Total={total:.2f}")
|
||||
|
||||
sa_costs.append(total)
|
||||
sa_routes.append(full_route)
|
||||
sa_u1_list.append(u1)
|
||||
sa_u2_list.append(u2)
|
||||
sa_uav_costs.append((c1, c2)) # <-- store both costs
|
||||
|
||||
print("\n🔥 Running SQA (with repair, always feasible)...")
|
||||
for r in range(NUM_RUNS):
|
||||
u1, u2, c1, c2, total = sample_and_repair(sqa, Q, N, D)
|
||||
|
||||
full_route = [0] + u1 + u2
|
||||
|
||||
print(f"SQA Run {r+1:02d}: UAV1={u1}, UAV2={u2}, "
|
||||
f"UAV1_cost={c1:.2f}, UAV2_cost={c2:.2f}, Total={total:.2f}")
|
||||
|
||||
sqa_costs.append(total)
|
||||
sqa_routes.append(full_route)
|
||||
sqa_u1_list.append(u1)
|
||||
sqa_u2_list.append(u2)
|
||||
sqa_uav_costs.append((c1, c2)) # <-- store both costs
|
||||
|
||||
sa_results = (sa_costs, sa_routes, sa_u1_list, sa_u2_list, sa_uav_costs)
|
||||
sqa_results = (sqa_costs, sqa_routes, sqa_u1_list, sqa_u2_list, sqa_uav_costs)
|
||||
|
||||
return sa_results, sqa_results
|
||||
|
||||
# ============================
|
||||
# 12. 綜合分析圖表(你指定的版本)
|
||||
# ============================
|
||||
def create_comprehensive_charts(sa_results, sqa_results, classic_results, D, N):
|
||||
"""創建綜合分析圖表"""
|
||||
fig = plt.figure(figsize=(18, 12))
|
||||
|
||||
# 解包結果
|
||||
sa_costs, sa_routes, sa_uav1, sa_uav2, sa_uav_costs = sa_results
|
||||
sqa_costs, sqa_routes, sqa_uav1, sqa_uav2, sqa_uav_costs = sqa_results
|
||||
classic_uav1, classic_uav2, classic_cost, classic_route = classic_results
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 1. MTSP 成本分布比較 (左上) - 系統總成本
|
||||
# ---------------------------------------------------------
|
||||
ax1 = plt.subplot(2, 3, 1)
|
||||
bins = range(int(min(sa_costs + sqa_costs)) - 1,
|
||||
int(max(sa_costs + sqa_costs)) + 2)
|
||||
ax1.hist(sa_costs, bins=bins, alpha=0.6, label="SA Total", density=True, color='lightblue')
|
||||
ax1.hist(sqa_costs, bins=bins, alpha=0.6, label="SQA Total", density=True, color='lightcoral')
|
||||
|
||||
# 添加KDE
|
||||
if len(sa_costs) > 1:
|
||||
xs_sa, ys_sa = kde_1d(sa_costs)
|
||||
ax1.plot(xs_sa, ys_sa, label="SA KDE", color='blue', linewidth=2)
|
||||
if len(sqa_costs) > 1:
|
||||
xs_sqa, ys_sqa = kde_1d(sqa_costs)
|
||||
ax1.plot(xs_sqa, ys_sqa, label="SQA KDE", color='red', linewidth=2)
|
||||
|
||||
ax1.axvline(x=classic_cost, color='green', linestyle='--',
|
||||
label=f'Classic: {classic_cost}', linewidth=2)
|
||||
ax1.set_title("Total System Cost Distribution")
|
||||
ax1.set_xlabel("Total Cost (UAV1 + UAV2)")
|
||||
ax1.set_ylabel("Density")
|
||||
ax1.legend()
|
||||
ax1.grid(True, alpha=0.3)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 2. 單一 UAV 成本分布比較 (右上) - 修正為「個別 UAV」視角
|
||||
# ---------------------------------------------------------
|
||||
ax2 = plt.subplot(2, 3, 2)
|
||||
|
||||
# 展平數據:將 [(c1, c2), (c1, c2)...] 變成 [c1, c2, c1, c2...]
|
||||
# 這樣可以看到「單台無人機」通常飛多遠
|
||||
sa_single_costs = [c for costs in sa_uav_costs for c in costs]
|
||||
sqa_single_costs = [c for costs in sqa_uav_costs for c in costs]
|
||||
|
||||
# 計算 classic 的單機成本
|
||||
c1_classic = uav_cost(classic_uav1, D)
|
||||
c2_classic = uav_cost(classic_uav2, D)
|
||||
|
||||
# 設定 bins
|
||||
all_single = sa_single_costs + sqa_single_costs
|
||||
uav_bins = range(int(min(all_single)) - 1, int(max(all_single)) + 2)
|
||||
|
||||
ax2.hist(sa_single_costs, bins=uav_bins, alpha=0.6, label="SA Single UAV", density=True, color='skyblue')
|
||||
ax2.hist(sqa_single_costs, bins=uav_bins, alpha=0.6, label="SQA Single UAV", density=True, color='salmon')
|
||||
|
||||
ax2.axvline(x=c1_classic, color='darkgreen', linestyle=':', label='Classic UAV1')
|
||||
ax2.axvline(x=c2_classic, color='limegreen', linestyle=':', label='Classic UAV2')
|
||||
|
||||
ax2.set_title("Individual UAV Cost Distribution")
|
||||
ax2.set_xlabel("Cost per UAV")
|
||||
ax2.set_ylabel("Density")
|
||||
ax2.legend()
|
||||
ax2.grid(True, alpha=0.3)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 3. 箱型圖比較 (中上) - 修正重點
|
||||
# ---------------------------------------------------------
|
||||
ax3 = plt.subplot(2, 3, 3)
|
||||
|
||||
# 數據 1 & 2: 系統總成本 (Total Cost)
|
||||
# 數據 3 & 4: 單機成本 (Individual UAV Cost) - 使用上面展平的數據
|
||||
# 這樣 Box Plot 會有明顯的高低落差(總成本高,單機成本低)
|
||||
|
||||
box_data = [sa_costs, sqa_costs, sa_single_costs, sqa_single_costs]
|
||||
|
||||
bp = ax3.boxplot(
|
||||
box_data,
|
||||
labels=["SA\nTotal", "SQA\nTotal", "SA\nSingle", "SQA\nSingle"],
|
||||
patch_artist=True,
|
||||
showmeans=True
|
||||
)
|
||||
|
||||
colors = ['lightblue', 'lightcoral', 'skyblue', 'salmon']
|
||||
for patch, color in zip(bp['boxes'], colors):
|
||||
patch.set_facecolor(color)
|
||||
|
||||
ax3.set_title("Cost Comparison: Total vs Single UAV")
|
||||
ax3.set_ylabel("Cost")
|
||||
ax3.grid(True, alpha=0.3)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 4. Route diversity heatmap - SA (左下)
|
||||
# ---------------------------------------------------------
|
||||
ax4 = plt.subplot(2, 3, 4)
|
||||
sa_mat = route_diversity_matrix(sa_routes, N)
|
||||
im1 = ax4.imshow(sa_mat, aspect='auto', origin='lower', cmap='viridis')
|
||||
ax4.set_title("SA Route Diversity")
|
||||
ax4.set_xlabel("Position")
|
||||
ax4.set_ylabel("City")
|
||||
plt.colorbar(im1, ax=ax4, shrink=0.8)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 5. Route diversity heatmap - SQA (右下)
|
||||
# ---------------------------------------------------------
|
||||
ax5 = plt.subplot(2, 3, 5)
|
||||
sqa_mat = route_diversity_matrix(sqa_routes, N)
|
||||
im2 = ax5.imshow(sqa_mat, aspect='auto', origin='lower', cmap='viridis')
|
||||
ax5.set_title("SQA Route Diversity")
|
||||
ax5.set_xlabel("Position")
|
||||
ax5.set_ylabel("City")
|
||||
plt.colorbar(im2, ax=ax5, shrink=0.8)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# 6. 最佳路徑可視化 (中下)
|
||||
# ---------------------------------------------------------
|
||||
ax6 = plt.subplot(2, 3, 6)
|
||||
|
||||
# 找出 SA 或 SQA 的最佳解
|
||||
best_sa_idx = sa_costs.index(min(sa_costs))
|
||||
best_sqa_idx = sqa_costs.index(min(sqa_costs))
|
||||
|
||||
best_sa_u1 = sa_uav1[best_sa_idx]
|
||||
best_sa_u2 = sa_uav2[best_sa_idx]
|
||||
|
||||
best_sqa_u1 = sqa_uav1[best_sqa_idx]
|
||||
best_sqa_u2 = sqa_uav2[best_sqa_idx]
|
||||
|
||||
# 用成本較好的那組
|
||||
if min(sa_costs) <= min(sqa_costs):
|
||||
u1 = best_sa_u1
|
||||
u2 = best_sa_u2
|
||||
route_name = "SA"
|
||||
route_cost = min(sa_costs)
|
||||
else:
|
||||
u1 = best_sqa_u1
|
||||
u2 = best_sqa_u2
|
||||
route_name = "SQA"
|
||||
route_cost = min(sqa_costs)
|
||||
|
||||
# --- 繪製城市 ---
|
||||
angles = np.linspace(0, 2*np.pi, N, endpoint=False)
|
||||
x_pos = np.cos(angles)
|
||||
y_pos = np.sin(angles)
|
||||
|
||||
ax6.scatter(x_pos, y_pos, s=200, c='red', zorder=5)
|
||||
for i, (x, y) in enumerate(zip(x_pos, y_pos)):
|
||||
ax6.annotate(str(i), (x, y), ha='center', va='center',
|
||||
fontsize=10, fontweight='bold', color='white', zorder=6)
|
||||
|
||||
# --- UAV1: 藍色 ---
|
||||
if len(u1) > 0:
|
||||
pts = [0] + u1 + [0] # 加入 depot
|
||||
xs = [x_pos[c] for c in pts]
|
||||
ys = [y_pos[c] for c in pts]
|
||||
ax6.plot(xs, ys, '-o', color='blue', linewidth=2, alpha=0.8,
|
||||
label=f'UAV1 ({len(u1)})')
|
||||
|
||||
# --- UAV2: 橘色 ---
|
||||
if len(u2) > 0:
|
||||
pts = [0] + u2 + [0] # 加入 depot
|
||||
xs = [x_pos[c] for c in pts]
|
||||
ys = [y_pos[c] for c in pts]
|
||||
ax6.plot(xs, ys, '-o', color='orange', linewidth=2, alpha=0.8,
|
||||
label=f'UAV2 ({len(u2)})')
|
||||
|
||||
ax6.set_title(f"Best {route_name} MTSP Solution\nTotal Cost: {route_cost:.2f}")
|
||||
ax6.set_xlim(-1.3, 1.3)
|
||||
ax6.set_ylim(-1.3, 1.3)
|
||||
ax6.set_aspect('equal')
|
||||
ax6.axis('off')
|
||||
ax6.legend(loc='upper right')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig("mtsp_two_uav_comprehensive_analysis.png", dpi=300, bbox_inches='tight')
|
||||
print(f"\n📊 綜合分析圖表已保存: mtsp_two_uav_comprehensive_analysis.png")
|
||||
plt.show()
|
||||
|
||||
# ============================
|
||||
# 13. main:整合執行
|
||||
# ============================
|
||||
if __name__ == "__main__":
|
||||
# 距離矩陣:10 城市
|
||||
D = np.array([
|
||||
[0, 2, 8, 5, 7, 6, 3, 9, 4, 2],
|
||||
[2, 0, 5, 7, 3, 8, 4, 6, 9, 1],
|
||||
[8, 5, 0, 2, 6, 7, 3, 4, 1, 5],
|
||||
[5, 7, 2, 0, 4, 3, 8, 6, 2, 7],
|
||||
[7, 3, 6, 4, 0, 5, 9, 2, 7, 3],
|
||||
[6, 8, 7, 3, 5, 0, 4, 7, 9, 6],
|
||||
[3, 4, 3, 8, 9, 4, 0, 5, 6, 2],
|
||||
[9, 6, 4, 6, 2, 7, 5, 0, 8, 3],
|
||||
[4, 9, 1, 2, 7, 9, 6, 8, 0, 5],
|
||||
[2, 1, 5, 7, 3, 6, 2, 3, 5, 0]
|
||||
])
|
||||
N = D.shape[0]
|
||||
|
||||
print("🚀 2-UAV M-TSP (Quantum + Repair vs Classic) 開始運行...\n")
|
||||
print(f"Distance Matrix ({N}x{N}):")
|
||||
print(D)
|
||||
|
||||
# 建立 QUBO
|
||||
Q, _ = build_mtsp_qubo_variable(D, penalty=20.0)
|
||||
|
||||
# SA / SQA,每 run 一定是合法解
|
||||
NUM_RUNS = 10
|
||||
sa_results, sqa_results = run_mtsp_sa_sqa(Q, N, D, NUM_RUNS=NUM_RUNS)
|
||||
|
||||
# Classic 暴力全域最佳
|
||||
print("\n🎯 Running Classic 2-UAV MTSP (Bruteforce ALL solutions)...")
|
||||
classic_uav1, classic_uav2, classic_total_cost, classic_route = classic_mtsp_two_uav_bruteforce(D)
|
||||
print(f"Classic UAV1={classic_uav1}, UAV2={classic_uav2}, Cost={classic_total_cost}")
|
||||
print(f"Classic merged route (0 + perm): {classic_route}")
|
||||
|
||||
# Summary
|
||||
sa_costs, _, _, _, _ = sa_results
|
||||
sqa_costs, _, _, _, _ = sqa_results
|
||||
print("\n📊 Result Summary:")
|
||||
print(f"SA best cost = {min(sa_costs):.2f}, mean = {np.mean(sa_costs):.2f}")
|
||||
print(f"SQA best cost = {min(sqa_costs):.2f}, mean = {np.mean(sqa_costs):.2f}")
|
||||
print(f"Classic global optimum = {classic_total_cost:.2f}")
|
||||
|
||||
# 畫圖
|
||||
classic_results = (classic_uav1, classic_uav2, classic_total_cost, classic_route)
|
||||
print("\n🎨 Creating comprehensive analysis charts...")
|
||||
create_comprehensive_charts(sa_results, sqa_results, classic_results, D, N)
|
||||
|
||||
print("\n🏁 Done.")
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from collections import Counter
|
||||
import math
|
||||
import openjij as oj
|
||||
|
||||
# ============================
|
||||
# 啟發距離矩陣:10-city TSP
|
||||
# ============================
|
||||
D = np.array([
|
||||
[0, 2, 8, 5, 7, 6, 3, 9, 4, 2],
|
||||
[2, 0, 5, 7, 3, 8, 4, 6, 9, 1],
|
||||
[8, 5, 0, 2, 6, 7, 3, 4, 1, 5],
|
||||
[5, 7, 2, 0, 4, 3, 8, 6, 2, 7],
|
||||
[7, 3, 6, 4, 0, 5, 9, 2, 7, 3],
|
||||
[6, 8, 7, 3, 5, 0, 4, 7, 9, 6],
|
||||
[3, 4, 3, 8, 9, 4, 0, 5, 6, 2],
|
||||
[9, 6, 4, 6, 2, 7, 5, 0, 8, 3],
|
||||
[4, 9, 1, 2, 7, 9, 6, 8, 0, 5],
|
||||
[2, 1, 5, 7, 3, 6, 2, 3, 5, 0]
|
||||
])
|
||||
|
||||
N = D.shape[0]
|
||||
penalty = 20 # 罰則 > max(D) 即可
|
||||
|
||||
def idx(i, t, N):
|
||||
return i * N + t
|
||||
|
||||
# ============================
|
||||
# 建立 QUBO:位置編碼 + 固定起點 0
|
||||
# ============================
|
||||
def build_qubo(D, penalty, fix_start=True):
|
||||
N = D.shape[0]
|
||||
Q = {}
|
||||
|
||||
# 距離項
|
||||
for t in range(N):
|
||||
t2 = (t + 1) % N
|
||||
for i in range(N):
|
||||
for j in range(N):
|
||||
if i != j:
|
||||
u = idx(i, t, N)
|
||||
v = idx(j, t2, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + D[i, j]
|
||||
|
||||
# 約束A: 每個 time slot 有且只有一個城市
|
||||
for t in range(N):
|
||||
for i in range(N):
|
||||
u = idx(i, t, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) - penalty
|
||||
for j in range(i + 1, N):
|
||||
v = idx(j, t, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + 2 * penalty
|
||||
|
||||
# 約束B: 每個城市被訪問一次
|
||||
for i in range(N):
|
||||
for t in range(N):
|
||||
u = idx(i, t, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) - penalty
|
||||
for t2 in range(t + 1, N):
|
||||
v = idx(i, t2, N)
|
||||
Q[(u, v)] = Q.get((u, v), 0) + 2 * penalty
|
||||
|
||||
# 固定起點:t=0 必須是 city 0
|
||||
if fix_start:
|
||||
big = 9999.0
|
||||
# 禁止 i!=0 在 t=0
|
||||
for i in range(1, N):
|
||||
u = idx(i, 0, N)
|
||||
Q[(u, u)] = Q.get((u, u), 0) + big
|
||||
# 獎勵 city 0 在 t=0
|
||||
u0 = idx(0, 0, N)
|
||||
Q[(u0, u0)] = Q.get((u0, u0), 0) - big
|
||||
|
||||
return Q, N
|
||||
|
||||
# ============================
|
||||
# Route decode & cost
|
||||
# ============================
|
||||
def decode_route(sample, N):
|
||||
route = []
|
||||
for t in range(N):
|
||||
city_for_t = None
|
||||
for i in range(N):
|
||||
if sample.get(idx(i, t, N), 0) == 1:
|
||||
city_for_t = i
|
||||
break
|
||||
# 若有問題就塞 -1,方便debug
|
||||
if city_for_t is None:
|
||||
city_for_t = -1
|
||||
route.append(city_for_t)
|
||||
return route
|
||||
|
||||
def compute_cost(route, D):
|
||||
N = len(route)
|
||||
total = 0
|
||||
for k in range(N):
|
||||
a = route[k]
|
||||
b = route[(k + 1) % N]
|
||||
total += D[a, b]
|
||||
return total
|
||||
|
||||
# ============================
|
||||
# 簡單 KDE 實作(Gaussian kernel)
|
||||
# ============================
|
||||
def kde_1d(samples, num_points=200):
|
||||
xs = np.linspace(min(samples), max(samples), num_points)
|
||||
n = len(samples)
|
||||
if n < 2:
|
||||
return xs, np.zeros_like(xs)
|
||||
std = np.std(samples)
|
||||
if std == 0:
|
||||
std = 1.0
|
||||
# Silverman's rule
|
||||
h = 1.06 * std * (n ** (-1/5))
|
||||
if h == 0:
|
||||
h = 1.0
|
||||
ys = []
|
||||
inv_sqrt_2pi = 1.0 / math.sqrt(2.0 * math.pi)
|
||||
for x in xs:
|
||||
s = 0.0
|
||||
for xi in samples:
|
||||
z = (x - xi) / h
|
||||
s += math.exp(-0.5 * z * z) * inv_sqrt_2pi
|
||||
ys.append(s / (n * h))
|
||||
return xs, np.array(ys)
|
||||
|
||||
# ============================
|
||||
# Route diversity heatmap
|
||||
# ============================
|
||||
def route_diversity_matrix(routes, N):
|
||||
# freq[i, t] = city i 在位置 t 出現次數
|
||||
freq = np.zeros((N, N), dtype=float)
|
||||
for route in routes:
|
||||
for t, city in enumerate(route):
|
||||
if 0 <= city < N:
|
||||
freq[city, t] += 1
|
||||
# normalize to probabilities
|
||||
if len(routes) > 0:
|
||||
freq /= len(routes)
|
||||
return freq
|
||||
|
||||
# ============================
|
||||
# 跑 SA or SQA 多次
|
||||
# ============================
|
||||
def run_algorithm(name, sampler, Q, D, N, num_runs=20, num_reads=20):
|
||||
all_costs = []
|
||||
all_routes = []
|
||||
|
||||
print(f"\n=== Running {name} ===")
|
||||
for r in range(num_runs):
|
||||
result = sampler.sample_qubo(Q, num_reads=num_reads)
|
||||
best = result.first.sample
|
||||
route = decode_route(best, N)
|
||||
cost = compute_cost(route, D)
|
||||
all_costs.append(cost)
|
||||
all_routes.append(tuple(route))
|
||||
print(f"{name} Run {r+1:02d}: Route={route}, Cost={cost}")
|
||||
|
||||
return all_costs, all_routes
|
||||
|
||||
# ============================
|
||||
# Main
|
||||
# ============================
|
||||
if __name__ == "__main__":
|
||||
Q, N = build_qubo(D, penalty, fix_start=True)
|
||||
|
||||
# Samplers
|
||||
sqa_sampler = oj.SQASampler()
|
||||
sa_sampler = oj.SASampler()
|
||||
|
||||
# 可自行調整次數與 num_reads
|
||||
NUM_RUNS = 20
|
||||
NUM_READS = 20
|
||||
|
||||
sqa_costs, sqa_routes = run_algorithm("SQA", sqa_sampler, Q, D, N, NUM_RUNS, NUM_READS)
|
||||
sa_costs, sa_routes = run_algorithm("SA", sa_sampler, Q, D, N, NUM_RUNS, NUM_READS)
|
||||
|
||||
# ----------------------------
|
||||
# 基本統計
|
||||
# ----------------------------
|
||||
print("\n=== Summary ===")
|
||||
print(f"SQA: min={min(sqa_costs)}, max={max(sqa_costs)}, mean={np.mean(sqa_costs):.2f}, std={np.std(sqa_costs):.2f}")
|
||||
print(f"SA : min={min(sa_costs)}, max={max(sa_costs)}, mean={np.mean(sa_costs):.2f}, std={np.std(sa_costs):.2f}")
|
||||
|
||||
# ============================
|
||||
# 圖 1: Cost histogram + KDE
|
||||
# ============================
|
||||
fig1, ax1 = plt.subplots(figsize=(8,5))
|
||||
bins = range(min(sa_costs + sqa_costs), max(sa_costs + sqa_costs) + 2)
|
||||
|
||||
ax1.hist(sa_costs, bins=bins, alpha=0.4, label="SA", density=True)
|
||||
ax1.hist(sqa_costs, bins=bins, alpha=0.4, label="SQA", density=True)
|
||||
|
||||
xs_sa, ys_sa = kde_1d(sa_costs)
|
||||
xs_sqa, ys_sqa = kde_1d(sqa_costs)
|
||||
ax1.plot(xs_sa, ys_sa, label="SA KDE")
|
||||
ax1.plot(xs_sqa, ys_sqa, label="SQA KDE")
|
||||
|
||||
ax1.set_title("Tour Cost Distribution (SA vs SQA)")
|
||||
ax1.set_xlabel("Tour Cost")
|
||||
ax1.set_ylabel("Density")
|
||||
ax1.legend()
|
||||
fig1.tight_layout()
|
||||
fig1.savefig("tsp_cost_hist_kde_sa_vs_sqa.png")
|
||||
|
||||
# ============================
|
||||
# 圖 2: Violin plot + Box plot
|
||||
# ============================
|
||||
fig2, axs2 = plt.subplots(1, 2, figsize=(10,5))
|
||||
|
||||
# Violin
|
||||
axs2[0].violinplot([sa_costs, sqa_costs], positions=[1,2], showmeans=True)
|
||||
axs2[0].set_xticks([1,2])
|
||||
axs2[0].set_xticklabels(["SA", "SQA"])
|
||||
axs2[0].set_title("Violin Plot of Tour Costs")
|
||||
|
||||
# Box plot
|
||||
axs2[1].boxplot([sa_costs, sqa_costs], labels=["SA", "SQA"], showmeans=True)
|
||||
axs2[1].set_title("Box Plot of Tour Costs")
|
||||
|
||||
fig2.tight_layout()
|
||||
fig2.savefig("tsp_cost_violin_box_sa_vs_sqa.png")
|
||||
|
||||
# ============================
|
||||
# 圖 3: Route diversity heatmap
|
||||
# ============================
|
||||
sqa_mat = route_diversity_matrix(sqa_routes, N)
|
||||
sa_mat = route_diversity_matrix(sa_routes, N)
|
||||
|
||||
fig3, axs3 = plt.subplots(1, 2, figsize=(12,5))
|
||||
|
||||
im0 = axs3[0].imshow(sa_mat, aspect='auto', origin='lower')
|
||||
axs3[0].set_title("Route Diversity (SA)")
|
||||
axs3[0].set_xlabel("Position t")
|
||||
axs3[0].set_ylabel("City index")
|
||||
fig3.colorbar(im0, ax=axs3[0])
|
||||
|
||||
im1 = axs3[1].imshow(sqa_mat, aspect='auto', origin='lower')
|
||||
axs3[1].set_title("Route Diversity (SQA)")
|
||||
axs3[1].set_xlabel("Position t")
|
||||
axs3[1].set_ylabel("City index")
|
||||
fig3.colorbar(im1, ax=axs3[1])
|
||||
|
||||
fig3.tight_layout()
|
||||
fig3.savefig("tsp_route_diversity_heatmap_sa_vs_sqa.png")
|
||||
|
||||
plt.show()
|
||||
Reference in New Issue
Block a user