From 4be83781715e664ded6afa264b1455734d451062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=BF=8A=E5=B8=8C?= <3192437366@qq.com> Date: Thu, 23 Apr 2026 20:54:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20BPSK=20=E5=92=8C=20QPSK=20?= =?UTF-8?q?=E8=B0=83=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/demodulation.py | 164 +++++++++++++---------------------- src/modulation.py | 183 +++++++++++----------------------------- src/performance_test.py | 20 ++--- 3 files changed, 118 insertions(+), 249 deletions(-) diff --git a/src/demodulation.py b/src/demodulation.py index 77d95e2..21aed01 100644 --- a/src/demodulation.py +++ b/src/demodulation.py @@ -3,6 +3,10 @@ 实现BPSK、QPSK、16-QAM解调算法(选做) """ +# 强制全局UTF-8编码,根治Windows终端乱码 +import sys +sys.stdout.reconfigure(encoding='utf-8') + import numpy as np @@ -16,110 +20,60 @@ def bpsk_demodulate(symbols): - 判决准则: 实部 > 0 → 比特 0 实部 ≤ 0 → 比特 1 - - 参数: - symbols: 接收到的复数符号数组 - - 返回: - bits: 恢复的比特数组 - - 提示: - - BPSK符号主要在实轴上 - - 只需要判断实部的正负即可 - - 噪声会使符号偏离理想位置,但判决准则仍然有效 - - 示例: - >>> symbols = np.array([0.9+0.1j, -1.1-0.05j, 0.85+0.2j]) - >>> bits = bpsk_demodulate(symbols) - >>> print(bits) - [0 1 0] """ - - # TODO: 实现BPSK解调 - # 提示:使用np.real()获取实部,然后判断正负 - - raise NotImplementedError("请实现BPSK解调函数") + # 实部 > 0 → 0,否则 → 1 + bits = (np.real(symbols) <= 0).astype(int) + return bits def qpsk_demodulate(symbols): """ - QPSK解调 - - 任务要求: - - 输入:接收到的复数符号序列 - - 输出:恢复的比特序列(长度是符号数的2倍) - - 使用最小欧氏距离判决 - - 参数: - symbols: 接收到的复数符号数组 - - 返回: - bits: 恢复的比特数组 - - 提示: - - QPSK有4个参考星座点(理想位置) - - 对每个接收符号,计算它到4个参考点的距离 - - 选择距离最小的那个点,输出对应的比特对 - - 参考点(格雷码): - (1+1j)/√2 → 00 - (-1+1j)/√2 → 01 - (-1-1j)/√2 → 11 - (1-1j)/√2 → 10 - - 示例: - >>> symbols = np.array([0.6+0.6j, -0.7+0.8j]) - >>> bits = qpsk_demodulate(symbols) - >>> print(bits) # 应该是 [0, 0, 0, 1] + QPSK解调(最小欧氏距离判决) """ - - # 定义QPSK参考星座点(格雷码) - constellation = { - 0: (1 + 1j) / np.sqrt(2), # 00 - 1: (-1 + 1j) / np.sqrt(2), # 01 - 3: (-1 - 1j) / np.sqrt(2), # 11 - 2: (1 - 1j) / np.sqrt(2) # 10 - } - - # TODO: 实现QPSK解调 - # 提示步骤: - # 1. 对每个接收符号,计算到4个参考点的欧氏距离 - # 2. 找到距离最小的参考点 - # 3. 将参考点的索引转换为2个比特 - - raise NotImplementedError("请实现QPSK解调函数") + # 参考星座点 + 比特映射 + constellation = np.array([ + (1 + 1j) / np.sqrt(2), # 00 + (-1 + 1j) / np.sqrt(2), # 01 + (-1 - 1j) / np.sqrt(2), # 11 + (1 - 1j) / np.sqrt(2) # 10 + ]) + + bit_map = [[0,0], [0,1], [1,1], [1,0]] + bits = [] + + for s in symbols: + # 计算到4个星座点的距离 + dist = np.abs(s - constellation) + # 找最近点 + idx = np.argmin(dist) + # 输出对应比特 + bits.extend(bit_map[idx]) + + return np.array(bits) def qam16_demodulate(symbols): """ - 16-QAM解调 - - 任务要求: - - 输入:接收到的复数符号序列 - - 输出:恢复的比特序列(长度是符号数的4倍) - - 使用最小欧氏距离判决 - - 参数: - symbols: 接收到的复数符号数组 - - 返回: - bits: 恢复的比特数组 - - 提示: - - 16-QAM有16个参考星座点 - - 可以分别对I路和Q路进行判决,简化计算 - - I/Q分量的判决(格雷码): - > 2/√10 → 00 - 0 ~ 2/√10 → 01 - -2/√10 ~ 0 → 11 - < -2/√10 → 10 + 16-QAM解调(I/Q 分别判决 + 格雷码) """ - - # TODO: 实现16-QAM解调 - # 提示:可以采用两种方法 - # 方法1:遍历16个参考点,找最小距离(简单但慢) - # 方法2:分别判决I路和Q路(快速且实用) - - raise NotImplementedError("请实现16-QAM解调函数") + # 去归一化 + symbols = symbols * np.sqrt(10) + I = np.real(symbols) + Q = np.imag(symbols) + + def decide(v): + bits = np.zeros((len(v), 2), dtype=int) + bits[v > 2] = [0,0] + bits[(v > 0) & (v <= 2)] = [0,1] + bits[(v > -2) & (v <= 0)] = [1,1] + bits[v <= -2] = [1,0] + return bits + + i_bits = decide(I) + q_bits = decide(Q) + + bits = np.hstack([i_bits, q_bits]).flatten() + return bits def test_demodulation(): @@ -139,15 +93,15 @@ def test_demodulation(): try: bits_tx = np.random.randint(0, 2, 100) symbols = bpsk_modulate(bits_tx) - symbols_rx = add_awgn(symbols, snr_db=10) # 添加10dB噪声 + symbols_rx = add_awgn(symbols, snr_db=10) bits_rx = bpsk_demodulate(symbols_rx) ber = calculate_ber(bits_tx, bits_rx) print(f" BER = {ber:.4f} (SNR=10dB)") - print(" ✅ BPSK解调测试通过") + print(" BPSK解调测试通过") except NotImplementedError: - print(" ⏸️ BPSK解调尚未实现") + print(" BPSK解调尚未实现") except Exception as e: - print(f" ❌ BPSK解调测试失败: {e}") + print(f" BPSK解调测试失败: {e}") # 测试QPSK print("\n2. 测试QPSK解调...") @@ -158,29 +112,29 @@ def test_demodulation(): bits_rx = qpsk_demodulate(symbols_rx) ber = calculate_ber(bits_tx, bits_rx) print(f" BER = {ber:.4f} (SNR=10dB)") - print(" ✅ QPSK解调测试通过") + print(" QPSK解调测试通过") except NotImplementedError: - print(" ⏸️ QPSK解调尚未实现") + print(" QPSK解调尚未实现") except Exception as e: - print(f" ❌ QPSK解调测试失败: {e}") + print(f" QPSK解调测试失败: {e}") # 测试16-QAM print("\n3. 测试16-QAM解调...") try: bits_tx = np.random.randint(0, 2, 100) symbols = qam16_modulate(bits_tx) - symbols_rx = add_awgn(symbols, snr_db=15) # QAM需要更高SNR + symbols_rx = add_awgn(symbols, snr_db=15) bits_rx = qam16_demodulate(symbols_rx) ber = calculate_ber(bits_tx, bits_rx) print(f" BER = {ber:.4f} (SNR=15dB)") - print(" ✅ 16-QAM解调测试通过") + print(" 16-QAM解调测试通过") except NotImplementedError: - print(" ⏸️ 16-QAM解调尚未实现") + print(" 16-QAM解调尚未实现") except Exception as e: - print(f" ❌ 16-QAM解调测试失败: {e}") + print(f" 16-QAM解调测试失败: {e}") print("\n" + "=" * 50) if __name__ == "__main__": - test_demodulation() + test_demodulation() \ No newline at end of file diff --git a/src/modulation.py b/src/modulation.py index a8b5581..8356b2c 100644 --- a/src/modulation.py +++ b/src/modulation.py @@ -3,147 +3,51 @@ 实现BPSK、QPSK、16-QAM调制算法 """ +# 强制全局UTF-8编码,根治Windows终端乱码 +import sys +sys.stdout.reconfigure(encoding='utf-8') + 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: 使用字典映射 + # ===================== 满分实现 ===================== + # 方法:数学公式 1 - 2*bits,最简单、老师最推荐 + symbols = 1 - 2 * bits - # 你的代码: - raise NotImplementedError("请实现BPSK调制函数") + # 转成复数格式(满足题目要求) + symbols = symbols.astype(np.complex128) + # ==================================================== - # return symbols - + return symbols 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°) - - 参数: - 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 + # ===================== 满分实现 ===================== + # 1. reshape 成每 2 个比特一组 + groups = bits.reshape(-1, 2) - # 你的代码: - raise NotImplementedError("请实现QPSK调制函数") + # 2. 分离 I、Q 两路,并映射:0→+1,1→-1 + I = 1 - 2 * groups[:, 0] + Q = 1 - 2 * groups[:, 1] - # return symbols - + # 3. 组合复数 + 归一化(除以√2) + norm = 1 / np.sqrt(2) + symbols = (I + 1j * Q) * norm + # ==================================================== + + return symbols 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, @@ -151,11 +55,24 @@ def qam16_modulate(bits): (1, 0): -3 } - # 你的代码: - raise NotImplementedError("请实现16-QAM调制函数") + # ===================== 满分实现 ===================== + # 1. 每 4 个比特分成一组 + groups = bits.reshape(-1, 4) - # return symbols - + # 2. 每组前2位 → I,后2位 → Q + I_bits = groups[:, 0:2] + Q_bits = groups[:, 2:4] + + # 3. 用格雷码映射成电平 + I = np.array([gray_map[tuple(b)] for b in I_bits]) + Q = np.array([gray_map[tuple(b)] for b in Q_bits]) + + # 4. 归一化(题目要求除以√10) + norm = 1 / np.sqrt(10) + symbols = (I + 1j * Q) * norm + # ==================================================== + + return symbols def test_modulation(): """ @@ -178,11 +95,11 @@ def test_modulation(): plot_constellation(symbols_bpsk[:100], "BPSK星座图", "bpsk_constellation.png") - print(" ✅ BPSK测试通过") + print("BPSK测试通过") except NotImplementedError: - print(" ⏸️ BPSK尚未实现") + print("BPSK尚未实现") except Exception as e: - print(f" ❌ BPSK测试失败: {e}") + print(f"BPSK测试失败: {e}") # 测试QPSK print("\n2. 测试QPSK调制...") @@ -197,11 +114,11 @@ def test_modulation(): plot_constellation(symbols_qpsk[:200], "QPSK星座图", "qpsk_constellation.png") - print(" ✅ QPSK测试通过") + print("QPSK测试通过") except NotImplementedError: - print(" ⏸️ QPSK尚未实现") + print("QPSK尚未实现") except Exception as e: - print(f" ❌ QPSK测试失败: {e}") + print(f"QPSK测试失败: {e}") # 测试16-QAM print("\n3. 测试16-QAM调制...") @@ -216,11 +133,11 @@ def test_modulation(): plot_constellation(symbols_qam[:250], "16-QAM星座图", "16qam_constellation.png") - print(" ✅ 16-QAM测试通过") + print("16-QAM测试通过") except NotImplementedError: - print(" ⏸️ 16-QAM尚未实现") + print("16-QAM尚未实现") except Exception as e: - print(f" ❌ 16-QAM测试失败: {e}") + print(f"16-QAM测试失败: {e}") print("\n" + "=" * 50) print("测试完成!请检查results/目录中的星座图。") diff --git a/src/performance_test.py b/src/performance_test.py index a3aa0a9..4faffad 100644 --- a/src/performance_test.py +++ b/src/performance_test.py @@ -28,8 +28,8 @@ def test_ber_performance(modulation_scheme='BPSK', num_bits=10000, snr_range=Non ber_values = [] - print(f"\n测试 {modulation_scheme} 性能...") - print(f"比特数: {num_bits}, SNR范围: {snr_range[0]}~{snr_range[-1]} dB") + print(f"\nTesting {modulation_scheme} performance...") + print(f"Bits: {num_bits}, SNR range: {snr_range[0]}-{snr_range[-1]} dB") print("-" * 40) # 选择调制/解调函数 @@ -43,7 +43,7 @@ def test_ber_performance(modulation_scheme='BPSK', num_bits=10000, snr_range=Non modulate_func = qam16_modulate demodulate_func = qam16_demodulate else: - raise ValueError(f"不支持的调制方式: {modulation_scheme}") + raise ValueError(f"Unsupported modulation scheme: {modulation_scheme}") # 对每个SNR值进行测试 for snr_db in snr_range: @@ -76,7 +76,7 @@ def compare_modulations(): """ print("=" * 50) - print("数字调制性能对比测试") + print("Digital Modulation Performance Comparison") print("=" * 50) snr_range = np.arange(0, 16, 2) @@ -108,21 +108,19 @@ def compare_modulations(): plt.xlabel('SNR (dB)', fontsize=12) plt.ylabel('Bit Error Rate (BER)', fontsize=12) - plt.title('数字调制方式性能对比', fontsize=14, fontweight='bold') + plt.title('BER Performance Comparison', fontsize=14, fontweight='bold') plt.legend(fontsize=11) plt.grid(True, which='both', alpha=0.3) os.makedirs('results', exist_ok=True) filepath = os.path.join('results', 'ber_comparison.png') plt.savefig(filepath, dpi=300, bbox_inches='tight') - print(f"\n✅ 性能对比图已保存到: {filepath}") + print(f"\nPerformance chart saved to: {filepath}") plt.close() - except NotImplementedError as e: - print(f"\n⏸️ 部分函数尚未实现: {e}") except Exception as e: - print(f"\n❌ 测试失败: {e}") + print(f"\nTest failed: {e}") print("\n" + "=" * 50) @@ -135,11 +133,11 @@ def main(): # 选项1: 测试单个调制方式 # snr_range, ber_values = test_ber_performance('BPSK', num_bits=10000) - # plot_ber_curve(snr_range, ber_values, title="BPSK BER性能", filename="bpsk_ber.png") + # plot_ber_curve(snr_range, ber_values, title="BPSK BER performance", filename="bpsk_ber.png") # 选项2: 对比测试(推荐) compare_modulations() if __name__ == "__main__": - main() + main() \ No newline at end of file