Files

156 lines
7.4 KiB
Python

import torch
import numpy as np
import matplotlib.pyplot as plt
import time
# ==========================================
# 0. 全局绘图参数与 CUDA 引擎初始化
# ==========================================
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)
N_ant, N_RF = 24, 4
clutter_angle = -15.0
noise_power = 1.0
INR_linear = 10**(30/10)
# 统一使用非均匀线阵 (NULA) 物理间隔
fc = 4.9e9
lam = 3e8 / fc
an_a = np.arange(0, 11*43 + 1, 43)
an_b = np.array([11*43 + 63])
an_c = 11*43 + 63 + np.arange(43, 11*43 + 1, 43)
delta_d = 1e-3 * np.concatenate((an_a, an_b, an_c)) # Shape: (24,)
delta_d_tensor = torch.tensor(delta_d, dtype=torch.float32, device=device).unsqueeze(1)
def gen_a(theta_deg):
theta_rad = torch.deg2rad(torch.tensor(theta_deg, dtype=torch.float32, device=device))
# 注意这里统一使用 NULA 的物理模型和负指数
return torch.exp(-1j * 2 * torch.pi * delta_d_tensor * torch.sin(theta_rad) / lam)
a_clutter = gen_a(clutter_angle)
R_interference = noise_power * torch.eye(N_ant, device=device) + INR_linear * (a_clutter @ a_clutter.mH)
dic_angles_full = torch.arange(-90, 90.5, 0.5, device=device)
A_dic_full = torch.cat([gen_a(th) / np.sqrt(N_ant) for th in dic_angles_full], dim=1)
angle_ranges = [np.arange(0, 11), np.arange(11, 21), np.arange(21, 31), np.arange(31, 41)]
snr_test_list = [0, 5, 10, 15, 20]
Monte_Carlo = 100
L_snapshots = 100
colors = ['#0072BD', '#D95319', '#EDB120', '#7E2F8E', '#77AC30']
def run_omp(w_opt, A_dic):
res = w_opt.clone()
F_RF_list = []
for k in range(N_RF):
idx = torch.argmax(torch.abs(A_dic.mH @ res))
F_RF_list.append(A_dic[:, idx:idx+1])
F_RF_mat = torch.cat(F_RF_list, dim=1)
res = w_opt - F_RF_mat @ (torch.linalg.pinv(F_RF_mat) @ w_opt)
return F_RF_mat
print(f"启动 CUDA 并行引擎,对 4 大扇区进行极速扫雷测试 (MC={Monte_Carlo})...")
start_time = time.time()
# 取消了这里原本的 fig, axs = plt.subplots(2, 2)
# 改为在循环内部动态创建单图
for fig_idx, test_angles in enumerate(angle_ranges):
print(f"正在计算扇区 {test_angles[0]}° ~ {test_angles[-1]}° ...")
# 【修改点 1】:为每一个扇区单独生成一张独立画布
fig, ax = plt.subplots(figsize=(9, 7))
RMSE_DFT_All = np.zeros((len(snr_test_list), len(test_angles)))
RMSE_FULL_All = np.zeros((len(snr_test_list), len(test_angles)))
RMSE_TSC_All = np.zeros((len(snr_test_list), len(test_angles)))
for snr_idx, snr_dB in enumerate(snr_test_list):
sig_p = 10**(snr_dB/10)
for a_idx, t_angle in enumerate(test_angles):
true_target_angle = t_angle + 0.04
theta_prior = round(true_target_angle)
window = 5
dic_tsc = torch.arange(theta_prior - window, theta_prior + window + 0.1, 0.1, device=device)
A_dic_tsc = torch.cat([gen_a(th) / np.sqrt(N_ant) for th in dic_tsc], dim=1)
angles_dft = torch.linspace(theta_prior - window, theta_prior + window, N_RF, device=device)
F_RF_dft = torch.cat([gen_a(th) / np.sqrt(N_ant) for th in angles_dft], dim=1)
a_prior_broad = torch.zeros((N_ant, 1), dtype=torch.complex64, device=device)
for tb in torch.arange(theta_prior - window, theta_prior + window + 0.1, 0.5, device=device):
a_prior_broad += gen_a(tb)
a_prior_broad /= torch.norm(a_prior_broad)
w_dig_prior = torch.linalg.inv(R_interference) @ a_prior_broad
w_dig_prior /= torch.norm(w_dig_prior)
F_RF_full = run_omp(w_dig_prior, A_dic_full)
F_RF_tsc = run_omp(w_dig_prior, A_dic_tsc)
scan_grid = torch.arange(theta_prior - window, theta_prior + window + 0.01, 0.01, device=device)
A_scan = torch.cat([gen_a(th) for th in scan_grid], dim=1)
A_eff_dft = F_RF_dft.mH @ A_scan
A_eff_full = F_RF_full.mH @ A_scan
A_eff_tsc = F_RF_tsc.mH @ A_scan
a_target = gen_a(true_target_angle)
s_t = np.sqrt(sig_p/2) * (torch.randn(Monte_Carlo, 1, L_snapshots, device=device) + 1j * torch.randn(Monte_Carlo, 1, L_snapshots, device=device))
s_c = np.sqrt(INR_linear/2) * (torch.randn(Monte_Carlo, 1, L_snapshots, device=device) + 1j * torch.randn(Monte_Carlo, 1, L_snapshots, device=device))
n_t = np.sqrt(noise_power/2) * (torch.randn(Monte_Carlo, N_ant, L_snapshots, device=device) + 1j * torch.randn(Monte_Carlo, N_ant, L_snapshots, device=device))
X = a_target.unsqueeze(0) * s_t + a_clutter.unsqueeze(0) * s_c + n_t
def process_hybrid(F_RF, A_eff):
Y = F_RF.mH.unsqueeze(0) @ X
R = (Y @ Y.mH) / L_snapshots
R += (0.05 * torch.diagonal(R, dim1=-2, dim2=-1).sum(-1).view(-1, 1, 1) / N_RF) * torch.eye(N_RF, device=device).unsqueeze(0)
inv_R = torch.linalg.inv(R)
P = 1.0 / torch.abs(torch.einsum('si, bij, js -> bs', A_eff.conj().T, inv_R, A_eff))
return np.sqrt(np.mean(((scan_grid[torch.argmax(P, dim=1)] - true_target_angle)**2).cpu().numpy()))
RMSE_DFT_All[snr_idx, a_idx] = process_hybrid(F_RF_dft, A_eff_dft)
RMSE_FULL_All[snr_idx, a_idx] = process_hybrid(F_RF_full, A_eff_full)
RMSE_TSC_All[snr_idx, a_idx] = process_hybrid(F_RF_tsc, A_eff_tsc)
c_str = colors[snr_idx]
lbl_suffix = f" (SNR={snr_dB}dB)"
ax.semilogy(test_angles, RMSE_DFT_All[snr_idx, :], '--^', color=c_str, linewidth=1.5, label='DFT'+lbl_suffix)
ax.semilogy(test_angles, RMSE_FULL_All[snr_idx, :], ':x', color=c_str, linewidth=2, label='传统OMP'+lbl_suffix)
ax.semilogy(test_angles, RMSE_TSC_All[snr_idx, :], '-o', color=c_str, linewidth=2.5, label='TSC-OMP'+lbl_suffix)
ax.set_xlim([test_angles[0], test_angles[-1]])
ax.set_xticks(test_angles)
ax.grid(True, which="both", ls="--", alpha=0.5)
ax.set_ylim([1e-3, 20])
ax.set_xlabel('目标真实出现角度 $\\theta (\\circ)$', fontsize=12, fontweight='bold')
ax.set_ylabel(r'测角 RMSE $(^\circ)$', fontsize=12, fontweight='bold')
ax.set_title(f'宽波束先验引导下,扇区 ({test_angles[0]}°~{test_angles[-1]}°) 鲁棒性评估', fontsize=13, fontweight='bold')
# 【修改点 2】:独立设置每张图的图例。
plt.tight_layout()
fig.subplots_adjust(bottom=0.25)
# 【关键调整】:将 bbox_to_anchor 的 Y 坐标从 -0.15 提上来,改为 -0.08
# 这样图例就会离 X 轴更近,避开底部操作条
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.10), ncol=3, fontsize=10)
# 【修改点 3】:自动保存
file_name = f'RMSE_Sector_{test_angles[0]}_{test_angles[-1]}.png'
plt.savefig(file_name, dpi=300, bbox_inches='tight')
print(f" 已保存独立图表: {file_name}")
print(f"全扇区计算完毕!总耗时: {time.time() - start_time:.4f} 秒。")
# 一次性在屏幕上弹出生成的4张独立图表
plt.show()