Spaces:
Runtime error
Runtime error
GitHub Actions commited on
Commit ·
63cc479
0
Parent(s):
Deploy App to HF Spaces
Browse files- Dockerfile +28 -0
- README.md +10 -0
- pyproject.toml +19 -0
- src/app/__init__.py +0 -0
- src/app/api/__init__.py +0 -0
- src/app/api/routes.py +71 -0
- src/app/core/__init__.py +0 -0
- src/app/core/config.py +8 -0
- src/app/core/state.py +23 -0
- src/app/main.py +51 -0
- src/app/models/__init__.py +0 -0
- src/app/models/schemas.py +9 -0
- src/app/rl/__init__.py +1 -0
- src/app/rl/train.py +28 -0
- src/app/simulation/__init__.py +0 -0
- src/app/simulation/engine.py +221 -0
- src/app/simulation/utils.py +9 -0
- src/app/train_main.py +27 -0
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Create the user according to Hugging Face specs
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
USER user
|
| 6 |
+
ENV HOME=/home/user \
|
| 7 |
+
PATH=/home/user/.local/bin:$PATH \
|
| 8 |
+
PYTHONUNBUFFERED=1
|
| 9 |
+
|
| 10 |
+
WORKDIR $HOME/app
|
| 11 |
+
|
| 12 |
+
# Install poetry
|
| 13 |
+
RUN pip install --no-cache-dir poetry
|
| 14 |
+
|
| 15 |
+
# Copy dependency files
|
| 16 |
+
COPY --chown=user pyproject.toml poetry.lock* ./
|
| 17 |
+
|
| 18 |
+
# Install dependencies without virtualenvs setup for container
|
| 19 |
+
RUN poetry config virtualenvs.create false && \
|
| 20 |
+
poetry install --no-interaction --no-ansi --no-root
|
| 21 |
+
|
| 22 |
+
# Copy rest of the application
|
| 23 |
+
COPY --chown=user src/ ./src/
|
| 24 |
+
|
| 25 |
+
EXPOSE 7860
|
| 26 |
+
|
| 27 |
+
# Command to run the application space
|
| 28 |
+
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: TrafficSimApp
|
| 3 |
+
emoji: 🚗
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: apache-2.0
|
| 9 |
+
short_description: app for traffic simulation project
|
| 10 |
+
---
|
pyproject.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.poetry]
|
| 2 |
+
name = "sim-backend"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Simulation backend for vehicular traffic offloading"
|
| 5 |
+
authors = ["Author <author@example.com>"]
|
| 6 |
+
readme = "README.md"
|
| 7 |
+
packages = [{include = "app", from = "src"}]
|
| 8 |
+
|
| 9 |
+
[tool.poetry.dependencies]
|
| 10 |
+
python = "^3.10"
|
| 11 |
+
fastapi = "^0.103.0"
|
| 12 |
+
uvicorn = "^0.23.0"
|
| 13 |
+
pydantic = "^2.3.0"
|
| 14 |
+
osmnx = "^1.6.0"
|
| 15 |
+
networkx = "^3.1"
|
| 16 |
+
|
| 17 |
+
[build-system]
|
| 18 |
+
requires = ["poetry-core"]
|
| 19 |
+
build-backend = "poetry.core.masonry.api"
|
src/app/__init__.py
ADDED
|
File without changes
|
src/app/api/__init__.py
ADDED
|
File without changes
|
src/app/api/routes.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from app.core.state import sim_state
|
| 3 |
+
from app.models.schemas import ConfigState, ResetConfig
|
| 4 |
+
from app.simulation.engine import run_step, init_sim_entities
|
| 5 |
+
|
| 6 |
+
router = APIRouter()
|
| 7 |
+
|
| 8 |
+
@router.post("/start")
|
| 9 |
+
def start_sim():
|
| 10 |
+
sim_state.running = True
|
| 11 |
+
return {"status": "started"}
|
| 12 |
+
|
| 13 |
+
@router.post("/pause")
|
| 14 |
+
def pause_sim():
|
| 15 |
+
sim_state.running = False
|
| 16 |
+
return {"status": "paused"}
|
| 17 |
+
|
| 18 |
+
@router.post("/step")
|
| 19 |
+
def step_sim():
|
| 20 |
+
run_step()
|
| 21 |
+
return {"status": "stepped"}
|
| 22 |
+
|
| 23 |
+
@router.post("/algorithm")
|
| 24 |
+
def set_algorithm(config: ConfigState):
|
| 25 |
+
sim_state.algorithm = config.algorithm
|
| 26 |
+
return {"status": "success", "algorithm": sim_state.algorithm}
|
| 27 |
+
|
| 28 |
+
@router.post("/reset")
|
| 29 |
+
def reset_sim(config: ResetConfig):
|
| 30 |
+
sim_state.num_hubs = config.num_hubs
|
| 31 |
+
sim_state.num_vehicles = config.num_vehicles
|
| 32 |
+
sim_state.event_rate = config.event_rate
|
| 33 |
+
init_sim_entities()
|
| 34 |
+
return {"status": "reset_successful"}
|
| 35 |
+
|
| 36 |
+
@router.get("/roads")
|
| 37 |
+
def get_roads():
|
| 38 |
+
return sim_state.roads
|
| 39 |
+
|
| 40 |
+
@router.get("/state")
|
| 41 |
+
def get_state():
|
| 42 |
+
return {
|
| 43 |
+
"running": sim_state.running,
|
| 44 |
+
"step": sim_state.step,
|
| 45 |
+
"algorithm": sim_state.algorithm,
|
| 46 |
+
"total_generated_mb": sim_state.total_generated_mb,
|
| 47 |
+
"total_offloaded_mb": sim_state.total_offloaded_mb,
|
| 48 |
+
"vehicles": sim_state.vehicles,
|
| 49 |
+
"hubs": sim_state.hubs,
|
| 50 |
+
"recent_events": sim_state.recent_events
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
@router.get("/report")
|
| 54 |
+
def export_report():
|
| 55 |
+
avg_latency = 0
|
| 56 |
+
if sim_state.priority_events_completed > 0:
|
| 57 |
+
avg_latency = sim_state.priority_latency_sum / sim_state.priority_events_completed
|
| 58 |
+
|
| 59 |
+
hub_utilization = {f"Hub_{h['id']}": round(h["total_offloaded_mb"], 2) for h in sim_state.hubs}
|
| 60 |
+
|
| 61 |
+
return {
|
| 62 |
+
"algorithm": sim_state.algorithm,
|
| 63 |
+
"total_steps": sim_state.step,
|
| 64 |
+
"completed_events": sim_state.priority_events_completed,
|
| 65 |
+
"average_priority_latency_steps": round(avg_latency, 2),
|
| 66 |
+
"total_buffer_overflows": sim_state.total_buffer_overflows,
|
| 67 |
+
"hub_utilization_mb": hub_utilization,
|
| 68 |
+
"total_generated_mb": round(sim_state.total_generated_mb, 2),
|
| 69 |
+
"total_offloaded_mb": round(sim_state.total_offloaded_mb, 2),
|
| 70 |
+
"overall_efficiency": f"{round((sim_state.total_offloaded_mb / max(1, sim_state.total_generated_mb)) * 100, 2)}%"
|
| 71 |
+
}
|
src/app/core/__init__.py
ADDED
|
File without changes
|
src/app/core/config.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration
|
| 2 |
+
LOCATION = "Faizabad Interchange, Islamabad, Pakistan"
|
| 3 |
+
WIFI_RANGE_METERS = 100
|
| 4 |
+
WIFI_RATE_MB_S = 1.5 # MB per 0.1s step (effectively 15 MB/s)
|
| 5 |
+
DATA_GEN_RATE_MB_STEP = 0.1 # effectively 1 MB/s
|
| 6 |
+
NUM_VEHICLES = 50
|
| 7 |
+
NUM_HUBS = 15
|
| 8 |
+
MAX_BUFFER_MB = 50.0
|
src/app/core/state.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.core.config import NUM_HUBS, NUM_VEHICLES
|
| 2 |
+
|
| 3 |
+
class SimulationState:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.running = False
|
| 6 |
+
self.step = 0
|
| 7 |
+
self.algorithm = "greedy" # 'greedy' or 'predictive'
|
| 8 |
+
self.total_generated_mb = 0.0
|
| 9 |
+
self.total_offloaded_mb = 0.0
|
| 10 |
+
self.total_buffer_overflows = 0
|
| 11 |
+
self.priority_latency_sum = 0
|
| 12 |
+
self.priority_events_completed = 0
|
| 13 |
+
self.num_hubs = NUM_HUBS
|
| 14 |
+
self.num_vehicles = NUM_VEHICLES
|
| 15 |
+
self.event_rate = 0.0005
|
| 16 |
+
self.graph = None
|
| 17 |
+
self.hubs = []
|
| 18 |
+
self.vehicles = []
|
| 19 |
+
self.roads = []
|
| 20 |
+
self.recent_events = []
|
| 21 |
+
|
| 22 |
+
# Single global instance
|
| 23 |
+
sim_state = SimulationState()
|
src/app/main.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
import asyncio
|
| 4 |
+
import osmnx as ox
|
| 5 |
+
|
| 6 |
+
from app.api.routes import router as api_router
|
| 7 |
+
from app.core.state import sim_state
|
| 8 |
+
from app.core.config import LOCATION
|
| 9 |
+
from app.simulation.engine import init_sim_entities, simulation_loop
|
| 10 |
+
|
| 11 |
+
app = FastAPI(title="Simulation Backend")
|
| 12 |
+
|
| 13 |
+
app.add_middleware(
|
| 14 |
+
CORSMiddleware,
|
| 15 |
+
allow_origins=["*"],
|
| 16 |
+
allow_methods=["*"],
|
| 17 |
+
allow_headers=["*"],
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
app.include_router(api_router)
|
| 21 |
+
|
| 22 |
+
@app.on_event("startup")
|
| 23 |
+
async def startup_event():
|
| 24 |
+
print(f"Loading graph for {LOCATION}...")
|
| 25 |
+
try:
|
| 26 |
+
sim_state.graph = ox.graph_from_place(LOCATION, network_type="drive")
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print("Failed to download exact place, using bounding box or default fallback...", e)
|
| 29 |
+
sim_state.graph = ox.graph_from_point((33.6515, 73.0801), dist=2500, network_type="drive")
|
| 30 |
+
|
| 31 |
+
# Extract roads for frontend rendering
|
| 32 |
+
roads = []
|
| 33 |
+
for u, v, data in sim_state.graph.edges(data=True):
|
| 34 |
+
if 'geometry' in data:
|
| 35 |
+
coords = list(data['geometry'].coords)
|
| 36 |
+
roads.append([[pt[1], pt[0]] for pt in coords])
|
| 37 |
+
else:
|
| 38 |
+
u_node = sim_state.graph.nodes[u]
|
| 39 |
+
v_node = sim_state.graph.nodes[v]
|
| 40 |
+
roads.append([[u_node['y'], u_node['x']], [v_node['y'], v_node['x']]])
|
| 41 |
+
sim_state.roads = roads
|
| 42 |
+
|
| 43 |
+
init_sim_entities()
|
| 44 |
+
|
| 45 |
+
print("Simulation initialized.")
|
| 46 |
+
asyncio.create_task(simulation_loop())
|
| 47 |
+
|
| 48 |
+
if __name__ == "__main__":
|
| 49 |
+
import uvicorn
|
| 50 |
+
# Make sure to run the file inside `src/` to correctly access `app.main:app` module.
|
| 51 |
+
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
|
src/app/models/__init__.py
ADDED
|
File without changes
|
src/app/models/schemas.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
class ConfigState(BaseModel):
|
| 4 |
+
algorithm: str
|
| 5 |
+
|
| 6 |
+
class ResetConfig(BaseModel):
|
| 7 |
+
num_hubs: int
|
| 8 |
+
num_vehicles: int
|
| 9 |
+
event_rate: float
|
src/app/rl/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# This file initializes the RL module
|
src/app/rl/train.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import logging
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
+
async def start_training_loop():
|
| 8 |
+
"""
|
| 9 |
+
Minimal dummy RL training loop that runs asynchronously.
|
| 10 |
+
"""
|
| 11 |
+
logger.info("Initializing RL Training environment...")
|
| 12 |
+
|
| 13 |
+
# Ensure models directory exists
|
| 14 |
+
os.makedirs("models", exist_ok=True)
|
| 15 |
+
|
| 16 |
+
epoch = 0
|
| 17 |
+
while True:
|
| 18 |
+
logger.info(f"Running training epoch {epoch}...")
|
| 19 |
+
|
| 20 |
+
# Simulate training work
|
| 21 |
+
await asyncio.sleep(10)
|
| 22 |
+
|
| 23 |
+
# Save a dummy model
|
| 24 |
+
with open(f"models/dummy_model_v{epoch}.pkl", "w") as f:
|
| 25 |
+
f.write(f"Model parameters for epoch {epoch}")
|
| 26 |
+
|
| 27 |
+
logger.info(f"Epoch {epoch} complete and model saved.")
|
| 28 |
+
epoch += 1
|
src/app/simulation/__init__.py
ADDED
|
File without changes
|
src/app/simulation/engine.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import random
|
| 3 |
+
import math
|
| 4 |
+
import networkx as nx
|
| 5 |
+
|
| 6 |
+
from app.core.state import sim_state
|
| 7 |
+
from app.core.config import DATA_GEN_RATE_MB_STEP, MAX_BUFFER_MB, WIFI_RANGE_METERS
|
| 8 |
+
from app.simulation.utils import euclidean_dist
|
| 9 |
+
|
| 10 |
+
def init_sim_entities():
|
| 11 |
+
nodes = list(sim_state.graph.nodes(data=True))
|
| 12 |
+
sim_state.hubs = []
|
| 13 |
+
sim_state.vehicles = []
|
| 14 |
+
sim_state.step = 0
|
| 15 |
+
sim_state.total_generated_mb = 0.0
|
| 16 |
+
sim_state.total_offloaded_mb = 0.0
|
| 17 |
+
sim_state.total_buffer_overflows = 0
|
| 18 |
+
sim_state.priority_latency_sum = 0
|
| 19 |
+
sim_state.priority_events_completed = 0
|
| 20 |
+
sim_state.recent_events = []
|
| 21 |
+
|
| 22 |
+
center_lat = 33.6515
|
| 23 |
+
center_lon = 73.0801
|
| 24 |
+
top_right_nodes = [n for n in nodes if n[1]["y"] > center_lat + 0.005 and n[1]["x"] > center_lon + 0.005]
|
| 25 |
+
if len(top_right_nodes) >= 2 and sim_state.num_hubs >= 2:
|
| 26 |
+
tr_hubs = random.sample(top_right_nodes, 2)
|
| 27 |
+
other_nodes = [n for n in nodes if n not in tr_hubs]
|
| 28 |
+
hub_nodes = tr_hubs + random.sample(other_nodes, sim_state.num_hubs - 2)
|
| 29 |
+
else:
|
| 30 |
+
hub_nodes = random.sample(nodes, sim_state.num_hubs)
|
| 31 |
+
|
| 32 |
+
for hid, node in enumerate(hub_nodes):
|
| 33 |
+
sim_state.hubs.append({
|
| 34 |
+
"id": hid,
|
| 35 |
+
"lat": node[1]["y"],
|
| 36 |
+
"lon": node[1]["x"],
|
| 37 |
+
"range": WIFI_RANGE_METERS,
|
| 38 |
+
"rate": random.choice([1.0, 2.5, 5.0]),
|
| 39 |
+
"active": False,
|
| 40 |
+
"online": True,
|
| 41 |
+
"total_offloaded_mb": 0.0
|
| 42 |
+
})
|
| 43 |
+
|
| 44 |
+
node_ids = list(sim_state.graph.nodes())
|
| 45 |
+
for vid in range(sim_state.num_vehicles):
|
| 46 |
+
n1 = random.choice(node_ids)
|
| 47 |
+
n2 = random.choice(node_ids)
|
| 48 |
+
try:
|
| 49 |
+
path = nx.shortest_path(sim_state.graph, n1, n2, weight='length')
|
| 50 |
+
except:
|
| 51 |
+
path = [n1]
|
| 52 |
+
|
| 53 |
+
start_data = sim_state.graph.nodes[n1]
|
| 54 |
+
target_lat = start_data["y"]
|
| 55 |
+
target_lon = start_data["x"]
|
| 56 |
+
|
| 57 |
+
if len(path) > 1:
|
| 58 |
+
next_node = path[1]
|
| 59 |
+
target_lat = sim_state.graph.nodes[next_node]["y"]
|
| 60 |
+
target_lon = sim_state.graph.nodes[next_node]["x"]
|
| 61 |
+
|
| 62 |
+
base_spd = random.uniform(0.00004, 0.00008)
|
| 63 |
+
sim_state.vehicles.append({
|
| 64 |
+
"id": vid,
|
| 65 |
+
"lat": start_data["y"],
|
| 66 |
+
"lon": start_data["x"],
|
| 67 |
+
"buffer_mb": 0.0,
|
| 68 |
+
"routine_mb": 0.0,
|
| 69 |
+
"priority_mb": 0.0,
|
| 70 |
+
"panic": False,
|
| 71 |
+
"target_lat": target_lat,
|
| 72 |
+
"target_lon": target_lon,
|
| 73 |
+
"path": path,
|
| 74 |
+
"path_idx": 1 if len(path) > 1 else 0,
|
| 75 |
+
"base_speed": base_spd,
|
| 76 |
+
"speed": base_spd,
|
| 77 |
+
"events": []
|
| 78 |
+
})
|
| 79 |
+
|
| 80 |
+
async def simulation_loop():
|
| 81 |
+
while True:
|
| 82 |
+
if sim_state.running:
|
| 83 |
+
run_step()
|
| 84 |
+
await asyncio.sleep(0.05)
|
| 85 |
+
|
| 86 |
+
def run_step():
|
| 87 |
+
sim_state.step += 1
|
| 88 |
+
|
| 89 |
+
for hub in sim_state.hubs:
|
| 90 |
+
hub["active"] = False
|
| 91 |
+
if random.random() < 0.001:
|
| 92 |
+
hub["online"] = not hub["online"]
|
| 93 |
+
|
| 94 |
+
node_ids = list(sim_state.graph.nodes())
|
| 95 |
+
edge_traffic = {}
|
| 96 |
+
for v in sim_state.vehicles:
|
| 97 |
+
if v["path"] and v["path_idx"] < len(v["path"]):
|
| 98 |
+
prev = v["path"][v["path_idx"]-1] if v["path_idx"] > 0 else v["path"][0]
|
| 99 |
+
curr = v["path"][v["path_idx"]]
|
| 100 |
+
edge_traffic[(prev, curr)] = edge_traffic.get((prev, curr), 0) + 1
|
| 101 |
+
|
| 102 |
+
EVENT_TYPES = ["Pothole", "Minor Crash", "Debris", "Speed Trap", "Traffic Jam"]
|
| 103 |
+
|
| 104 |
+
for v in sim_state.vehicles:
|
| 105 |
+
v["routine_mb"] += DATA_GEN_RATE_MB_STEP
|
| 106 |
+
sim_state.total_generated_mb += DATA_GEN_RATE_MB_STEP
|
| 107 |
+
|
| 108 |
+
if v["path"] and v["path_idx"] < len(v["path"]):
|
| 109 |
+
prev = v["path"][v["path_idx"]-1] if v["path_idx"] > 0 else v["path"][0]
|
| 110 |
+
curr = v["path"][v["path_idx"]]
|
| 111 |
+
vehicles_on_edge = edge_traffic.get((prev, curr), 1)
|
| 112 |
+
v["speed"] = v["base_speed"] / max(1, vehicles_on_edge)
|
| 113 |
+
|
| 114 |
+
if random.random() < sim_state.event_rate:
|
| 115 |
+
evt_type = random.choice(EVENT_TYPES)
|
| 116 |
+
evt_size = random.uniform(5.0, 10.0)
|
| 117 |
+
v["events"].append({"type": evt_type, "size": evt_size, "timestamp": sim_state.step})
|
| 118 |
+
v["priority_mb"] += evt_size
|
| 119 |
+
sim_state.total_generated_mb += evt_size
|
| 120 |
+
|
| 121 |
+
total_buf = v["routine_mb"] + v["priority_mb"]
|
| 122 |
+
if total_buf > MAX_BUFFER_MB:
|
| 123 |
+
overflow_amt = total_buf - MAX_BUFFER_MB
|
| 124 |
+
dropped_mb = min(overflow_amt, v["routine_mb"])
|
| 125 |
+
if dropped_mb > 0:
|
| 126 |
+
v["routine_mb"] -= dropped_mb
|
| 127 |
+
sim_state.total_buffer_overflows += 1
|
| 128 |
+
|
| 129 |
+
v["buffer_mb"] = v["routine_mb"] + v["priority_mb"]
|
| 130 |
+
|
| 131 |
+
dx = v["target_lon"] - v["lon"]
|
| 132 |
+
dy = v["target_lat"] - v["lat"]
|
| 133 |
+
dist = math.sqrt(dx*dx + dy*dy)
|
| 134 |
+
if dist < v["speed"]:
|
| 135 |
+
v["lon"] = v["target_lon"]
|
| 136 |
+
v["lat"] = v["target_lat"]
|
| 137 |
+
v["path_idx"] += 1
|
| 138 |
+
if v["path_idx"] < len(v["path"]):
|
| 139 |
+
next_node = v["path"][v["path_idx"]]
|
| 140 |
+
v["target_lat"] = sim_state.graph.nodes[next_node]["y"]
|
| 141 |
+
v["target_lon"] = sim_state.graph.nodes[next_node]["x"]
|
| 142 |
+
else:
|
| 143 |
+
start_node = v["path"][-1] if v["path"] else random.choice(node_ids)
|
| 144 |
+
end_node = random.choice(node_ids)
|
| 145 |
+
try:
|
| 146 |
+
new_path = nx.shortest_path(sim_state.graph, start_node, end_node, weight='length')
|
| 147 |
+
except:
|
| 148 |
+
new_path = [start_node]
|
| 149 |
+
|
| 150 |
+
v["path"] = new_path
|
| 151 |
+
v["path_idx"] = 1
|
| 152 |
+
if len(new_path) > 1:
|
| 153 |
+
next_node = new_path[1]
|
| 154 |
+
v["target_lat"] = sim_state.graph.nodes[next_node]["y"]
|
| 155 |
+
v["target_lon"] = sim_state.graph.nodes[next_node]["x"]
|
| 156 |
+
else:
|
| 157 |
+
v["target_lat"] = v["lat"]
|
| 158 |
+
v["target_lon"] = v["lon"]
|
| 159 |
+
else:
|
| 160 |
+
v["lon"] += (dx/dist) * v["speed"]
|
| 161 |
+
v["lat"] += (dy/dist) * v["speed"]
|
| 162 |
+
|
| 163 |
+
closest_hub = None
|
| 164 |
+
min_dist = float('inf')
|
| 165 |
+
for hub in sim_state.hubs:
|
| 166 |
+
d = euclidean_dist(v["lat"], v["lon"], hub["lat"], hub["lon"])
|
| 167 |
+
if d < min_dist:
|
| 168 |
+
min_dist = d
|
| 169 |
+
closest_hub = hub
|
| 170 |
+
|
| 171 |
+
if closest_hub and min_dist <= closest_hub["range"]:
|
| 172 |
+
if not closest_hub["online"]:
|
| 173 |
+
v["panic"] = True
|
| 174 |
+
else:
|
| 175 |
+
skip_offload = False
|
| 176 |
+
|
| 177 |
+
if sim_state.algorithm == "predictive" and not v.get("panic", False) and (v["routine_mb"] + v["priority_mb"]) < MAX_BUFFER_MB * 0.8:
|
| 178 |
+
future_nodes = v["path"][v["path_idx"]:v["path_idx"]+10]
|
| 179 |
+
for f_node in future_nodes:
|
| 180 |
+
ny = sim_state.graph.nodes[f_node]["y"]
|
| 181 |
+
nx_coord = sim_state.graph.nodes[f_node]["x"]
|
| 182 |
+
for h in sim_state.hubs:
|
| 183 |
+
if h["online"] and h["rate"] > closest_hub["rate"] and euclidean_dist(ny, nx_coord, h["lat"], h["lon"]) <= h["range"]:
|
| 184 |
+
skip_offload = True
|
| 185 |
+
break
|
| 186 |
+
if skip_offload:
|
| 187 |
+
break
|
| 188 |
+
|
| 189 |
+
if not skip_offload:
|
| 190 |
+
amt_to_offload = closest_hub["rate"]
|
| 191 |
+
|
| 192 |
+
while v["events"] and amt_to_offload > 0:
|
| 193 |
+
evt = v["events"][0]
|
| 194 |
+
if evt["size"] <= amt_to_offload:
|
| 195 |
+
amt_to_offload -= evt["size"]
|
| 196 |
+
v["priority_mb"] -= evt["size"]
|
| 197 |
+
sim_state.total_offloaded_mb += evt["size"]
|
| 198 |
+
closest_hub["total_offloaded_mb"] += evt["size"]
|
| 199 |
+
latency = sim_state.step - evt["timestamp"]
|
| 200 |
+
sim_state.priority_latency_sum += latency
|
| 201 |
+
sim_state.priority_events_completed += 1
|
| 202 |
+
v["events"].pop(0)
|
| 203 |
+
sim_state.recent_events.insert(0, f"[{sim_state.step}] Veh {v['id']} offloaded '{evt['type']}' to Hub {closest_hub['id']} (Lat: {latency}s)")
|
| 204 |
+
else:
|
| 205 |
+
evt["size"] -= amt_to_offload
|
| 206 |
+
v["priority_mb"] -= amt_to_offload
|
| 207 |
+
sim_state.total_offloaded_mb += amt_to_offload
|
| 208 |
+
closest_hub["total_offloaded_mb"] += amt_to_offload
|
| 209 |
+
amt_to_offload = 0
|
| 210 |
+
|
| 211 |
+
if amt_to_offload > 0 and v["routine_mb"] > 0:
|
| 212 |
+
off_routine = min(v["routine_mb"], amt_to_offload)
|
| 213 |
+
v["routine_mb"] -= off_routine
|
| 214 |
+
sim_state.total_offloaded_mb += off_routine
|
| 215 |
+
closest_hub["total_offloaded_mb"] += off_routine
|
| 216 |
+
|
| 217 |
+
closest_hub["active"] = True
|
| 218 |
+
v["panic"] = False
|
| 219 |
+
|
| 220 |
+
v["buffer_mb"] = v["routine_mb"] + v["priority_mb"]
|
| 221 |
+
sim_state.recent_events = sim_state.recent_events[:30]
|
src/app/simulation/utils.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
def euclidean_dist(lat1, lon1, lat2, lon2):
|
| 4 |
+
# Rough approximation for small distances, returning meters
|
| 5 |
+
# 1 deg lat = 111,320m
|
| 6 |
+
lat_mid = (lat1 + lat2) / 2.0
|
| 7 |
+
dx = (lon2 - lon1) * 111320.0 * math.cos(math.radians(lat_mid))
|
| 8 |
+
dy = (lat2 - lat1) * 111320.0
|
| 9 |
+
return math.sqrt(dx*dx + dy*dy)
|
src/app/train_main.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import logging
|
| 3 |
+
from fastapi import FastAPI
|
| 4 |
+
from app.rl.train import start_training_loop
|
| 5 |
+
|
| 6 |
+
logging.basicConfig(level=logging.INFO)
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
app = FastAPI(title="RL Training Headless Space")
|
| 10 |
+
|
| 11 |
+
@app.on_event("startup")
|
| 12 |
+
async def startup_event():
|
| 13 |
+
logger.info("Starting headless RL training server...")
|
| 14 |
+
# Launch the RL training loop as a background task
|
| 15 |
+
asyncio.create_task(start_training_loop())
|
| 16 |
+
|
| 17 |
+
@app.get("/health")
|
| 18 |
+
def health_check():
|
| 19 |
+
"""
|
| 20 |
+
Minimal health check endpoint required by Hugging Face Spaces on port 7860.
|
| 21 |
+
"""
|
| 22 |
+
return {"status": "ok", "message": "Training is running"}
|
| 23 |
+
|
| 24 |
+
if __name__ == "__main__":
|
| 25 |
+
import uvicorn
|
| 26 |
+
# Hugging Face spaces expect port 7860
|
| 27 |
+
uvicorn.run("app.train_main:app", host="0.0.0.0", port=7860)
|