diff --git a/codebook.py b/codebook.py new file mode 100644 index 0000000..a274881 --- /dev/null +++ b/codebook.py @@ -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 \ No newline at end of file diff --git a/decoder.py b/decoder.py new file mode 100644 index 0000000..409c6fe --- /dev/null +++ b/decoder.py @@ -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 \ No newline at end of file diff --git a/encoder.py b/encoder.py new file mode 100644 index 0000000..3e1674f --- /dev/null +++ b/encoder.py @@ -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 \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 7d1eb25..0000000 --- a/main.py +++ /dev/null @@ -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}") \ No newline at end of file diff --git a/message.txt b/message.txt deleted file mode 100644 index 80f2190..0000000 --- a/message.txt +++ /dev/null @@ -1 +0,0 @@ -Hello this is my message with 40 chars! \ No newline at end of file diff --git a/receiver.py b/receiver.py deleted file mode 100644 index fe90cce..0000000 --- a/receiver.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/test_local.py b/test_local.py new file mode 100644 index 0000000..98da1f2 --- /dev/null +++ b/test_local.py @@ -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() \ No newline at end of file diff --git a/test_server.py b/test_server.py new file mode 100644 index 0000000..60b8212 --- /dev/null +++ b/test_server.py @@ -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() \ No newline at end of file diff --git a/transmitter.py b/transmitter.py deleted file mode 100644 index 946b907..0000000 --- a/transmitter.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..dadf44b --- /dev/null +++ b/utils.py @@ -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 \ No newline at end of file