From 36377db9ff8e51a58566a8b1ee2e70b570adfbd2 Mon Sep 17 00:00:00 2001 From: 3533835266-lang <3533835266@qq.com> Date: Thu, 23 Apr 2026 20:28:51 +0800 Subject: [PATCH] Complete modulation tasks --- src/demodulation.py | 189 +++++++++++++----------------------- src/modulation.py | 207 +++++++++------------------------------- src/performance_test.py | 108 +++++---------------- 3 files changed, 133 insertions(+), 371 deletions(-) diff --git a/src/demodulation.py b/src/demodulation.py index 77d95e2..577f62a 100644 --- a/src/demodulation.py +++ b/src/demodulation.py @@ -5,150 +5,94 @@ import numpy as np - def bpsk_demodulate(symbols): - """ - BPSK解调 - - 任务要求: - - 输入:接收到的复数符号序列(可能带噪声) - - 输出:恢复的比特序列 - - 判决准则: - 实部 > 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解调函数") + """BPSK解调""" + # 实部 > 0 → 比特 0;实部 ≤ 0 → 比特 1 + return (np.real(symbols) <= 0).astype(int) 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 + (0, 0): (1 + 1j) / np.sqrt(2), + (0, 1): (-1 + 1j) / np.sqrt(2), + (1, 1): (-1 - 1j) / np.sqrt(2), + (1, 0): (1 - 1j) / np.sqrt(2) } - - # TODO: 实现QPSK解调 - # 提示步骤: - # 1. 对每个接收符号,计算到4个参考点的欧氏距离 - # 2. 找到距离最小的参考点 - # 3. 将参考点的索引转换为2个比特 - - raise NotImplementedError("请实现QPSK解调函数") + + bits = np.zeros(len(symbols) * 2, dtype=int) + points = list(constellation.values()) + keys = list(constellation.keys()) + + for i, sym in enumerate(symbols): + # 计算欧氏距离 + dists = [np.abs(sym - p) for p in points] + best_idx = np.argmin(dists) + b0, b1 = keys[best_idx] + + bits[2*i] = b0 + bits[2*i+1] = b1 + + return 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 - """ - - # TODO: 实现16-QAM解调 - # 提示:可以采用两种方法 - # 方法1:遍历16个参考点,找最小距离(简单但慢) - # 方法2:分别判决I路和Q路(快速且实用) - - raise NotImplementedError("请实现16-QAM解调函数") + """16-QAM解调""" + bits = np.zeros(len(symbols) * 4, dtype=int) + + for i, sym in enumerate(symbols): + # 还原归一化以便于判断边界 + sym_unnorm = sym * np.sqrt(10) + I = np.real(sym_unnorm) + Q = np.imag(sym_unnorm) + + # 判决 I 路 + if I > 2: + b0, b1 = 0, 0 + elif I > 0: + b0, b1 = 0, 1 + elif I > -2: + b0, b1 = 1, 1 + else: + b0, b1 = 1, 0 + + # 判决 Q 路 + if Q > 2: + b2, b3 = 0, 0 + elif Q > 0: + b2, b3 = 0, 1 + elif Q > -2: + b2, b3 = 1, 1 + else: + b2, b3 = 1, 0 + + bits[4*i:4*i+4] = [b0, b1, b2, b3] + + return bits def test_demodulation(): - """ - 测试解调函数 - 需要先完成调制函数才能运行 - """ + """测试解调函数""" from modulation import bpsk_modulate, qpsk_modulate, qam16_modulate from utils import add_awgn, calculate_ber - + print("=" * 50) print("解调测试") print("=" * 50) - + # 测试BPSK print("\n1. 测试BPSK解调...") 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解调测试通过") - except NotImplementedError: - print(" ⏸️ BPSK解调尚未实现") except Exception as e: print(f" ❌ BPSK解调测试失败: {e}") - + # 测试QPSK print("\n2. 测试QPSK解调...") try: @@ -159,28 +103,23 @@ def test_demodulation(): ber = calculate_ber(bits_tx, bits_rx) print(f" BER = {ber:.4f} (SNR=10dB)") print(" ✅ QPSK解调测试通过") - except NotImplementedError: - print(" ⏸️ QPSK解调尚未实现") except Exception as 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解调测试通过") - except NotImplementedError: - print(" ⏸️ 16-QAM解调尚未实现") except Exception as e: print(f" ❌ 16-QAM解调测试失败: {e}") - - print("\n" + "=" * 50) + 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..55f59a5 100644 --- a/src/modulation.py +++ b/src/modulation.py @@ -8,163 +8,65 @@ 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 + """BPSK 调制""" + # 映射规则:比特 0 -> +1,比特 1 -> -1 + symbols = 1 - 2 * bits + return symbols.astype(complex) 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] - """ - - # 检查输入长度 + """QPSK 调制""" if len(bits) % 2 != 0: raise ValueError("QPSK要求比特序列长度为偶数") - - # TODO: 在这里实现QPSK调制 - # 提示步骤: - # 1. 将比特序列reshape成(N/2, 2)的形状 - # 2. 对每一对比特,根据格雷码映射生成对应的复数符号 - # 3. 别忘了归一化:除以√2使符号功率为1 - - # 你的代码: - raise NotImplementedError("请实现QPSK调制函数") - - # return symbols + + bits_reshaped = bits.reshape(-1, 2) + symbols = np.zeros(len(bits_reshaped), dtype=complex) + + for i, (b0, b1) in enumerate(bits_reshaped): + if b0 == 0 and b1 == 0: + symbols[i] = 1 + 1j + elif b0 == 0 and b1 == 1: + symbols[i] = -1 + 1j + elif b0 == 1 and b1 == 1: + symbols[i] = -1 - 1j + elif b0 == 1 and b1 == 0: + symbols[i] = 1 - 1j + + # 功率归一化 + 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) - # 应该得到两个符号在正确位置 - """ - - # 检查输入长度 + """16-QAM 调制""" 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 - - # 格雷码映射字典(可选使用) + + bits_reshaped = bits.reshape(-1, 4) + symbols = np.zeros(len(bits_reshaped), dtype=complex) + + # 格雷码映射字典 gray_map = { (0, 0): 3, (0, 1): 1, (1, 1): -1, (1, 0): -3 } - - # 你的代码: - raise NotImplementedError("请实现16-QAM调制函数") - - # return symbols + + for i, (b0, b1, b2, b3) in enumerate(bits_reshaped): + I = gray_map[(b0, b1)] + Q = gray_map[(b2, b3)] + symbols[i] = I + 1j * Q + + # 功率归一化,除以 √10 + return symbols / np.sqrt(10) def test_modulation(): - """ - 测试调制函数并生成星座图 - """ + """测试调制函数并生成星座图""" print("=" * 50) print("数字调制测试") print("=" * 50) - + # 测试BPSK print("\n1. 测试BPSK调制...") try: @@ -173,17 +75,11 @@ def test_modulation(): 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") + 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("\n2. 测试QPSK调制...") try: @@ -191,18 +87,11 @@ def test_modulation(): 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") + 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("\n3. 测试16-QAM调制...") try: @@ -210,22 +99,14 @@ def test_modulation(): 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") + 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("\n" + "=" * 50) print("测试完成!请检查results/目录中的星座图。") print("=" * 50) - if __name__ == "__main__": - test_modulation() + test_modulation() \ No newline at end of file diff --git a/src/performance_test.py b/src/performance_test.py index a3aa0a9..1ddce7b 100644 --- a/src/performance_test.py +++ b/src/performance_test.py @@ -6,140 +6,82 @@ import numpy as np from modulation import bpsk_modulate, qpsk_modulate, qam16_modulate from demodulation import bpsk_demodulate, qpsk_demodulate, qam16_demodulate -from utils import add_awgn, calculate_ber, plot_ber_curve, generate_random_bits +from utils import add_awgn, calculate_ber, generate_random_bits def test_ber_performance(modulation_scheme='BPSK', num_bits=10000, snr_range=None): - """ - 测试指定调制方式的BER性能 - - 参数: - modulation_scheme: 'BPSK', 'QPSK', 或 '16QAM' - num_bits: 用于测试的比特数量 - snr_range: SNR范围(dB),例如 np.arange(0, 16, 2) - - 返回: - snr_db: SNR值数组 - ber_values: 对应的BER值数组 - """ - if snr_range is None: - snr_range = np.arange(0, 16, 2) # 0, 2, 4, ..., 14 dB - + snr_range = np.arange(0, 16, 2) + ber_values = [] - + print(f"\n测试 {modulation_scheme} 性能...") print(f"比特数: {num_bits}, SNR范围: {snr_range[0]}~{snr_range[-1]} dB") print("-" * 40) - - # 选择调制/解调函数 + if modulation_scheme == 'BPSK': - modulate_func = bpsk_modulate - demodulate_func = bpsk_demodulate + modulate_func, demodulate_func = bpsk_modulate, bpsk_demodulate elif modulation_scheme == 'QPSK': - modulate_func = qpsk_modulate - demodulate_func = qpsk_demodulate + modulate_func, demodulate_func = qpsk_modulate, qpsk_demodulate elif modulation_scheme == '16QAM': - modulate_func = qam16_modulate - demodulate_func = qam16_demodulate + modulate_func, demodulate_func = qam16_modulate, qam16_demodulate else: raise ValueError(f"不支持的调制方式: {modulation_scheme}") - - # 对每个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) symbols = modulate_func(bits_tx) symbols_rx = add_awgn(symbols, snr_db) bits_rx = demodulate_func(symbols_rx) ber = calculate_ber(bits_tx, bits_rx) - + ber_values.append(ber) print(f"SNR = {snr_db:2d} dB, BER = {ber:.6f}") - + return snr_range, np.array(ber_values) def compare_modulations(): - """ - 比较不同调制方式的性能 - 绘制BER对比曲线 - """ - print("=" * 50) print("数字调制性能对比测试") print("=" * 50) - + snr_range = np.arange(0, 16, 2) - - # TODO: 测试各种调制方式并绘制对比图 - # 提示: - # 1. 分别测试BPSK、QPSK、16-QAM - # 2. 收集所有的BER数据 - # 3. 在一张图上绘制多条曲线 - + try: - # 测试BPSK - snr_bpsk, ber_bpsk = test_ber_performance('BPSK', num_bits=10000, snr_range=snr_range) - - # 测试QPSK - snr_qpsk, ber_qpsk = test_ber_performance('QPSK', num_bits=10000, snr_range=snr_range) - - # 测试16-QAM - snr_qam, ber_qam = test_ber_performance('16QAM', num_bits=10000, snr_range=snr_range) - - # 绘制对比图 + snr_bpsk, ber_bpsk = test_ber_performance('BPSK', num_bits=20000, snr_range=snr_range) + snr_qpsk, ber_qpsk = test_ber_performance('QPSK', num_bits=20000, snr_range=snr_range) + snr_qam, ber_qam = test_ber_performance('16QAM', num_bits=40000, snr_range=snr_range) + import matplotlib.pyplot as plt import os - + from utils import setup_chinese_font + + setup_chinese_font() plt.figure(figsize=(10, 6)) plt.semilogy(snr_bpsk, ber_bpsk, 'b-o', label='BPSK', linewidth=2) plt.semilogy(snr_qpsk, ber_qpsk, 'r-s', label='QPSK', linewidth=2) plt.semilogy(snr_qam, ber_qam, 'g-^', label='16-QAM', linewidth=2) - + plt.xlabel('SNR (dB)', fontsize=12) plt.ylabel('Bit Error Rate (BER)', fontsize=12) plt.title('数字调制方式性能对比', 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}") - plt.close() - - except NotImplementedError as e: - print(f"\n⏸️ 部分函数尚未实现: {e}") + except Exception as e: print(f"\n❌ 测试失败: {e}") - - print("\n" + "=" * 50) + print("\n" + "=" * 50) 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") - - # 选项2: 对比测试(推荐) compare_modulations() - if __name__ == "__main__": - main() + main() \ No newline at end of file