doc: add README

This commit is contained in:
appellet 2025-05-27 11:32:50 +02:00
parent 094d90715e
commit 0262a27f2c
2 changed files with 153 additions and 35 deletions

94
README.md Normal file
View file

@ -0,0 +1,94 @@
# Communication System README
This repository contains a Python-based transmitter/receiver system for sending 40-character text messages over a simulated noisy channel (local or remote server).
---
## Prerequisites
* Python 3.7+
* NumPy
Install dependencies:
```bash
pip install numpy
```
---
## Files
* `encoder.py` — builds codebook and encodes messages.
* `decoder.py` — decodes received samples back into text.
* `channel.py` — local channel simulator.
* `client.py` — client stub to send samples to the remote server.
* `main.py` — main driver: wraps encode → channel → decode, plus performance testing.
---
## Usage
All commands assume you are in the project root directory.
### 1. Test locally for 1 trial
```bash
python3 main.py \
--message "This is a 40-character test message...." \
--trials 1 \
--mode local
```
### 2. Test locally for 500 trials
```bash
python3 main.py \
--message "This is a 40-character test message...." \
--trials 500 \
--mode local
```
### 3. Test on the remote server (1 trial)
This will write `input.txt` and `output.txt` in your working directory.
```bash
python3 main.py \
--message "This is a 40-character test message...." \
--trials 1 \
--mode server \
--input_file input.txt \
--output_file output.txt \
--hostname iscsrv72.epfl.ch \
--port 80
```
> After running, `input.txt` contains your transmitted samples, and `output.txt` contains the noisy output from the server.
---
## Manual decoding of server output
If you want to decode `output.txt` yourself:
```bash
python3 - << 'EOF'
import numpy as np
import encoder, decoder
# Load noisy samples
Y = np.loadtxt("output.txt")
# Rebuild codebook
C = encoder.make_codebook(r=5, num_blocks=40)
# Decode
msg = decoder.decode_blocks(Y, C)
print("Decoded message:", msg)
EOF
```
---
Feel free to adjust `--trials` or swap between `local` and `server` modes as needed.

94
main.py
View file

