Skip to content
Open
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
90 changes: 90 additions & 0 deletions REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 无线通信技术实验报告:信道编码与信道均衡

## 1. 实验目的

本实验围绕《无线通信技术》课程中“信道编码”与“信道均衡”两部分内容,在统一代码框架下完成算法补全与仿真验证,主要希望达到以下目标:

1. 理解信道编码通过增加冗余提高传输可靠性的基本思想,掌握线性分组码中 **Hamming(7,4)** 的编码、伴随式检测与 **单比特纠错** 流程,并观察编码前后误比特率(BER)的变化。
2. 理解多径信道引起的 **符号间干扰(ISI)**,掌握 **迫零(ZF)** 均衡器与 **LMS 自适应均衡** 的基本实现思路,能够从波形与误差曲线上对比均衡效果。
3. (选做)了解 **(2,1,3) 卷积码** 与 **Viterbi 硬判决译码** 的格图思想,体会与分组码在冗余与译码方式上的差异。
4. 熟悉在本地运行脚本与 `pytest` 自检、生成 `results/` 结果图,以及通过仓库与自动评分流程提交实验的完整链路。

## 2. 实验原理

### 2.1 信道编码

**Hamming(7,4)** 是一种线性分组码:每 **4** 个信息比特经生成矩阵 **G** 映射为 **7** 维二元码字,在 **GF(2)** 上满足 **c = uG (mod 2)**。本实验采用 **系统码** 形式,信息位位于码字前 4 位,后 3 位为校验位。接收端用校验矩阵 **H** 计算 **伴随式 s = rH^T (mod 2)**。若 **s = 0**,在单比特错误模型下认为无错;若 **s ≠ 0**,伴随式与 **H** 的某一列一致,可唯一定位错误位置并翻转,从而实现 **单比特纠错**。若错误多于 1 比特,伴随式可能对应错误判决,本实验主要讨论单比特场景。

**编码增益**体现在:在相同物理信道误码条件下,经译码后的等效信息比特错误率低于未编码传输,其代价是 **码率 R = 4/7 < 1**,即每传输 7 个二元符号仅携带 4 个信息比特,引入了冗余。

**卷积码(选做)**:本实验采用 **(2,1,3)** 码,约束长度对应 2 级寄存器记忆;生成多项式 **g1 = 111**、**g2 = 101**(二进制),每输入 1 比特输出 2 比特。编码器在末尾追加 **2 个尾比特 0**,使状态回到全零,便于接收端 **Viterbi** 在格图上以 **汉明距离** 为度量进行最大似然意义的硬判决路径搜索。

### 2.2 信道均衡

多径信道的离散模型可看作发送符号与 **信道冲激响应** 的卷积,接收样本由当前及相邻符号叠加而成,从而产生 **ISI**。均衡的目标是构造 **FIR 滤波器**(抽头系数 **w**),使 **信道与均衡器级联** 的整体冲激响应接近 **δ 脉冲**(或尽可能集中能量于一个采样点),从而在采样时刻恢复发送符号。

**ZF(迫零)均衡**:在频域思想上使 **H(f)W(f)=1**,离散上等价为求解使 **h * w** 逼近指定延迟处单位脉冲的线性方程;本实验用 **最小二乘** 近似求解抽头,使 ISI 被“迫零”。其缺点是当信道在频域存在深度衰落时,均衡器会 **放大噪声**(噪声增强现象)。

**LMS 自适应均衡**:在已知训练序列 **d[n]** 与接收 **r[n]** 时,以横向滤波器 **y[n]=w^T x[n]** 逼近 **d[n]**,误差 **e[n]=d[n]-y[n]**,按 **w ← w + μ e[n] x[n]** 更新。**步长 μ** 控制收敛速度与稳定性:过大易振荡甚至发散,过小则收敛慢、跟踪能力差。

## 3. 实验环境

