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()