上传文件至「Graduation Design」
This commit is contained in:
@@ -0,0 +1,625 @@
|
||||
|
||||
import math
|
||||
import time
|
||||
import numpy as np
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
|
||||
import torch
|
||||
import matplotlib as mpl
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
||||
from matplotlib import font_manager
|
||||
|
||||
|
||||
# =========================================================
|
||||
# 0. 全局设置
|
||||
# =========================================================
|
||||
def configure_matplotlib_fonts():
|
||||
"""自动选择可用中文字体,修复中文显示问题。"""
|
||||
candidate_fonts = [
|
||||
"Microsoft YaHei",
|
||||
"SimHei",
|
||||
"Noto Sans CJK SC",
|
||||
"Noto Serif CJK SC",
|
||||
"WenQuanYi Zen Hei",
|
||||
"AR PL UMing CN",
|
||||
"PingFang SC",
|
||||
"Heiti SC",
|
||||
"STHeiti",
|
||||
"Songti SC",
|
||||
]
|
||||
installed = {f.name for f in font_manager.fontManager.ttflist}
|
||||
selected = None
|
||||
for name in candidate_fonts:
|
||||
if name in installed:
|
||||
selected = name
|
||||
break
|
||||
|
||||
mpl.rcParams["axes.unicode_minus"] = False
|
||||
mpl.rcParams["mathtext.fontset"] = "stix"
|
||||
if selected:
|
||||
mpl.rcParams["font.sans-serif"] = [selected]
|
||||
else:
|
||||
mpl.rcParams["font.sans-serif"] = ["DejaVu Sans"]
|
||||
|
||||
|
||||
configure_matplotlib_fonts()
|
||||
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
torch.manual_seed(2024)
|
||||
np.random.seed(2024)
|
||||
|
||||
L_SNAPSHOTS_DEFAULT = 100
|
||||
MC_DEFAULT = 100
|
||||
|
||||
# 颜色表:修复 METHOD_COLORS 未定义
|
||||
METHOD_COLORS = {
|
||||
"Analog": "#77AC30",
|
||||
"Digital": "#000000",
|
||||
"DFT": "#4DBEEE",
|
||||
"OMP": "#D95319",
|
||||
"TSC-OMP": "#0072BD",
|
||||
}
|
||||
|
||||
|
||||
# =========================================================
|
||||
# 1. 算法引擎
|
||||
# =========================================================
|
||||
class BeamEngine:
|
||||
def __init__(self):
|
||||
self.device = device
|
||||
self.N_ant = 24
|
||||
self.N_RF = 4
|
||||
self.noise_power = 1.0
|
||||
self.INR_linear = 10 ** (30 / 10)
|
||||
|
||||
# ---------- 阵列模型 ----------
|
||||
def get_positions_lambda(self, array_type: str, n_ant: int):
|
||||
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)
|
||||
|
||||
# 24 阵元 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))
|
||||
pos = delta_d / lam
|
||||
return pos.astype(np.float32)
|
||||
|
||||
# 兜底广义 NULA
|
||||
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(self, theta_deg, array_type="ULA"):
|
||||
pos = self.get_positions_lambda(array_type, self.N_ant)
|
||||
pos_t = torch.tensor(pos, dtype=torch.float32, device=self.device).unsqueeze(1)
|
||||
theta_rad = torch.deg2rad(torch.tensor(theta_deg, dtype=torch.float32, device=self.device))
|
||||
|
||||
if array_type.upper() == "ULA":
|
||||
return torch.exp(1j * 2 * torch.pi * pos_t * torch.sin(theta_rad))
|
||||
return torch.exp(-1j * 2 * torch.pi * pos_t * torch.sin(theta_rad))
|
||||
|
||||
def build_dictionary(self, angle_grid, array_type="ULA"):
|
||||
return torch.cat(
|
||||
[self.gen_a(float(th), array_type) / math.sqrt(self.N_ant) for th in angle_grid],
|
||||
dim=1
|
||||
)
|
||||
|
||||
# ---------- 子程序 ----------
|
||||
def extract_omp(self, w_opt, a_dic):
|
||||
res = w_opt.clone()
|
||||
f_rf = None
|
||||
for _ in range(self.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 make_prior_broad(self, theta_prior, window_half_width, array_type="ULA"):
|
||||
a_prior_broad = torch.zeros((self.N_ant, 1), dtype=torch.complex64, device=self.device)
|
||||
for tb in torch.arange(theta_prior - window_half_width,
|
||||
theta_prior + window_half_width + 0.1,
|
||||
0.5,
|
||||
device=self.device):
|
||||
a_prior_broad += self.gen_a(float(tb), array_type)
|
||||
a_prior_broad /= torch.norm(a_prior_broad)
|
||||
return a_prior_broad
|
||||
|
||||
def calc_hybrid_weight(self, f_rf, a_target, r_in):
|
||||
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 / self.N_RF) * torch.eye(self.N_RF, device=self.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(self, target_angle, clutter_angle, array_type="ULA", window_half_width=5.0):
|
||||
theta_prior = round(target_angle)
|
||||
a_target = self.gen_a(target_angle, array_type)
|
||||
a_clutter = self.gen_a(clutter_angle, array_type)
|
||||
|
||||
# 干扰+噪声先验协方差
|
||||
r_interference_prior = (
|
||||
self.noise_power * torch.eye(self.N_ant, device=self.device)
|
||||
+ self.INR_linear * (a_clutter @ a_clutter.mH)
|
||||
)
|
||||
|
||||
a_prior_broad = self.make_prior_broad(theta_prior, window_half_width, array_type)
|
||||
w_digital_prior = torch.linalg.inv(r_interference_prior) @ a_prior_broad
|
||||
w_digital_prior /= torch.norm(w_digital_prior)
|
||||
|
||||
# 传统 OMP / TSC-OMP 字典
|
||||
dic_angles_full = np.arange(-90, 90.5, 0.5)
|
||||
dic_angles_tsc = np.arange(
|
||||
theta_prior - window_half_width,
|
||||
theta_prior + window_half_width + 0.1,
|
||||
0.1
|
||||
)
|
||||
|
||||
a_dic_full = self.build_dictionary(dic_angles_full, array_type)
|
||||
a_dic_tsc = self.build_dictionary(dic_angles_tsc, array_type)
|
||||
|
||||
# DFT 射频矩阵
|
||||
angles_dft = torch.linspace(
|
||||
theta_prior - window_half_width,
|
||||
theta_prior + window_half_width,
|
||||
self.N_RF,
|
||||
device=self.device
|
||||
)
|
||||
f_rf_dft = torch.cat(
|
||||
[self.gen_a(float(th), array_type) / math.sqrt(self.N_ant) for th in angles_dft],
|
||||
dim=1
|
||||
)
|
||||
|
||||
f_rf_omp = self.extract_omp(w_digital_prior, a_dic_full)
|
||||
f_rf_tsc = self.extract_omp(w_digital_prior, a_dic_tsc)
|
||||
|
||||
# 静态理想协方差,用于方向图和纯数字权值
|
||||
sig_power_static = 10 ** (0 / 10)
|
||||
r_in_static = (
|
||||
self.noise_power * torch.eye(self.N_ant, device=self.device)
|
||||
+ sig_power_static * (a_target @ a_target.mH)
|
||||
+ self.INR_linear * (a_clutter @ a_clutter.mH)
|
||||
)
|
||||
|
||||
r_in_static_dl = r_in_static + (
|
||||
0.01 * torch.trace(r_in_static).real / self.N_ant
|
||||
) * torch.eye(self.N_ant, device=self.device)
|
||||
|
||||
w_analog = a_target / math.sqrt(self.N_ant)
|
||||
|
||||
inv_r_stat = torch.linalg.inv(r_in_static_dl)
|
||||
w_digital = (inv_r_stat @ a_target) / (a_target.mH @ inv_r_stat @ a_target)
|
||||
w_digital /= torch.norm(w_digital)
|
||||
|
||||
# DFT / OMP / TSC-OMP 混合
|
||||
w_dft = self.calc_hybrid_weight(f_rf_dft, a_target, r_in_static)
|
||||
w_omp = self.calc_hybrid_weight(f_rf_omp, a_target, r_in_static)
|
||||
w_tsc = self.calc_hybrid_weight(f_rf_tsc, a_target, r_in_static)
|
||||
|
||||
return {
|
||||
"theta_prior": theta_prior,
|
||||
"a_target": a_target,
|
||||
"a_clutter": a_clutter,
|
||||
"w_analog": w_analog,
|
||||
"w_digital": w_digital,
|
||||
"w_dft": w_dft,
|
||||
"w_omp": w_omp,
|
||||
"w_tsc": w_tsc,
|
||||
"f_rf_dft": f_rf_dft,
|
||||
"f_rf_omp": f_rf_omp,
|
||||
"f_rf_tsc": f_rf_tsc,
|
||||
}
|
||||
|
||||
# ---------- 主功能1:波束方向图 ----------
|
||||
def beam_patterns(self, target_angle, clutter_angle, array_type="ULA",
|
||||
methods=None, window_half_width=5.0):
|
||||
if methods is None:
|
||||
methods = ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]
|
||||
|
||||
structures = self.build_all_structures(target_angle, clutter_angle, array_type, window_half_width)
|
||||
theta_scan = torch.arange(-90, 90.1, 0.1, device=self.device)
|
||||
a_scan = torch.cat([self.gen_a(float(th), array_type) for th in theta_scan], dim=1)
|
||||
|
||||
weights = {
|
||||
"Analog": structures["w_analog"],
|
||||
"Digital": structures["w_digital"],
|
||||
"DFT": structures["w_dft"],
|
||||
"OMP": structures["w_omp"],
|
||||
"TSC-OMP": structures["w_tsc"],
|
||||
}
|
||||
|
||||
result = {"theta_scan": theta_scan.cpu().numpy()}
|
||||
for method in methods:
|
||||
w = weights[method]
|
||||
gain = torch.abs(a_scan.mH @ w) ** 2
|
||||
bp = 10 * torch.log10((gain / torch.max(gain)).squeeze())
|
||||
result[method] = bp.cpu().numpy()
|
||||
return result
|
||||
|
||||
# ---------- 主功能2:RMSE 曲线 ----------
|
||||
def rmse_curves(self, target_angle, clutter_angle, array_type="ULA",
|
||||
methods=None, window_half_width=5.0,
|
||||
mc=100, snapshots=100, snr_values=None):
|
||||
"""
|
||||
RMSE 计算逻辑参考:
|
||||
- ULA: RMSE_Test_ula(2).py
|
||||
- NULA: RMSE_Test_nula(2).py
|
||||
其中 DFT / OMP / TSC-OMP 均采用先验宽波束 + 低维 Capon 超分辨。
|
||||
"""
|
||||
if methods is None:
|
||||
methods = ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]
|
||||
if snr_values is None:
|
||||
snr_values = np.arange(-10, 22, 2)
|
||||
|
||||
structures = self.build_all_structures(target_angle, clutter_angle, array_type, window_half_width)
|
||||
theta_prior = structures["theta_prior"]
|
||||
a_target = structures["a_target"]
|
||||
a_clutter = structures["a_clutter"]
|
||||
|
||||
scan_grid = torch.arange(
|
||||
theta_prior - window_half_width,
|
||||
theta_prior + window_half_width + 0.01,
|
||||
0.01,
|
||||
device=self.device
|
||||
)
|
||||
a_scan = torch.cat([self.gen_a(float(th), array_type) for th in scan_grid], dim=1)
|
||||
|
||||
curves = {m: [] for m in methods}
|
||||
|
||||
a_eff_dft = structures["f_rf_dft"].mH @ a_scan
|
||||
a_eff_omp = structures["f_rf_omp"].mH @ a_scan
|
||||
a_eff_tsc = structures["f_rf_tsc"].mH @ a_scan
|
||||
|
||||
for snr in snr_values:
|
||||
sig_power = 10 ** (snr / 10)
|
||||
|
||||
s_t = np.sqrt(sig_power / 2) * (
|
||||
torch.randn(mc, 1, snapshots, device=self.device) +
|
||||
1j * torch.randn(mc, 1, snapshots, device=self.device)
|
||||
)
|
||||
s_c = np.sqrt(self.INR_linear / 2) * (
|
||||
torch.randn(mc, 1, snapshots, device=self.device) +
|
||||
1j * torch.randn(mc, 1, snapshots, device=self.device)
|
||||
)
|
||||
n_t = np.sqrt(self.noise_power / 2) * (
|
||||
torch.randn(mc, self.N_ant, snapshots, device=self.device) +
|
||||
1j * torch.randn(mc, self.N_ant, snapshots, device=self.device)
|
||||
)
|
||||
|
||||
x = a_target.unsqueeze(0) * s_t + a_clutter.unsqueeze(0) * s_c + n_t
|
||||
r_in = torch.bmm(x, x.mH) / snapshots
|
||||
|
||||
# 纯模拟:Bartlett
|
||||
if "Analog" in methods:
|
||||
p_ana = torch.einsum('si, bij, js -> bs', a_scan.conj().T, r_in, a_scan).abs()
|
||||
ang_ana = scan_grid[torch.argmax(p_ana, dim=1)]
|
||||
curves["Analog"].append(torch.sqrt(torch.mean((ang_ana - target_angle) ** 2)).item())
|
||||
|
||||
# 纯数字:全维 Capon / MVDR
|
||||
if "Digital" in methods:
|
||||
trace_r_in = torch.diagonal(r_in, dim1=-2, dim2=-1).sum(-1).real
|
||||
r_in_dl = r_in + (
|
||||
0.05 * trace_r_in / self.N_ant
|
||||
).view(-1, 1, 1) * torch.eye(self.N_ant, device=self.device).unsqueeze(0)
|
||||
inv_r_in_dl = 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_dl, a_scan))
|
||||
ang_dig = scan_grid[torch.argmax(p_dig, dim=1)]
|
||||
curves["Digital"].append(torch.sqrt(torch.mean((ang_dig - target_angle) ** 2)).item())
|
||||
|
||||
def process_hybrid(f_rf, a_eff):
|
||||
y = f_rf.mH.unsqueeze(0) @ x
|
||||
r = (y @ y.mH) / snapshots
|
||||
r = r + (
|
||||
0.05 * torch.diagonal(r, dim1=-2, dim2=-1).sum(-1).view(-1, 1, 1) / self.N_RF
|
||||
) * torch.eye(self.N_RF, device=self.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))
|
||||
ang = scan_grid[torch.argmax(p, dim=1)]
|
||||
return torch.sqrt(torch.mean((ang - target_angle) ** 2)).item()
|
||||
|
||||
if "DFT" in methods:
|
||||
curves["DFT"].append(process_hybrid(structures["f_rf_dft"], a_eff_dft))
|
||||
|
||||
if "OMP" in methods:
|
||||
curves["OMP"].append(process_hybrid(structures["f_rf_omp"], a_eff_omp))
|
||||
|
||||
if "TSC-OMP" in methods:
|
||||
curves["TSC-OMP"].append(process_hybrid(structures["f_rf_tsc"], a_eff_tsc))
|
||||
|
||||
curves["snr"] = np.array(snr_values)
|
||||
return curves
|
||||
|
||||
|
||||
# =========================================================
|
||||
# 2. 绘图面板
|
||||
# =========================================================
|
||||
class PlotPanel(ttk.Frame):
|
||||
def __init__(self, master):
|
||||
super().__init__(master)
|
||||
self.figure = Figure(figsize=(8.6, 5.8), dpi=100)
|
||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self)
|
||||
self.canvas_widget = self.canvas.get_tk_widget()
|
||||
self.canvas_widget.pack(fill="both", expand=True)
|
||||
|
||||
self.toolbar = NavigationToolbar2Tk(self.canvas, self, pack_toolbar=False)
|
||||
self.toolbar.update()
|
||||
self.toolbar.pack(fill="x")
|
||||
|
||||
def redraw(self):
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def save_figure(self):
|
||||
path = filedialog.asksaveasfilename(
|
||||
defaultextension=".png",
|
||||
filetypes=[("PNG 图片", "*.png"), ("PDF 文件", "*.pdf"), ("SVG 文件", "*.svg")]
|
||||
)
|
||||
if path:
|
||||
self.figure.savefig(path, dpi=300, bbox_inches="tight")
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================
|
||||
# 3. GUI 主体
|
||||
# =========================================================
|
||||
class HBFWorkbench(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("大规模阵列 MIMO 波束优化综合平台")
|
||||
self.geometry("1450x900")
|
||||
self.minsize(1200, 760)
|
||||
|
||||
self.engine = BeamEngine()
|
||||
self._setup_style()
|
||||
|
||||
self.status_var = tk.StringVar(value=f"就绪 | 设备: {device}")
|
||||
self._build_ui()
|
||||
|
||||
def _setup_style(self):
|
||||
style = ttk.Style(self)
|
||||
try:
|
||||
style.theme_use("clam")
|
||||
except Exception:
|
||||
pass
|
||||
style.configure("TNotebook.Tab", padding=(14, 8), font=("Microsoft YaHei", 10))
|
||||
style.configure("Header.TLabel", font=("Microsoft YaHei", 12, "bold"))
|
||||
style.configure("Accent.TButton", font=("Microsoft YaHei", 10, "bold"))
|
||||
|
||||
def _build_ui(self):
|
||||
main = ttk.Frame(self)
|
||||
main.pack(fill="both", expand=True, padx=10, pady=10)
|
||||
|
||||
left = ttk.Frame(main)
|
||||
left.pack(side="left", fill="y", padx=(0, 8))
|
||||
|
||||
right = ttk.Frame(main)
|
||||
right.pack(side="right", fill="both", expand=True)
|
||||
|
||||
# ---------- 参数卡 ----------
|
||||
card1 = ttk.LabelFrame(left, text="场景参数")
|
||||
card1.pack(fill="x", pady=(0, 8))
|
||||
|
||||
self.var_target = tk.DoubleVar(value=5.0)
|
||||
self.var_clutter = tk.DoubleVar(value=-15.0)
|
||||
self.var_window = tk.DoubleVar(value=5.0)
|
||||
self.var_mc = tk.IntVar(value=MC_DEFAULT)
|
||||
self.var_snap = tk.IntVar(value=L_SNAPSHOTS_DEFAULT)
|
||||
self.var_array = tk.StringVar(value="ULA")
|
||||
self.var_function = tk.StringVar(value="beam")
|
||||
|
||||
self._add_labeled_entry(card1, "目标角度 (°)", self.var_target)
|
||||
self._add_labeled_entry(card1, "干扰角度 (°)", self.var_clutter)
|
||||
self._add_labeled_entry(card1, "波门半宽 (°)", self.var_window)
|
||||
self._add_labeled_entry(card1, "Monte Carlo", self.var_mc)
|
||||
self._add_labeled_entry(card1, "快拍数", self.var_snap)
|
||||
|
||||
row = ttk.Frame(card1)
|
||||
row.pack(fill="x", pady=4)
|
||||
ttk.Label(row, text="阵列类型", width=12).pack(side="left")
|
||||
ttk.Combobox(row, textvariable=self.var_array, values=["ULA", "NULA"], state="readonly", width=12).pack(side="left")
|
||||
|
||||
# ---------- 功能卡 ----------
|
||||
card2 = ttk.LabelFrame(left, text="功能选择")
|
||||
card2.pack(fill="x", pady=(0, 8))
|
||||
ttk.Radiobutton(card2, text="波束方向图显示", variable=self.var_function, value="beam").pack(anchor="w")
|
||||
ttk.Radiobutton(card2, text="测角 RMSE 曲线", variable=self.var_function, value="rmse").pack(anchor="w")
|
||||
|
||||
# ---------- 算法卡 ----------
|
||||
card3 = ttk.LabelFrame(left, text="算法选择(可多选)")
|
||||
card3.pack(fill="x", pady=(0, 8))
|
||||
|
||||
self.alg_vars = {
|
||||
"Analog": tk.BooleanVar(value=True),
|
||||
"Digital": tk.BooleanVar(value=True),
|
||||
"DFT": tk.BooleanVar(value=True),
|
||||
"OMP": tk.BooleanVar(value=True),
|
||||
"TSC-OMP": tk.BooleanVar(value=True),
|
||||
}
|
||||
labels = {
|
||||
"Analog": "纯模拟",
|
||||
"Digital": "纯数字(MVDR)",
|
||||
"DFT": "DFT",
|
||||
"OMP": "传统OMP",
|
||||
"TSC-OMP": "TSC-OMP",
|
||||
}
|
||||
for k in ["Analog", "Digital", "DFT", "OMP", "TSC-OMP"]:
|
||||
ttk.Checkbutton(card3, text=labels[k], variable=self.alg_vars[k]).pack(anchor="w")
|
||||
|
||||
# ---------- 按钮 ----------
|
||||
btns = ttk.Frame(left)
|
||||
btns.pack(fill="x", pady=(4, 8))
|
||||
ttk.Button(btns, text="运行", style="Accent.TButton", command=self.run_main_task).pack(fill="x", pady=3)
|
||||
ttk.Button(btns, text="保存当前图", command=lambda: self._save_panel(self.main_plot)).pack(fill="x", pady=3)
|
||||
|
||||
# ---------- 说明 ----------
|
||||
note = ttk.LabelFrame(left, text="说明")
|
||||
note.pack(fill="x", pady=(0, 8))
|
||||
ttk.Label(
|
||||
note,
|
||||
text="• 设置好您所需的参数,算法可以多选\n"
|
||||
"• 选择您所需要的图表,有波束方向图和测角RMSE比较图可选\n"
|
||||
"• 单击“运行”即可输出所需图表,可以随时保存",
|
||||
justify="left"
|
||||
).pack(anchor="w", padx=4, pady=4)
|
||||
|
||||
# ---------- 绘图区域 ----------
|
||||
self.main_plot = PlotPanel(right)
|
||||
self.main_plot.pack(fill="both", expand=True)
|
||||
|
||||
# ---------- 状态栏 ----------
|
||||
status = ttk.Label(self, textvariable=self.status_var, anchor="w")
|
||||
status.pack(fill="x", padx=10, pady=(0, 8))
|
||||
|
||||
def _add_labeled_entry(self, master, label, var, width=12):
|
||||
row = ttk.Frame(master)
|
||||
row.pack(fill="x", pady=4)
|
||||
ttk.Label(row, text=label, width=12).pack(side="left")
|
||||
ttk.Entry(row, textvariable=var, width=width).pack(side="left")
|
||||
|
||||
def _save_panel(self, panel):
|
||||
path = panel.save_figure()
|
||||
if path:
|
||||
self.status_var.set(f"已保存图像: {path}")
|
||||
|
||||
def get_selected_methods(self):
|
||||
return [k for k, v in self.alg_vars.items() if v.get()]
|
||||
|
||||
def _set_busy(self, msg):
|
||||
self.config(cursor="watch")
|
||||
self.status_var.set(msg)
|
||||
self.update_idletasks()
|
||||
|
||||
def _set_idle(self, msg="就绪"):
|
||||
self.config(cursor="")
|
||||
self.status_var.set(msg)
|
||||
self.update_idletasks()
|
||||
|
||||
def run_main_task(self):
|
||||
try:
|
||||
target = float(self.var_target.get())
|
||||
clutter = float(self.var_clutter.get())
|
||||
array_type = self.var_array.get()
|
||||
func = self.var_function.get()
|
||||
window = float(self.var_window.get())
|
||||
mc = int(self.var_mc.get())
|
||||
snapshots = int(self.var_snap.get())
|
||||
methods = self.get_selected_methods()
|
||||
|
||||
if not methods:
|
||||
messagebox.showwarning("提示", "请至少选择一种算法。")
|
||||
return
|
||||
|
||||
t0 = time.time()
|
||||
self._set_busy("正在运行,请稍候...")
|
||||
|
||||
fig = self.main_plot.figure
|
||||
fig.clear()
|
||||
ax = fig.add_subplot(111)
|
||||
|
||||
if func == "beam":
|
||||
data = self.engine.beam_patterns(target, clutter, array_type, methods, window)
|
||||
theta = data["theta_scan"]
|
||||
|
||||
name_map = {
|
||||
"Analog": "纯模拟",
|
||||
"Digital": "纯数字(MVDR)",
|
||||
"DFT": "DFT",
|
||||
"OMP": "传统OMP",
|
||||
"TSC-OMP": "TSC-OMP",
|
||||
}
|
||||
ls_map = {
|
||||
"Analog": "-.",
|
||||
"Digital": "-",
|
||||
"DFT": "--",
|
||||
"OMP": ":",
|
||||
"TSC-OMP": "-",
|
||||
}
|
||||
|
||||
for m in methods:
|
||||
ax.plot(
|
||||
theta,
|
||||
data[m],
|
||||
linestyle=ls_map[m],
|
||||
color=METHOD_COLORS[m],
|
||||
linewidth=2.4,
|
||||
label=name_map[m]
|
||||
)
|
||||
|
||||
ax.axvline(target, color="g", linestyle="--", linewidth=1.8, label=f"目标 ({target:.2f}°)")
|
||||
ax.axvline(clutter, color="r", linestyle="--", linewidth=1.8, label=f"干扰 ({clutter:.2f}°)")
|
||||
ax.set_xlim([-60, 60])
|
||||
ax.set_ylim([-60, 2])
|
||||
ax.set_title(f"波束方向图 ({array_type})", fontsize=13, fontweight="bold")
|
||||
ax.set_xlabel("角度 θ (°)")
|
||||
ax.set_ylabel("归一化增益 (dB)")
|
||||
ax.grid(True, ls="--", alpha=0.6)
|
||||
ax.legend(loc="lower center", ncol=min(4, len(methods) + 2), fontsize=9)
|
||||
|
||||
else:
|
||||
curves = self.engine.rmse_curves(
|
||||
target_angle=target,
|
||||
clutter_angle=clutter,
|
||||
array_type=array_type,
|
||||
methods=methods,
|
||||
window_half_width=window,
|
||||
mc=mc,
|
||||
snapshots=snapshots,
|
||||
)
|
||||
|
||||
name_map = {
|
||||
"Analog": "纯模拟",
|
||||
"Digital": "纯数字(MVDR)",
|
||||
"DFT": "DFT",
|
||||
"OMP": "传统OMP",
|
||||
"TSC-OMP": "TSC-OMP",
|
||||
}
|
||||
marker_map = {
|
||||
"Analog": "v",
|
||||
"Digital": "o",
|
||||
"DFT": "d",
|
||||
"OMP": "*",
|
||||
"TSC-OMP": "s",
|
||||
}
|
||||
|
||||
for m in methods:
|
||||
ax.semilogy(
|
||||
curves["snr"],
|
||||
curves[m],
|
||||
marker=marker_map[m],
|
||||
color=METHOD_COLORS[m],
|
||||
linewidth=2.2,
|
||||
label=name_map[m]
|
||||
)
|
||||
|
||||
ax.set_title(f"测角 RMSE-SNR 曲线 ({array_type})", fontsize=13, fontweight="bold")
|
||||
ax.set_xlabel("输入 SNR (dB)")
|
||||
ax.set_ylabel("RMSE (°)")
|
||||
ax.set_ylim([1e-3, 20])
|
||||
ax.grid(True, which="both", ls="--", alpha=0.6)
|
||||
ax.legend()
|
||||
|
||||
fig.tight_layout()
|
||||
self.main_plot.redraw()
|
||||
self._set_idle(f"运行完成,用时 {time.time() - t0:.2f}s | 设备: {device}")
|
||||
|
||||
except Exception as e:
|
||||
self._set_idle("运行失败")
|
||||
messagebox.showerror("错误", str(e))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = HBFWorkbench()
|
||||
app.mainloop()
|
||||
Reference in New Issue
Block a user