- **Python 版本**:建议 **3.9 及以上 64 位** 解释器(Windows 下 32 位 Python 常无法通过 pip 安装 SciPy 等预编译包,易导致依赖安装失败)。
- **主要依赖**:NumPy、SciPy、Matplotlib、pytest、pylint(以仓库 `requirements.txt` 为准);绘图与 BER 曲线由 `utils` 中封装函数完成。
- **AI 助手使用情况**:实验过程中使用 **Cursor / Copilot 类 AI 助手** 辅助理解 Hamming 伴随式与列的对应关系、ZF 卷积矩阵构造、LMS 向量时间对齐及卷积码格图实现要点;**核心公式与代码逻辑均经本地运行与自测核对**,未直接提交未运行代码。本报告中对原理的表述与结果讨论由本人整理与校验。

## 4. 实验方法与步骤

### 4.1 Part 1:信道编码

1. 在 `part1_channel_coding.py` 中实现 **Hamming(7,4)** 的 **编码**(`u` 分块与 **G** 相乘 mod 2)、**伴随式**(`r H^T` mod 2)与 **单比特纠错译码**(伴随式匹配 **H** 列、翻转错误位后取前 4 位信息比特)。
2. 使用 `generate_bits` 生成信息序列,经 **BSC** 分别对“未编码比特流”与“编码后比特流”加噪,对后者做 **Hamming 译码**,用 `calculate_ber` 统计 BER。
3. 调用 `plot_ber_curve` 在给定误码概率点上绘制 **未编码与 Hamming(7,4)** 的 BER 曲线,保存为 `results/coding_ber_curve.png`。
4. (选做)实现 **卷积码编码** 与 **Viterbi 硬判决译码**,在本地用小长度序列做环回与单比特扰动测试。

### 4.2 Part 2:信道均衡

1. 在 `part2_equalization.py` 中实现 **ZF 均衡器估计**:构造 **h 与 w 卷积** 对应的线性方程组,目标向量在 **级联响应长度** 的 **中心位置** 取 1,用 **lstsq** 求抽头;实现 **FIR 滤波**(`full` 卷积后截取与输入等长)。
2. 实现 **LMS**:抽头初值取 **中心抽头为 1**;从 **n ≥ L−1** 起用接收向量 **x[n]** 与期望训练符号 **d[n]** 计算误差并更新抽头,记录误差序列。
3. 使用 `multipath_channel` 得到接收序列,分别经过 **ZF** 与 **LMS** 均衡后,调用 `plot_equalization_results` 与 `plot_mse_curve` 生成 `results/equalization_eye_comparison.png` 与 `results/equalization_mse_curve.png`,并比较 **BPSK 硬判决 BER**。

## 5. 实验结果

以下为本地运行 `part1_channel_coding.py` 与 `part2_equalization.py` 后生成的结果图(路径相对于仓库根目录)。

![编码BER曲线](results/coding_ber_curve.png)

![均衡眼图对比](results/equalization_eye_comparison.png)

![LMS误差曲线](results/equalization_mse_curve.png)

从 BER 曲线可以定性看到:在中高误码概率区域,**Hamming(7,4) 译码后的信息 BER 明显低于未编码**;均衡结果图中 **LMS 均衡输出** 相对 **多径接收** 更接近发送符号轨迹;LMS 训练段的 **瞬时平方误差** 随迭代整体呈下降趋势(曲线为对数纵轴,便于观察数量级变化)。

## 6. 结果分析与讨论

1. **Hamming(7,4) 为什么能纠正单比特错误?**
伴随式 **s = rH^T** 是接收码字在校验意义下的“症状”。对 Hamming(7,4) 的 **H** 设计,**任一单比特错误** 所产生的 **s** 与 **H 的某一列一一对应**,因而可唯一定位并翻转该位;纠错后满足 **cH^T=0**,再取系统位即得信息。

2. **为什么信道编码会引入冗余并降低码率?**
可靠性来自 **可检可纠的结构约束**,必须以 **额外校验符号** 为代价;码率 **R=k/n<1** 表示每信道使用一次所传输的“净信息”减少,换取 **相同信道下更低的等效信息错误率**(或等价地,为达到同一目标 BER 所需更低的物理 SNR/误码条件)。

