diff --git a/REPORT.md b/REPORT.md new file mode 100644 index 0000000..c49e831 --- /dev/null +++ b/REPORT.md @@ -0,0 +1,74 @@ +# 无线通信技术实验报告:信道编码与信道均衡 + +## 1. 实验目的 + +掌握 Hamming(7,4) 信道编码与单比特纠错的基本方法;理解多径信道导致的 ISI 以及 ZF/LMS 均衡器的设计与效果评估;熟悉使用 GitHub 自动评分与本地脚本进行实验结果验证,并能结合结果图进行解释与分析。 + +## 2. 实验原理 + +### 2.1 信道编码 + +Hamming(7,4) 是一种线性分组码,将 4 比特信息映射为 7 比特码字,通过冗余校验实现纠错能力。伴随式由接收码字与校验矩阵 $H$ 相乘得到,非零伴随式对应某一列,指示错误位置,从而实现单比特纠错。编码增益指在相同误码率下,编码系统相对未编码系统所需信噪比降低的程度。由于引入冗余,码率从 $4/7$ 降低,但可靠性提高,适合噪声较强的传输场景。 + +### 2.2 信道均衡 + +多径信道的冲激响应会在符号间产生叠加,导致符号间干扰(ISI)。ZF 均衡器通过设计 FIR 抽头使等效信道尽量逼近冲激响应,从而抵消 ISI,但在信道频谱深衰落处会放大噪声。LMS 自适应均衡器利用训练序列迭代更新抽头,使均衡输出逐步逼近期望符号,实现对未知或时变信道的跟踪。LMS 的收敛速度与稳态误差受步长影响,需要在稳定性与收敛速度之间权衡。 + +## 3. 实验环境 + +- Python 版本:3.x(Windows) +- 主要依赖:NumPy、Matplotlib、pytest +- AI 助手使用情况:使用 GitHub Copilot(GPT-5.2-Codex)协助理解实验要求与补全代码,但对关键算法与结果进行了自行验证与解释 + +## 4. 实验方法与步骤 + +### 4.1 Part 1:信道编码 + +1. 生成随机比特序列,并按 4 比特分组。 +2. 使用 Hamming(7,4) 生成矩阵完成编码得到 7 比特码字。 +3. 通过二元对称信道以不同翻转概率传输,得到带噪码字。 +4. 计算伴随式并进行单比特纠错译码,取系统码前 4 位恢复信息比特。 +5. 统计未编码与编码后的 BER,并绘制对比曲线,用于观察编码增益。 + +### 4.2 Part 2:信道均衡 + +1. 生成 BPSK 符号序列并通过多径 FIR 信道,加入噪声得到接收序列。 +2. 使用最小二乘法估计 ZF 均衡器抽头作为参考。 +3. 采用 LMS 算法,利用训练序列迭代更新均衡器抽头。 +4. 通过均衡输出进行硬判决,计算均衡前后 BER,并绘制均衡波形与 LMS 误差曲线,观察 ISI 抑制效果与训练收敛过程。 + +## 5. 实验结果 + +插入结果图: + +![编码BER曲线](./results/coding_ber_curve.png) +![均衡眼图对比](./results/equalization_eye_comparison.png) +![LMS误差曲线](./results/equalization_mse_curve.png) + +从 BER 曲线可见,Hamming(7,4) 编码在中低误码概率区域具有明显性能优势。均衡波形对比显示多径引起的畸变被 LMS 均衡器有效抑制,LMS 误差曲线随迭代下降,体现出自适应学习过程。 + +## 6. 结果分析与讨论 + +1. Hamming(7,4) 为什么能纠正单比特错误? +答:Hamming(7,4) 码的校验矩阵 $H$ 的每一列唯一对应一个比特位置,单比特错误会产生非零伴随式,且其值与某一列一致,因此可以定位并翻转该位完成纠错。 + +2. 为什么信道编码会引入冗余并降低码率? +答:编码通过添加校验位提升纠错能力,等价于用更多的比特表达相同信息量,因此码率降低为信息比特数与码长之比。 + +3. ZF 均衡为什么可能放大噪声? +答:ZF 追求消除 ISI,会在频域对信道的深衰落频点进行强增益补偿,从而同时放大噪声,导致噪声增强。 + +4. LMS 的步长过大或过小会出现什么问题? +答:步长过大会导致算法不稳定甚至发散;步长过小会收敛缓慢,训练时间变长,难以及时跟踪信道变化。 + +5. 均衡前后 ISI 有什么变化? +答:均衡后等效信道响应趋近冲激,符号间叠加减弱,ISI 明显减小,眼图开口增大,误码率下降。 + +## 7. 实验心得 + +通过本实验理解了分组码的纠错机理与多径信道均衡的核心思路。编码能够在噪声环境下提升可靠性,而均衡器可显著减轻 ISI 的影响。结合自动评分与结果可视化,能够快速定位问题并验证实现是否正确。AI 编程辅助主要用于梳理实现步骤与检查细节,提高了效率,但关键算法仍需自行理解与验证,确保结果可信。 + +## 8. 参考资料 + +- 课程课件:第6章 信道编码 +- 课程课件:第7章 均衡 diff --git a/REPORT_TEMPLATE.md b/REPORT_TEMPLATE.md index 5b435ee..6566210 100644 --- a/REPORT_TEMPLATE.md +++ b/REPORT_TEMPLATE.md @@ -2,57 +2,71 @@ ## 1. 实验目的 -说明本实验希望掌握的信道编码、信道均衡和 GitHub 自动评分技能。 +掌握 Hamming(7,4) 信道编码与单比特纠错的基本方法;理解多径信道导致的 ISI 以及 ZF/LMS 均衡器的设计与效果评估;熟悉使用 GitHub 自动评分与本地脚本进行实验结果验证。 ## 2. 实验原理 ### 2.1 信道编码 -说明 Hamming(7,4)、伴随式、单比特纠错、编码增益等概念。 +Hamming(7,4) 是一种线性分组码,将 4 比特信息映射为 7 比特码字,通过冗余校验实现纠错能力。伴随式由接收码字与校验矩阵 H 相乘得到,非零伴随式对应某一列,指示错误位置,从而实现单比特纠错。编码增益指在相同误码率下,编码系统相对未编码系统所需信噪比降低的程度。 ### 2.2 信道均衡 -说明 ISI、多径信道、ZF、LMS 自适应均衡等概念。 +多径信道的冲激响应会在符号间产生叠加,导致符号间干扰(ISI)。ZF 均衡器通过设计 FIR 抽头使等效信道尽量逼近冲激,从而消除 ISI,但可能放大噪声。LMS 自适应均衡器利用训练序列迭代更新抽头,使均衡输出逐步逼近期望符号,实现对未知或时变信道的跟踪。 ## 3. 实验环境 -- Python 版本: +- Python 版本:3.x(Windows) - 主要依赖:NumPy、Matplotlib、pytest -- AI 助手使用情况: +- AI 助手使用情况:使用 GitHub Copilot(GPT-5.2-Codex)协助理解实验要求与补全代码 ## 4. 实验方法与步骤 ### 4.1 Part 1:信道编码 -描述编码、加噪/翻转、译码、BER 对比流程。 +1. 生成随机比特序列,并按 4 比特分组。 +2. 使用 Hamming(7,4) 生成矩阵完成编码得到 7 比特码字。 +3. 通过二元对称信道以不同翻转概率传输,得到带噪码字。 +4. 计算伴随式并进行单比特纠错译码,取系统码前 4 位恢复信息比特。 +5. 统计未编码与编码后的 BER,并绘制对比曲线。 ### 4.2 Part 2:信道均衡 -描述多径信道、ZF/LMS 均衡器设计、均衡效果评估流程。 +1. 生成 BPSK 符号序列并通过多径 FIR 信道,加入噪声得到接收序列。 +2. 使用最小二乘法估计 ZF 均衡器抽头作为参考。 +3. 采用 LMS 算法,利用训练序列迭代更新均衡器抽头。 +4. 通过均衡输出进行硬判决,计算均衡前后 BER,并绘制均衡波形与 LMS 误差曲线。 ## 5. 实验结果 插入结果图: -```markdown -![编码BER曲线](results/coding_ber_curve.png) -![均衡眼图对比](results/equalization_eye_comparison.png) -![LMS误差曲线](results/equalization_mse_curve.png) -``` +![编码BER曲线](./results/coding_ber_curve.png) +![均衡眼图对比](./results/equalization_eye_comparison.png) +![LMS误差曲线](./results/equalization_mse_curve.png) ## 6. 结果分析与讨论 回答: 1. Hamming(7,4) 为什么能纠正单比特错误? +答:Hamming(7,4) 码的校验矩阵 H 的每一列唯一对应一个比特位置,单比特错误会产生非零伴随式,且其值与某一列一致,因此可以定位并翻转该位完成纠错。 + 2. 为什么信道编码会引入冗余并降低码率? +答:编码通过添加校验位提升纠错能力,等价于用更多的比特表达相同信息量,因此码率降低为信息比特数与码长之比。 + 3. ZF 均衡为什么可能放大噪声? +答:ZF 追求消除 ISI,会在频域对信道的深衰落频点进行强增益补偿,从而同时放大噪声,导致噪声增强。 + 4. LMS 的步长过大或过小会出现什么问题? +答:步长过大会导致算法不稳定甚至发散;步长过小会收敛缓慢,训练时间变长,难以及时跟踪信道变化。 + 5. 均衡前后 ISI 有什么变化? +答:均衡后等效信道响应趋近冲激,符号间叠加减弱,ISI 明显减小,星座/眼图开口增大,误码率下降。 ## 7. 实验心得 -说明你对信道编码、信道均衡、自动评分和 AI 编程辅助的理解。 +通过本实验理解了分组码的纠错机理与多径信道均衡的核心思路。编码能够在噪声环境下提升可靠性,而均衡器可显著减轻 ISI 的影响。结合自动评分与结果可视化,能够快速定位问题并验证实现是否正确。AI 编程辅助主要用于梳理实现步骤与检查细节,提高了效率,但关键算法仍需自行理解与验证。 ## 8. 参考资料 diff --git a/results/coding_ber_curve.png b/results/coding_ber_curve.png new file mode 100644 index 0000000..39e865b Binary files /dev/null and b/results/coding_ber_curve.png differ diff --git a/results/equalization_eye_comparison.png b/results/equalization_eye_comparison.png new file mode 100644 index 0000000..5c2dab8 Binary files /dev/null and b/results/equalization_eye_comparison.png differ diff --git a/results/equalization_mse_curve.png b/results/equalization_mse_curve.png new file mode 100644 index 0000000..49cf922 Binary files /dev/null and b/results/equalization_mse_curve.png differ diff --git a/src/part1_channel_coding.py b/src/part1_channel_coding.py index 1ecf55e..36f56dd 100644 --- a/src/part1_channel_coding.py +++ b/src/part1_channel_coding.py @@ -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): @@ -70,8 +71,8 @@ def hamming74_syndrome(codewords): if codewords.shape[1] != 7: raise ValueError('每个 Hamming(7,4) 码字长度必须为 7') - # TODO: 计算 s = r H^T mod 2。 - raise NotImplementedError('请实现伴随式计算') + syndromes = (codewords @ HAMMING_H.T) % 2 + return syndromes def hamming74_decode(received): @@ -94,8 +95,20 @@ 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) 译码') + codewords = received.reshape(-1, 7).copy() + syndromes = hamming74_syndrome(codewords) + + # 预计算伴随式到错误位置的映射 + syndrome_map = {tuple(HAMMING_H[:, i] % 2): i for i in range(7)} + + for idx, syndrome in enumerate(syndromes): + if np.any(syndrome): + error_pos = syndrome_map.get(tuple(syndrome)) + if error_pos is not None: + codewords[idx, error_pos] ^= 1 + + decoded_bits = codewords[:, :4].reshape(-1) + return decoded_bits def convolutional_encode(bits): @@ -108,8 +121,17 @@ def convolutional_encode(bits): if not np.all((bits == 0) | (bits == 1)): raise ValueError('bits 只能包含 0 或 1') - # TODO: 选做任务,可参考课件第6章卷积码部分。 - raise NotImplementedError('选做:请实现卷积码编码') + tail = np.zeros(2, dtype=int) + bits = np.concatenate([bits, tail]) + state = [0, 0] # s1, s2 + outputs = [] + for bit in bits: + s1, s2 = state + g1 = bit ^ s1 ^ s2 + g2 = bit ^ s2 + outputs.extend([g1, g2]) + state = [bit, s1] + return np.array(outputs, dtype=int) def viterbi_decode_hard(received_bits): @@ -120,8 +142,58 @@ def viterbi_decode_hard(received_bits): if len(received_bits) % 2 != 0: raise ValueError('卷积码接收序列长度必须是 2 的倍数') - # TODO: 选做任务,可使用汉明距离作为路径度量。 - raise NotImplementedError('选做:请实现 Viterbi 硬判决译码') + num_symbols = len(received_bits) // 2 + received_pairs = received_bits.reshape(-1, 2) + + num_states = 4 # memory=2 + inf = 1e9 + path_metrics = np.full(num_states, inf, dtype=float) + path_metrics[0] = 0.0 + prev_state = np.full((num_symbols, num_states), -1, dtype=int) + prev_bit = np.full((num_symbols, num_states), -1, dtype=int) + + # State representation: (s1, s2) -> s1*2 + s2 + for t in range(num_symbols): + rx = received_pairs[t] + next_metrics = np.full(num_states, inf, dtype=float) + next_prev_state = np.full(num_states, -1, dtype=int) + next_prev_bit = np.full(num_states, -1, dtype=int) + + for state in range(num_states): + if path_metrics[state] >= inf: + continue + s1 = (state >> 1) & 1 + s2 = state & 1 + for bit in (0, 1): + g1 = bit ^ s1 ^ s2 + g2 = bit ^ s2 + dist = (g1 != rx[0]) + (g2 != rx[1]) + next_state = (bit << 1) | s1 + metric = path_metrics[state] + dist + if metric < next_metrics[next_state]: + next_metrics[next_state] = metric + next_prev_state[next_state] = state + next_prev_bit[next_state] = bit + + path_metrics = next_metrics + prev_state[t] = next_prev_state + prev_bit[t] = next_prev_bit + + # Prefer terminating to all-zero state when tail bits are used + final_state = 0 + if path_metrics[final_state] >= inf: + final_state = int(np.argmin(path_metrics)) + + decoded = np.zeros(num_symbols, dtype=int) + state = final_state + for t in range(num_symbols - 1, -1, -1): + bit = prev_bit[t, state] + decoded[t] = 0 if bit < 0 else bit + state = prev_state[t, state] if prev_state[t, state] >= 0 else 0 + + if num_symbols >= 2: + decoded = decoded[:-2] + return decoded def run_coding_demo(): diff --git a/src/part2_equalization.py b/src/part2_equalization.py index 8cbf1d8..3d0ee22 100644 --- a/src/part2_equalization.py +++ b/src/part2_equalization.py @@ -38,8 +38,21 @@ def estimate_zf_equalizer(channel, num_taps): if num_taps < 1: raise ValueError('num_taps 必须为正整数') - # TODO: 构造卷积矩阵并求解 ZF 均衡器抽头。 - raise NotImplementedError('请实现 ZF 均衡器估计') + channel_len = len(channel) + conv_len = channel_len + num_taps - 1 + conv_matrix = np.zeros((conv_len, num_taps), dtype=float) + for row in range(conv_len): + for col in range(num_taps): + idx = row - col + if 0 <= idx < channel_len: + conv_matrix[row, col] = channel[idx] + + desired = np.zeros(conv_len, dtype=float) + center = (channel_len - 1) // 2 + (num_taps - 1) // 2 + desired[center] = 1.0 + + taps, *_ = np.linalg.lstsq(conv_matrix, desired, rcond=None) + return taps def apply_fir_filter(signal, taps): @@ -58,8 +71,8 @@ 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 滤波') + filtered = np.convolve(signal, taps, mode='full')[: len(signal)] + return filtered def lms_equalizer(rx_train, tx_train, num_taps, step_size=0.01): @@ -89,8 +102,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 = [] + + for n in range(num_taps - 1, len(rx_train)): + x = rx_train[n - num_taps + 1: n + 1][::-1] + y = np.dot(taps, x) + error = tx_train[n] - y + taps = taps + step_size * error * x + errors.append(error) + + return taps, np.asarray(errors, dtype=float) def run_equalization_demo():