diff --git a/codebook.py b/codebook.py deleted file mode 100644 index a274881..0000000 --- a/codebook.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/evaluate_zero_error_ratio.py b/evaluate_zero_error_ratio.py deleted file mode 100644 index 986c74f..0000000 --- a/evaluate_zero_error_ratio.py +++ /dev/null @@ -1,54 +0,0 @@ -# evaluate_zero_error_ratio.py - -import argparse -import numpy as np -from encoder import text_to_signal -from decoder import signal_to_text -from channel import channel - -def zero_error_ratio(message: str, r: int, Eb: float, n_runs: int) -> float: - """ - Runs the encode–channel–decode pipeline n_runs times on the same message, - and returns the fraction of runs with 0 character errors. - """ - zero_count = 0 - for _ in range(n_runs): - # encode - x, codebook = text_to_signal(message, r=r, Eb=Eb) - # pass through channel - y = channel(x) - # decode - decoded = signal_to_text(y, codebook, r=r) - # count errors - errors = sum(1 for a, b in zip(message, decoded) if a != b) - if errors == 0: - zero_count += 1 - return zero_count / n_runs - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Estimate the zero-error run ratio over n trials." - ) - parser.add_argument( - "--message", type=str, required=True, - help="The 40-char message to test." - ) - parser.add_argument( - "--r", type=int, default=6, - help="Codebook parameter r (default: 6)." - ) - parser.add_argument( - "--Eb", type=float, default=3.0, - help="Energy per bit Eb (default: 3)." - ) - parser.add_argument( - "--n", type=int, default=1000, - help="Number of runs (default: 1000)." - ) - args = parser.parse_args() - - if len(args.message) != 40: - raise ValueError("Message must be exactly 40 characters long.") - - ratio = zero_error_ratio(args.message, args.r, args.Eb, args.n) - print(f"Zero-error runs: {ratio:.3%} ({ratio:.4f} of {args.n})") diff --git a/main.py b/main.py new file mode 100644 index 0000000..34f2bb3 --- /dev/null +++ b/main.py @@ -0,0 +1,58 @@ +# main.py +#!/usr/bin/env python3 +import argparse +import socket +import numpy as np +import channel_helper as ch +from encoder import make_codebook, encode_message +from decoder import decode_blocks, count_errors +from channel import channel as external_channel + + +def send_and_recv(x: np.ndarray, host: str, port: int) -> np.ndarray: + """Send samples x to server and receive output via TCP""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((host, port)) + header = b'0' + b'dUV' + ch.send_msg(sock, header, x) + _, Y = ch.recv_msg(sock) + return Y + + +def main(): + p = argparse.ArgumentParser(description="PDC Tx/Rx local or server") + p.add_argument("--message", required=True, help="40-character message to send") + p.add_argument("--srv_hostname", help="Server hostname") + p.add_argument("--srv_port", type=int, help="Server port") + p.add_argument("--local", action='store_true', help="Use local channel simulation") + args = p.parse_args() + + msg = args.message + if len(msg) != 40: + raise ValueError("Message must be exactly 40 characters.") + num_blocks = len(msg) + C = make_codebook(r=5, num_blocks=num_blocks, margin=0.95) + x = encode_message(msg, C) + print(f"→ Total samples = {x.size}, total energy = {np.sum(x*x):.1f}") + + if args.local: + print("-- Local simulation mode --") + Y = external_channel(x) + else: + if not args.srv_hostname or not args.srv_port: + raise ValueError("Must specify --srv_hostname and --srv_port unless --local") + Y = send_and_recv(x, args.srv_hostname, args.srv_port) + + msg_hat = decode_blocks(Y, C) + print(f"↓ Decoded message: {msg_hat}") + + errors = count_errors(msg, msg_hat) + print(f"Errors: {len(errors)} / {len(msg)} characters differ") + if errors: + for i, o, e in errors: + print(f" Pos {i}: sent '{o}' but got '{e}'") + else: + print("✔️ No decoding errors!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/performance_local.py b/performance_local.py new file mode 100644 index 0000000..8bac46f --- /dev/null +++ b/performance_local.py @@ -0,0 +1,37 @@ +# performance_local.py +#!/usr/bin/env python3 +import argparse +import numpy as np +import random +from encoder import make_codebook, encode_message, ALPHABET +from decoder import decode_blocks, count_errors +from channel import channel + + +def random_message(length): + return ''.join(random.choice(ALPHABET) for _ in range(length)) + + +def main(): + parser = argparse.ArgumentParser(description="Monte Carlo evaluation over local channel") + parser.add_argument("--num", type=int, required=True, help="Number of trials") + parser.add_argument("--r", type=int, default=5, help="Hadamard order (default 5)") + args = parser.parse_args() + + num_trials = args.num + successes = 0 + + for _ in range(num_trials): + msg = random_message(40) + C = make_codebook(r=args.r, num_blocks=len(msg)) + x = encode_message(msg, C) + Y = channel(x) + msg_hat = decode_blocks(Y, C) + if msg_hat == msg: + successes += 1 + + ratio = successes / num_trials + print(f"Correctly decoded messages: {successes}/{num_trials} ({ratio:.2%})") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/utils.py b/utils.py deleted file mode 100644 index dadf44b..0000000 --- a/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -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