You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

690 lines
28 KiB
Python

import time
import numpy as np
import matplotlib.pyplot as plt
from itertools import permutations
import math
import openjij as oj
# ==================== 可調整參數區 ====================
# 問題規模設定
CITIES = 30 # 初始核心問題規模 (測試建議先用 12-15)
CITIES_BOUND = 10 # 問題規模的上下範圍 (單一數字控制:以 CITIES 為中心,例如 15±5即 10~20)
RANDOM = False
RANDOM_SEED = 42
COORD_RANGE = (0.0, 10.0)
# 【核心創新】:環境變異數控制與複雜度壓力測試參數
N_LIST = list(range(CITIES - CITIES_BOUND, CITIES + CITIES_BOUND + 1, 4)) # 自動產生測試的問題規模範圍
COORD_STD = 15.0 # 空間分佈變異數 (越大代表城市分佈越不均勻、越崎嶇)
RISK_STD = 10.0 # 擾動風險變異數 (越大代表某些路段特別危險H-infinity 衝突極大)
# 演算法執行設定
NUM_RUNS = 10 # 正式比較的執行次數
BETA = 15.0 # 🌟 稍微調高 (原為 10.0),讓低溫結冰得更紮實
# QUBO 參數
PENALTY = 500.0 # 🌟 從 3000 大幅降回 500 (解開高爾夫球場效應!)
BIG_PENALTY = 2000.0 # 🌟 起點約束降到 2000 即可
# TSP 求解參數
EXACT_LIMIT = 8
# 魯棒優化參數 (H-infinity Robust QUBO)
USE_ROBUST = True # 強制開啟,這是本論文的核心
GAMMA = 0.5
SIGMA = 1.0
ALPHA = 10.0
# =====================================================
# ----------------- 基礎輔助函數 -----------------
def generate_distance_matrix(num_cities, random=True, seed=None, coord_range=None):
if seed is None: seed = RANDOM_SEED
if coord_range is None: coord_range = COORD_RANGE
if not random: np.random.seed(seed)
else: np.random.seed(None)
low, high = coord_range
coords = np.random.uniform(low, high, size=(num_cities, 2))
D = np.zeros((num_cities, num_cities), dtype=float)
for i in range(num_cities):
for j in range(i + 1, num_cities):
dist = np.linalg.norm(coords[i] - coords[j])
D[i, j] = dist
D[j, i] = dist
return np.round(D, 2), coords
def generate_disturbance_matrix(num_cities, seed=None):
if seed is None: seed = RANDOM_SEED
np.random.seed(seed)
F = np.random.rand(num_cities, num_cities)
F = (F + F.T) / 2
np.fill_diagonal(F, 0.0)
return F
def idx_mtsp(k, i, p, N):
return k * (N * N) + i * N + p
def build_robust_qubo(D, F, gamma=GAMMA, sigma=SIGMA, alpha=ALPHA, penalty=PENALTY, big_penalty=BIG_PENALTY):
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, fij = D[i, j], F[i, j]
if dij == 0: continue
# 這裡就是 H-infinity 的核心風險項!完全保留著!
risk_term = (sigma / (gamma**2)) * (fij**2)
total_weight = dij + (alpha * risk_term)
u, v = idx_mtsp(k, i, p, N), idx_mtsp(k, j, q, N)
addQ(u, v, total_weight)
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)
for i in range(1, N):
vars_city = [idx_mtsp(k, i, p, N) for k in range(2) for p in range(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)
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
# ---------------------------------------------------------
# 🌟 [新增] 產生平滑的 SQA 與 Pause SQA Schedule
# ---------------------------------------------------------
def get_smooth_sqa_schedule(beta, total_sweeps, num_steps=100):
"""產生標準 SQA 的平滑退火排程 (s 從 0.0 遞增到 1.0)"""
schedule = []
sweeps_per_step = max(1, int(total_sweeps / num_steps))
for s in np.linspace(0.0, 1.0, num_steps):
schedule.append([float(s), float(beta), int(sweeps_per_step)])
return schedule
def get_pause_sqa_schedule(s_star, beta, total_anneal_sweeps, pause_sweeps, num_steps=100):
"""產生帶有 Pause 策略的平滑退火排程"""
schedule = []
# 階段 1從 s=0.0 平滑退火到 s_star
steps_p1 = max(1, int(num_steps * s_star))
sweeps_p1 = max(1, int((total_anneal_sweeps / 2) / steps_p1))
for s in np.linspace(0.0, s_star, steps_p1, endpoint=False):
schedule.append([float(s), float(beta), int(sweeps_p1)])
# 階段 2在相變點 s_star 暫停 (Pause),讓量子系統充分熱化
schedule.append([float(s_star), float(beta), int(pause_sweeps)])
# 階段 3從 s_star 平滑退火到 s=1.0
steps_p3 = max(1, num_steps - steps_p1)
sweeps_p3 = max(1, int((total_anneal_sweeps / 2) / steps_p3))
for s in np.linspace(s_star, 1.0, steps_p3):
schedule.append([float(s), float(beta), int(sweeps_p3)])
return schedule
def decode_slots(sample, N):
slots = []
for k in range(2):
row = []
for p in range(N):
chosen = 0
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]
def repair_routes_from_slots(u1_slots, u2_slots, N):
count1, count2 = [0]*N, [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):
if count1[city] > count2[city]: assign1.append(city)
elif count2[city] > count1[city]: assign2.append(city)
else:
if len(assign1) <= len(assign2): assign1.append(city)
else: assign2.append(city)
return sorted(assign1), sorted(assign2)
def uav_cost(path, D):
if not path or 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 uav_disturbance_energy(path, F, gamma=GAMMA, sigma=SIGMA):
if not path or len(path) == 0: return 0.0
risk = (sigma / (gamma**2)) * (F[0, path[0]]**2)
for i in range(len(path)-1): risk += (sigma / (gamma**2)) * (F[path[i], path[i+1]]**2)
risk += (sigma / (gamma**2)) * (F[path[-1], 0]**2)
return float(risk)
def best_order_for_cities(cities, D, exact_limit=EXACT_LIMIT):
cities = list(cities)
if len(cities) <= 1: return cities, uav_cost(cities, D)
if len(cities) <= exact_limit:
best_perm, best_cost = None, float('inf')
for perm in permutations(cities):
c = uav_cost(list(perm), D)
if c < best_cost: best_cost, best_perm = c, list(perm)
return best_perm, best_cost
else: # 簡單的 nearest neighbor heuristic 當作 fallback
rem = set(cities)
curr = list(rem)[0]
rem.remove(curr)
route = [curr]
while rem:
nxt = min(rem, key=lambda x: D[curr, x])
route.append(nxt)
rem.remove(nxt)
curr = nxt
return route, uav_cost(route, D)
def get_makespan_and_risk(sample, N, D, F):
u1, u2 = decode_slots(sample, N)
a1, a2 = repair_routes_from_slots(u1, u2, N)
p1, c1 = best_order_for_cities(a1, D)
p2, c2 = best_order_for_cities(a2, D)
makespan = max(c1, c2)
dist_energy = uav_disturbance_energy(p1, F) + uav_disturbance_energy(p2, F)
# 確保回傳路徑,以便印出
return makespan, dist_energy, p1, p2
# ============================
# 核心創新功能區 (Pause Strategy)
# ============================
def pilot_search_phase_transition(Q, N, D, F):
print("\n🔍 [階段一] 前導偵查 (Pilot Search): 尋找最佳暫停點 s*")
test_s_list = np.arange(0.2, 1.0, 0.1)
variances = []
makespans = []
sampler = oj.SQASampler()
for s in test_s_list:
sched_var = [[s, BETA, 30]]
res_var = sampler.sample_qubo(Q, schedule=sched_var, num_reads=10)
energies = [d.energy for d in res_var.data()]
variances.append(np.var(energies))
sched_pause = [[s, BETA, 20], [s, BETA, 40], [1.0, BETA, 20]]
res_pause = sampler.sample_qubo(Q, schedule=sched_pause, num_reads=5)
mk, _, p1, p2 = get_makespan_and_risk(res_pause.first.sample, N, D, F)
makespans.append(mk)
print(f" 測試 s={s:.1f} | 變異數: {variances[-1]:.0f} | Makespan: {mk:.2f}")
best_s = test_s_list[np.argmin(makespans)]
print(f"⭐ 鎖定最佳暫停點 s* = {best_s:.1f}")
return test_s_list, variances, makespans, best_s
def test_pause_durations(Q, N, D, F, s_star):
print(f"\n⏳ [階段二] 暫停時間測試 (在 s*={s_star:.1f} 進行熱化)")
durations = [0, 20, 50, 100, 150]
duration_makespans = []
sampler = oj.SQASampler()
for d in durations:
if d == 0:
sched = get_smooth_sqa_schedule(BETA, total_sweeps=1000)
else:
sched = get_pause_sqa_schedule(s_star, BETA, total_anneal_sweeps=1000, pause_sweeps=d)
res = sampler.sample_qubo(Q, schedule=sched, num_reads=10)
mk, _, p1, p2 = get_makespan_and_risk(res.first.sample, N, D, F)
duration_makespans.append(mk)
print(f" 暫停 {d} 步 -> Makespan: {mk:.2f}")
return durations, duration_makespans
def run_comparative_evaluations(Q, N, D, F, s_star):
print(f"\n⚔️ [階段三] 正式對決 (SA vs SQA vs Pause-SQA, {NUM_RUNS} runs)")
results = {
'sa': {'mk': [], 'risk': [], 'energy': []},
'sqa': {'mk': [], 'risk': [], 'energy': []},
'pause': {'mk': [], 'risk': [], 'energy': []}
}
sqa_sampler = oj.SQASampler()
sa_sampler = oj.SASampler()
# 🌟 核心修改:建立 1000 Sweeps 的退火排程
TOTAL_SWEEPS = 1000
PAUSE_SWEEPS = 500 # 讓 Pause-SQA 在相變點多停留 500 步熱化
smooth_sched = get_smooth_sqa_schedule(beta=BETA, total_sweeps=TOTAL_SWEEPS, num_steps=100)
pause_sched = get_pause_sqa_schedule(s_star=s_star, beta=BETA, total_anneal_sweeps=TOTAL_SWEEPS, pause_sweeps=PAUSE_SWEEPS, num_steps=100)
# (選配) 在 pilot_search 和 test_pause 中也等比例放大 sweeps
# 例如 pilot_search 改成 [[s, BETA, 300]]
for r in range(NUM_RUNS):
# SA
res_sa = sa_sampler.sample_qubo(Q, num_reads=10)
mk, risk, p1, p2 = get_makespan_and_risk(res_sa.first.sample, N, D, F)
results['sa']['mk'].append(mk)
results['sa']['risk'].append(risk)
results['sa']['energy'].append(res_sa.first.energy)
print(f" [SA 執行 {r+1}/{NUM_RUNS}] 路徑 1: [0, {', '.join(map(str, p1))}, 0], 路徑 2: [0, {', '.join(map(str, p2))}, 0] | 成本: {mk:.2f}")
# Standard SQA
t0 = time.time()
res_std = sqa_sampler.sample_qubo(Q, schedule=smooth_sched, num_reads=10)
mk, risk, p1, p2 = get_makespan_and_risk(res_std.first.sample, N, D, F)
results['sqa']['mk'].append(mk)
results['sqa']['risk'].append(risk)
results['sqa']['energy'].append(res_std.first.energy)
print(f" [Std-SQA 執行 {r+1}/{NUM_RUNS}] 耗時: {time.time()-t0:.2f}s | 路徑 1: [0, {', '.join(map(str, p1))}, 0], 路徑 2: [0, {', '.join(map(str, p2))}, 0] | 成本: {mk:.2f}")
# Pause SQA
t0 = time.time()
res_pause = sqa_sampler.sample_qubo(Q, schedule=pause_sched, num_reads=10)
mk, risk, p1, p2 = get_makespan_and_risk(res_pause.first.sample, N, D, F)
results['pause']['mk'].append(mk)
results['pause']['risk'].append(risk)
results['pause']['energy'].append(res_pause.first.energy)
print(f" [Pause-SQA 執行 {r+1}/{NUM_RUNS}] 耗時: {time.time()-t0:.2f}s | 路徑 1: [0, {', '.join(map(str, p1))}, 0], 路徑 2: [0, {', '.join(map(str, p2))}, 0] | 成本: {mk:.2f}")
return results
# ============================
# 學術圖表繪製 (Academic Visualization)
# ============================
def create_basic_distribution_charts(results):
"""基本分佈分析圖"""
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
fig.suptitle(f"Algorithm Performance Distributions (N={CITIES})", fontsize=16, fontweight='bold')
labels = ['SA', 'Standard-SQA', 'Pause-SQA']
colors = ['gray', 'steelblue', 'firebrick']
# 1. Makespan 分布
parts1 = axes[0].violinplot([results['sa']['mk'], results['sqa']['mk'], results['pause']['mk']], showmeans=True)
axes[0].set_title("Makespan (Distance Cost) Distribution")
axes[0].set_ylabel("Distance Cost")
# 2. Risk 分布
parts2 = axes[1].violinplot([results['sa']['risk'], results['sqa']['risk'], results['pause']['risk']], showmeans=True)
axes[1].set_title(r"$H_\infty$ Disturbance Risk Distribution")
axes[1].set_ylabel("Risk Penalty")
# 3. Energy 分布
parts3 = axes[2].violinplot([results['sa']['energy'], results['sqa']['energy'], results['pause']['energy']], showmeans=True)
axes[2].set_title("QUBO System Raw Energy")
axes[2].set_ylabel("System Energy")
# 設定提琴圖外觀
for i, ax in enumerate(axes):
ax.set_xticks([1, 2, 3])
ax.set_xticklabels(labels, fontsize=11)
ax.grid(alpha=0.3, axis='y')
parts = [parts1, parts2, parts3][i]
for pc, color in zip(parts['bodies'], colors):
pc.set_facecolor(color)
pc.set_alpha(0.7)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.savefig("distribution_comparison.png", dpi=300)
print("\n🎨 圖表已繪製: distribution_comparison.png")
# plt.show() (Moved to the end)
def create_academic_charts(s_list, variances, makespans, best_s, durations, dur_makespans, results):
"""原有的圖表二Pause Strategy 深度分析圖"""
fig = plt.figure(figsize=(16, 10))
fig.suptitle(f"Quantum Pause Strategy in H-infinity Robust mTSP (N={CITIES})", fontsize=16, fontweight='bold')
# Chart 1: Phase Transition & U-Valley
ax1 = plt.subplot(2, 2, 1)
color1, color2 = 'tab:blue', 'tab:red'
ax1.set_title("Chart 1: Phase Transition & U-Valley", fontsize=12)
ax1.set_xlabel("Annealing Parameter $s$")
ax1.set_ylabel("Trotter Energy Variance", color=color1)
ax1.plot(s_list, variances, marker='o', color=color1, linewidth=2)
ax1.tick_params(axis='y', labelcolor=color1)
ax1_twin = ax1.twinx()
ax1_twin.set_ylabel("Makespan", color=color2)
ax1_twin.plot(s_list, makespans, marker='s', color=color2, linestyle='--', linewidth=2)
ax1_twin.tick_params(axis='y', labelcolor=color2)
ax1.axvline(x=best_s, color='green', linestyle=':', linewidth=2)
ax1.grid(alpha=0.3)
# Subplot 2 (was Chart 4): Effect of Thermalization Time
ax2 = plt.subplot(2, 2, 2)
ax2.set_title(f"Chart 2: Effect of Thermalization Time at $s^*={best_s:.1f}$", fontsize=12)
ax2.plot(durations, dur_makespans, marker='D', color='darkorange', linewidth=2.5, markersize=8)
ax2.set_xlabel("Pause Duration (Sweeps)")
ax2.set_ylabel("Optimized Makespan")
ax2.grid(alpha=0.3, linestyle='--')
# Subplot 3 (was Chart 2): Pareto Front
ax3 = plt.subplot(2, 2, 3)
ax3.set_title("Chart 3: Robustness Pareto Front", fontsize=12)
ax3.scatter(results['sqa']['mk'], results['sqa']['risk'], c='steelblue', alpha=0.7, s=80, label='Standard-SQA')
ax3.scatter(results['pause']['mk'], results['pause']['risk'], c='firebrick', alpha=0.9, s=120, marker='*', label='Pause-SQA')
ax3.set_xlabel("Makespan")
ax3.set_ylabel("Disturbance Risk")
ax3.legend()
ax3.grid(alpha=0.3, linestyle='--')
# Subplot 4 (was Chart 3): Reliability Violin Plot
ax4 = plt.subplot(2, 2, 4)
ax4.set_title("Chart 4: Final Makespan Comparison", fontsize=12)
parts = ax4.violinplot([results['sqa']['mk'], results['pause']['mk']], showmeans=True)
parts['bodies'][0].set_facecolor('steelblue')
parts['bodies'][1].set_facecolor('firebrick')
for body in parts['bodies']: body.set_alpha(0.7)
ax4.set_xticks([1, 2])
ax4.set_xticklabels(['Standard-SQA', 'Pause-SQA'])
sa_mean = np.mean(results['sa']['mk'])
ax4.axhline(y=sa_mean, color='gray', linestyle=':', linewidth=2, label=f'SA Mean: {sa_mean:.1f}')
ax4.legend()
ax4.grid(alpha=0.3, axis='y')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.savefig("academic_pause_strategy.png", dpi=300)
print("🎨 原有圖表已繪製: academic_pause_strategy.png")
# plt.show() (Moved to the end)
# ============================
# 複雜度與環境控制功能 (新增)
# ============================
def inject_hidden_shortcut(D, F):
"""
注入隱藏的黃金捷徑 (Hidden Shortcut)
創造一條極度狹窄且成本極低的路線 (例如 4 -> 5 -> 6 -> 7)
如果沒照順序走成本會極高一旦走對總成本會大幅下降
"""
N = D.shape[0]
if N < 8:
return D, F # 城市太少無法做長捷徑
# 定義黃金路線的節點 (假設是 4, 5, 6, 7)
golden_nodes = [4, 5, 6, 7]
# 1. 築起高牆:先把這幾個城市之間的所有連線,都變成極高成本 (阻止 SA 亂湊)
for i in golden_nodes:
for j in golden_nodes:
if i != j:
D[i, j] = 8.0
F[i, j] = 0.8 # 高風險
# 2. 挖出深谷:只開通 4->5, 5->6, 6->7 這條唯一且完美的捷徑
for i in range(len(golden_nodes) - 1):
n1 = golden_nodes[i]
n2 = golden_nodes[i + 1]
# 距離極短,風險為 0
D[n1, n2] = 0.05
D[n2, n1] = 0.05
F[n1, n2] = 0.0
F[n2, n1] = 0.0
print("\n✨ [隱藏捷徑已佈署] 演算法將挑戰尋找極狹窄的黃金路線 (4->5->6->7)")
return D, F
def inject_deceptive_trap(D, F, alpha=10.0, gamma=0.5, sigma=1.0):
"""
注入欺騙性陷阱 (Deceptive Trap)
改造矩陣創造一條看似完美的捷徑測試演算法是否會陷入局部陷阱
"""
N = D.shape[0]
if N < 4:
return D, F # 城市太少無法做陷阱
# 定義陷阱節點
trap_start = 1
trap_end = 2
safe_detour = 3
# ==========================================
# 陷阱 1致命捷徑 (距離極度誘人,但風險爆表)
# ==========================================
D[trap_start, trap_end] = 0.1
D[trap_end, trap_start] = 0.1
F[trap_start, trap_end] = 0.99
F[trap_end, trap_start] = 0.99
# ==========================================
# 陷阱 2安全繞路 (距離較遠,但完全無風險)
# ==========================================
# 路線: trap_start -> safe_detour -> trap_end
D[trap_start, safe_detour] = 4.0
D[safe_detour, trap_start] = 4.0
D[safe_detour, trap_end] = 4.0
D[trap_end, safe_detour] = 4.0
F[trap_start, safe_detour] = 0.01
F[safe_detour, trap_start] = 0.01
F[safe_detour, trap_end] = 0.01
F[trap_end, safe_detour] = 0.01
# 計算並印出真實成本,讓您確認陷阱是否成立
# 真實成本 = 距離 + alpha * (sigma / gamma^2) * F^2
risk_multiplier = alpha * (sigma / (gamma**2))
shortcut_cost = 0.1 + risk_multiplier * (0.99**2)
detour_cost = (4.0 + risk_multiplier * (0.01**2)) + (4.0 + risk_multiplier * (0.01**2))
print(f" ➤ 致命捷徑 (1->2) 表觀距離: 0.1 | 隱藏真實成本: {shortcut_cost:.2f}")
print(f" ➤ 安全繞路 (1->3->2) 表觀距離: 8.0 | 隱藏真實成本: {detour_cost:.2f}")
return D, F
def generate_controlled_matrices(num_cities, coord_std, risk_std, seed=42):
"""根據指定的標準差(變異數)生成 D 與 F 矩陣,精準控制問題複雜度"""
np.random.seed(seed)
# 1. 生成距離矩陣 D (使用常態分佈控制空間聚集度)
coords = np.random.normal(loc=0.0, scale=coord_std, size=(num_cities, 2))
D = np.zeros((num_cities, num_cities), dtype=float)
for i in range(num_cities):
for j in range(i + 1, num_cities):
dist = np.linalg.norm(coords[i] - coords[j])
D[i, j] = D[j, i] = dist
# 2. 生成擾動矩陣 F (使用常態分佈控制風險極端值)
F = np.abs(np.random.normal(loc=1.0, scale=risk_std, size=(num_cities, num_cities)))
F = (F + F.T) / 2 # 確保對稱
np.fill_diagonal(F, 0.0)
return np.round(D, 2), np.round(F, 2)
def decode_and_eval(sample, N, D):
# 快速解碼與貪婪/暴力求成本
u1, u2 = [], []
for p in range(N):
for i in range(N):
if sample.get(idx_mtsp(0, i, p, N), 0) == 1: u1.append(i)
if sample.get(idx_mtsp(1, i, p, N), 0) == 1: u2.append(i)
# 簡單修復 (去除重複,補齊缺失)
c1, c2 = set(u1), set(u2)
a1, a2 = [], []
for city in range(1, N):
if city in c1 and city not in c2: a1.append(city)
elif city in c2 and city not in c1: a2.append(city)
else:
if len(a1) <= len(a2): a1.append(city)
else: a2.append(city)
# 評估成本
def eval_cost(cities):
if not cities: return 0
if len(cities) <= EXACT_LIMIT:
bc = float('inf')
for p in permutations(cities):
c = D[0, p[0]] + sum(D[p[i], p[i+1]] for i in range(len(p)-1)) + D[p[-1], 0]
bc = min(bc, c)
return bc
# 貪婪
rem, path, curr = set(cities), [], 0
while rem:
nxt = min(rem, key=lambda x: D[curr, x])
path.append(nxt)
rem.remove(nxt)
curr = nxt
return D[0, path[0]] + sum(D[path[i], path[i+1]] for i in range(len(path)-1)) + D[path[-1], 0]
return max(eval_cost(a1), eval_cost(a2))
def run_complexity_scaling():
print(f"\n🚀 開始複雜度壓力測試...")
print(f"⚙️ 空間變異數(COORD_STD)={COORD_STD}, 風險變異數(RISK_STD)={RISK_STD}")
std_means, pause_means = [], []
std_errs, pause_errs = [], []
sampler = oj.SQASampler()
for N in N_LIST:
print(f"\n📊 測試規模 N={N} ...")
D, F = generate_controlled_matrices(N, COORD_STD, RISK_STD)
# 動態計算懲罰值
max_d = np.max(D)
max_f = np.max(F) if F is not None else 0
max_possible_cost = max_d + max_f
dyn_penalty = max_possible_cost * 12.0
dyn_big_penalty = dyn_penalty * 5.0
Q, _ = build_robust_qubo(D, F, penalty=dyn_penalty, big_penalty=dyn_big_penalty)
std_results, pause_results = [], []
# 為了節省時間,在壓力測試中我們使用固定的經驗暫停點 s=0.7
# 總步數統一為 200 步
std_sched = get_smooth_sqa_schedule(BETA, total_sweeps=200, num_steps=20)
pause_sched = get_pause_sqa_schedule(0.7, BETA, total_anneal_sweeps=200, pause_sweeps=100, num_steps=30)
# 這裡設定每次 N 跑的次數,與原本 NUM_RUNS 獨立,建議 10 次
runs_for_scale = 10
for r in range(runs_for_scale):
# Standard SQA
res_std = sampler.sample_qubo(Q, schedule=std_sched, num_reads=5)
std_results.append(decode_and_eval(res_std.first.sample, N, D))
# Pause SQA
res_pause = sampler.sample_qubo(Q, schedule=pause_sched, num_reads=5)
pause_results.append(decode_and_eval(res_pause.first.sample, N, D))
mean_std, err_std = np.mean(std_results), np.std(std_results)
mean_pause, err_pause = np.mean(pause_results), np.std(pause_results)
std_means.append(mean_std)
std_errs.append(err_std)
pause_means.append(mean_pause)
pause_errs.append(err_pause)
print(f" Standard-SQA 平均: {mean_std:.2f} ± {err_std:.2f}")
print(f" Pause-SQA 平均: {mean_pause:.2f} ± {err_pause:.2f}")
return std_means, std_errs, pause_means, pause_errs
def plot_crossover(std_means, std_errs, pause_means, pause_errs):
plt.figure(figsize=(10, 6))
plt.title(f"Complexity Scaling: Algorithm Performance vs Problem Size (N)\n(Coord Std={COORD_STD}, Risk Std={RISK_STD})", fontsize=14, fontweight='bold')
# 畫線與誤差棒
plt.errorbar(N_LIST, std_means, yerr=std_errs, fmt='-o', color='steelblue',
linewidth=2.5, capsize=5, markersize=8, label='Standard-SQA')
plt.errorbar(N_LIST, pause_means, yerr=pause_errs, fmt='-s', color='firebrick',
linewidth=2.5, capsize=5, markersize=8, label='Pause-SQA')
plt.xlabel("Problem Scale (Number of Cities $N$)", fontsize=12)
plt.ylabel("Optimized Makespan (Distance Cost)", fontsize=12)
plt.xticks(N_LIST)
plt.grid(alpha=0.4, linestyle='--')
plt.legend(fontsize=11)
plt.tight_layout()
plt.savefig("complexity_crossover.png", dpi=300)
print("\n🎨 交叉點分析圖已儲存為: complexity_crossover.png")
# plt.show() (Moved to the end)
# ============================
# 主程式執行入口
# ============================
if __name__ == "__main__":
D, city_coords = generate_distance_matrix(CITIES, random=RANDOM)
F = generate_disturbance_matrix(CITIES)
# 1. 佈署死亡陷阱 (測試演算法是否會被騙)
D, F = inject_deceptive_trap(D, F, alpha=ALPHA, gamma=GAMMA, sigma=SIGMA)
# 2. 🌟 佈署黃金捷徑 (測試演算法能否找到狹窄深谷)
D, F = inject_hidden_shortcut(D, F)
# ======== 🌟 新增:動態自適應懲罰值 ========
# 找出矩陣中最遙遠的距離與最大的擾動
max_d = np.max(D)
max_f = np.max(F) if F is not None else 0
max_possible_cost = max_d + max_f
# 根據最大可能成本,動態設定懲罰值 (通常設為 10 倍 ~ 15 倍最穩定)
PENALTY = max_possible_cost * 12.0
BIG_PENALTY = PENALTY * 5.0
print(f"🔧 [自動調校] 偵測到單趟最大成本: {max_possible_cost:.2f}")
print(f"🔧 [自動調校] 動態 PENALTY 設為: {PENALTY:.2f}, BIG_PENALTY 設為: {BIG_PENALTY:.2f}")
# ==========================================
print("==================================================")
print(f"🚀 Quantum Pause Strategy Optimization (mTSP N={CITIES})")
print("==================================================")
print("\n[初始化] 距離矩陣 D:")
print(D)
print("\n[初始化] 擾動矩陣 F:")
print(F)
Q, _ = build_robust_qubo(D, F, penalty=PENALTY, big_penalty=BIG_PENALTY)
# 階段 1找相變點
s_list, variances, pilot_mks, best_s = pilot_search_phase_transition(Q, CITIES, D, F)
# 階段 2測試暫停時間
durations, dur_mks = test_pause_durations(Q, CITIES, D, F, best_s)
# 階段 3正式評估 (一次抓齊所有數據)
results = run_comparative_evaluations(Q, CITIES, D, F, best_s)
# 階段 4複雜度壓力測試與交叉點分析
s_m, s_e, p_m, p_e = run_complexity_scaling()
print("\n🏁 所有運算完成!開始繪製圖表...")
# 繪製圖表一:三大基礎分布對比
create_basic_distribution_charts(results)
# 繪製圖表二Pause SQA 深度分析
create_academic_charts(s_list, variances, pilot_mks, best_s, durations, dur_mks, results)
# 繪製圖表三:複雜度壓力測試與交叉點分析
plot_crossover(s_m, s_e, p_m, p_e)
print("🏁 所有實驗與圖表繪製完成!")
plt.show()