3. **ZF 均衡为什么可能放大噪声?**
ZF 强制在 ISI 零点处满足理想逆滤波,相当于在频域对信道 **H(f)** 取逆;当 **|H(f)|** 很小时,逆滤波增益很大,**噪声被同比例放大**,输出 SNR 反而恶化。实验中 ZF 主要用于理解 **ISI 抑制与噪声增强** 的折中。

4. **LMS 的步长过大或过小会出现什么问题?**
**过大**:更新量过大,权向量围绕最优解剧烈摆动甚至 **发散**,MSE 不降反升。**过小**:每次修正太小,**收敛慢**,在有限训练长度下可能仍未充分接近最优,且对时变信道跟踪慢。

5. **均衡前后 ISI 有什么变化?**
均衡前,接收样本含 **相邻符号的拖尾叠加**,眼图/波形上表现为 **幅度扩散与符号间“粘连”**;均衡后,通过 **逆多径合成** 使 **能量更集中于当前符号采样点**,波形起伏减小,有利于 **硬判决与降低 BER**;但受噪声与有限抽头长度影响,不可能完全理想。

## 7. 实验心得

通过本次实验,我对 **“冗余换可靠性”** 与 **“均衡换 ISI”** 两条主线有了更具体的认识:Hamming 码把纠错能力落实为 **伴随式与列的代数结构**;均衡则把 **卷积逆问题** 落实为 **线性方程(ZF)** 与 **随机梯度(LMS)** 两类可实现的数值解法。自动评分与 `pytest` 促使实现必须与 **接口约定、数值边界(等长卷积、训练对齐)** 严格一致,这是工程里很重要的一环。

关于 **AI 编程辅助**:适合用于对照公式检查矩阵维度、对照格图检查状态转移,以及快速定位环境依赖问题;但不替代自己对 **码率、尾比特、目标脉冲位置** 等概念的理解。今后仍会以 **“能解释、能复现、能改参数”** 为标准使用 AI。

## 8. 参考资料

- 课程课件:第 6 章 信道编码(Hamming 码、伴随式译码、卷积码与 Viterbi 思想)。
- 课程课件:第 7 章 均衡(多径与 ISI、ZF 与自适应 LMS)。
- 实验仓库说明:`README.md`、`materials/teacher_lab_guide.md` 与 `docs/theory_channel_coding.md`、`docs/theory_equalization.md`(若仓库中包含理论摘要文档,可作为复习对照)。
2 changes: 1 addition & 1 deletion grading/check_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def check_report_content(path):

sections_found = sum(1 for section in REQUIRED_SECTIONS if section in content)
score += sections_found * 2
feedback.append(f'📋 章节完整性: {sections_found}/{len(REQUIRED_SECTIONS)}')
feedback.append(f'[INFO] 章节完整性: {sections_found}/{len(REQUIRED_SECTIONS)}')

image_refs = re.findall(r'!\[.*?\]\(.*?\)', content)
if len(image_refs) >= 3:
Expand Down
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Wireless channel coding and equalization experiment dependencies
#
# Windows 重要:请使用「64 位」Python(安装程序须选 64-bit)。
# 若为 32 位 Python(win32),PyPI 通常没有 SciPy 的 wheel,pip 会下载源码并编译,
# 在未安装 Visual Studio C++ 生成工具时会报错(meson / Unknown compiler)。
#
# 若已使用 64 位 Python 仍尝试编译 SciPy,可先:
# python -m pip install -U pip wheel
# pip cache purge
# pip install -r requirements.txt
#
numpy>=1.23.0
scipy>=1.9.0
matplotlib>=3.6.0
Expand Down
92 changes: 81 additions & 11 deletions src/part1_channel_coding.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ def hamming74_encode(bits):
if not np.all((bits == 0) | (bits == 1)):
raise ValueError('bits 只能包含 0 或 1')

# TODO: 将 bits reshape 为 (-1, 4),再与 HAMMING_G 相乘并对 2 取模。
raise NotImplementedError('请实现 Hamming(7,4) 编码')
blocks = bits.reshape(-1, 4)
encoded = (blocks @ HAMMING_G) % 2
return encoded.reshape(-1)


def hamming74_syndrome(codewords):
Expand All @@ -70,8 +71,7 @@ def hamming74_syndrome(codewords):
if codewords.shape[1] != 7:
raise ValueError('每个 Hamming(7,4) 码字长度必须为 7')

