Files
boc/iom/intelligence/simulation_engine.py
T

385 lines
13 KiB
Python
Raw Normal View History

"""
Simulation Engine (Digital Twin 2.0)
Simulates "what if" scenarios
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class SimulationResult:
"""Result of a simulation"""
scenario_name: str
baseline: Dict
simulated: Dict
changes: Dict
impacted_indexes: List[Dict]
def to_dict(self) -> Dict:
return {
"scenario": self.scenario_name,
"baseline": self.baseline,
"simulated": self.simulated,
"changes": self.changes,
"impacted_indexes": self.impacted_indexes
}
class SimulationEngine:
"""Simulates urban scenarios"""
def __init__(self):
self.impact_models = self._load_impact_models()
def _load_impact_models(self) -> Dict:
"""Load models for how changes affect indexes"""
return {
# Add bike lanes
"add_bike_lanes": {
"description": "Add dedicated bicycle lanes",
"changes": {
"bicycle_friendliness": +40,
"walkability": +10,
"traffic_intensity": -5,
"noise_level": -3
},
"cost_estimate_usd": 150000,
"implementation_months": 6
},
# Remove parking
"remove_parking": {
"description": "Remove on-street parking",
"changes": {
"walkability": +15,
"pedestrian_flow": +20,
"retail_density": +10,
"traffic_intensity": -10
},
"cost_estimate_usd": 50000,
"implementation_months": 3
},
# Plant trees
"plant_40_trees": {
"description": "Plant 40 new trees",
"changes": {
"tree_coverage": +40,
"heat_stress": -15,
"shade_index": +25,
"walkability": +10,
"property_value": +5
},
"cost_estimate_usd": 80000,
"implementation_months": 12
},
# Improve lighting
"improve_lighting": {
"description": "Upgrade street lighting to LED",
"changes": {
"lighting": +30,
"safety_index": +15,
"night_activity": +20,
"energy_efficiency": +25
},
"cost_estimate_usd": 120000,
"implementation_months": 4
},
# Add playground
"add_playground": {
"description": "Add children's playground",
"changes": {
"family_presence": +35,
"greenery": +10,
"community_activity": +20,
"property_value": +8
},
"cost_estimate_usd": 200000,
"implementation_months": 8
},
# Mixed-use development
"mixed_use_development": {
"description": "Convert ground floor to mixed-use",
"changes": {
"retail_density": +30,
"public_life": +25,
"pedestrian_flow": +20,
"functional_density": +35,
"property_value": +15
},
"cost_estimate_usd": 500000,
"implementation_months": 18
}
}
def simulate(
self,
current_state: Dict[str, float],
scenario_name: str
) -> SimulationResult:
"""
Simulate a scenario
Args:
current_state: Current signal values
scenario_name: Name of scenario to simulate
Returns:
Simulation result
"""
model = self.impact_models.get(scenario_name)
if not model:
return SimulationResult(
scenario_name=scenario_name,
baseline=current_state,
simulated=current_state,
changes={},
impacted_indexes=[]
)
# Apply changes
simulated_state = current_state.copy()
changes = {}
for signal, delta in model["changes"].items():
if signal in simulated_state:
old_value = simulated_state[signal]
new_value = min(100, max(0, old_value + delta))
simulated_state[signal] = new_value
changes[signal] = {
"old": old_value,
"new": new_value,
"delta": round(new_value - old_value, 1)
}
# Calculate impacted indexes
impacted = self._calculate_impacted_indexes(current_state, simulated_state)
return SimulationResult(
scenario_name=scenario_name,
baseline=current_state,
simulated=simulated_state,
changes=changes,
impacted_indexes=impacted
)
def compare_scenarios(
self,
current_state: Dict[str, float],
scenarios: List[str]
) -> Dict:
"""Compare multiple scenarios"""
results = []
for scenario_name in scenarios:
result = self.simulate(current_state, scenario_name)
model = self.impact_models.get(scenario_name, {})
# Calculate overall improvement
total_improvement = sum(
c["delta"] for c in result.changes.values()
if c["delta"] > 0
)
results.append({
"scenario": scenario_name,
"description": model.get("description", ""),
"total_improvement": round(total_improvement, 1),
"cost_usd": model.get("cost_estimate_usd", 0),
"implementation_months": model.get("implementation_months", 0),
"changes": result.changes,
"impacted_indexes": result.impacted_indexes
})
# Sort by improvement
results.sort(key=lambda x: x["total_improvement"], reverse=True)
return {
"baseline": current_state,
"scenarios": results,
"best_scenario": results[0]["scenario"] if results else None
}
def optimize(
self,
current_state: Dict[str, float],
target_index: str,
budget_usd: float
) -> Dict:
"""
Find optimal combination of interventions within budget
Example:
"Maximize family-friendly within $300k budget"
"""
# Simple greedy optimization
affordable = []
for scenario_name, model in self.impact_models.items():
if model.get("cost_estimate_usd", float('inf')) <= budget_usd:
result = self.simulate(current_state, scenario_name)
# Calculate target improvement
target_improvement = 0
for idx in result.impacted_indexes:
if idx["index_name"] == target_index:
target_improvement = idx["improvement"]
break
affordable.append({
"scenario": scenario_name,
"cost": model["cost_estimate_usd"],
"target_improvement": target_improvement,
"roi": target_improvement / max(model["cost_estimate_usd"], 1) * 100000
})
# Sort by ROI
affordable.sort(key=lambda x: x["roi"], reverse=True)
# Select best combination within budget
selected = []
remaining_budget = budget_usd
for option in affordable:
if option["cost"] <= remaining_budget:
selected.append(option)
remaining_budget -= option["cost"]
total_cost = sum(s["cost"] for s in selected)
total_improvement = sum(s["target_improvement"] for s in selected)
return {
"target_index": target_index,
"budget_usd": budget_usd,
"selected_scenarios": [s["scenario"] for s in selected],
"total_cost": total_cost,
"total_improvement": round(total_improvement, 1),
"remaining_budget": remaining_budget,
"efficiency": round(total_improvement / max(total_cost, 1) * 100000, 2)
}
def _calculate_impacted_indexes(
self,
baseline: Dict,
simulated: Dict
) -> List[Dict]:
"""Calculate how indexes change"""
impacted = []
# Family Friendly Index
family_signals = ["family_presence", "safety_index", "greenery", "playground", "noise_level"]
family_baseline = sum(baseline.get(s, 50) for s in family_signals) / len(family_signals)
family_simulated = sum(simulated.get(s, 50) for s in family_signals) / len(family_signals)
if abs(family_simulated - family_baseline) > 1:
impacted.append({
"index_name": "family_friendly",
"baseline": round(family_baseline, 1),
"simulated": round(family_simulated, 1),
"improvement": round(family_simulated - family_baseline, 1)
})
# Walkability Index
walk_signals = ["sidewalk_width", "traffic_intensity", "shade_index", "bicycle_friendliness"]
walk_baseline = sum(baseline.get(s, 50) for s in walk_signals) / len(walk_signals)
walk_simulated = sum(simulated.get(s, 50) for s in walk_signals) / len(walk_signals)
if abs(walk_simulated - walk_baseline) > 1:
impacted.append({
"index_name": "walkability",
"baseline": round(walk_baseline, 1),
"simulated": round(walk_simulated, 1),
"improvement": round(walk_simulated - walk_baseline, 1)
})
# Property Value Index
property_signals = ["walkability", "transit_access", "greenery", "safety_index", "retail_density"]
prop_baseline = sum(baseline.get(s, 50) for s in property_signals) / len(property_signals)
prop_simulated = sum(simulated.get(s, 50) for s in property_signals) / len(property_signals)
if abs(prop_simulated - prop_baseline) > 1:
impacted.append({
"index_name": "property_value",
"baseline": round(prop_baseline, 1),
"simulated": round(prop_simulated, 1),
"improvement": round(prop_simulated - prop_baseline, 1)
})
return impacted
# Example usage
def example_simulation():
"""Example: Simulate scenarios"""
engine = SimulationEngine()
# Current state
current_state = {
"walkability": 60,
"bicycle_friendliness": 30,
"traffic_intensity": 70,
"noise_level": 75,
"tree_coverage": 20,
"heat_stress": 80,
"shade_index": 25,
"lighting": 40,
"safety_index": 45,
"night_activity": 35,
"family_presence": 30,
"playground": 10,
"greenery": 25,
"retail_density": 50,
"pedestrian_flow": 55,
"property_value": 60,
"functional_density": 45
}
# Simulate single scenario
print("=== Simulate: Plant 40 Trees ===")
result = engine.simulate(current_state, "plant_40_trees")
print(f"Changes:")
for signal, change in result.changes.items():
print(f" {signal}: {change['old']}{change['new']} ({change['delta']:+.1f})")
print(f"\nImpacted Indexes:")
for idx in result.impacted_indexes:
print(f" {idx['index_name']}: {idx['baseline']}{idx['simulated']} ({idx['improvement']:+.1f})")
# Compare scenarios
print("\n=== Compare Scenarios ===")
comparison = engine.compare_scenarios(current_state, [
"plant_40_trees",
"add_bike_lanes",
"improve_lighting",
"add_playground"
])
for scenario in comparison["scenarios"]:
print(f"\n{scenario['scenario']}:")
print(f" Improvement: {scenario['total_improvement']}")
print(f" Cost: ${scenario['cost_usd']:,}")
print(f" Time: {scenario['implementation_months']} months")
print(f"\nBest scenario: {comparison['best_scenario']}")
# Optimize
print("\n=== Optimize: Family Friendly ($300k budget) ===")
optimization = engine.optimize(current_state, "family_friendly", 300000)
print(f"Selected: {optimization['selected_scenarios']}")
print(f"Total cost: ${optimization['total_cost']:,}")
print(f"Total improvement: {optimization['total_improvement']}")
print(f"Efficiency: {optimization['efficiency']}")
return result
if __name__ == '__main__':
example_simulation()