TripAI / modules /ai_link_flow_emulator.py
mahbubchula's picture
Upload 8 files
a58f1b7 verified
# modules/ai_link_flow_emulator.py
# TripAI – AI Emulator for Link Flows
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, Tuple
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from .route_assignment import aon_assignment, Network
@dataclass
class LinkFlowEmulator:
"""
AI emulator for link flows under scaled OD demand.
Attributes
----------
model : RandomForestRegressor
Multi-output regressor mapping demand scale -> link flows.
link_ids : np.ndarray
IDs of links in the same order as training outputs.
base_total_demand : float
Total baseline car OD (for reference).
"""
model: RandomForestRegressor
link_ids: np.ndarray
base_total_demand: float
def _generate_training_scenarios(
base_od: np.ndarray,
network: Network,
n_scenarios: int = 20,
low_scale: float = 0.7,
high_scale: float = 1.3,
) -> Tuple[np.ndarray, np.ndarray]:
"""
Generate training scenarios by scaling baseline OD and performing
AON assignment to obtain link flows.
Parameters
----------
base_od : np.ndarray
Baseline OD matrix (veh/h).
network : Network
n_scenarios : int
Number of random scaling scenarios.
low_scale : float
Minimum demand scale factor.
high_scale : float
Maximum demand scale factor.
Returns
-------
X : np.ndarray
Feature matrix of shape (n_scenarios, 1) – the demand scale.
Y : np.ndarray
Target matrix of shape (n_scenarios, n_links) – link flows.
"""
n_zones = base_od.shape[0]
n_links = len(network.links)
scales = np.random.uniform(low_scale, high_scale, size=n_scenarios)
X = scales.reshape(-1, 1)
Y = np.zeros((n_scenarios, n_links), dtype=float)
# Build index -> (from_zone, to_zone) map to reuse AON logic
# We will call the existing aon_assignment with scaled OD each time.
# Convert base OD to DataFrame with synthetic zone index 0..n-1.
zones = np.arange(n_zones)
base_od_df = pd.DataFrame(base_od, index=zones, columns=zones)
for idx, s in enumerate(scales):
od_scaled = base_od_df * s
flows_df = aon_assignment(od_scaled, network)
Y[idx, :] = flows_df["flow_vehph"].to_numpy()
return X, Y
def train_link_flow_emulator(
base_car_od: np.ndarray,
network: Network,
n_scenarios: int = 20,
) -> tuple[LinkFlowEmulator, pd.DataFrame]:
"""
Train a simple RandomForest-based emulator that maps a single
scalar 'demand scale' to resulting link flows.
Parameters
----------
base_car_od : np.ndarray
Baseline car OD matrix (veh/h).
network : Network
n_scenarios : int
Number of training scenarios.
Returns
-------
emulator : LinkFlowEmulator
training_history : pd.DataFrame
Scenario scales and corresponding total flows, for diagnostics.
"""
X, Y = _generate_training_scenarios(base_car_od, network, n_scenarios=n_scenarios)
model = RandomForestRegressor(
n_estimators=200,
max_depth=12,
random_state=42,
)
model.fit(X, Y)
link_ids = network.links.index.to_numpy()
base_total = float(base_car_od.sum())
# Build training history for inspection
total_flows = Y.sum(axis=1)
training_history = pd.DataFrame(
{
"scale": X.flatten(),
"total_link_flow": total_flows,
}
)
emulator = LinkFlowEmulator(
model=model,
link_ids=link_ids,
base_total_demand=base_total,
)
return emulator, training_history
def predict_link_flows(
emulator: LinkFlowEmulator,
scale: float,
network: Network,
) -> pd.DataFrame:
"""
Predict link flows for a new demand scale using the trained emulator.
Parameters
----------
emulator : LinkFlowEmulator
scale : float
Multiplicative scaling factor relative to baseline OD.
network : Network
Returns
-------
pd.DataFrame
Link table with predicted flows in column 'flow_vehph_emulated'.
"""
X_new = np.array([[scale]], dtype=float)
y_pred = emulator.model.predict(X_new).flatten()
links = network.links.copy()
links["flow_vehph_emulated"] = y_pred
return links