232 lines
5.8 KiB
Python
232 lines
5.8 KiB
Python
"""
|
|
Experiment Runner for LoRa Mesh Simulation.
|
|
|
|
Provides automated experiment execution with parameter sweeps.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from typing import List, Dict, Any
|
|
from itertools import product
|
|
|
|
from sim.main import run_simulation
|
|
|
|
|
|
def run_single_experiment(
|
|
routing: str,
|
|
node_count: int,
|
|
area_size: float,
|
|
sim_time: float,
|
|
seed: int = 42,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Run a single experiment with given parameters.
|
|
|
|
Args:
|
|
routing: Routing algorithm ("gradient", "flooding", "random")
|
|
node_count: Number of nodes
|
|
area_size: Area size in meters
|
|
sim_time: Simulation time in seconds
|
|
seed: Random seed
|
|
|
|
Returns:
|
|
Dictionary with experiment results
|
|
"""
|
|
results = run_simulation(
|
|
num_nodes=node_count,
|
|
area_size=area_size,
|
|
sim_time=sim_time,
|
|
seed=seed,
|
|
routing_type=routing,
|
|
)
|
|
|
|
m = results["metrics"]
|
|
|
|
return {
|
|
"routing": routing,
|
|
"nodes": node_count,
|
|
"area": area_size,
|
|
"sim_time": sim_time,
|
|
"seed": seed,
|
|
"pdr": m["pdr"],
|
|
"max_hop": m["max_hop"],
|
|
"avg_hop": m["avg_hop"],
|
|
"total_sent": m["total_sent"],
|
|
"total_received": m["total_received"],
|
|
"total_forwarded": m["total_forwarded"],
|
|
"collisions": m["collisions"],
|
|
"convergence_time": m["convergence_time"],
|
|
"route_changes": m["route_changes"],
|
|
}
|
|
|
|
|
|
def run_parameter_sweep(
|
|
routings: List[str] = None,
|
|
node_counts: List[int] = None,
|
|
area_sizes: List[float] = None,
|
|
sim_time: float = 200,
|
|
seeds: List[int] = None,
|
|
output_file: str = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Run parameter sweep experiments.
|
|
|
|
Args:
|
|
routings: List of routing algorithms
|
|
node_counts: List of node counts
|
|
area_sizes: List of area sizes
|
|
sim_time: Simulation time (same for all)
|
|
seeds: List of random seeds (for averaging)
|
|
output_file: Optional output CSV file
|
|
|
|
Returns:
|
|
List of experiment results
|
|
"""
|
|
# Default parameters
|
|
if routings is None:
|
|
routings = ["gradient", "flooding", "random"]
|
|
if node_counts is None:
|
|
node_counts = [6, 9, 12, 15]
|
|
if area_sizes is None:
|
|
area_sizes = [500, 800, 1000]
|
|
if seeds is None:
|
|
seeds = [42, 123, 456] # Multiple seeds for averaging
|
|
|
|
results = []
|
|
|
|
# Generate all parameter combinations
|
|
total_experiments = len(routings) * len(node_counts) * len(area_sizes) * len(seeds)
|
|
current = 0
|
|
|
|
print(f"Running {total_experiments} experiments...")
|
|
|
|
for routing, nodes, area, seed in product(routings, node_counts, area_sizes, seeds):
|
|
current += 1
|
|
print(
|
|
f" [{current}/{total_experiments}] {routing}, nodes={nodes}, area={area}, seed={seed}"
|
|
)
|
|
|
|
result = run_single_experiment(
|
|
routing=routing,
|
|
node_count=nodes,
|
|
area_size=area,
|
|
sim_time=sim_time,
|
|
seed=seed,
|
|
)
|
|
results.append(result)
|
|
|
|
# Save to CSV if requested
|
|
if output_file:
|
|
save_results_csv(results, output_file)
|
|
|
|
return results
|
|
|
|
|
|
def save_results_csv(results: List[Dict[str, Any]], filename: str):
|
|
"""Save experiment results to CSV file."""
|
|
import csv
|
|
|
|
if not results:
|
|
return
|
|
|
|
# Get all keys from first result
|
|
keys = list(results[0].keys())
|
|
|
|
with open(filename, "w", newline="") as f:
|
|
writer = csv.DictWriter(f, fieldnames=keys)
|
|
writer.writeheader()
|
|
writer.writerows(results)
|
|
|
|
print(f"Results saved to {filename}")
|
|
|
|
|
|
def compute_averages(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
"""
|
|
Compute average results over multiple seeds.
|
|
|
|
Args:
|
|
results: List of experiment results with varying seeds
|
|
|
|
Returns:
|
|
List of averaged results
|
|
"""
|
|
from collections import defaultdict
|
|
|
|
# Group by (routing, nodes, area)
|
|
groups = defaultdict(list)
|
|
|
|
for r in results:
|
|
key = (r["routing"], r["nodes"], r["area"])
|
|
groups[key].append(r)
|
|
|
|
# Average each group
|
|
averaged = []
|
|
numeric_keys = [
|
|
"pdr",
|
|
"max_hop",
|
|
"avg_hop",
|
|
"total_sent",
|
|
"total_received",
|
|
"total_forwarded",
|
|
"collisions",
|
|
"convergence_time",
|
|
"route_changes",
|
|
]
|
|
|
|
for key, group in groups.items():
|
|
avg_result = {
|
|
"routing": key[0],
|
|
"nodes": key[1],
|
|
"area": key[2],
|
|
"num_seeds": len(group),
|
|
}
|
|
|
|
for nk in numeric_keys:
|
|
values = [g[nk] for g in group]
|
|
avg_result[f"avg_{nk}"] = sum(values) / len(values)
|
|
avg_result[f"min_{nk}"] = min(values)
|
|
avg_result[f"max_{nk}"] = max(values)
|
|
|
|
averaged.append(avg_result)
|
|
|
|
return averaged
|
|
|
|
|
|
def run_quick_comparison(
|
|
routing: str = "gradient",
|
|
node_count: int = 12,
|
|
area_size: float = 800,
|
|
sim_time: float = 200,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Run a quick comparison of all routing algorithms.
|
|
|
|
Returns results for gradient, flooding, and random.
|
|
"""
|
|
results = {}
|
|
|
|
for r in ["gradient", "flooding", "random"]:
|
|
print(f"Running {r}...")
|
|
results[r] = run_single_experiment(
|
|
routing=r,
|
|
node_count=node_count,
|
|
area_size=area_size,
|
|
sim_time=sim_time,
|
|
)
|
|
|
|
return results
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Quick test
|
|
print("Running quick comparison...")
|
|
results = run_quick_comparison()
|
|
|
|
print("\n=== Results ===")
|
|
for routing, data in results.items():
|
|
print(f"\n{routing.upper()}:")
|
|
print(f" PDR: {data['pdr']:.2f}%")
|
|
print(f" Max Hop: {data['max_hop']}")
|
|
print(f" Avg Hop: {data['avg_hop']:.2f}")
|
|
print(f" Sent: {data['total_sent']}, Received: {data['total_received']}")
|