Compare commits

...

2 commits

Author SHA1 Message Date
appellet
0c19f7ce40 Merge remote-tracking branch 'origin/main' 2025-05-24 18:59:11 +02:00
appellet
bbcf5d5fe7 feat: improve results basing on theory 2025-05-24 18:59:01 +02:00
9 changed files with 113 additions and 125 deletions

18
codebook.py Normal file
View file

@ -0,0 +1,18 @@
# codebook.py
import numpy as np
def generate_Br(r):
if r == 0:
return np.array([[1]])
M = generate_Br(r - 1)
top = np.hstack((M, M))
bottom = np.hstack((M, -M))
return np.vstack((top, bottom))
def construct_codebook(r, Eb):
Br = generate_Br(r)
n = 2 * Br.shape[1] # Since codeword is [Br | Br]
m = Br.shape[0] # Number of messages
alpha = Eb * 2 / ((1 + 10) * n) # G=10
codebook = np.sqrt(alpha) * np.hstack((Br, Br))
return codebook, n, m, alpha

28
decoder.py Normal file
View file

@ -0,0 +1,28 @@
# decoder.py
import numpy as np
from utils import index_to_char
from codebook import construct_codebook
def decode_message(Y, codebook):
Y1, Y2 = Y[::2], Y[1::2]
scores = []
for i, c in enumerate(codebook):
c1, c2 = c[::2], c[1::2]
s1 = np.sqrt(10) * np.dot(Y1, c1) + np.dot(Y2, c2)
s2 = np.dot(Y1, c1) + np.sqrt(10) * np.dot(Y2, c2)
scores.append(max(s1, s2))
return np.argmax(scores)
def signal_to_text(Y, codebook, r=6):
_, n, _, _ = construct_codebook(r, 1)
seg_len = n
text = ''
for i in range(40):
seg = Y[i * seg_len:(i + 1) * seg_len]
index = decode_message(seg, codebook)
if index in index_to_char:
text += index_to_char[index]
else:
text += '?' # Unknown character
return text

11
encoder.py Normal file
View file

@ -0,0 +1,11 @@
import numpy as np
from codebook import construct_codebook
from utils import char_to_index, normalize_energy
def text_to_signal(text, r=9, Eb=4):
assert len(text) == 40, "Message must be exactly 40 characters."
codebook, n, m, alpha = construct_codebook(r, Eb)
msg_indices = [char_to_index[c] for c in text]
signal = np.concatenate([codebook[i] for i in msg_indices])
signal = normalize_energy(signal, energy_limit=2000)
return signal, codebook

28
main.py
View file

@ -1,28 +0,0 @@
import numpy as np
from transmitter import transmitter
from receiver import receiver
from channel import channel
# Define the alphabet
alphabet = (
'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789 .'
)
# Example 40-character message
test_message = 'ERROR 404. Motivation not found. Retry .'
# Transmit
x = transmitter(test_message)
# Simulate channel
Y = channel(x)
# Receive
decoded_message = receiver(Y)
# Print results
print(f"Original message: {test_message}")
print(f"Decoded message: {decoded_message}")
print(f"Bit error rate: {(np.array(list(test_message)) != np.array(list(decoded_message))).mean():.4f}")

View file

@ -1,42 +0,0 @@
import numpy as np
def bits_to_text(bits, alphabet):
char_to_idx = {char: idx for idx, char in enumerate(alphabet)}
idx_to_char = {idx: char for char, idx in char_to_idx.items()}
text = ''
for i in range(0, len(bits), 6):
idx = int(''.join(map(str, bits[i:i + 6])), 2)
text += idx_to_char.get(idx, '?')
return text
def receiver(Y):
G = 10 # Match channel.py
sigma2 = 10
alphabet = (
'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789 .'
)
if Y.size != 480:
raise ValueError("Received signal must have length 480.")
n = 480
bits = np.zeros(240, dtype=int)
E = 4
# Estimate channel state
even_energy = np.mean(np.abs(Y[::2]))
odd_energy = np.mean(np.abs(Y[1::2]))
# If even indices have higher energy, likely s=1; else s=2
s_hat = 1 if even_energy > odd_energy else 2
for i in range(240):
y_even = Y[2 * i]
y_odd = Y[2 * i + 1]
if s_hat == 1:
llr = (y_even * np.sqrt(E) * np.sqrt(G) + y_odd * np.sqrt(E)) / sigma2
else:
llr = (y_even * np.sqrt(E) + y_odd * np.sqrt(E) * np.sqrt(G)) / sigma2
bits[i] = 1 if llr < 0 else 0
text = bits_to_text(bits, alphabet)
return text

