import os import time import math import numpy as np import torch import matplotlib.pyplot as plt # ========================================================= # 0. 全局设置 # ========================================================= plt.rcParams['axes.unicode_minus'] = False plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] plt.rcParams['mathtext.fontset'] = 'stix' device = torch.device("cuda" if torch.cuda.is_available() else "cpu") torch.manual_seed(2024) np.random.seed(2024) print(f"使用设备: {device}") SAVE_DIR = "analysis_results_ula_nula" os.makedirs(SAVE_DIR, exist_ok=True) ARRAY_TYPES = ["ULA", "NULA"] # ========================================================= # 1. 基本参数 # ========================================================= N_ant_default = 24 N_RF_default = 4 target_angle_default = 5.0 clutter_angle_default = -15.0 noise_power = 1.0 INR_linear = 10 ** (30 / 10) L_snapshots = 100 # 分析配置 SNR_dB_test = 10 Monte_Carlo_test = 80 SCAN_STEP = 0.02 # 颜色 method_colors = { "Analog": "#77AC30", "Digital": "#000000", "DFT": "#4DBEEE", "OMP": "#D95319", "TSC-OMP": "#0072BD", } array_colors = { "ULA": "#7E2F8E", "NULA": "#EDB120", } # ========================================================= # 2. 阵列几何 # ========================================================= def get_positions_lambda(array_type: str, N_ant: int): """ 返回阵列位置,单位统一为“波长 λ” - ULA: 半波长均匀线阵 - NULA: * N_ant == 24 时,严格复刻你现有主线脚本中的非均匀阵列物理位置 * 否则使用一个可复现的广义非均匀阵列,用于复杂度规模扫描 """ array_type = array_type.upper() if array_type == "ULA": pos = 0.5 * np.arange(-(N_ant - 1) / 2, (N_ant - 1) / 2 + 1) return pos.astype(np.float32) # 精确复刻你当前 NULA 主线 if array_type == "NULA" and N_ant == 24: fc = 4.9e9 lam = 3e8 / fc a = np.arange(0, 11 * 43 + 1, 43) b = np.array([11 * 43 + 63]) c = 11 * 43 + 63 + np.arange(43, 11 * 43 + 1, 43) delta_d = 1e-3 * np.concatenate((a, b, c)) # meter pos = delta_d / lam return pos.astype(np.float32) # 广义 NULA,用于 N_ant != 24 的复杂度缩放分析 base = 0.5 * np.arange(N_ant) jitter = 0.08 * np.sin(np.linspace(0, 3 * np.pi, N_ant)) pos = base + jitter pos = pos - np.mean(pos) return pos.astype(np.float32) def gen_a(theta_deg, N_ant, array_type="NULA"): """ 导向矢量,统一使用 exp(-j 2π p sinθ) p 为以 λ 为单位的阵元位置 """ pos = get_positions_lambda(array_type, N_ant) pos_t = torch.tensor(pos, dtype=torch.float32, device=device).unsqueeze(1) theta_rad = torch.deg2rad(torch.tensor(theta_deg, dtype=torch.float32, device=device)) return torch.exp(-1j * 2 * torch.pi * pos_t * torch.sin(theta_rad)) def build_dictionary(angle_grid, N_ant, array_type="NULA"): return torch.cat( [gen_a(float(th), N_ant, array_type) / math.sqrt(N_ant) for th in angle_grid], dim=1 ) def sync_if_needed(): if device.type == "cuda": torch.cuda.synchronize() # ========================================================= # 3. 五种架构模块 # ========================================================= def extract_omp(w_opt, A_dic, N_RF): """ 传统 OMP / TSC-OMP 公用提取函数 """ res = w_opt.clone() F_RF = None for _ in range(N_RF): proj = A_dic.mH @ res idx = torch.argmax(torch.abs(proj)) atom = A_dic[:, idx:idx+1] F_RF = atom if F_RF is None else torch.cat((F_RF, atom), dim=1) res = w_opt - F_RF @ (torch.linalg.pinv(F_RF) @ w_opt) return F_RF def build_dft_rf(theta_prior, window_half_width, N_ant, N_RF, array_type="NULA"): angles_dft = torch.linspace( theta_prior - window_half_width, theta_prior + window_half_width, N_RF, device=device ) return torch.cat( [gen_a(float(th), N_ant, array_type) / math.sqrt(N_ant) for th in angles_dft], dim=1 ) def make_prior_broad(theta_prior, window_half_width, N_ant, array_type="NULA"): a_prior_broad = torch.zeros((N_ant, 1), dtype=torch.complex64, device=device) for tb in torch.arange( theta_prior - window_half_width, theta_prior + window_half_width + 0.1, 0.5, device=device ): a_prior_broad += gen_a(float(tb), N_ant, array_type) a_prior_broad /= torch.norm(a_prior_broad) return a_prior_broad def calc_hybrid_weight(F_RF, a_target, R_in, N_RF): a_eff = F_RF.mH @ a_target R_eff = F_RF.mH @ R_in @ F_RF R_eff = R_eff + (0.05 * torch.trace(R_eff).real / N_RF) * torch.eye(N_RF, device=device) inv_R_eff = torch.linalg.inv(R_eff) F_BB = (inv_R_eff @ a_eff) / (a_eff.mH @ inv_R_eff @ a_eff) w = F_RF @ F_BB return w / torch.norm(w) def build_all_structures(true_target_angle, clutter_angle, N_ant, N_RF, theta_prior, window_half_width, array_type="NULA"): """ 构建五种架构所需的静态权值/射频矩阵 """ a_target = gen_a(true_target_angle, N_ant, array_type) a_clutter = gen_a(clutter_angle, N_ant, array_type) R_interf = noise_power * torch.eye(N_ant, device=device) + INR_linear * (a_clutter @ a_clutter.mH) # 用于 OMP / TSC-OMP 的先验宽波束 a_prior_broad = make_prior_broad(theta_prior, window_half_width, N_ant, array_type) w_digital_prior = torch.linalg.inv(R_interf) @ a_prior_broad w_digital_prior /= torch.norm(w_digital_prior) full_grid = np.arange(-90, 90.5, 0.5) tsc_grid = np.arange( theta_prior - window_half_width, theta_prior + window_half_width + 0.1, 0.1 ) A_dic_full = build_dictionary(full_grid, N_ant, array_type) A_dic_tsc = build_dictionary(tsc_grid, N_ant, array_type) F_RF_dft = build_dft_rf(theta_prior, window_half_width, N_ant, N_RF, array_type) F_RF_omp = extract_omp(w_digital_prior, A_dic_full, N_RF) F_RF_tsc = extract_omp(w_digital_prior, A_dic_tsc, N_RF) # 静态协方差,用于纯数字与混合权值展示 sig_power_static = 10 ** (0 / 10) R_static = (noise_power * torch.eye(N_ant, device=device) + sig_power_static * (a_target @ a_target.mH) + INR_linear * (a_clutter @ a_clutter.mH)) R_static_dl = R_static + (0.01 * torch.trace(R_static).real / N_ant) * torch.eye(N_ant, device=device) # 纯模拟 w_analog = a_target / torch.norm(a_target) # 纯数字 MVDR inv_R = torch.linalg.inv(R_static_dl) w_digital = (inv_R @ a_target) / (a_target.mH @ inv_R @ a_target) w_digital = w_digital / torch.norm(w_digital) return { "a_target": a_target, "a_clutter": a_clutter, "w_analog": w_analog, "w_digital": w_digital, "F_RF_dft": F_RF_dft, "F_RF_omp": F_RF_omp, "F_RF_tsc": F_RF_tsc, } # ========================================================= # 4. 统一 RMSE 评估 # ========================================================= def estimate_rmse_all_methods(true_target_angle, clutter_angle, N_ant, N_RF, snr_db, theta_prior, window_half_width, array_type="NULA", mc=80, scan_step=0.02): """ 返回: analog_rmse, digital_rmse, dft_rmse, omp_rmse, tsc_rmse """ sig_power = 10 ** (snr_db / 10) structures = build_all_structures( true_target_angle, clutter_angle, N_ant, N_RF, theta_prior, window_half_width, array_type ) a_target = structures["a_target"] a_clutter = structures["a_clutter"] w_analog = structures["w_analog"] w_digital = structures["w_digital"] F_RF_dft = structures["F_RF_dft"] F_RF_omp = structures["F_RF_omp"] F_RF_tsc = structures["F_RF_tsc"] scan_grid = torch.arange( theta_prior - window_half_width, theta_prior + window_half_width + 0.001, scan_step, device=device ) A_scan = torch.cat([gen_a(float(th), N_ant, array_type) for th in scan_grid], dim=1) s_t = np.sqrt(sig_power / 2) * ( torch.randn(mc, 1, L_snapshots, device=device) + 1j * torch.randn(mc, 1, L_snapshots, device=device) ) s_c = np.sqrt(INR_linear / 2) * ( torch.randn(mc, 1, L_snapshots, device=device) + 1j * torch.randn(mc, 1, L_snapshots, device=device) ) n_t = np.sqrt(noise_power / 2) * ( torch.randn(mc, N_ant, L_snapshots, device=device) + 1j * torch.randn(mc, N_ant, L_snapshots, device=device) ) X = a_target.unsqueeze(0) * s_t + a_clutter.unsqueeze(0) * s_c + n_t # -------- 纯模拟:Bartlett -------- P_ana = torch.abs(torch.einsum('si,bij,js->bs', A_scan.conj().T, torch.bmm(X, X.mH) / L_snapshots, A_scan)) ang_ana = scan_grid[torch.argmax(P_ana, dim=1)] # -------- 纯数字 MVDR:Capon -------- R_in = torch.bmm(X, X.mH) / L_snapshots trace_R_in = torch.diagonal(R_in, dim1=-2, dim2=-1).sum(-1).real R_in_dl = R_in + (0.05 * trace_R_in / N_ant).view(-1, 1, 1) * torch.eye(N_ant, device=device).unsqueeze(0) inv_R_in = torch.linalg.inv(R_in_dl) P_dig = 1.0 / torch.abs(torch.einsum('si,bij,js->bs', A_scan.conj().T, inv_R_in, A_scan)) ang_dig = scan_grid[torch.argmax(P_dig, dim=1)] # -------- 混合三种 -------- def process_hybrid(F_RF): A_eff = F_RF.mH @ A_scan Y = F_RF.mH.unsqueeze(0) @ X R_hyb = torch.bmm(Y, Y.mH) / L_snapshots trace_R = torch.diagonal(R_hyb, dim1=-2, dim2=-1).sum(-1).real R_hyb = R_hyb + (0.05 * trace_R / N_RF).view(-1, 1, 1) * torch.eye(N_RF, device=device).unsqueeze(0) inv_R_hyb = torch.linalg.inv(R_hyb) P = 1.0 / torch.abs(torch.einsum('si,bij,js->bs', A_eff.conj().T, inv_R_hyb, A_eff)) return scan_grid[torch.argmax(P, dim=1)] ang_dft = process_hybrid(F_RF_dft) ang_omp = process_hybrid(F_RF_omp) ang_tsc = process_hybrid(F_RF_tsc) def rmse(x): return torch.sqrt(torch.mean((x - true_target_angle) ** 2)).item() return rmse(ang_ana), rmse(ang_dig), rmse(ang_dft), rmse(ang_omp), rmse(ang_tsc) # ========================================================= # 5. 复杂度分析 # ========================================================= def complexity_proxy(N_ant, N_RF, D_full, D_tsc): """ 五种架构的复杂度代理量 """ c_ana = N_ant c_dig = N_ant ** 3 c_dft = N_ant * N_RF + N_RF ** 3 c_omp = N_RF * N_ant * D_full + N_RF ** 4 c_tsc = N_RF * N_ant * D_tsc + N_RF ** 4 return c_ana, c_dig, c_dft, c_omp, c_tsc def run_complexity_analysis(): print("\n========== 开始复杂度分析(ULA + NULA) ==========") target_angle = target_angle_default clutter_angle = clutter_angle_default N_RF = N_RF_default window_half_width = 5.0 repeat_times = 20 N_ant_list = [8, 12, 16, 20, 24, 28, 32] results_runtime = {} results_proxy = {} for array_type in ARRAY_TYPES: print(f"\n--- 复杂度分析: {array_type} ---") t_ana, t_dig, t_dft, t_omp, t_tsc = [], [], [], [], [] c_ana_list, c_dig_list, c_dft_list, c_omp_list, c_tsc_list = [], [], [], [], [] for N_ant in N_ant_list: a_target = gen_a(target_angle, N_ant, array_type) a_clutter = gen_a(clutter_angle, N_ant, array_type) theta_prior = round(target_angle) full_grid = np.arange(-90, 90.5, 0.5) tsc_grid = np.arange(theta_prior - window_half_width, theta_prior + window_half_width + 0.1, 0.1) A_dic_full = build_dictionary(full_grid, N_ant, array_type) A_dic_tsc = build_dictionary(tsc_grid, N_ant, array_type) R_interf = noise_power * torch.eye(N_ant, device=device) + INR_linear * (a_clutter @ a_clutter.mH) a_prior_broad = make_prior_broad(theta_prior, window_half_width, N_ant, array_type) # 纯模拟 sync_if_needed() tic = time.perf_counter() for _ in range(repeat_times): _ = a_target / torch.norm(a_target) sync_if_needed() t_ana.append((time.perf_counter() - tic) / repeat_times * 1e3) # 纯数字 MVDR sync_if_needed() tic = time.perf_counter() for _ in range(repeat_times): R_static = (noise_power * torch.eye(N_ant, device=device) + (a_target @ a_target.mH) + INR_linear * (a_clutter @ a_clutter.mH)) R_static_dl = R_static + (0.01 * torch.trace(R_static).real / N_ant) * torch.eye(N_ant, device=device) inv_R = torch.linalg.inv(R_static_dl) _ = (inv_R @ a_target) / (a_target.mH @ inv_R @ a_target) sync_if_needed() t_dig.append((time.perf_counter() - tic) / repeat_times * 1e3) # DFT sync_if_needed() tic = time.perf_counter() for _ in range(repeat_times): F_RF_dft = build_dft_rf(theta_prior, window_half_width, N_ant, N_RF, array_type) R_static = (noise_power * torch.eye(N_ant, device=device) + (a_target @ a_target.mH) + INR_linear * (a_clutter @ a_clutter.mH)) _ = calc_hybrid_weight(F_RF_dft, a_target, R_static, N_RF) sync_if_needed() t_dft.append((time.perf_counter() - tic) / repeat_times * 1e3) # OMP/TSC w_digital_prior = torch.linalg.inv(R_interf) @ a_prior_broad w_digital_prior /= torch.norm(w_digital_prior) sync_if_needed() tic = time.perf_counter() for _ in range(repeat_times): _ = extract_omp(w_digital_prior, A_dic_full, N_RF) sync_if_needed() t_omp.append((time.perf_counter() - tic) / repeat_times * 1e3) sync_if_needed() tic = time.perf_counter() for _ in range(repeat_times): _ = extract_omp(w_digital_prior, A_dic_tsc, N_RF) sync_if_needed() t_tsc.append((time.perf_counter() - tic) / repeat_times * 1e3) c_ana, c_dig, c_dft, c_omp, c_tsc = complexity_proxy( N_ant, N_RF, len(full_grid), len(tsc_grid) ) c_ana_list.append(c_ana) c_dig_list.append(c_dig) c_dft_list.append(c_dft) c_omp_list.append(c_omp) c_tsc_list.append(c_tsc) results_runtime[array_type] = { "N_ant": N_ant_list, "Analog": t_ana, "Digital": t_dig, "DFT": t_dft, "OMP": t_omp, "TSC-OMP": t_tsc, } results_proxy[array_type] = { "N_ant": N_ant_list, "Analog": c_ana_list, "Digital": c_dig_list, "DFT": c_dft_list, "OMP": c_omp_list, "TSC-OMP": c_tsc_list, } # 每种阵列单独出图 plt.figure(figsize=(10, 6)) for method in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]: plt.plot(N_ant_list, results_runtime[array_type][method], linewidth=2, label=method, color=method_colors[method]) plt.grid(True, ls='--', alpha=0.6) plt.xlabel('阵元数 N_ant') plt.ylabel('平均运行时间 (ms)') plt.title(f'复杂度分析:运行时间随阵元数变化 ({array_type})') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, f'complexity_runtime_vs_ant_{array_type}.png'), dpi=300) plt.show() plt.figure(figsize=(10, 6)) for method in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]: plt.plot(N_ant_list, results_proxy[array_type][method], linewidth=2, label=method, color=method_colors[method]) plt.grid(True, ls='--', alpha=0.6) plt.xlabel('阵元数 N_ant') plt.ylabel('复杂度代理量') plt.title(f'复杂度分析:复杂度代理量随阵元数变化 ({array_type})') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, f'complexity_proxy_vs_ant_{array_type}.png'), dpi=300) plt.show() # ULA vs NULA 对照图(固定 24 阵元) idx_24 = N_ant_list.index(24) methods = ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"] ula_vals = [results_runtime["ULA"][m][idx_24] for m in methods] nula_vals = [results_runtime["NULA"][m][idx_24] for m in methods] x = np.arange(len(methods)) width = 0.35 plt.figure(figsize=(10, 6)) plt.bar(x - width/2, ula_vals, width, label='ULA', color=array_colors["ULA"]) plt.bar(x + width/2, nula_vals, width, label='NULA', color=array_colors["NULA"]) plt.xticks(x, methods) plt.ylabel('平均运行时间 (ms)') plt.title('复杂度分析:24阵元下 ULA 与 NULA 运行时间对比') plt.grid(True, axis='y', ls='--', alpha=0.5) plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, 'complexity_runtime_compare_ULA_NULA_24ant.png'), dpi=300) plt.show() print("复杂度分析完成。") return results_runtime, results_proxy # ========================================================= # 6. 局限性分析 # ========================================================= def run_limitation_analysis(): print("\n========== 开始局限性分析(ULA + NULA) ==========") N_ant = N_ant_default N_RF = N_RF_default true_target_angle = target_angle_default clutter_angle = clutter_angle_default snr_db = SNR_dB_test limitation_results = {} for array_type in ARRAY_TYPES: print(f"\n--- 局限性分析: {array_type} ---") limitation_results[array_type] = {} # 6.1 先验误差敏感性 prior_errors = np.arange(-8, 9, 1) curves_prior = {m: [] for m in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]} for e in prior_errors: theta_prior = round(true_target_angle) + e r = estimate_rmse_all_methods( true_target_angle, clutter_angle, N_ant, N_RF, snr_db, theta_prior, 5.0, array_type, mc=Monte_Carlo_test, scan_step=SCAN_STEP ) for m, v in zip(["Analog", "Digital", "DFT", "OMP", "TSC-OMP"], r): curves_prior[m].append(v) limitation_results[array_type]["prior_errors"] = prior_errors limitation_results[array_type]["prior_curves"] = curves_prior plt.figure(figsize=(10, 6)) for method in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]: plt.semilogy(prior_errors, curves_prior[method], linewidth=2, label=method, color=method_colors[method]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('先验角误差 (°)') plt.ylabel('RMSE (°)') plt.title(f'局限性分析:先验误差敏感性 ({array_type}, SNR={snr_db}dB)') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, f'limitation_prior_error_{array_type}.png'), dpi=300) plt.show() # 6.2 波门宽度敏感性 widths = [2, 3, 4, 5, 6, 8, 10] curves_width = {m: [] for m in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]} for w in widths: theta_prior = round(true_target_angle) r = estimate_rmse_all_methods( true_target_angle, clutter_angle, N_ant, N_RF, snr_db, theta_prior, float(w), array_type, mc=Monte_Carlo_test, scan_step=SCAN_STEP ) for m, v in zip(["Analog", "Digital", "DFT", "OMP", "TSC-OMP"], r): curves_width[m].append(v) limitation_results[array_type]["widths"] = widths limitation_results[array_type]["width_curves"] = curves_width plt.figure(figsize=(10, 6)) for method in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]: plt.semilogy(widths, curves_width[method], linewidth=2, label=method, color=method_colors[method]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('波门半宽 (°)') plt.ylabel('RMSE (°)') plt.title(f'局限性分析:波门宽度敏感性 ({array_type}, SNR={snr_db}dB)') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, f'limitation_window_width_{array_type}.png'), dpi=300) plt.show() # 6.3 杂波逼近目标时的性能退化 clutter_list = np.array([-25, -20, -15, -10, -8, -6, -4, -2]) sep_deg = np.abs(clutter_list - true_target_angle) curves_sep = {m: [] for m in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]} for cang in clutter_list: theta_prior = round(true_target_angle) r = estimate_rmse_all_methods( true_target_angle, float(cang), N_ant, N_RF, snr_db, theta_prior, 5.0, array_type, mc=Monte_Carlo_test, scan_step=SCAN_STEP ) for m, v in zip(["Analog", "Digital", "DFT", "OMP", "TSC-OMP"], r): curves_sep[m].append(v) limitation_results[array_type]["sep_deg"] = sep_deg limitation_results[array_type]["sep_curves"] = curves_sep plt.figure(figsize=(10, 6)) for method in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]: plt.semilogy(sep_deg, curves_sep[method], linewidth=2, label=method, color=method_colors[method]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('目标-杂波角距 |θ_t-θ_c| (°)') plt.ylabel('RMSE (°)') plt.title(f'局限性分析:杂波逼近目标时的性能退化 ({array_type})') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, f'limitation_clutter_separation_{array_type}.png'), dpi=300) plt.show() # ---------------- ULA vs NULA 对照图(重点方法可读性更高) ---------------- # 先验误差:比较 TSC-OMP plt.figure(figsize=(10, 6)) for array_type in ARRAY_TYPES: x = limitation_results[array_type]["prior_errors"] y = limitation_results[array_type]["prior_curves"]["TSC-OMP"] plt.semilogy(x, y, linewidth=2.5, label=f'TSC-OMP ({array_type})', color=array_colors[array_type]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('先验角误差 (°)') plt.ylabel('RMSE (°)') plt.title('ULA 与 NULA 对照:TSC-OMP 先验误差敏感性') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, 'compare_prior_error_TSC_ULA_NULA.png'), dpi=300) plt.show() # 波门宽度:比较 TSC-OMP plt.figure(figsize=(10, 6)) for array_type in ARRAY_TYPES: x = limitation_results[array_type]["widths"] y = limitation_results[array_type]["width_curves"]["TSC-OMP"] plt.semilogy(x, y, linewidth=2.5, label=f'TSC-OMP ({array_type})', color=array_colors[array_type]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('波门半宽 (°)') plt.ylabel('RMSE (°)') plt.title('ULA 与 NULA 对照:TSC-OMP 波门宽度敏感性') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, 'compare_window_width_TSC_ULA_NULA.png'), dpi=300) plt.show() # 杂波逼近:比较 TSC-OMP plt.figure(figsize=(10, 6)) for array_type in ARRAY_TYPES: x = limitation_results[array_type]["sep_deg"] y = limitation_results[array_type]["sep_curves"]["TSC-OMP"] plt.semilogy(x, y, linewidth=2.5, label=f'TSC-OMP ({array_type})', color=array_colors[array_type]) plt.grid(True, which='both', ls='--', alpha=0.6) plt.xlabel('目标-杂波角距 |θ_t-θ_c| (°)') plt.ylabel('RMSE (°)') plt.title('ULA 与 NULA 对照:TSC-OMP 杂波邻近敏感性') plt.legend() plt.tight_layout() plt.savefig(os.path.join(SAVE_DIR, 'compare_clutter_sep_TSC_ULA_NULA.png'), dpi=300) plt.show() print("局限性分析完成。") return limitation_results # ========================================================= # 7. 主程序 # ========================================================= if __name__ == "__main__": runtime_results, proxy_results = run_complexity_analysis() limitation_results = run_limitation_analysis() print("\n全部分析完成,结果保存在:", SAVE_DIR)