英文:
Issue with message reconstruction using 16 QAM mapping in Python code
问题
I have reviewed the provided Python code and your description of the issue. It appears that the issue you are facing may be related to noise introduced during transmission and demodulation. The 16 QAM mapping and demapping processes seem to be implemented correctly based on the provided code.
Here are some points to consider:
-
Noise Level: The
channel
function introduces noise to the transmitted signal with a specifiednoise_factor
. You might need to experiment with different noise levels to find the optimal setting that minimizes the discrepancy between the original and reconstructed messages. Lowering the noise factor may improve accuracy. -
Signal-to-Noise Ratio (SNR): You can calculate the SNR in the
channel
function to get an idea of the signal quality. A higher SNR generally leads to better reception and message accuracy. -
Error Handling: Check if there are any bit errors occurring during the transmission and demodulation processes. Implement error checking and correction techniques like checksums or error-correcting codes if necessary.
-
Constellation Mapping: Ensure that the constellation points in the
symbol_map
dictionaries of both thetransmitter
andreceiver
functions match exactly. Any small discrepancies can lead to decoding errors. -
Padding and Message Length: Double-check that the padding and slicing of the binary message in the
transmitter
andreceiver
functions are working as expected. Ensure that message length remains consistent throughout the process. -
Character Encoding: The code assumes a 'latin-1' encoding when converting binary back to text. Ensure that this encoding is appropriate for your use case. You may need to change the encoding depending on the message content.
-
Testing: Conduct extensive testing with different types of input messages and noise levels to identify any specific scenarios where the code might fail.
-
Display Output: To aid in debugging, you can add print statements at various stages of your code to see how the message changes as it goes through the transmitter, channel, and receiver. This can help pinpoint where the issue is occurring.
By addressing these points and carefully adjusting the noise level and other parameters, you should be able to improve the accuracy of message reconstruction.
英文:
I am working on a Python code that involves message transmission
and reception
using 16 QAM mapping
. However, I am encountering an issue where I am not getting the original message as expected.
code:
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}")
output:
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
Description
I have implemented a code that uses 16 QAM mapping to transmit and receive messages. The code consists of the following components:
-
transmitter
: Converts the message to binary, pads it, and maps each symbol to a complex value using a predefined constellation. -
receiver
: Demodulates the received symbols, checks if they are valid ASCII characters, and reconstructs the message. -
channel
: Simulates the channel and introduces noise to the transmitted signal.
generate_random_string: Generates a random message for testing.
Issue:
>The problem I am facing is that when I run the code, the reconstructed message is not the same as the original message.
Expected Behavior:
I expect the reconstructed message to be identical to the original message.
Question:
-
What could be causing the discrepancy between the original and reconstructed messages in my code?
-
Are there any possible errors or improvements that I may have overlooked?
-
How can I modify the code to ensure the accurate reconstruction of the original message?
Any guidance, suggestions, or explanations would be greatly appreciated.
答案1
得分: 1
首先,感谢您提供了一个完全功能的代码。这对帮助很大。
不需要的填充
padded_binary_message = binary_message + '0' * (4 - len(binary_message) % 4)
您是否注意到结果数组的大小是101?这是因为 (4 - len(binary_message) % 4) 等于4,而不是0。只有在模数不等于4时,您才应该进行填充。而且,您的字符串的8位表示将始终是4的倍数。您应该只是移除填充。
符号调制
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位,然后连接结果。
其他改进
- 只定义一次您的星座,并从您的第一定义创建您的反向星座。避免在两个实体之间产生差异。
- 使用
string.encode
来直接操作字节,而不是将您的ASCII字符串转换为位字符串,每个字节占用一个完整字符。这非常重,并且容易出错,因为您希望将它们视为位而不是字符。 - 使用Numpy可以更好地进行算法线性化,使用numpy.argmin,您的解调可以更快。
- 一个函数应该只做一件事情。重构您的代码以确保每个函数都有正确的命名,应该可以为您提供关于问题所在的指导。
- 您的变量应该有更好的命名。
请注意,我的代码片段旨在与您的代码匹配。不应将它们视为清晰编码的示例。
英文:
First of all, thank your for providing a fully functional code. This helps a lot.
Unneeded padding
padded_binary_message = binary_message + '0' * (4 - len(binary_message) % 4)
Did you notice that resulting array was of size 101? It's because (4 - len(binary_message) % 4) equals 4, not 0. You should do padding only if modulo is NOT equals 4. Moreover, your 8-bits representation of your string will alway be a multiple of 4. You should just remove padding.
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
Symbol demodulation
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
This code block tries to compute the closest 4-bits constellation point to a 8-bits symbols. You should instead demodulate each 4-bit independently then concatenate results.
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)
As pointed in the comments, we should first test without noise :
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)
Success?
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
Noise
As for the reconstruction of the noisy signal, I am afraid that you are generating too much noise for your input.
print(received_signal - sent_signal)
[-4.00022976e+01 -40.j -3.99901691e+01 -40.j ... ]
The culprit is A = np.array([[11, 10], [10, 11]])
this amplify your signal by a factors of at least 10.
Is this code even needed? your Z
variable already contains some noise, so let's try to only use that noise:
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
a bit bad, however lets try with a lesser noise ratio:
Y = channel(X, noise_factor=0.25)
Original message: ...
Reconstructed message: ...
Similarity ratio: 0.82
that's better. with noise_ratio=0.15 your algorithm capable of fully reconstructing your signal:
Y = channel(X, noise_factor=0.15)
Original message: ...
Reconstructed message: ...
Similarity ratio: 1.0
Other improvements
- Define your constellation only once and create your reverse constellation from your first definition. Avoiding discrepancies between your two entities.
- use
string.encode
to directly manipulate bytes instead of transforming your ascii string into a string of bits, each of theses bytes takes a whole character. this is very heavy and error prone since you want to manipulate them as bits, not char. - Numpy allows you more algorithm linearization, your demodulation could be a lot faster using numpy.argmin
- A function should do one and only one thing. Refactoring your code to ensure that every function is correctly named should easily provides you guidance on what is wrong.
- Your variables should have better naming.
Please note that my code snippets are designed to match yours. They should not be taken as example of clean coding.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论