# TODO: 计算 s = r H^T mod 2。
raise NotImplementedError('请实现伴随式计算')
return (codewords @ HAMMING_H.T) % 2


def hamming74_decode(received):
Expand All @@ -94,8 +94,26 @@ def hamming74_decode(received):
if received.ndim != 1 or len(received) % 7 != 0:
raise ValueError('received 必须是一维数组,长度为 7 的倍数')

# TODO: 使用 hamming74_syndrome 完成单比特纠错,并返回前 4 个信息位。
raise NotImplementedError('请实现 Hamming(7,4) 译码')
corrected = received.reshape(-1, 7).copy()
syndromes = hamming74_syndrome(corrected)
for index, syndrome in enumerate(syndromes):
if np.any(syndrome != 0):
for position in range(7):
if np.array_equal(syndrome, HAMMING_H[:, position]):
corrected[index, position] ^= 1
break
return corrected[:, :4].reshape(-1)


def _convolutional_outputs_and_next_state(state, input_bit):
"""(2,1,3) 一步:状态为 (u_{n-2}, u_{n-1}),输入 u_n,返回 (c1,c2) 与下一状态编码 0..3。"""
x0 = state // 2
x1 = state % 2
u = int(input_bit) & 1
c1 = u ^ x1 ^ x0
c2 = u ^ x0
next_state = 2 * x1 + u
return c1, c2, next_state


def convolutional_encode(bits):
Expand All @@ -105,23 +123,75 @@ def convolutional_encode(bits):
默认在末尾添加 2 个 0 作为尾比特,使状态回到全零。
"""
bits = np.asarray(bits, dtype=int)
if bits.ndim != 1:
raise ValueError('bits 必须是一维数组')
if not np.all((bits == 0) | (bits == 1)):
raise ValueError('bits 只能包含 0 或 1')

# TODO: 选做任务,可参考课件第6章卷积码部分。
raise NotImplementedError('选做:请实现卷积码编码')
padded = np.concatenate([bits, np.zeros(2, dtype=int)])
state = 0
output = []
for u in padded:
c1, c2, state = _convolutional_outputs_and_next_state(state, u)
output.extend([c1, c2])
return np.asarray(output, dtype=int)


def viterbi_decode_hard(received_bits):
"""
选做:实现 (2,1,3) 卷积码硬判决 Viterbi 译码。
"""
received_bits = np.asarray(received_bits, dtype=int)
if received_bits.ndim != 1:
raise ValueError('received_bits 必须是一维数组')
if len(received_bits) % 2 != 0:
raise ValueError('卷积码接收序列长度必须是 2 的倍数')

# TODO: 选做任务,可使用汉明距离作为路径度量。
raise NotImplementedError('选做:请实现 Viterbi 硬判决译码')
if not np.all((received_bits == 0) | (received_bits == 1)):
raise ValueError('received_bits 只能包含 0 或 1')

num_steps = len(received_bits) // 2
if num_steps == 0:
return np.zeros(0, dtype=int)

large = 1e18
cost = np.full(4, large, dtype=float)
cost[0] = 0.0
survivor_prev = np.zeros((num_steps, 4), dtype=int)
survivor_bit = np.zeros((num_steps, 4), dtype=int)

for step in range(num_steps):
r0 = int(received_bits[2 * step])
r1 = int(received_bits[2 * step + 1])
new_cost = np.full(4, large, dtype=float)
for prev_state in range(4):
path = cost[prev_state]
if path >= large:
continue
x0 = prev_state // 2
x1 = prev_state % 2
for u in (0, 1):
c1 = u ^ x1 ^ x0
c2 = u ^ x0
next_state = 2 * x1 + u
branch = int(r0 != c1) + int(r1 != c2)
candidate = path + branch
if candidate < new_cost[next_state]:
new_cost[next_state] = candidate
survivor_prev[step, next_state] = prev_state
survivor_bit[step, next_state] = u
cost = new_cost

end_state = int(np.argmin(cost))
decoded_rev = []
state = end_state
for step in range(num_steps - 1, -1, -1):
decoded_rev.append(int(survivor_bit[step, state]))
state = int(survivor_prev[step, state])

with_tail = np.asarray(decoded_rev[::-1], dtype=int)
if len(with_tail) < 2:
return with_tail
return with_tail[:-2]


def run_coding_demo():
Expand Down
36 changes: 30 additions & 6 deletions src/part2_equalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
学生需要完成 ZF 均衡器估计、FIR 滤波应用和 LMS 自适应均衡。
"""

