diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/grading.yml b/.github/workflows/grading.yml index 2a546b7..4d5cd71 100644 --- a/.github/workflows/grading.yml +++ b/.github/workflows/grading.yml @@ -73,7 +73,7 @@ jobs: cat grade_result.txt - name: 上传评分报告 - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: grading-report path: | diff --git a/REPORT.md b/REPORT.md new file mode 100644 index 0000000..cc8cf70 --- /dev/null +++ b/REPORT.md @@ -0,0 +1,211 @@ +# 数字调制解调实验报告 + +**实验名称**:数字调制解调实验 +**学生姓名**:齐宇恒 +**学号**:2024140044 +**实验日期**:2026年4月23日 +**提交日期**:2026年4月23日 + +--- + +## 1. 实验目的 + +本实验围绕数字通信中的三种基础调制方式展开,目标是通过编程实现和结果可视化,将抽象的通信理论落到可运行的代码与图形上。具体来说,实验首先要求完成 BPSK、QPSK 与 16-QAM 三类调制函数,理解比特序列如何经过映射转换为复平面上的离散符号点;其次要求对生成的符号进行星座图可视化,以观察不同调制方式在复平面中的几何结构和能量分布;进一步地,通过实现对应的解调函数和 BER 性能测试,分析不同调制方式在 AWGN 信道中的抗噪声能力、频谱效率与实现复杂度之间的权衡关系。 + +除通信原理外,本实验也强调工程能力训练,包括 Python 科学计算环境配置、 NumPy 向量化实现、 Matplotlib 图像输出、自动评分脚本验证,以及在 AI 助手辅助下进行代码补全、调试和文档整理。通过完整完成本实验,可以建立从“理论公式”到“系统仿真”再到“结果分析”的完整认知链路。 + +--- + +## 2. 实验原理 + +### 2.1 BPSK 调制原理 + +BPSK(Binary Phase Shift Keying)是最基础的二进制相移键控方式,每个符号携带 1 比特信息。其核心思想是使用两个相位相差 180° 的载波状态表示比特 0 和比特 1。在本实验中采用最常见的基带等效表示,直接将比特映射为实轴上的两个点: + +$$ +s = 1 - 2b,\quad b \in \{0,1\} +$$ + +因此可得映射关系: + +- 比特 `0 -> +1` +- 比特 `1 -> -1` + +BPSK 的星座图仅有两个点,分布在实轴正负两端,符号间欧氏距离大,因此抗噪声能力较强,但每个符号仅传输 1 比特,频谱效率相对较低。 + +### 2.2 QPSK 调制原理 + +QPSK(Quadrature Phase Shift Keying)每个符号携带 2 比特信息,可视为在 I 路和 Q 路上同时进行二进制相移键控。实验中采用格雷码映射,以降低相邻星座点误判时的比特翻转数。映射规则为: + +- `00 -> (1+1j)/sqrt(2)` +- `01 -> (-1+1j)/sqrt(2)` +- `11 -> (-1-1j)/sqrt(2)` +- `10 -> (1-1j)/sqrt(2)` + +除以 `sqrt(2)` 的目的是将平均符号功率归一化为 1。QPSK 的四个点均匀分布在单位圆上,相比 BPSK 频谱效率翻倍,同时在相同平均功率下仍具有较好的抗噪性能。 + +### 2.3 16-QAM 调制原理 + +16-QAM(16-Quadrature Amplitude Modulation)属于正交振幅调制,每个符号携带 4 比特信息。实验中将 4 比特拆成两部分,前 2 位决定 I 分量,后 2 位决定 Q 分量,并分别采用格雷码映射: + +- `00 -> +3` +- `01 -> +1` +- `11 -> -1` +- `10 -> -3` + +因此 I/Q 两路组合后形成 4×4 共 16 个星座点。由于原始 I/Q 取值为 `±1, ±3`,单路平均能量为 5,复符号平均功率为 10,因此需要除以 `sqrt(10)` 完成归一化。16-QAM 的优点是频谱效率高,但点间距离更近,对噪声更敏感,解调复杂度也更高。 + +--- + +## 3. 实验方法与步骤 + +### 3.1 环境配置 + +按照实验说明,先创建名为 `wireless` 的 conda 虚拟环境,并在该环境内安装 `numpy`、`scipy`、`matplotlib`、`pytest`、`pytest-cov` 和 `pylint` 等依赖。随后运行 `src/test_environment.py` 验证 Python 版本、第三方库导入、NumPy 运算和 Matplotlib 绘图是否全部正常。 + +### 3.2 BPSK 实现 + +BPSK 调制采用向量化公式 `1 - 2 * bits` 完成映射,并统一转换为复数数组输出。这样实现简洁、无循环、易读且性能稳定。对应的解调则只需根据接收符号实部的正负号进行硬判决:实部大于 0 判定为 0,否则判定为 1。 + +```python +def bpsk_modulate(bits): + bits = np.asarray(bits, dtype=int) + symbols = (1 - 2 * bits).astype(np.complex128) + return symbols +``` + +### 3.3 QPSK 实现 + +QPSK 调制先对输入比特序列按 2 比特分组,再依据格雷码映射为 4 个理想星座点,最后进行单位功率归一化。解调部分采用最小欧氏距离判决:对每个接收符号计算其与 4 个参考点的距离,选择距离最小的点,并映射回对应比特对。 + +### 3.4 16-QAM 实现 + +16-QAM 调制先将输入序列按 4 比特分组,再将前两位映射到 I 分量、后两位映射到 Q 分量,最后组合成复数符号并除以 `sqrt(10)` 归一化。解调时,为提高效率,不遍历 16 个参考点,而是对 I/Q 分量分别进行门限判决:依据归一化前的取值区间恢复 2 比特格雷码,从而得到完整的 4 比特输出。 + +### 3.5 BER 性能测试 + +性能测试流程如下: + +1. 生成固定长度随机比特序列; +2. 选择一种调制方式进行调制; +3. 在不同 SNR 条件下加入复高斯白噪声; +4. 使用对应解调函数恢复比特; +5. 计算发送比特与接收比特之间的 BER; +6. 在半对数坐标下绘制 BER vs SNR 曲线,并对三种调制方式进行对比。 + +该流程较好地模拟了数字通信链路中的“发射机 - 信道 - 接收机”闭环过程。 + +--- + +## 4. 实验结果 + +### 4.1 BPSK 星座图 + +![BPSK星座图](results/bpsk_constellation.png) + +从生成结果可以看到,BPSK 星座图仅包含两个点,分别位于实轴正负方向,虚部接近 0。该结果与理论完全一致,说明 `0 -> +1`、`1 -> -1` 的映射已正确实现。 + +### 4.2 QPSK 星座图 + +![QPSK星座图](results/qpsk_constellation.png) + +QPSK 星座图包含四个分布在单位圆上的点,分别对应四组格雷码比特。四点相位间隔约为 90°,且幅度均约为 1,符合归一化 QPSK 的理论特性。 + +### 4.3 16-QAM 星座图 + +![16-QAM星座图](results/16qam_constellation.png) + +16-QAM 星座图呈现规则的 4×4 网格结构,实部和虚部分别取四种离散值。图中不同点的能量不同,外圈点功率更高,内圈点功率更低,这与 QAM 同时利用相位与幅度承载信息的机制一致。 + +### 4.4 BER 性能测试结果 + +![BER性能曲线](results/ber_comparison.png) + +性能对比曲线显示,在低到中等 SNR 条件下,BPSK 的 BER 最低,QPSK 次之,16-QAM 相对最高。这说明更高阶调制虽然能提升每个符号携带的比特数,但会缩小星座点间距,导致噪声干扰下更容易发生判决错误。 + +--- + +## 5. 结果分析与讨论 + +### 5.1 星座图对比分析 + +从星座图结构上看,BPSK 最简单,仅在实轴上使用两个点;QPSK 将信息扩展到二维复平面,在功率不变前提下使单符号承载信息量翻倍;16-QAM 则进一步在 I/Q 两路采用多级振幅组合,大幅提高频谱效率,但也使解调门限变得更密集。 + +几何上,星座点之间的最小欧氏距离与系统误码性能密切相关。BPSK 的点间距离最大,因此最稳健;QPSK 在归一化后仍保持较好的点间间隔;16-QAM 点密度显著增加,对噪声、幅度漂移和定时偏差更加敏感。 + +### 5.2 性能对比分析 + +三种调制方式体现了通信系统中的经典权衡: + +- 频谱效率:`16-QAM > QPSK > BPSK` +- 抗噪性能:`BPSK > QPSK > 16-QAM` +- 实现复杂度:`16-QAM > QPSK > BPSK` + +如果系统工作在低 SNR 环境,例如远距离、弱信号或强干扰链路,BPSK 更可靠;若希望在有限带宽内传输更多数据,QPSK 和 16-QAM 更有优势,但通常需要更高的接收信噪比与更精细的同步、均衡机制。 + +### 5.3 遇到的问题与解决方法 + +1. **问题**:初始代码中调制和解调函数均为占位实现,直接运行会抛出 `NotImplementedError`。 + **原因分析**:实验模板仅提供接口说明与提示,未提供可执行逻辑。 + **解决方法**:依据 README 和评分脚本逐项补全映射关系、输入校验和归一化逻辑,并通过自动测试反向验证实现正确性。 + +2. **问题**:性能测试涉及不同调制方式时,输入比特长度必须满足分组要求。 + **原因分析**:QPSK 要求长度为 2 的倍数,16-QAM 要求长度为 4 的倍数,否则无法按符号分组。 + **解决方法**:在 BER 测试流程中根据每种调制的每符号比特数调整有效比特长度,确保生成的随机序列可以直接整除。 + +3. **问题**:Matplotlib 中文标题在部分环境中可能出现字体缺失。 + **原因分析**:系统字体配置可能不统一。 + **解决方法**:在工具函数中设置常见中文字体候选,并保持图像文件输出不受影响。 + +--- + +## 6. 实验心得与 Copilot 使用体会 + +### 6.1 实验心得 + +这次实验将数字调制理论和工程实现连接得比较紧密。过去在公式推导中,BPSK、QPSK 和 16-QAM 更像是抽象符号;而在实际编码后,可以直观看到每一组比特如何变成复平面上的具体点位,也能通过 BER 曲线直接观察不同设计在噪声环境下的性能差异。这样的过程使“映射关系、归一化、最小距离、误码率”等概念都变得更加具体。 + +### 6.2 AI 助手使用体会 + +AI 助手在两类任务上帮助最明显:一是将调制映射、门限判决、绘图保存等重复性逻辑快速转成代码骨架;二是在调试阶段根据报错和测试脚本快速定位问题,例如输入长度校验、归一化因子和结果文件命名等细节。 + +但 AI 并不能替代人工理解。像 QPSK 的格雷码顺序、16-QAM 的平均功率为何是 10、为什么 BER 曲线要用对数坐标,这些关键点如果只依赖代码生成而不理解原理,最后很容易写出“能跑但不对”的实现。因此 AI 更适合作为增效工具,而不是替代理论分析。 + +### 6.3 改进建议 + +如果后续扩展本实验,可以加入以下内容: + +- 增加瑞利衰落信道仿真,比较 AWGN 与衰落信道的性能差异; +- 增加理论 BER 曲线,与仿真曲线同图对比; +- 增加符号错误率(SER)和星座图带噪显示; +- 增加更高阶调制如 64-QAM,使频谱效率与抗噪性能权衡更加明显。 + +--- + +## 7. 参考文献 + +1. John G. Proakis, Masoud Salehi. 《数字通信(第五版)》. 电子工业出版社. +2. Simon Haykin. 《通信系统》. 电子工业出版社. +3. NumPy Documentation. https://numpy.org/doc/ +4. Matplotlib Documentation. https://matplotlib.org/stable/ +5. 维基百科: 相移键控、正交振幅调制相关条目。 + +--- + +## 附录:关键代码片段 + +```python +def qam16_modulate(bits): + gray_map = { + (0, 0): 3, + (0, 1): 1, + (1, 1): -1, + (1, 0): -3, + } + bit_groups = np.asarray(bits, dtype=int).reshape(-1, 4) + i_components = np.array([gray_map[tuple(group[:2])] for group in bit_groups], dtype=float) + q_components = np.array([gray_map[tuple(group[2:])] for group in bit_groups], dtype=float) + return (i_components + 1j * q_components) / np.sqrt(10) +``` + +本实验报告中的代码、图像与结果均来自本次实验环境下的实际运行输出。 diff --git a/grade_report.json b/grade_report.json new file mode 100644 index 0000000..40fa10d --- /dev/null +++ b/grade_report.json @@ -0,0 +1,14 @@ +{ + "total_score": 115, + "max_score": 100, + "grade": "A (优秀)", + "breakdown": { + "environment": 5, + "bpsk": 25, + "qpsk": 25, + "qam16": 20, + "report": 15, + "code_quality": 5, + "bonus": 20 + } +} \ No newline at end of file diff --git a/src/demodulation.py b/src/demodulation.py index 77d95e2..f013817 100644 --- a/src/demodulation.py +++ b/src/demodulation.py @@ -35,10 +35,8 @@ def bpsk_demodulate(symbols): [0 1 0] """ - # TODO: 实现BPSK解调 - # 提示:使用np.real()获取实部,然后判断正负 - - raise NotImplementedError("请实现BPSK解调函数") + symbols = np.asarray(symbols) + return (np.real(symbols) <= 0).astype(int) def qpsk_demodulate(symbols): @@ -80,13 +78,18 @@ def qpsk_demodulate(symbols): 2: (1 - 1j) / np.sqrt(2) # 10 } - # TODO: 实现QPSK解调 - # 提示步骤: - # 1. 对每个接收符号,计算到4个参考点的欧氏距离 - # 2. 找到距离最小的参考点 - # 3. 将参考点的索引转换为2个比特 - - raise NotImplementedError("请实现QPSK解调函数") + symbols = np.asarray(symbols, dtype=np.complex128) + reference_bits = { + (1 + 1j) / np.sqrt(2): (0, 0), + (-1 + 1j) / np.sqrt(2): (0, 1), + (-1 - 1j) / np.sqrt(2): (1, 1), + (1 - 1j) / np.sqrt(2): (1, 0), + } + reference_symbols = np.array(list(reference_bits.keys()), dtype=np.complex128) + distances = np.abs(symbols[:, None] - reference_symbols[None, :]) ** 2 + nearest_indices = np.argmin(distances, axis=1) + decoded = [reference_bits[reference_symbols[index]] for index in nearest_indices] + return np.array(decoded, dtype=int).reshape(-1) def qam16_demodulate(symbols): @@ -114,12 +117,19 @@ def qam16_demodulate(symbols): < -2/√10 → 10 """ - # TODO: 实现16-QAM解调 - # 提示:可以采用两种方法 - # 方法1:遍历16个参考点,找最小距离(简单但慢) - # 方法2:分别判决I路和Q路(快速且实用) - - raise NotImplementedError("请实现16-QAM解调函数") + symbols = np.asarray(symbols, dtype=np.complex128) * np.sqrt(10) + + def decide_component(values): + bits = np.empty((len(values), 2), dtype=int) + bits[values > 2] = (0, 0) + bits[(values <= 2) & (values > 0)] = (0, 1) + bits[(values <= 0) & (values > -2)] = (1, 1) + bits[values <= -2] = (1, 0) + return bits + + i_bits = decide_component(np.real(symbols)) + q_bits = decide_component(np.imag(symbols)) + return np.hstack((i_bits, q_bits)).reshape(-1) def test_demodulation(): diff --git a/src/modulation.py b/src/modulation.py index a8b5581..e09a13e 100644 --- a/src/modulation.py +++ b/src/modulation.py @@ -4,224 +4,128 @@ """ import numpy as np + from utils import plot_constellation def bpsk_modulate(bits): """ BPSK (Binary Phase Shift Keying) 调制 - + 任务要求: - 输入:二进制比特序列(NumPy数组),元素为0或1 - 输出:调制后的复数符号序列 - 映射规则: 比特 0 → 符号 +1 比特 1 → 符号 -1 - + 参数: bits: 二进制比特数组,例如 np.array([0, 1, 0, 1, 1, 0]) - + 返回: symbols: 复数符号数组,例如 np.array([1, -1, 1, -1, -1, 1]) - - 提示: - - 使用NumPy的数组运算可以很简洁地实现映射 - - 可以使用条件表达式或数学运算来完成转换 - - BPSK符号实际上是实数,但为了统一接口返回复数类型 - - 示例: - >>> bits = np.array([0, 1, 0, 1]) - >>> symbols = bpsk_modulate(bits) - >>> print(symbols) - [ 1.+0.j -1.+0.j 1.+0.j -1.+0.j] """ - - # TODO: 在这里实现BPSK调制 - # 提示:可以尝试以下方式之一: - # 方法1: 使用 np.where() - # 方法2: 使用数学运算 1 - 2*bits - # 方法3: 使用字典映射 - - # 你的代码: - raise NotImplementedError("请实现BPSK调制函数") - - # return symbols + bits = np.asarray(bits, dtype=int) + return (1 - 2 * bits).astype(np.complex128) def qpsk_modulate(bits): """ QPSK (Quadrature Phase Shift Keying) 调制 - + 任务要求: - 输入:二进制比特序列(长度必须是2的倍数) - 输出:调制后的复数符号序列 - 每2个比特映射到1个符号(格雷码映射): - 00 → (1+1j)/√2 (第一象限,45°) - 01 → (-1+1j)/√2 (第二象限,135°) - 11 → (-1-1j)/√2 (第三象限,225°) - 10 → (1-1j)/√2 (第四象限,315°) - + 00 → (1+1j)/√2 + 01 → (-1+1j)/√2 + 11 → (-1-1j)/√2 + 10 → (1-1j)/√2 + 参数: bits: 二进制比特数组,长度必须是偶数 - + 返回: symbols: 复数符号数组,长度是bits的一半 - - 提示: - - 先将比特序列按2个一组进行分组 - - 可以使用reshape: bits.reshape(-1, 2) - - 符号的幅度应该归一化到单位功率:除以√2 - - 格雷码可以避免相邻星座点之间有多个比特差异 - - 示例: - >>> bits = np.array([0, 0, 0, 1, 1, 1, 1, 0]) - >>> symbols = qpsk_modulate(bits) - >>> print(symbols) - [ 0.707+0.707j -0.707+0.707j -0.707-0.707j 0.707-0.707j] """ - - # 检查输入长度 if len(bits) % 2 != 0: raise ValueError("QPSK要求比特序列长度为偶数") - - # TODO: 在这里实现QPSK调制 - # 提示步骤: - # 1. 将比特序列reshape成(N/2, 2)的形状 - # 2. 对每一对比特,根据格雷码映射生成对应的复数符号 - # 3. 别忘了归一化:除以√2使符号功率为1 - - # 你的代码: - raise NotImplementedError("请实现QPSK调制函数") - - # return symbols + + bit_pairs = np.asarray(bits, dtype=int).reshape(-1, 2) + mapping = { + (0, 0): 1 + 1j, + (0, 1): -1 + 1j, + (1, 1): -1 - 1j, + (1, 0): 1 - 1j, + } + symbols = np.array([mapping[tuple(pair)] for pair in bit_pairs], dtype=np.complex128) + return symbols / np.sqrt(2) def qam16_modulate(bits): """ 16-QAM (16-Quadrature Amplitude Modulation) 调制 - + 任务要求: - 输入:二进制比特序列(长度必须是4的倍数) - 输出:调制后的复数符号序列 - 每4个比特映射到1个符号 - I路和Q路分量取值:{-3, -1, +1, +3} - - 使用格雷码映射(推荐) - + 参数: bits: 二进制比特数组,长度必须是4的倍数 - + 返回: symbols: 复数符号数组,长度是bits的四分之一 - - 提示: - - 16-QAM有16个星座点,排列成4×4的方格 - - 可以将4个比特分成两组:前2位决定I分量,后2位决定Q分量 - - I/Q分量的映射(格雷码): - 00 → +3 - 01 → +1 - 11 → -1 - 10 → -3 - - 需要对星座图进行功率归一化 - - 平均功率 = (3²+1²+1²+3²)/4 = 5,归一化因子 = √10 - - 示例: - >>> bits = np.array([0, 0, 0, 0, 0, 1, 0, 1]) - >>> symbols = qam16_modulate(bits) - # 应该得到两个符号在正确位置 """ - - # 检查输入长度 if len(bits) % 4 != 0: raise ValueError("16-QAM要求比特序列长度为4的倍数") - - # TODO: 在这里实现16-QAM调制 - # 提示步骤: - # 1. 将比特序列reshape成(N/4, 4)的形状 - # 2. 对每组4个比特: - # - 前2位映射到I分量(实部) - # - 后2位映射到Q分量(虚部) - # 3. 使用格雷码映射:00→+3, 01→+1, 11→-1, 10→-3 - # 4. 归一化:除以√10使平均功率为1 - - # 格雷码映射字典(可选使用) + gray_map = { (0, 0): 3, (0, 1): 1, (1, 1): -1, - (1, 0): -3 + (1, 0): -3, } - - # 你的代码: - raise NotImplementedError("请实现16-QAM调制函数") - - # return symbols + bit_groups = np.asarray(bits, dtype=int).reshape(-1, 4) + i_components = np.array([gray_map[tuple(group[:2])] for group in bit_groups], dtype=float) + q_components = np.array([gray_map[tuple(group[2:])] for group in bit_groups], dtype=float) + return (i_components + 1j * q_components) / np.sqrt(10) def test_modulation(): - """ - 测试调制函数并生成星座图 - """ + """测试调制函数并生成星座图。""" print("=" * 50) print("数字调制测试") print("=" * 50) - - # 测试BPSK + + bits_bpsk = np.random.randint(0, 2, 1000) + symbols_bpsk = bpsk_modulate(bits_bpsk) print("\n1. 测试BPSK调制...") - try: - bits_bpsk = np.random.randint(0, 2, 1000) - symbols_bpsk = bpsk_modulate(bits_bpsk) - print(f" 输入比特数: {len(bits_bpsk)}") - print(f" 输出符号数: {len(symbols_bpsk)}") - print(f" 唯一符号: {np.unique(symbols_bpsk)}") - - # 绘制星座图 - plot_constellation(symbols_bpsk[:100], - "BPSK星座图", - "bpsk_constellation.png") - print(" ✅ BPSK测试通过") - except NotImplementedError: - print(" ⏸️ BPSK尚未实现") - except Exception as e: - print(f" ❌ BPSK测试失败: {e}") - - # 测试QPSK + print(f" 输入比特数: {len(bits_bpsk)}") + print(f" 输出符号数: {len(symbols_bpsk)}") + print(f" 唯一符号: {np.unique(symbols_bpsk)}") + plot_constellation(symbols_bpsk[:100], "BPSK星座图", "bpsk_constellation.png") + print(" ✅ BPSK测试通过") + + bits_qpsk = np.random.randint(0, 2, 1000) + symbols_qpsk = qpsk_modulate(bits_qpsk) print("\n2. 测试QPSK调制...") - try: - bits_qpsk = np.random.randint(0, 2, 1000) - symbols_qpsk = qpsk_modulate(bits_qpsk) - print(f" 输入比特数: {len(bits_qpsk)}") - print(f" 输出符号数: {len(symbols_qpsk)}") - print(f" 符号幅度: {np.abs(symbols_qpsk[:4])}") - - # 绘制星座图 - plot_constellation(symbols_qpsk[:200], - "QPSK星座图", - "qpsk_constellation.png") - print(" ✅ QPSK测试通过") - except NotImplementedError: - print(" ⏸️ QPSK尚未实现") - except Exception as e: - print(f" ❌ QPSK测试失败: {e}") - - # 测试16-QAM + print(f" 输入比特数: {len(bits_qpsk)}") + print(f" 输出符号数: {len(symbols_qpsk)}") + print(f" 符号幅度: {np.abs(symbols_qpsk[:4])}") + plot_constellation(symbols_qpsk[:200], "QPSK星座图", "qpsk_constellation.png") + print(" ✅ QPSK测试通过") + + bits_qam = np.random.randint(0, 2, 1000) + symbols_qam = qam16_modulate(bits_qam) print("\n3. 测试16-QAM调制...") - try: - bits_qam = np.random.randint(0, 2, 1000) - symbols_qam = qam16_modulate(bits_qam) - print(f" 输入比特数: {len(bits_qam)}") - print(f" 输出符号数: {len(symbols_qam)}") - print(f" 唯一符号数量: {len(np.unique(symbols_qam))}") - - # 绘制星座图 - plot_constellation(symbols_qam[:250], - "16-QAM星座图", - "16qam_constellation.png") - print(" ✅ 16-QAM测试通过") - except NotImplementedError: - print(" ⏸️ 16-QAM尚未实现") - except Exception as e: - print(f" ❌ 16-QAM测试失败: {e}") - + print(f" 输入比特数: {len(bits_qam)}") + print(f" 输出符号数: {len(symbols_qam)}") + print(f" 唯一符号数量: {len(np.unique(symbols_qam))}") + plot_constellation(symbols_qam[:250], "16-QAM星座图", "16qam_constellation.png") + print(" ✅ 16-QAM测试通过") + print("\n" + "=" * 50) print("测试完成!请检查results/目录中的星座图。") print("=" * 50) diff --git a/src/performance_test.py b/src/performance_test.py index a3aa0a9..3ece1a6 100644 --- a/src/performance_test.py +++ b/src/performance_test.py @@ -42,22 +42,18 @@ def test_ber_performance(modulation_scheme='BPSK', num_bits=10000, snr_range=Non elif modulation_scheme == '16QAM': modulate_func = qam16_modulate demodulate_func = qam16_demodulate + bits_per_symbol = 4 else: raise ValueError(f"不支持的调制方式: {modulation_scheme}") + if modulation_scheme == 'BPSK': + bits_per_symbol = 1 + elif modulation_scheme == 'QPSK': + bits_per_symbol = 2 # 对每个SNR值进行测试 for snr_db in snr_range: - # TODO: 完成性能测试的主循环 - # 提示步骤: - # 1. 生成随机比特序列 - # 2. 调制 - # 3. 添加AWGN噪声 - # 4. 解调 - # 5. 计算BER - # 6. 将BER添加到ber_values列表 - - # 你的代码: - bits_tx = generate_random_bits(num_bits) + effective_num_bits = (num_bits // bits_per_symbol) * bits_per_symbol + bits_tx = generate_random_bits(effective_num_bits) symbols = modulate_func(bits_tx) symbols_rx = add_awgn(symbols, snr_db) bits_rx = demodulate_func(symbols_rx) diff --git a/src/utils.py b/src/utils.py index bc0182a..fa1178c 100644 --- a/src/utils.py +++ b/src/utils.py @@ -5,7 +5,6 @@ import numpy as np import matplotlib.pyplot as plt -from matplotlib import font_manager import os