From 538efaee8c82e1b4ad584d556e09ba3eaec339e3 Mon Sep 17 00:00:00 2001 From: 2059353675 <2059353675@qq.com> Date: Thu, 23 Apr 2026 20:00:11 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=20BPSK=E3=80=81QPSK?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A16-QAM=20=E7=9A=84=E8=B0=83=E5=88=B6=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E8=A7=A3=E8=B0=83=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E8=BF=98=E8=BF=9B=E8=A1=8C=E4=BA=86=20BER=20=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 1 - src/demodulation.py | 93 +++++++++++++++++++++++++++++++++++---------- src/modulation.py | 78 +++++++++++++++++++------------------ 3 files changed, 113 insertions(+), 59 deletions(-) diff --git a/requirements.txt b/requirements.txt index 48749dc..d20c59e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -# 实验所需的Python依赖包 numpy>=1.21.0 scipy>=1.7.0 matplotlib>=3.4.0 diff --git a/src/demodulation.py b/src/demodulation.py index 77d95e2..f4e8ba4 100644 --- a/src/demodulation.py +++ b/src/demodulation.py @@ -34,11 +34,14 @@ def bpsk_demodulate(symbols): >>> print(bits) [0 1 0] """ - - # TODO: 实现BPSK解调 - # 提示:使用np.real()获取实部,然后判断正负 - - raise NotImplementedError("请实现BPSK解调函数") + + # 获取复数符号的实部 + real_parts = np.real(symbols) + + # 判决:实部 > 0 → 比特0,实部 ≤ 0 → 比特1 + bits = (real_parts <= 0).astype(int) + + return bits def qpsk_demodulate(symbols): @@ -79,14 +82,35 @@ def qpsk_demodulate(symbols): 3: (-1 - 1j) / np.sqrt(2), # 11 2: (1 - 1j) / np.sqrt(2) # 10 } - - # TODO: 实现QPSK解调 - # 提示步骤: - # 1. 对每个接收符号,计算到4个参考点的欧氏距离 - # 2. 找到距离最小的参考点 - # 3. 将参考点的索引转换为2个比特 - - raise NotImplementedError("请实现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 + } + + bits = [] + + # 遍历每个接收符号 + for symbol in symbols: + min_distance = float('inf') + best_index = 0 + + # 步骤1&2: 计算到4个参考点的欧氏距离,找最小距离的点 + for index, constellation_point in constellation.items(): + distance = abs(symbol - constellation_point) # 欧氏距离 + if distance < min_distance: + min_distance = distance + best_index = index + + # 步骤3: 将参考点索引转换为2个比特 + # 索引0->00, 1->01, 2->10, 3->11 + bits.append((best_index >> 1) & 1) # 高位比特 + bits.append(best_index & 1) # 低位比特 + + return bits def qam16_demodulate(symbols): @@ -113,13 +137,42 @@ def qam16_demodulate(symbols): -2/√10 ~ 0 → 11 < -2/√10 → 10 """ - - # TODO: 实现16-QAM解调 - # 提示:可以采用两种方法 - # 方法1:遍历16个参考点,找最小距离(简单但慢) - # 方法2:分别判决I路和Q路(快速且实用) - - raise NotImplementedError("请实现16-QAM解调函数") + + # 判决门限 + threshold = 2 / np.sqrt(10) + + # 初始化输出比特序列 + bits = [] + + for symbol in symbols: + # 分离 I 路和 Q 路 + I = symbol.real + Q = symbol.imag + + # ========== I 路判决 (2比特) ========== + if I > threshold: + bits_i = [0, 0] # > 2/√10 → 00 + elif I >= 0: + bits_i = [0, 1] # 0 ~ 2/√10 → 01 + elif I >= -threshold: + bits_i = [1, 1] # -2/√10 ~ 0 → 11 + else: + bits_i = [1, 0] # < -2/√10 → 10 + + # ========== Q 路判决 (2比特) ========== + if Q > threshold: + bits_q = [0, 0] # > 2/√10 → 00 + elif Q >= 0: + bits_q = [0, 1] # 0 ~ 2/√10 → 01 + elif Q >= -threshold: + bits_q = [1, 1] # -2/√10 ~ 0 → 11 + else: + bits_q = [1, 0] # < -2/√10 → 10 + + # 合并 I 路和 Q 路比特 + bits.extend(bits_i + bits_q) + + return np.array(bits) def test_demodulation(): diff --git a/src/modulation.py b/src/modulation.py index a8b5581..721f093 100644 --- a/src/modulation.py +++ b/src/modulation.py @@ -35,17 +35,10 @@ def 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 + + symbols = (1 - 2 * bits).astype(complex) + + return symbols def qpsk_modulate(bits): @@ -83,17 +76,22 @@ def qpsk_modulate(bits): # 检查输入长度 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 = bits.reshape(-1, 2) + indices = bit_pairs[:, 0] * 2 + bit_pairs[:, 1] + + # 星座点映射表 + constellation = np.array([ + (1 + 1j), # 00 → 0 + (-1 + 1j), # 01 → 1 + (-1 - 1j), # 11 → 2 + (1 - 1j) # 10 → 3 + ]) + + norm_factor = 1 / np.sqrt(2) + symbols = constellation[indices] * norm_factor + + return symbols def qam16_modulate(bits): @@ -133,29 +131,33 @@ def qam16_modulate(bits): # 检查输入长度 if len(bits) % 4 != 0: raise ValueError("16-QAM要求比特序列长度为4的倍数") - - # TODO: 在这里实现16-QAM调制 - # 提示步骤: + # 1. 将比特序列reshape成(N/4, 4)的形状 + bits = np.array(bits, dtype=int) + n_symbols = len(bits) // 4 + bits_reshaped = bits.reshape(n_symbols, 4) + # 2. 对每组4个比特: # - 前2位映射到I分量(实部) # - 后2位映射到Q分量(虚部) + i_index = bits_reshaped[:, 0] * 2 + bits_reshaped[:, 1] # 前2位 + q_index = bits_reshaped[:, 2] * 2 + bits_reshaped[:, 3] # 后2位 + + # 格雷码映射表(用于向量化查找) + # 行索引: 前2位组成的2比特数 (0-3) + # 列索引: 后2位组成的2比特数 (0-3) + gray_table = np.array([ + [3 + 3j, 1 + 3j, -1 + 3j, -3 + 3j], # I=00 + [3 + 1j, 1 + 1j, -1 + 1j, -3 + 1j], # I=01 + [3 - 1j, 1 - 1j, -1 - 1j, -3 - 1j], # I=11 + [3 - 3j, 1 - 3j, -1 - 3j, -3 - 3j], # I=10 + ]) + # 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 - } - - # 你的代码: - raise NotImplementedError("请实现16-QAM调制函数") - - # return symbols + symbols = gray_table[i_index, q_index] / np.sqrt(10) + return symbols def test_modulation(): """ From 23878a458ad6633a22bdb341410aaa6e5576c58c Mon Sep 17 00:00:00 2001 From: 2059353675 <2059353675@qq.com> Date: Thu, 23 Apr 2026 20:38:40 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=AE=8C=E6=88=90=20BPSK?= =?UTF-8?q?=E3=80=81QPSK=E4=BB=A5=E5=8F=8A16-QAM=20=E7=9A=84=E8=B0=83?= =?UTF-8?q?=E5=88=B6=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grading/test_bpsk.py | 2 +- grading/test_qam16.py | 2 +- grading/test_qpsk.py | 2 +- src/modulation.py | 79 +++++++++++++++++++++++------------------ src/performance_test.py | 4 +-- 5 files changed, 49 insertions(+), 40 deletions(-) diff --git a/grading/test_bpsk.py b/grading/test_bpsk.py index 4d191f9..6754eee 100644 --- a/grading/test_bpsk.py +++ b/grading/test_bpsk.py @@ -76,7 +76,7 @@ def test_large_sequence(self): def test_constellation_file_exists(): """测试是否生成了星座图文件""" - constellation_file = os.path.join('results', 'bpsk_constellation.png') + constellation_file = os.path.join('src', 'results', 'bpsk_constellation.png') # 如果文件不存在,尝试运行modulation.py生成 if not os.path.exists(constellation_file): diff --git a/grading/test_qam16.py b/grading/test_qam16.py index bc8d4e4..998aa9a 100644 --- a/grading/test_qam16.py +++ b/grading/test_qam16.py @@ -147,7 +147,7 @@ def test_corner_points(self): def test_constellation_file_exists(): """测试是否生成了星座图文件""" - constellation_file = os.path.join('results', '16qam_constellation.png') + constellation_file = os.path.join('src', 'results', '16qam_constellation.png') if not os.path.exists(constellation_file): pytest.skip("16-QAM星座图文件不存在,请运行modulation.py生成") diff --git a/grading/test_qpsk.py b/grading/test_qpsk.py index 6d2334d..86944c5 100644 --- a/grading/test_qpsk.py +++ b/grading/test_qpsk.py @@ -108,7 +108,7 @@ def test_consecutive_pairs(self): def test_constellation_file_exists(): """测试是否生成了星座图文件""" - constellation_file = os.path.join('results', 'qpsk_constellation.png') + constellation_file = os.path.join('src', 'results', 'qpsk_constellation.png') if not os.path.exists(constellation_file): pytest.skip("QPSK星座图文件不存在,请运行modulation.py生成") diff --git a/src/modulation.py b/src/modulation.py index 721f093..bce8d51 100644 --- a/src/modulation.py +++ b/src/modulation.py @@ -72,24 +72,28 @@ def qpsk_modulate(bits): >>> print(symbols) [ 0.707+0.707j -0.707+0.707j -0.707-0.707j 0.707-0.707j] """ + # 确保输入是 numpy 数组,方便矩阵运算 + bits = np.asarray(bits) # 检查输入长度 if len(bits) % 2 != 0: raise ValueError("QPSK要求比特序列长度为偶数") - bit_pairs = bits.reshape(-1, 2) - indices = bit_pairs[:, 0] * 2 + bit_pairs[:, 1] + # 1. 将比特序列从一维 [b0, b1, b2, b3...] 转换为二维 [[b0, b1], [b2, b3]...] + # 每行代表一个符号的两个比特:[bit_I_ref, bit_Q_ref] + # 映射逻辑如下: + # 00 -> (1+1j), 01 -> (-1+1j), 11 -> (-1-1j), 10 -> (1-1j) + pairs = bits.reshape(-1, 2) - # 星座点映射表 - constellation = np.array([ - (1 + 1j), # 00 → 0 - (-1 + 1j), # 01 → 1 - (-1 - 1j), # 11 → 2 - (1 - 1j) # 10 → 3 - ]) + # 2. 根据映射逻辑计算实部和虚部 + # 规律推导: + # 第一位(bits[:, 0])控制虚部: 0 -> +1, 1 -> -1 => Imag = 1 - 2*bit0 + # 第二位(bits[:, 1])控制实部: 0 -> +1, 1 -> -1 => Real = 1 - 2*bit1 + real_part = 1 - 2 * pairs[:, 1] + imag_part = 1 - 2 * pairs[:, 0] - norm_factor = 1 / np.sqrt(2) - symbols = constellation[indices] * norm_factor + # 3. 组合成复数符号并进行功率归一化 + symbols = (real_part + 1j * imag_part) / np.sqrt(2) return symbols @@ -127,35 +131,40 @@ def qam16_modulate(bits): >>> symbols = qam16_modulate(bits) # 应该得到两个符号在正确位置 """ + + # 确保输入是 numpy 数组,方便操作 + bits = np.asanyarray(bits) # 检查输入长度 if len(bits) % 4 != 0: raise ValueError("16-QAM要求比特序列长度为4的倍数") - # 1. 将比特序列reshape成(N/4, 4)的形状 - bits = np.array(bits, dtype=int) - n_symbols = len(bits) // 4 - bits_reshaped = bits.reshape(n_symbols, 4) - - # 2. 对每组4个比特: - # - 前2位映射到I分量(实部) - # - 后2位映射到Q分量(虚部) - i_index = bits_reshaped[:, 0] * 2 + bits_reshaped[:, 1] # 前2位 - q_index = bits_reshaped[:, 2] * 2 + bits_reshaped[:, 3] # 后2位 - - # 格雷码映射表(用于向量化查找) - # 行索引: 前2位组成的2比特数 (0-3) - # 列索引: 后2位组成的2比特数 (0-3) - gray_table = np.array([ - [3 + 3j, 1 + 3j, -1 + 3j, -3 + 3j], # I=00 - [3 + 1j, 1 + 1j, -1 + 1j, -3 + 1j], # I=01 - [3 - 1j, 1 - 1j, -1 - 1j, -3 - 1j], # I=11 - [3 - 3j, 1 - 3j, -1 - 3j, -3 - 3j], # I=10 - ]) - - # 3. 使用格雷码映射:00→+3, 01→+1, 11→-1, 10→-3 - # 4. 归一化:除以√10使平均功率为1 - symbols = gray_table[i_index, q_index] / np.sqrt(10) + # 1. 将比特序列重塑 (N/4, 4) + # 每一行代表一个 QAM 符号的 4 个比特 [b0, b1, b2, b3] + reshaped_bits = bits.reshape(-1, 4) + + # 定义格雷码映射表 + # 映射关系:00→3, 01→1, 11→-1, 10→-3 + gray_map = { + (0, 0): 3, + (0, 1): 1, + (1, 1): -1, + (1, 0): -3 + } + + # 3. 映射比特到 I/Q 分量 + # 前两位控制 I (实部),后两位控制 Q (虚部) + i_parts = np.array([gray_map[tuple(b)] for b in reshaped_bits[:, :2]]) + q_parts = np.array([gray_map[tuple(b)] for b in reshaped_bits[:, 2:]]) + + # 组合成复数符号 + symbols = i_parts + 1j * q_parts + + # 4. 功率归一化 + # 16-QAM 平均功率为 10 (对于分量 {±1, ±3}) + # 归一化因子为 1/sqrt(10) + normalization_factor = np.sqrt(10) + symbols = symbols / normalization_factor return symbols diff --git a/src/performance_test.py b/src/performance_test.py index a3aa0a9..8b11ba4 100644 --- a/src/performance_test.py +++ b/src/performance_test.py @@ -112,8 +112,8 @@ def compare_modulations(): 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') + # os.makedirs('results', exist_ok=True) + filepath = os.path.join('src', 'results', 'ber_comparison.png') plt.savefig(filepath, dpi=300, bbox_inches='tight') print(f"\n✅ 性能对比图已保存到: {filepath}")