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/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..bce8d51 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): @@ -79,21 +72,30 @@ 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要求比特序列长度为偶数") - - # TODO: 在这里实现QPSK调制 - # 提示步骤: - # 1. 将比特序列reshape成(N/2, 2)的形状 - # 2. 对每一对比特,根据格雷码映射生成对应的复数符号 - # 3. 别忘了归一化:除以√2使符号功率为1 - - # 你的代码: - raise NotImplementedError("请实现QPSK调制函数") - - # return symbols + + # 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) + + # 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] + + # 3. 组合成复数符号并进行功率归一化 + symbols = (real_part + 1j * imag_part) / np.sqrt(2) + + return symbols def qam16_modulate(bits): @@ -129,33 +131,42 @@ def qam16_modulate(bits): >>> symbols = qam16_modulate(bits) # 应该得到两个符号在正确位置 """ + + # 确保输入是 numpy 数组,方便操作 + bits = np.asanyarray(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 - - # 格雷码映射字典(可选使用) + + # 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 } - - # 你的代码: - raise NotImplementedError("请实现16-QAM调制函数") - - # return symbols + # 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 def test_modulation(): """ 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}")