fix: a good version

This commit is contained in:
appellet 2025-05-20 19:17:08 +02:00
parent f41309e21b
commit 3ef68b4380
2 changed files with 111 additions and 103 deletions

View file

@ -1,59 +1,69 @@
import numpy as np
ALPHABET = (
list('abcdefghijklmnopqrstuvwxyz') +
list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') +
list('0123456789') +
[' ', '.']
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 receiver(Y):
"""
Decode the received signal Y to a 40-character text message.
Parameters:
Y (np.ndarray): Received signal of length 480.
Returns:
str: Decoded 40-character text message.
"""
# Define constants
G = 10 # Power gain
sigma2 = 10 # Noise variance
alphabet = (
'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'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
# Input validation
if Y.size != 480:
raise ValueError("Received signal must have length 480.")
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
n = 480
bits = np.zeros(240, dtype=int)
E = 4 # Energy per bit, must match transmitter
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)
# 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
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)
# 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
)
if __name__ == '__main__':
main()
# 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

View file

@ -1,57 +1,55 @@
import numpy as np
import sys
# Character Set and coding
ALPHABET = (
list('abcdefghijklmnopqrstuvwxyz') +
list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') +
list('0123456789') +
[' ', '.']
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 .'
)
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
# 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.")
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)
# 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