-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
154 lines (125 loc) · 4.82 KB
/
main.py
File metadata and controls
154 lines (125 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env python3
"""
main.py — CLI entry point for the Bacterial Colony ABM Simulation.
Usage:
python main.py # Run with default config.yaml
python main.py --config my_config.yaml # Custom config
python main.py --epochs 300 # Override epochs
python main.py --dashboard # Launch live dashboard instead
"""
from __future__ import annotations
import argparse
import logging
import os
import sys
import time
logger = logging.getLogger(__name__)
import yaml
def load_config(path: str = "config.yaml") -> dict:
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def epoch_callback(epoch: int, sim) -> None:
"""Print progress every 10 epochs."""
if epoch % 10 == 0 or epoch == 1:
alive = len(sim.agents)
res = sim.env.mean_resource()
ab = sim.env.mean_antibiotic()
logger.info(" [epoch %4d] pop=%5d resource=%.3f antibiotic=%.4f",
epoch, alive, res, ab)
def run_simulation(cfg: dict) -> None:
"""Run the full headless simulation, export CSV and charts."""
from simulate import Simulation
from visualize import generate_all_plots
# Create output directories
out_dir = cfg["simulation"].get("output_dir", "output")
charts_dir = cfg["simulation"].get("charts_dir", "charts")
os.makedirs(out_dir, exist_ok=True)
os.makedirs(charts_dir, exist_ok=True)
total_epochs = cfg["simulation"]["epochs"]
logger.info("=" * 60)
logger.info(" Bacterial Colony ABM — Headless Simulation")
logger.info(" Epochs: %d", total_epochs)
logger.info(" Initial pop: %d", cfg['bacterium']['initial_count'])
logger.info(" Carrying capacity: %d", cfg['population']['carrying_capacity'])
logger.info(" Grid: %dx%d", cfg['grid']['width'], cfg['grid']['height'])
logger.info(" Seed: %s", cfg['simulation'].get('seed', 'random'))
logger.info("=" * 60)
t0 = time.time()
sim = Simulation(cfg)
metrics = sim.run(callback=epoch_callback)
elapsed = time.time() - t0
alive = len(sim.agents)
logger.info("-" * 60)
logger.info(" Simulation complete in %.1fs", elapsed)
logger.info(" Final population: %d", alive)
logger.info(" Total epochs run: %d", sim.epoch)
csv_path = sim.export_csv()
logger.info(" CSV saved: %s", csv_path)
logger.info(" Generating charts...")
chart_files = generate_all_plots(sim)
logger.info(" %d charts saved to %s/", len(chart_files), charts_dir)
for f in chart_files:
logger.info(" - %s", os.path.basename(f))
logger.info("=" * 60)
logger.info(" Done! Check output/ and charts/ directories.")
def run_dashboard(cfg: dict) -> None:
"""Launch the Flask+SocketIO live dashboard."""
logger.info("Launching live dashboard...")
os.makedirs("output", exist_ok=True)
os.makedirs("charts", exist_ok=True)
# Import and run the dashboard
import dashboard
dashboard.socketio.run(
dashboard.app, host="0.0.0.0", port=5000,
debug=False, allow_unsafe_werkzeug=True
)
def main():
parser = argparse.ArgumentParser(
description="Bacterial Colony Agent-Based Model Simulation"
)
parser.add_argument(
"--config", "-c", default="config.yaml",
help="Path to YAML configuration file (default: config.yaml)"
)
parser.add_argument(
"--epochs", "-e", type=int, default=None,
help="Override number of simulation epochs"
)
parser.add_argument(
"--seed", "-s", type=int, default=None,
help="Override random seed (omit for random)"
)
parser.add_argument(
"--dashboard", "-d", action="store_true",
help="Launch live web dashboard instead of headless run"
)
parser.add_argument(
"--initial-count", "-n", type=int, default=None,
help="Override initial population count"
)
parser.add_argument(
"--carrying-capacity", "-k", type=int, default=None,
help="Override carrying capacity"
)
args = parser.parse_args()
# Load config
if not os.path.exists(args.config):
logger.error("Config file '%s' not found.", args.config)
sys.exit(1)
cfg = load_config(args.config)
# Apply CLI overrides
if args.epochs is not None:
cfg["simulation"]["epochs"] = args.epochs
if args.seed is not None:
cfg["simulation"]["seed"] = args.seed
if args.initial_count is not None:
cfg["bacterium"]["initial_count"] = args.initial_count
if args.carrying_capacity is not None:
cfg["population"]["carrying_capacity"] = args.carrying_capacity
if args.dashboard:
run_dashboard(cfg)
else:
run_simulation(cfg)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s")
main()