在 Python 代码中使用 16 QAM 映射进行消息重建的问题

问题描述 投票:0回答:1

我正在编写一个涉及消息

transmission
和使用
reception
16 QAM mapping
的Python代码。但是,我遇到了一个问题,即我没有按预期收到原始消息。

代码:

import numpy as np
import string
import random
from difflib import SequenceMatcher

def transmitter(message):
    binary_message = ''.join(format(ord(char), '08b') for char in message)
    binary_chunks = [binary_message[i:i + 4] for i in range(0, len(binary_message), 4)]

    symbol_map = {'0000': complex(1, 1),
                  '0001': complex(1, 3),
                  '0010': complex(3, 1),
                  '0011': complex(3, 3),
                  '0100': complex(-1, 1),
                  '0101': complex(-1, 3),
                  '0110': complex(-3, 1),
                  '0111': complex(-3, 3),
                  '1000': complex(1, -1),
                  '1001': complex(1, -3),
                  '1010': complex(3, -1),
                  '1011': complex(3, -3),
                  '1100': complex(-1, -1),
                  '1101': complex(-1, -3),
                  '1110': complex(-3, -1),
                  '1111': complex(-3, -3)}

    qam_signal = [symbol_map[chunk] for chunk in binary_chunks]
    signal_parts = [(sample.real, sample.imag) for sample in qam_signal]
    flat_signal = [part for sample in signal_parts for part in sample]

    return flat_signal


def channel(sent_signal, noise_factor=1):
    sent_signal = np.array(sent_signal)
    assert np.size(sent_signal) <= 400, "n must be <= 200"
    n = np.size(sent_signal) // 2
    x = sent_signal[0:2*n]
    s = np.sum(x**2) / np.size(x)
    sigma = 1
    if s > 1:
        sigma = np.sqrt(s)
    Z = np.random.normal(0, sigma*noise_factor, size=(2*n,))
    A = np.array([[11, 10], [10, 11]])
    B = np.kron(np.eye(n), A)
    Y = B.dot(x) + Z.T
    return Y


def receiver(received_signal):
    def find_closest_point(point, symbol_map):
        # Find the constellation point closest to the received point
        distances = [np.abs(point - constellation_point) for constellation_point in symbol_map.keys()]
        closest_point = min(distances)
        closest_index = distances.index(closest_point)
        closest_complex = list(symbol_map.keys())[closest_index]
        closest_binary = symbol_map[closest_complex]
        return closest_binary

    received_signal = received_signal.flatten()

    qam_signal = [complex(received_signal[i], received_signal[i + 1]) for i in range(0, len(received_signal), 2)]

    # 16-QAM demodulation
    symbol_map = {complex(1, 1): '0000',
                  complex(1, 3): '0001',
                  complex(3, 1): '0010',
                  complex(3, 3): '0011',
                  complex(-1, 1): '0100',
                  complex(-1, 3): '0101',
                  complex(-3, 1): '0110',
                  complex(-3, 3): '0111',
                  complex(1, -1): '1000',
                  complex(1, -3): '1001',
                  complex(3, -1): '1010',
                  complex(3, -3): '1011',
                  complex(-1, -1): '1100',
                  complex(-1, -3): '1101',
                  complex(-3, -1): '1110',
                  complex(-3, -3): '1111'}

    demodulated_signal = [find_closest_point(point, symbol_map) for point in qam_signal]

    binary_message = ''.join(demodulated_signal)
    text_message = bytes([int(binary_message[i:i + 8], 2) for i in range(0, len(binary_message), 8)]).decode('latin-1')

    return text_message



def generate_random_string(length):
    # All ASCII characters
    ascii_characters = string.ascii_letters + string.digits + string.punctuation
    # Generate the random string
    random_string = ''.join(random.choice(ascii_characters) for _ in range(length))
    return random_string


# Example usage:
message = generate_random_string(50)
X = transmitter(message)  # Encode our message
Y = channel(X, noise_factor=0.5)  # Simulate the treatment done by the channel
reconstructed_message = receiver(Y)  # Decode the message received by the channel


print("Original message:", message)
print("Reconstructed message:", reconstructed_message)



def check_similarity(original_message, reconstructed_message):
    # Create a SequenceMatcher object
    matcher = SequenceMatcher(None, original_message, reconstructed_message)
    # Calculate the similarity ratio
    similarity_ratio = matcher.ratio()
    return similarity_ratio

# Similarity check
similarity_ratio = check_similarity(message, reconstructed_message)
print(f"Similarity ratio: {similarity_ratio:.2f}")

输出:

Original message: ]?XQ52jc?>$K{~=[kC;'QveIM^c5Yzg=u6I*0A~;Tj8IXM_m)F
Reconstructed message: ??8333óó??4K{?;ûC;73óOO?ó3?s÷?s?O33C;4ó8O8O?ÿ?O
Similarity ratio: 0.16

描述

我已经实现了一个使用 16 QAM 映射来发送和接收消息的代码。该代码由以下部分组成:

  • transmitter
    :将消息转换为二进制,填充它,并使用预定义的星座将每个符号映射到复数值。

  • receiver
    :解调接收到的符号,检查它们是否是有效的 ASCII 字符,并重建消息。

  • channel
    :模拟信道并向传输信号引入噪声。 generate_random_string:生成用于测试的随机消息。

问题:

我面临的问题是,当我运行代码时,重构的消息与原始消息不一样。

预期行为:

我希望重建的消息与原始消息相同。

问题:

  • 什么可能导致我的代码中原始消息和重构消息之间的差异?

  • 是否有任何我可能忽略的错误或改进?

  • 如何修改代码才能保证准确重建原始消息? 任何指导、建议或解释将不胜感激。

python character-encoding signal-processing serial-communication telecommunication
1个回答
1
投票

首先感谢您提供功能齐全的代码。这很有帮助。

不需要的填充物

padded_binary_message = binary_message + '0' * (4 - len(binary_message) % 4)

您是否注意到结果数组的大小为 101?这是因为 (4 - len(binary_message) % 4) 等于 4,而不是 0。只有当模数不等于 4 时,才应该进行填充。此外,字符串的 8 位表示将始终是 4 的倍数。您应该只需删除填充即可。

def transmitter(message):
    binary_message = ''.join(format(ord(c), '08b') for c in message)
    symbols = [binary_message[i:i+4] for i in range(0, len(binary_message), 4)]
    constellation = {
        '0000': complex(-6, 6), '0001': complex(-6, 2), '0010': complex(-6, -6),
        '0011': complex(-6, -2), '0100': complex(-2, 6), '0101': complex(-2, 2),
        '0110': complex(-2, -6), '0111': complex(-2, -2),'1000': complex(6, 6),
        '1001': complex(6, 2), '1010': complex(6, -6), '1011': complex(6, -2),
        '1100': complex(2, 6), '1101': complex(2, 2), '1110': complex(2, -6),
        '1111': complex(2, -2)
    }

    signal = [constellation[symbol] for symbol in symbols]
    return signal

符号解调

for constellation_point in constellation.keys():
    distance = np.abs(symbol[0] + symbol[1]*1j - constellation_point)
    if distance < min_distance:
        min_distance = distance
        closest_constellation_point = constellation_point

此代码块尝试计算最接近 8 位符号的 4 位星座点。您应该独立解调每个 4 位,然后连接结果。

def demodulate_4bits_signal(signal):
    constellation = {
        complex(-6, 6): 0, complex(-6, 2): 1, complex(-6, -6): 2,
        complex(-6, -2): 3, complex(-2, 6): 4, complex(-2, 2): 5,
        complex(-2, -6): 6, complex(-2, -2): 7, complex(6, 6): 8,
        complex(6, 2): 9, complex(6, -6): 10, complex(6, -2): 11,
        complex(2, 6): 12, complex(2, 2): 13, complex(2, -6): 14,
        complex(2, -2): 15
    }
    min_distance = float('inf')

    for constellation_point in constellation.keys():
        distance = np.abs(signal - constellation_point)
        if distance < min_distance:
            min_distance = distance
            closest_constellation_point = constellation_point

    return constellation[closest_constellation_point]


def demodulate_symbol(symbol):
    head, tail = symbol
    return (demodulate_4bits_signal(head) << 4) + demodulate_4bits_signal(tail)

正如评论中所指出的,我们应该首先在没有噪音的情况下进行测试:

random_message = generate_random_string(50)
sent_signal = transmitter(random_message)
reconstructed_message = receiver(sent_signal)

print("Original message:", random_message)
print("Reconstructed message:", reconstructed_message)

成功了吗?

Original message: UZWE}jeD-aw2<(7w"JZ:+J2_H$LQdN6JYO5PfX<aAIRdSi_T>W
Reconstructed message: UZWE}jeD-aw2<(7w"JZ:+J2_H$LQdN6JYO5PfX<aAIRdSi_T>W

噪音

至于噪声信号的重建,我担心您会为输入产生太多噪声。

print(received_signal - sent_signal)
[-4.00022976e+01 -40.j -3.99901691e+01 -40.j ... ]

罪魁祸首是

A = np.array([[11, 10], [10, 11]])
这会将您的信号放大至少 10 倍。

这个代码还需要吗?你的

Z
变量已经包含一些噪音,所以让我们尝试只使用这些噪音:

def channel(sent_signal, noise_factor=1):
    # ...
    Z = np.random.normal(0, sigma*noise_factor, size=(2*n,))
    Y = x + Z.T
    return Y
Original message: ...
Reconstructed message: ...
Similarity ratio: 0.26

有点糟糕,但是让我们尝试使用较小的噪声比:

Y = channel(X, noise_factor=0.25)
Original message: ...
Reconstructed message: ... 
Similarity ratio: 0.82

这样更好。当noise_ratio=0.15时,你的算法能够完全重建你的信号:

Y = channel(X, noise_factor=0.15)
Original message: ...
Reconstructed message: ... 
Similarity ratio: 1.0

其他改进

  • 仅定义一次星座,然后根据第一个定义创建反向星座。避免两个实体之间出现差异。
  • 使用
    string.encode
    直接操作字节,而不是将你的 ASCII 字符串转换为一串位,每个字节占用一个完整的字符。这非常繁重并且容易出错,因为您想将它们作为位而不是字符进行操作。
  • Numpy 允许您进行更多算法线性化,使用 numpy.argmin
  • 您的解调速度可能会快得多
  • 一个函数应该只做一件事。重构代码以确保每个函数都正确命名应该可以轻松地为您提供错误的指导。
  • 你的变量应该有更好的命名。

请注意,我的代码片段旨在与您的代码片段相匹配。它们不应该被视为干净编码的例子。

© www.soinside.com 2019 - 2024. All rights reserved.