@ -7,36 +7,52 @@ import encoder
import decoder import decoder
import channel import channel
import subprocess import subprocess
import tempfile
import pathlib import pathlib
import os import os
import tempfile
# Global paths for debugging
INPUT_FILE = None
OUTPUT_FILE = None
def transmit(msg, C): def transmit(msg, C):
""" '''
Transmitter: encodes the message into real-valued samples using the codebook C. Transmitter: encodes the message into real-valued samples using the codebook C.
""" '''
return encoder.encode_message(msg, C) return encoder.encode_message(msg, C)
def receive_local(c): def receive_local(c):
""" '''
Sends the samples through the local channel simulation. Sends the samples through the local channel simulation.
""" '''
return channel.channel(c) return channel.channel(c)
def receive_server(c, hostname, port): def receive_server(c, hostname, port):
""" '''
Sends the samples to the remote server via client.py and retrieves the output. Sends the samples to the remote server via client.py. If INPUT_FILE and/or
""" OUTPUT_FILE are set, uses those filenames (and preserves them); otherwise uses temporary files.
# Write input samples to a temporary file '''
with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as in_f: global INPUT_FILE, OUTPUT_FILE
np.savetxt(in_f.name, c) # Determine input file path
in_name = in_f.name if INPUT_FILE:
# Prepare output file in_name = INPUT_FILE
out_fd, out_name = tempfile.mkstemp(suffix='.txt') np.savetxt(in_name, c)
os.close(out_fd) delete_in = False
else:
with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as in_f:
np.savetxt(in_f.name, c)
in_name = in_f.name
delete_in = True
# Determine output file path
if OUTPUT_FILE:
out_name = OUTPUT_FILE
delete_out = False
else:
out_fd, out_name = tempfile.mkstemp(suffix='.txt')
os.close(out_fd)
delete_out = True
# Invoke client.py # Invoke client.py
cmd = [ cmd = [
sys.executable, sys.executable,
@ -50,30 +66,31 @@ def receive_server(c, hostname, port):
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True)
Y = np.loadtxt(out_name) Y = np.loadtxt(out_name)
finally: finally:
# Clean up temp files if delete_in and os.path.exists(in_name):
os.remove(in_name) os.remove(in_name)
os.remove(out_name) if delete_out and os.path.exists(out_name):
os.remove(out_name)
return Y return Y
def receive(c, mode, hostname, port): def receive(c, mode, hostname, port):
""" '''
Wrapper to choose local or server channel. Wrapper to choose local or server channel.
""" '''
if mode == 'local': if mode == 'local':
return receive_local(c) return receive_local(c)
elif mode == 'server': elif mode == 'server':
return receive_server(c, hostname, port) return receive_server(c, hostname, port)
else: else:
raise ValueError("Mode must be 'local' or 'server'") raise ValueError('Mode must be \'local\' or \'server\'')
def test_performance(msg, num_trials, mode, hostname, port): def test_performance(msg, num_trials, mode, hostname, port):
""" '''
Runs num_trials transmissions of msg through the specified channel and reports accuracy. Runs num_trials transmissions of msg through the specified channel and reports accuracy.
""" '''
if len(msg) != 40: if len(msg) != 40:
raise ValueError("Message must be exactly 40 characters.") raise ValueError('Message must be exactly 40 characters.')
# Build codebook for 64 symbols, 40 blocks # Build codebook for 64 symbols, 40 blocks
C = encoder.make_codebook(r=5, num_blocks=40) C = encoder.make_codebook(r=5, num_blocks=40)
successes = 0 successes = 0
@ -88,27 +105,34 @@ def test_performance(msg, num_trials, mode, hostname, port):
successes += 1 successes += 1
pct = successes / num_trials * 100 pct = successes / num_trials * 100
# Display results # Display results
print(f"Message: {msg}") print(f'Message: {msg}')
print(f"Trials: {num_trials}") print(f'Trials: {num_trials}')
print(f"Mode: {mode}") print(f'Mode: {mode}')
print(f"Correct decodings: {successes}") print(f'Correct decodings: {successes}')
print(f"Accuracy: {pct:.2f}%") print(f'Accuracy: {pct:.2f}%')
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description="Test communication system performance.") parser = argparse.ArgumentParser(description='Test communication system performance.')
parser.add_argument('--message', '-m', type=str, required=True, parser.add_argument('--message', '-m', type=str, required=True,
help="40-character message to send.") help='40-character message to send.')
parser.add_argument('--trials', '-n', type=int, default=1, parser.add_argument('--trials', '-n', type=int, default=200,
help="Number of trials.") help='Number of trials.')
parser.add_argument('--mode', choices=['local','server'], default='local', parser.add_argument('--mode', choices=['local','server'], default='local',
help="Channel mode: 'local' or 'server'.") help="Channel mode: 'local' or 'server'.")
parser.add_argument('--hostname', type=str, default='iscsrv72.epfl.ch', parser.add_argument('--hostname', type=str, default='iscsrv72.epfl.ch',
help="Server hostname for server mode.") help='Server hostname for server mode.')
parser.add_argument('--port', type=int, default=80, parser.add_argument('--port', type=int, default=80,
help="Server port for server mode.") help='Server port for server mode.')
parser.add_argument('--input_file', '-i', type=str, default=None,
help='Path to write server input samples (input.txt).')
parser.add_argument('--output_file', '-o', type=str, default=None,
help='Path to write server output samples (output.txt).')
return parser.parse_args() return parser.parse_args()
if __name__ == '__main__': if __name__ == '__main__':
args = parse_args() args = parse_args()
# Set global paths
INPUT_FILE = args.input_file
OUTPUT_FILE = args.output_file
test_performance(args.message, args.trials, args.mode, args.hostname, args.port) test_performance(args.message, args.trials, args.mode, args.hostname, args.port)