From 3ef68b4380e86b72407b52a0d7a61f418f44c2c1 Mon Sep 17 00:00:00 2001 From: appellet Date: Tue, 20 May 2025 19:17:08 +0200 Subject: [PATCH] fix: a good version --- receiver.py | 114 +++++++++++++++++++++++++++---------------------- transmitter.py | 100 +++++++++++++++++++++---------------------- 2 files changed, 111 insertions(+), 103 deletions(-) diff --git a/receiver.py b/receiver.py index 736198d..c8e048e 100644 --- a/receiver.py +++ b/receiver.py @@ -1,59 +1,69 @@ import numpy as np -ALPHABET = ( - list('abcdefghijklmnopqrstuvwxyz') + - list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + - list('0123456789') + - [' ', '.'] -) -def get_hadamard(n): - assert (n & (n - 1) == 0), "Hadamard order must be power of 2" - H = np.array([[1]]) - while H.shape[0] < n: - H = np.block([ - [ H, H], - [ H, -H] - ]) - return H +def bits_to_text(bits, alphabet): + """Convert binary bits to text using the 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): + # Convert 6 bits to an integer + idx = int(''.join(map(str, bits[i:i + 6])), 2) + text += idx_to_char.get(idx, '?') # Default to '?' if index invalid + return text -def decode_signal(signal, alphabet=ALPHABET): - code_length = 64 - n_chars = len(signal) // code_length - H = get_hadamard(64) - scale = 1 / np.sqrt(code_length) - codebook = H * scale - decoded = [] - for i in range(n_chars): - y = signal[i*code_length : (i+1)*code_length] - # The channel may have applied sqrt(10) gain to odds or evens - # We don't know which, so try both options and pick best - y_even = np.array(y) - y_even[::2] /= np.sqrt(10) - y_odd = np.array(y) - y_odd[1::2] /= np.sqrt(10) - # Try decoding both hypotheses - scores_even = codebook @ y_even - scores_odd = codebook @ y_odd - idx_even = np.argmax(scores_even) - idx_odd = np.argmax(scores_odd) - score_even = np.max(scores_even) - score_odd = np.max(scores_odd) - idx_best = idx_even if score_even > score_odd else idx_odd - decoded.append(alphabet[idx_best]) - return ''.join(decoded) +def receiver(Y): + """ + Decode the received signal Y to a 40-character text message. -def main(): - import argparse - parser = argparse.ArgumentParser(description="Receiver: decode y.txt to recovered message for PDC Project.") - parser.add_argument('--input_file', type=str, required=True, help="Received y.txt from channel/server") - parser.add_argument('--output_file', type=str, required=True, help="Text file for the decoded message") - args = parser.parse_args() - y = np.loadtxt(args.input_file) - decoded = decode_signal(y) - with open(args.output_file, 'w') as f: - f.write(decoded) + Parameters: + Y (np.ndarray): Received signal of length 480. -if __name__ == '__main__': - main() + Returns: + str: Decoded 40-character text message. + """ + # Define constants + G = 10 # Power gain + sigma2 = 10 # Noise variance + alphabet = ( + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '0123456789 .' + ) + + # Input validation + if Y.size != 480: + raise ValueError("Received signal must have length 480.") + + n = 480 + bits = np.zeros(240, dtype=int) + E = 4 # Energy per bit, must match transmitter + + # Process each bit (sent at indices 2i and 2i+1) + for i in range(240): + y_odd = Y[2 * i] # Even index in Y (0-based) + y_even = Y[2 * i + 1] # Odd index in Y + + # LLR for channel 1: odd indices have gain sqrt(G), even have gain 1 + # H0: bit = 0 (sent +sqrt(E)), H1: bit = 1 (sent -sqrt(E)) + llr_ch1 = ( + (y_odd * np.sqrt(E) * np.sqrt(G) / sigma2) + # Odd index term + (y_even * np.sqrt(E) / sigma2) # Even index term + ) + + # LLR for channel 2: even indices have gain sqrt(G), odd have gain 1 + llr_ch2 = ( + (y_odd * np.sqrt(E) / sigma2) + # Odd index term + (y_even * np.sqrt(E) * np.sqrt(G) / sigma2) # Even index term + ) + + # Combine LLRs (assuming equal prior probabilities for both channels) + llr = 0.5 * (llr_ch1 + llr_ch2) + + # Decode bit: LLR > 0 implies bit = 0, LLR < 0 implies bit = 1 + bits[i] = 1 if llr < 0 else 0 + + # Convert bits to text + text = bits_to_text(bits, alphabet) + return text \ No newline at end of file diff --git a/transmitter.py b/transmitter.py index 2bc7f8b..946b907 100644 --- a/transmitter.py +++ b/transmitter.py @@ -1,57 +1,55 @@ import numpy as np -import sys -# Character Set and coding -ALPHABET = ( - list('abcdefghijklmnopqrstuvwxyz') + - list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + - list('0123456789') + - [' ', '.'] -) -def get_hadamard(n): - assert (n & (n - 1) == 0), "Hadamard order must be power of 2" - H = np.array([[1]]) - while H.shape[0] < n: - H = np.block([ - [ H, H], - [ H, -H] - ]) - return H +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 encode_message(msg, alphabet=ALPHABET): - msg = msg.strip() - if len(msg) != 40: - raise Exception("Message must be exactly 40 characters!") - # Get Hadamard codes - H = get_hadamard(64) - code_length = 64 - # Normalize so signal energy stays bounded - # Each row has norm sqrt(64) = 8, so scale down by 1/8 - scale = 1 / np.sqrt(code_length) - signals = [] - for c in msg: - idx = alphabet.index(c) - signals.append(H[idx] * scale) - signal = np.concatenate(signals) - # Energy check (should be << 2000) - assert signal.shape[0] == 2560 - energy = np.sum(signal ** 2) + +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 Exception("Signal energy above allowed!") - return signal + raise ValueError(f"Signal energy {energy} exceeds limit of 2000.") -def main(): - import argparse - parser = argparse.ArgumentParser(description="Message to signal encoder for PDC Project") - parser.add_argument('--message_file', type=str, required=True, help="Text file with exactly 40 chars.") - parser.add_argument('--output_file', type=str, required=True, help="Output signal file for client.py.") - args = parser.parse_args() - - with open(args.message_file, 'r') as f: - msg = f.read().strip() - x = encode_message(msg) - np.savetxt(args.output_file, x) - -if __name__ == '__main__': - main() + return x \ No newline at end of file