Created
February 8, 2026 08:09
-
-
Save qiaoxu123/3161987f14dae78e6ca1683352865c08 to your computer and use it in GitHub Desktop.
V2I 信道模型教学演示 - 3GPP TR 38.901 路径损耗 + Nakagami-m 衰落 + Doppler + 链路持续 (独立脚本,无工程依赖)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| V2I 信道模型教学演示脚本 | |
| ======================== | |
| 本脚本完整实现了 V2I (Vehicle-to-Infrastructure) 通信系统的信道模型, | |
| 适合新人理解和学习 5G V2X 无线通信基础。 | |
| 信道模型基于: | |
| - 3GPP TR 38.901 v16.1.0 (路径损耗) | |
| - Nakagami-m 衰落分布 (小尺度衰落) | |
| - Doppler 效应 (高速移动惩罚) | |
| - 链路持续时间建模 (覆盖连续性) | |
| 依赖: pip install numpy scipy matplotlib | |
| 运行: python v2i-channel-model-demo.py | |
| Author: JLU-MCNS-MEC | |
| """ | |
| import numpy as np | |
| from scipy.stats import nakagami | |
| from scipy.special import gammainc | |
| import matplotlib.pyplot as plt | |
| import matplotlib | |
| matplotlib.rcParams["font.family"] = ["WenQuanYi Micro Hei", "SimHei", "sans-serif"] | |
| matplotlib.rcParams["axes.unicode_minus"] = False | |
| # ============================================================ | |
| # 第一部分: 通信系统参数 | |
| # ============================================================ | |
| # 这些参数定义了 V2I 无线信道的物理特性 | |
| # --- 发射端 (车辆) --- | |
| P_tx = 0.3 # 发射功率 (W),约 25 dBm | |
| h_veh = 1.5 # 车辆天线高度 (m) | |
| # --- 接收端 (RSU/MEC) --- | |
| h_rsu = 10.0 # RSU 天线高度 (m) | |
| R_coverage = 150.0 # 覆盖半径 (m) | |
| # --- 无线信道 --- | |
| f_c = 5.9e9 # 载波频率 (Hz),5.9 GHz 是 C-V2X 标准频段 | |
| c = 3e8 # 光速 (m/s) | |
| BW = 5e7 # 总带宽 (Hz),50 MHz | |
| N_0 = 0.8e-13 # AWGN 噪声功率 (W) | |
| sigma_sf = 6.0 # 阴影衰落标准差 (dB) | |
| # --- Nakagami-m 衰落参数 --- | |
| # m 越大表示衰落越轻微 (m=1 等价于 Rayleigh,m→∞ 等价于无衰落) | |
| m_LoS = 4 # 视距 (LoS) 信道 shape 参数 | |
| m_NLoS = 2 # 非视距 (NLoS) 信道 shape 参数 | |
| nakagami_avg_gain = 1 # 平均功率增益 | |
| # --- Doppler --- | |
| doppler_factor = 100.0 # Doppler 惩罚系数 | |
| # --- SNR 阈值 --- | |
| snr_threshold_dB = 10.0 # 中断 SNR 阈值 (dB) | |
| snr_threshold = 10 ** (snr_threshold_dB / 10) # 线性值 | |
| # ============================================================ | |
| # 第二部分: 信道模型核心函数 | |
| # ============================================================ | |
| def path_loss_dB(d): | |
| """ | |
| 3GPP TR 38.901 路径损耗模型 (dB) | |
| 分为近场和远场两个区间: | |
| - 近场 (d ≤ d_bp): 自由空间传播,损耗随 log₁₀(d) 线性增长 | |
| - 远场 (d > d_bp): 多径反射加剧,损耗更快增长 | |
| 断点距离 d_bp 取决于天线高度和载波频率。 | |
| 参考: 3GPP TR 38.901 Table 7.4.1-1 (UMi - Street Canyon LoS) | |
| """ | |
| d = np.maximum(d, 0.1) # 避免 log(0) | |
| # 断点距离: 一阶菲涅尔区与地面反射交汇点 | |
| d_bp = 4 * h_rsu * h_veh * (f_c / 1e9) / (c / 1e9) | |
| # 近场: 自由空间损耗为主 | |
| pl_near = 32.4 + 21 * np.log10(d) + 20 * np.log10(f_c / 1e9) + sigma_sf | |
| # 远场: 多径效应增强,衰减更快 | |
| pl_far = (32.4 + 40 * np.log10(d) + 20 * np.log10(f_c / 1e9) | |
| - 9.5 * np.log10(d**2 + (h_rsu - h_veh)**2) + sigma_sf) | |
| return np.where(d <= d_bp, pl_near, pl_far) | |
| def path_loss_nlos_dB(d): | |
| """ | |
| NLoS 路径损耗 (dB) | |
| 非视距场景下损耗更大,取 LoS 路径损耗和 NLoS 经验公式的较大值。 | |
| """ | |
| pl_los = path_loss_dB(d) | |
| d = np.maximum(d, 0.1) | |
| pl_nlos = 35.3 * np.log10(d) + 22.4 + 21.3 * np.log10(f_c / 1e9) - 0.3 * (h_veh - 1.5) | |
| return np.maximum(pl_los, pl_nlos) | |
| def prob_los(d): | |
| """ | |
| 视距 (LoS) 概率 | |
| 距离越远,被建筑物遮挡的可能性越大,LoS 概率越低。 | |
| d ≤ 18m 时假设必然 LoS。 | |
| """ | |
| d = np.asarray(d, dtype=float) | |
| p = np.where( | |
| d <= 18, | |
| 1.0, | |
| 18.0 / d + np.exp(-d / 36.0) * (1.0 - 18.0 / d) | |
| ) | |
| return p | |
| def compute_snr(d): | |
| """ | |
| 信噪比 (SNR) 计算 | |
| 综合 LoS 和 NLoS 路径,按各自概率加权: | |
| channel_gain = P_LoS × G_LoS + (1 - P_LoS) × G_NLoS | |
| SNR = P_tx × channel_gain / N_0 | |
| 其中 G 包含小尺度衰落 (Nakagami) 和大尺度路径损耗。 | |
| """ | |
| d = np.maximum(d, 1e-6) | |
| # 小尺度衰落: Nakagami-m 分布采样 | |
| small_fading = nakagami(m_LoS, loc=0, scale=np.sqrt(nakagami_avg_gain)) | |
| g_small = np.mean(small_fading.rvs(size=1) ** 2) | |
| # 大尺度路径损耗 (dB → 线性) | |
| g_los_large = 10 ** (-path_loss_dB(d) / 10) | |
| g_nlos_large = 10 ** (-path_loss_nlos_dB(d) / 10) | |
| # 综合信道增益 | |
| p = prob_los(d) | |
| g_total = p * (g_small * g_los_large) + (1 - p) * (g_small * g_nlos_large) | |
| return P_tx * g_total / N_0 | |
| def outage_probability(d): | |
| """ | |
| 中断概率 | |
| 当 SNR 低于阈值时通信中断。使用 Nakagami-m 分布的 CDF: | |
| P_out = P_LoS × P(SNR_LoS < γ) + (1-P_LoS) × P(SNR_NLoS < γ) | |
| 其中 P(SNR < γ) = γ_inc(m, m × γ / SNR_avg),γ_inc 是正则化下不完全 Gamma 函数。 | |
| """ | |
| d = np.maximum(d, 1e-6) | |
| # LoS / NLoS 的平均 SNR | |
| g_los = 10 ** (-path_loss_dB(d) / 10) | |
| g_nlos = 10 ** (-path_loss_nlos_dB(d) / 10) | |
| snr_los = P_tx * g_los / N_0 | |
| snr_nlos = P_tx * g_nlos / N_0 | |
| # Nakagami-m 中断概率 (正则化下不完全 Gamma 函数) | |
| x_los = m_LoS * snr_threshold / np.maximum(snr_los, 1e-20) | |
| p_out_los = gammainc(m_LoS, x_los) | |
| x_nlos = m_NLoS * snr_threshold / np.maximum(snr_nlos, 1e-20) | |
| p_out_nlos = gammainc(m_NLoS, x_nlos) | |
| p = prob_los(d) | |
| return p * p_out_los + (1 - p) * p_out_nlos | |
| def doppler_penalty(v): | |
| """ | |
| Doppler 效应惩罚因子 | |
| 车辆移动导致载波频率偏移 (多普勒频移): | |
| f_d = v × f_c / c | |
| 频移越大,信道相干时间越短,有效传输速率下降。 | |
| 惩罚模型: α = exp(-factor × 2f_d / BW) | |
| """ | |
| v = np.asarray(v, dtype=float) | |
| f_d = v * f_c / c # 多普勒频移 (Hz) | |
| B_d = 2 * f_d # 多普勒扩展 (Hz) | |
| alpha = np.exp(-doppler_factor * B_d / BW) | |
| return np.clip(alpha, 0.0, 1.0) | |
| def link_duration(d, v): | |
| """ | |
| 链路持续时间 | |
| 车辆在 MEC 覆盖区内的停留时间: | |
| T_link = 2 × √(R² - d²) / v | |
| 假设车辆沿弦方向穿越圆形覆盖区。 | |
| """ | |
| d = np.asarray(d, dtype=float) | |
| v = np.maximum(np.asarray(v, dtype=float), 0.1) | |
| inside = d < R_coverage | |
| remaining = np.where(inside, 2 * np.sqrt(np.maximum(R_coverage**2 - d**2, 0)), 0) | |
| t = np.clip(remaining / v, 0, 300) | |
| return t | |
| def link_duration_penalty(d, v): | |
| """ | |
| 链路持续惩罚 | |
| 连接时间太短意味着任务可能无法在断链前完成: | |
| < 5s → 指数衰减惩罚 (严重) | |
| 5-20s → 线性插值 0.5~1.0 | |
| 20-60s → Sigmoid 过渡 0.9~1.0 | |
| ≥ 60s → 无惩罚 | |
| """ | |
| t = link_duration(d, v) | |
| t = np.asarray(t, dtype=float) | |
| penalty = np.where( | |
| t <= 0, 0.0, | |
| np.where(t < 5, 1.0 - np.exp(-t / 2.0), | |
| np.where(t < 20, 0.5 + 0.5 * (t - 5) / 15.0, | |
| np.where(t < 60, 0.9 + 0.1 / (1 + np.exp(-0.1 * (t - 40))), | |
| 1.0)))) | |
| return np.clip(penalty, 0, 1) | |
| def transmission_rate(d, v=0.0, bw_ratio=0.5, n_vehicles=1): | |
| """ | |
| 有效传输速率 (核心公式) | |
| 综合考虑所有信道效应: | |
| Rate = B_eff × log₂(1 + SNR) × (1 - P_out) × α_doppler × β_link | |
| 其中: | |
| B_eff = BW × bw_ratio / n_vehicles (公平带宽分配) | |
| """ | |
| B_eff = BW * bw_ratio / max(n_vehicles, 1) | |
| snr = np.maximum(compute_snr(d), 1e-6) | |
| p_out = outage_probability(d) | |
| dp = doppler_penalty(v) | |
| lp = link_duration_penalty(d, v) | |
| base_rate = B_eff * np.log2(1 + snr) | |
| return base_rate * (1 - p_out) * dp * lp | |
| def transmission_delay(d, task_bits, v=0.0, bw_ratio=0.5): | |
| """传输时延 = 任务大小 / 传输速率""" | |
| rate = transmission_rate(d, v, bw_ratio) | |
| return np.where(rate > 0, task_bits / rate, 1e10) | |
| # ============================================================ | |
| # 第三部分: 可视化演示 | |
| # ============================================================ | |
| def demo_all(): | |
| """运行全部演示,生成 6 张子图""" | |
| distances = np.linspace(1, 300, 300) | |
| speeds = np.linspace(0, 30, 100) | |
| fig, axes = plt.subplots(2, 3, figsize=(16, 10)) | |
| fig.suptitle("V2I 信道模型教学演示 (3GPP TR 38.901)", fontsize=14, fontweight="bold") | |
| # ---- 图1: 路径损耗 vs 距离 ---- | |
| ax = axes[0, 0] | |
| ax.plot(distances, path_loss_dB(distances), "b-", label="LoS", linewidth=2) | |
| ax.plot(distances, path_loss_nlos_dB(distances), "r--", label="NLoS", linewidth=2) | |
| d_bp = 4 * h_rsu * h_veh * (f_c / 1e9) / (c / 1e9) | |
| ax.axvline(d_bp, color="gray", linestyle=":", alpha=0.7, label=f"断点 d_bp={d_bp:.0f}m") | |
| ax.set_xlabel("距离 (m)") | |
| ax.set_ylabel("路径损耗 (dB)") | |
| ax.set_title("① 路径损耗 vs 距离") | |
| ax.legend(fontsize=9) | |
| ax.grid(True, alpha=0.3) | |
| # ---- 图2: LoS 概率 + 中断概率 ---- | |
| ax = axes[0, 1] | |
| ax2 = ax.twinx() | |
| l1, = ax.plot(distances, prob_los(distances), "g-", linewidth=2, label="P(LoS)") | |
| l2, = ax2.plot(distances, outage_probability(distances), "r-", linewidth=2, label="P(中断)") | |
| ax.set_xlabel("距离 (m)") | |
| ax.set_ylabel("LoS 概率", color="g") | |
| ax2.set_ylabel("中断概率", color="r") | |
| ax.set_title("② LoS 概率 & 中断概率") | |
| ax.legend(handles=[l1, l2], fontsize=9) | |
| ax.grid(True, alpha=0.3) | |
| # ---- 图3: SNR vs 距离 ---- | |
| ax = axes[0, 2] | |
| snr_vals = compute_snr(distances) | |
| snr_dB = 10 * np.log10(np.maximum(snr_vals, 1e-20)) | |
| ax.plot(distances, snr_dB, "b-", linewidth=2) | |
| ax.axhline(snr_threshold_dB, color="r", linestyle="--", label=f"中断阈值 {snr_threshold_dB} dB") | |
| ax.set_xlabel("距离 (m)") | |
| ax.set_ylabel("SNR (dB)") | |
| ax.set_title("③ 信噪比 vs 距离") | |
| ax.legend(fontsize=9) | |
| ax.grid(True, alpha=0.3) | |
| # ---- 图4: Doppler 惩罚 vs 速度 ---- | |
| ax = axes[1, 0] | |
| ax.plot(speeds, doppler_penalty(speeds), "m-", linewidth=2) | |
| typical_speeds = [8.3, 13.9, 22.2] # 30, 50, 80 km/h | |
| for vs in typical_speeds: | |
| p = doppler_penalty(vs) | |
| ax.annotate(f"{vs*3.6:.0f} km/h\n({p:.3f})", xy=(vs, p), | |
| fontsize=8, ha="center", va="top", | |
| arrowprops=dict(arrowstyle="->", color="gray")) | |
| ax.set_xlabel("速度 (m/s)") | |
| ax.set_ylabel("惩罚因子") | |
| ax.set_title("④ Doppler 惩罚 vs 车速") | |
| ax.grid(True, alpha=0.3) | |
| # ---- 图5: 传输速率 (不同速度) ---- | |
| ax = axes[1, 1] | |
| for v, color, ls in [(0, "b", "-"), (10, "g", "--"), (20, "r", ":")]: | |
| rates = transmission_rate(distances, v=v, bw_ratio=0.5) / 1e6 | |
| label = f"v={v} m/s ({v*3.6:.0f} km/h)" | |
| ax.plot(distances, rates, color=color, linestyle=ls, linewidth=2, label=label) | |
| ax.axvline(R_coverage, color="gray", linestyle=":", alpha=0.5, label=f"覆盖边界 {R_coverage}m") | |
| ax.set_xlabel("距离 (m)") | |
| ax.set_ylabel("传输速率 (Mbps)") | |
| ax.set_title("⑤ 有效传输速率 vs 距离") | |
| ax.legend(fontsize=8) | |
| ax.grid(True, alpha=0.3) | |
| # ---- 图6: 链路持续时间 & 惩罚 ---- | |
| ax = axes[1, 2] | |
| ax2 = ax.twinx() | |
| for v, color in [(5, "b"), (10, "g"), (20, "r")]: | |
| t_link = link_duration(distances, v) | |
| penalty = link_duration_penalty(distances, v) | |
| ax.plot(distances, t_link, color=color, linestyle="-", linewidth=1.5, | |
| label=f"持续 v={v}m/s") | |
| ax2.plot(distances, penalty, color=color, linestyle="--", linewidth=1.5, alpha=0.6) | |
| ax.set_xlabel("距离 (m)") | |
| ax.set_ylabel("链路持续 (s)") | |
| ax2.set_ylabel("惩罚因子 (虚线)") | |
| ax.set_title("⑥ 链路持续时间 & 惩罚") | |
| ax.legend(fontsize=8, loc="upper right") | |
| ax.grid(True, alpha=0.3) | |
| plt.tight_layout() | |
| plt.savefig("v2i_channel_model_demo.png", dpi=150, bbox_inches="tight") | |
| print("图表已保存: v2i_channel_model_demo.png") | |
| plt.show() | |
| # ============================================================ | |
| # 第四部分: 数值验证 | |
| # ============================================================ | |
| def verify_parameters(): | |
| """打印关键参数并验证典型场景下的通信性能""" | |
| print("=" * 60) | |
| print("V2I 信道模型参数总览") | |
| print("=" * 60) | |
| print(f"\n【发射端】") | |
| print(f" 发射功率: {P_tx} W ({10*np.log10(P_tx*1000):.1f} dBm)") | |
| print(f" 车辆天线高度: {h_veh} m") | |
| print(f"\n【接收端】") | |
| print(f" RSU 天线高度: {h_rsu} m") | |
| print(f" 覆盖半径: {R_coverage} m") | |
| print(f"\n【信道特性】") | |
| print(f" 载波频率: {f_c/1e9} GHz (C-V2X)") | |
| print(f" 系统带宽: {BW/1e6} MHz") | |
| print(f" 噪声功率: {N_0:.1e} W") | |
| print(f" 阴影衰落: {sigma_sf} dB") | |
| print(f"\n【衰落模型】 Nakagami-m") | |
| print(f" LoS m = {m_LoS} (衰落轻微,接近 Rice)") | |
| print(f" NLoS m = {m_NLoS} (衰落较强,接近 Rayleigh)") | |
| d_bp = 4 * h_rsu * h_veh * (f_c / 1e9) / (c / 1e9) | |
| print(f"\n【路径损耗】 3GPP TR 38.901") | |
| print(f" 断点距离: {d_bp:.1f} m") | |
| print(f" 近场指数: 2.1 (∝ d^2.1)") | |
| print(f" 远场指数: 4.0 (∝ d^4.0)") | |
| print(f"\n{'=' * 60}") | |
| print(f"典型场景验证") | |
| print(f"{'=' * 60}") | |
| print(f"\n{'距离':>6s} {'PL(dB)':>8s} {'SNR(dB)':>8s} {'P_out':>8s} " | |
| f"{'Rate@0':>10s} {'Rate@15':>10s} {'T_link@10':>10s}") | |
| print("-" * 70) | |
| for d in [10, 30, 50, 80, 100, 130, 150, 200]: | |
| pl = path_loss_dB(d) | |
| snr = 10 * np.log10(max(compute_snr(d), 1e-20)) | |
| p_out = outage_probability(d) | |
| r0 = transmission_rate(d, v=0, bw_ratio=0.5) / 1e6 | |
| r15 = transmission_rate(d, v=15, bw_ratio=0.5) / 1e6 | |
| t = link_duration(d, 10) | |
| print(f"{d:>5d}m {pl:>8.1f} {snr:>8.1f} {p_out:>8.4f} " | |
| f"{r0:>8.2f} Mb {r15:>8.2f} Mb {t:>8.1f}s") | |
| # 传输时延对比 | |
| task_mb = 0.5 | |
| task_bits = task_mb * 8 * 1e6 | |
| print(f"\n任务大小: {task_mb} MB ({task_bits/1e6:.0f} Mbit)") | |
| print(f"\n{'距离':>6s} {'时延@v=0':>12s} {'时延@v=10':>12s} {'时延@v=20':>12s}") | |
| print("-" * 50) | |
| for d in [20, 50, 100, 140]: | |
| d0 = transmission_delay(d, task_bits, v=0, bw_ratio=0.5) * 1000 | |
| d10 = transmission_delay(d, task_bits, v=10, bw_ratio=0.5) * 1000 | |
| d20 = transmission_delay(d, task_bits, v=20, bw_ratio=0.5) * 1000 | |
| print(f"{d:>5d}m {d0:>10.2f} ms {d10:>10.2f} ms {d20:>10.2f} ms") | |
| # 带宽共享影响 | |
| print(f"\n带宽共享影响 (d=80m, v=10m/s, bw_ratio=0.5)") | |
| print(f"{'车辆数':>6s} {'速率':>12s} {'时延':>12s}") | |
| print("-" * 35) | |
| for n in [1, 3, 5, 10]: | |
| r = transmission_rate(80, v=10, bw_ratio=0.5, n_vehicles=n) / 1e6 | |
| t = task_bits / (r * 1e6) * 1000 if r > 0 else float("inf") | |
| print(f"{n:>5d} {r:>10.2f} Mb {t:>10.2f} ms") | |
| # ============================================================ | |
| # 主程序 | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| verify_parameters() | |
| print("\n正在生成可视化图表...") | |
| demo_all() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment