Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion grading/test_bpsk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion grading/test_qam16.py
Original file line number Diff line number Diff line change
Expand Up @@ -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生成")
Expand Down
2 changes: 1 addition & 1 deletion grading/test_qpsk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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生成")
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# 实验所需的Python依赖包
numpy>=1.21.0
scipy>=1.7.0
matplotlib>=3.4.0
Expand Down
93 changes: 73 additions & 20 deletions src/demodulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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():
Expand Down
87 changes: 49 additions & 38 deletions src/modulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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():
"""
Expand Down
4 changes: 2 additions & 2 deletions src/performance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand Down
Loading