17
test_local.py Normal file
View file

@ -0,0 +1,17 @@
from encoder import text_to_signal
from decoder import signal_to_text
from channel import channel
def test_local():
message = "This is the end of the PDC course. Good "
x, codebook = text_to_signal(message, r=9, Eb=5)
y = channel(x)
decoded = signal_to_text(y, codebook, r=9)
print(f"Original: {message}")
print(f"Decoded : {decoded}")
errors = sum(1 for a, b in zip(message, decoded) if a != b)
print(f"Character errors: {errors}/40")
if __name__ == "__main__":
test_local()

28
test_server.py Normal file
View file

@ -0,0 +1,28 @@
import subprocess
import numpy as np
from encoder import text_to_signal
from decoder import signal_to_text
def test_server():
message = "HelloWorld123 ThisIsATestMessage12345678"
x, codebook = text_to_signal(message, r=6, Eb=1)
np.savetxt("input.txt", x, fmt="%.10f")
subprocess.run([
"python3", "client.py",
"--input_file", "input.txt",
"--output_file", "output.txt",
"--srv_hostname", "iscsrv72.epfl.ch",
"--srv_port", "80"
])
y = np.loadtxt("output.txt")
decoded = signal_to_text(y, codebook, r=6)
print(f"Original: {message}")
print(f"Decoded : {decoded}")
errors = sum(1 for a, b in zip(message, decoded) if a != b)
print(f"Character errors: {errors}/40")
if __name__ == "__main__":
test_server()

View file

@ -1,55 +0,0 @@
import numpy as np
def text_to_bits(text, alphabet):
"""Convert text to binary bits based on alphabet indexing."""
char_to_idx = {char: idx for idx, char in enumerate(alphabet)}
bits = []
for char in text:
idx = char_to_idx[char]
# Convert index to 6-bit binary string, padded with zeros
bits.extend([int(b) for b in format(idx, '06b')])
return np.array(bits)
def transmitter(text):
"""
Convert a 40-character text message to a signal vector x.
Parameters:
text (str): Input text of 40 characters from the given alphabet.
Returns:
np.ndarray: Signal vector x of length 480 with energy <= 2000.
"""
# Define the alphabet
alphabet = (
'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789 .'
)
# Input validation
if len(text) != 40 or not all(c in alphabet for c in text):
raise ValueError("Text must be 40 characters from the given alphabet.")
# Convert text to 240 bits (40 chars * 6 bits/char)
bits = text_to_bits(text, alphabet)
# BPSK modulation: 0 -> +sqrt(E), 1 -> -sqrt(E)
E = 4 # Energy per bit
symbols = np.sqrt(E) * (1 - 2 * bits) # Map 0 to +sqrt(E), 1 to -sqrt(E)
# Create signal x: each bit is sent twice (odd and even indices)
n = 480 # Block length
x = np.zeros(n)
for i in range(240):
x[2 * i] = symbols[i] # Even index (2i)
x[2 * i + 1] = symbols[i] # Odd index (2i+1)
# Verify energy constraint
energy = np.sum(x ** 2)
if energy > 2000:
raise ValueError(f"Signal energy {energy} exceeds limit of 2000.")
return x

11
utils.py Normal file
View file

@ -0,0 +1,11 @@
alphabet = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .")
alphabet_size = len(alphabet)
char_to_index = {c: i for i, c in enumerate(alphabet)}
index_to_char = {i: c for i, c in enumerate(alphabet)}
import numpy as np
def normalize_energy(signal, energy_limit=2000):
norm = np.sqrt(np.sum(signal ** 2))
scale = np.sqrt(energy_limit) / norm
return signal * scale