import time import numpy as np import matplotlib.pyplot as plt from itertools import permutations import math import openjij as oj # ==================== 可調整參數區 ==================== # 問題規模設定 CITIES = 15 # 測試建議先用 12-15,確認圖表沒問題再上調到 30 RANDOM = False RANDOM_SEED = 42 COORD_RANGE = (0.0, 10.0) # 演算法執行設定 NUM_RUNS = 20 # 正式比較的執行次數 BETA = 10.0 # SQA 的逆溫度參數 # QUBO 參數 PENALTY = 20.0 BIG_PENALTY = 9999.0 # 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 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 # ============================ # 核心創新功能區 (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, _ = 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 = [[1.0, BETA, 100]] else: sched = [[s_star, BETA, 50], [s_star, BETA, d], [1.0, BETA, 50]] res = sampler.sample_qubo(Q, schedule=sched, num_reads=10) mk, _ = 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)") # 資料容器 (包含 Makespan, Risk, Energy) results = { 'sa': {'mk': [], 'risk': [], 'energy': []}, 'sqa': {'mk': [], 'risk': [], 'energy': []}, 'pause': {'mk': [], 'risk': [], 'energy': []} } sqa_sampler = oj.SQASampler() sa_sampler = oj.SASampler() std_sched = [[1.0, BETA, 200]] pause_sched = [[s_star, BETA, 50], [s_star, BETA, 100], [1.0, BETA, 50]] for r in range(NUM_RUNS): # 1. SA (作為全方位 Baseline,現在也跑多次) res_sa = sa_sampler.sample_qubo(Q, num_reads=10) mk_sa, risk_sa = get_makespan_and_risk(res_sa.first.sample, N, D, F) results['sa']['mk'].append(mk_sa) results['sa']['risk'].append(risk_sa) results['sa']['energy'].append(res_sa.first.energy) # 2. Standard SQA res_std = sqa_sampler.sample_qubo(Q, schedule=std_sched, num_reads=10) mk_std, risk_std = get_makespan_and_risk(res_std.first.sample, N, D, F) results['sqa']['mk'].append(mk_std) results['sqa']['risk'].append(risk_std) results['sqa']['energy'].append(res_std.first.energy) # 3. Pause SQA res_pause = sqa_sampler.sample_qubo(Q, schedule=pause_sched, num_reads=10) mk_pause, risk_pause = get_makespan_and_risk(res_pause.first.sample, N, D, F) results['pause']['mk'].append(mk_pause) results['pause']['risk'].append(risk_pause) results['pause']['energy'].append(res_pause.first.energy) print(f" SA 平均 Makespan: {np.mean(results['sa']['mk']):.2f}") print(f" Standard-SQA 平均 Makespan: {np.mean(results['sqa']['mk']):.2f}") print(f" Pause-SQA 平均 Makespan: {np.mean(results['pause']['mk']):.2f} (Proposed)") return results # ============================ # 學術圖表繪製 (Academic Visualization) # ============================ def create_basic_distribution_charts(results): """新增的圖表一:繪製三大指標的分布對比圖 (MinMax, Energy, Disturbance)""" fig, axes = plt.subplots(1, 3, figsize=(18, 6)) fig.suptitle(f"Algorithm Performance Distributions (N={CITIES})", fontsize=16, fontweight='bold') labels = ['SA', 'Standard-SQA', 'Pause-SQA'] colors = ['gray', 'steelblue', 'firebrick'] # 1. Makespan 分布 data_mk = [results['sa']['mk'], results['sqa']['mk'], results['pause']['mk']] parts1 = axes[0].violinplot(data_mk, showmeans=True) axes[0].set_title("Makespan (minMax) Distribution") axes[0].set_ylabel("Distance Cost") # 2. Energy 分布 data_energy = [results['sa']['energy'], results['sqa']['energy'], results['pause']['energy']] parts2 = axes[1].violinplot(data_energy, showmeans=True) axes[1].set_title("QUBO System Energy Distribution") axes[1].set_ylabel("System Energy") # 3. Disturbance Risk 分布 data_risk = [results['sa']['risk'], results['sqa']['risk'], results['pause']['risk']] parts3 = axes[2].violinplot(data_risk, showmeans=True) axes[2].set_title(r"$H_\infty$ Disturbance Risk Distribution") axes[2].set_ylabel("Risk Penalty") # 設定外觀 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() 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) # Chart 2: Pareto Front ax2 = plt.subplot(2, 2, 2) ax2.set_title("Chart 2: Robustness Pareto Front", fontsize=12) ax2.scatter(results['sqa']['mk'], results['sqa']['risk'], c='steelblue', alpha=0.7, s=80, label='Standard-SQA') ax2.scatter(results['pause']['mk'], results['pause']['risk'], c='firebrick', alpha=0.9, s=120, marker='*', label='Pause-SQA') ax2.set_xlabel("Makespan") ax2.set_ylabel("Disturbance Risk") ax2.legend() ax2.grid(alpha=0.3, linestyle='--') # Chart 3: Reliability Violin Plot ax3 = plt.subplot(2, 2, 3) ax3.set_title("Chart 3: Final Makespan Comparison", fontsize=12) parts = ax3.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) ax3.set_xticks([1, 2]) ax3.set_xticklabels(['Standard-SQA', 'Pause-SQA']) sa_mean = np.mean(results['sa']['mk']) ax3.axhline(y=sa_mean, color='gray', linestyle=':', linewidth=2, label=f'SA Mean: {sa_mean:.1f}') ax3.legend() ax3.grid(alpha=0.3, axis='y') # Chart 4: Effect of Thermalization Time ax4 = plt.subplot(2, 2, 4) ax4.set_title(f"Chart 4: Effect of Thermalization Time at $s^*={best_s:.1f}$", fontsize=12) ax4.plot(durations, dur_makespans, marker='D', color='darkorange', linewidth=2.5, markersize=8) ax4.set_xlabel("Pause Duration (Sweeps)") ax4.set_ylabel("Optimized Makespan") ax4.grid(alpha=0.3, linestyle='--') 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() # ============================ # 主程式執行入口 # ============================ if __name__ == "__main__": D, city_coords = generate_distance_matrix(CITIES, random=RANDOM) F = generate_disturbance_matrix(CITIES) print("==================================================") print(f"🚀 Quantum Pause Strategy Optimization (mTSP N={CITIES})") print("==================================================") Q, _ = build_robust_qubo(D, F) # 階段 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) # 繪製圖表一:三大基礎分布對比 create_basic_distribution_charts(results) # 繪製圖表二:Pause SQA 深度分析 create_academic_charts(s_list, variances, pilot_mks, best_s, durations, dur_mks, results) print("🏁 實驗完成!")