import matplotlib # Matplotlib 核心包
import matplotlib.pyplot as plt # noqa: F401 # pyplot;utils 中绘图会再次 import,此处显式声明实验依赖

import numpy as np
from utils import (
bpsk_demodulate,
Expand Down Expand Up @@ -38,8 +41,20 @@ def estimate_zf_equalizer(channel, num_taps):
if num_taps < 1:
raise ValueError('num_taps 必须为正整数')

# TODO: 构造卷积矩阵并求解 ZF 均衡器抽头。
raise NotImplementedError('请实现 ZF 均衡器估计')
length_channel = len(channel)
length_output = length_channel + num_taps - 1
matrix = np.zeros((length_output, num_taps), dtype=float)
for output_index in range(length_output):
for tap_index in range(num_taps):
channel_index = output_index - tap_index
if 0 <= channel_index < length_channel:
matrix[output_index, tap_index] = channel[channel_index]

target = np.zeros(length_output, dtype=float)
center = (length_output - 1) // 2
target[center] = 1.0
taps, *_ = np.linalg.lstsq(matrix, target, rcond=None)
return taps


def apply_fir_filter(signal, taps):
Expand All @@ -58,8 +73,7 @@ def apply_fir_filter(signal, taps):
if signal.ndim != 1 or taps.ndim != 1:
raise ValueError('signal 和 taps 必须是一维数组')

# TODO: 使用 np.convolve,并截取与 signal 等长的输出。
raise NotImplementedError('请实现 FIR 滤波')
return np.convolve(signal, taps, mode='full')[: len(signal)]


def lms_equalizer(rx_train, tx_train, num_taps, step_size=0.01):
Expand Down Expand Up @@ -89,8 +103,18 @@ def lms_equalizer(rx_train, tx_train, num_taps, step_size=0.01):
if num_taps < 1:
raise ValueError('num_taps 必须为正整数')

# TODO: 实现 LMS 自适应均衡训练。
raise NotImplementedError('请实现 LMS 均衡器')
taps = np.zeros(num_taps, dtype=float)
taps[(num_taps - 1) // 2] = 1.0
errors_list = []
for index in range(num_taps - 1, len(rx_train)):
window = rx_train[index - num_taps + 1 : index + 1]
input_vector = window[::-1]
output = float(np.dot(taps, input_vector))
error = float(tx_train[index] - output)
errors_list.append(error)
taps = taps + step_size * error * input_vector
errors = np.asarray(errors_list, dtype=float)
return taps, errors


def run_equalization_demo():
Expand Down
16 changes: 15 additions & 1 deletion src/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
"""环境测试脚本。"""

import struct
import sys


def test_python_architecture():
bits = struct.calcsize('P') * 8
print(f'Python 指针宽度: {bits} 位')
if bits < 64:
print(
'[WARN] 当前为 32 位 Python。SciPy 在 Windows 上通常无官方 pip 轮子,'
'会继续尝试源码编译并失败。请卸载后安装 64 位 Python,或使用 Miniconda(64 位)。'
)
return False
print('[OK] 64 位 Python,可正常安装 SciPy 等预编译包')
return True


def test_python_version():
version = sys.version_info
print(f'Python版本: {version.major}.{version.minor}.{version.micro}')
Expand Down Expand Up @@ -30,7 +44,7 @@ def main():
print('=' * 50)
print('信道编码与信道均衡实验 - 环境测试')
print('=' * 50)
results = [test_python_version(), test_packages()]
results = [test_python_version(), test_python_architecture(), test_packages()]
if all(results):
print('环境配置正确')
else:
Expand Down
Binary file added teacher_lab_guide_student_v2.pdf
Binary file not shown.
Loading