| """End-to-end smoke run of src/optimization on real project data. |
| |
| Usage: |
| uv run python scripts/smoke_optimization.py [--cell-size 200] [--shore-step 500] [-m 2] |
| """ |
|
|
| import argparse |
| import sys |
| import time |
| from pathlib import Path |
|
|
| import numpy as np |
|
|
| ROOT = Path(__file__).resolve().parent.parent |
| sys.path.insert(0, str(ROOT)) |
|
|
| from src.data import ( |
| classify_cells_by_zone, |
| get_passage_coords, |
| load_risk_scenarios, |
| load_shoreline, |
| load_stations, |
| load_water_polygon, |
| ) |
| from src.graph import build_graph |
| from src.grid import generate_grid |
| from src.risk_distribution import IncidentDistribution |
| from src.optimization import ( |
| Problem, |
| expected_failure, |
| from_stations, |
| greedy_then_swap, |
| mean_response_time, |
| sample_shore_candidates, |
| survival_increasing_intensity, |
| weighted_coverage, |
| ) |
|
|
|
|
| def stage(label: str, fn, *args, **kwargs): |
| t = time.perf_counter() |
| out = fn(*args, **kwargs) |
| print(f" [{time.perf_counter() - t:6.2f}s] {label}") |
| return out |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--cell-size", type=int, default=200, help="Grid cell size, m") |
| ap.add_argument("--shore-step", type=float, default=500.0, help="Shore sampling step, m") |
| ap.add_argument("--candidate-speed", type=float, default=40.0, help="km/h for new candidates") |
| ap.add_argument("-m", type=int, default=2, help="Number of stations to add") |
| ap.add_argument("--scenario", type=str, default="summer", help="Risk scenario name") |
| args = ap.parse_args() |
|
|
| print(f"# Pipeline (cell={args.cell_size} m, shore_step={args.shore_step} m, m={args.m})") |
| t_total = time.perf_counter() |
|
|
| water = stage("load_water_polygon", load_water_polygon) |
| lats, lons, dlat, dlon = stage( |
| "generate_grid", generate_grid, water, cell_size_m=args.cell_size |
| ) |
| print(f" grid cells N={len(lats)}") |
|
|
| zones = stage("classify_cells_by_zone", classify_cells_by_zone, lats, lons) |
| graph = stage( |
| "build_graph", build_graph, lats, lons, dlat, dlon, |
| cell_zones=zones, passage_coords=get_passage_coords(), |
| ) |
| print(f" graph edges = {graph.nnz}") |
|
|
| stations = load_stations() |
| existing = stage("travel times for existing stations", from_stations, |
| stations, graph=graph, grid_lats=lats, grid_lons=lons) |
|
|
| candidates = stage( |
| "sample shore candidates + travel times", |
| sample_shore_candidates, |
| step_m=args.shore_step, |
| speed_kmh=args.candidate_speed, |
| graph=graph, |
| grid_lats=lats, |
| grid_lons=lons, |
| exclude_grid_indices=existing.grid_index, |
| ) |
| print(f" K candidates = {candidates.K}") |
|
|
| scenarios = load_risk_scenarios() |
| dist = stage( |
| "build risk distribution", |
| IncidentDistribution.from_scenario, |
| args.scenario, lats, lons, scenarios, |
| water_polygon=water, shoreline=load_shoreline(), |
| ) |
|
|
| objectives = { |
| "mean_time": mean_response_time(dist.weights, t_cap_min=120.0), |
| "coverage_15min": weighted_coverage(dist.weights, threshold_min=15.0), |
| "expected_failure": expected_failure( |
| dist.weights, |
| survival_increasing_intensity(median_min=10.0, max_time_min=30.0), |
| ), |
| } |
|
|
| print() |
| for name, obj in objectives.items(): |
| print(f"## {name} (φ={obj.name})") |
| problem = Problem(existing=existing, candidates=candidates, objective=obj, m=args.m) |
| F0 = problem.base_value |
| sol = greedy_then_swap(problem) |
| F1 = sol.objective_value |
| delta = F0 - F1 |
| sign = "↓" if delta > 0 else ("=" if abs(delta) < 1e-12 else "↑") |
| print(f" F(base) = {F0:+.6f}") |
| print(f" F(after opt) = {F1:+.6f} ({sign} {abs(delta):.6f})") |
| print(f" history = {[f'{v:+.4f}' for v in sol.objective_history]}") |
| print(f" selected = {sol.selected.tolist()}") |
| for idx in sol.selected: |
| la, lo = candidates.lat[idx], candidates.lon[idx] |
| print(f" {candidates.labels[idx]:<14s} lat={la:.5f} lon={lo:.5f}") |
| print(f" meta = {sol.meta}") |
| print() |
|
|
| print(f"# total {time.perf_counter() - t_total:.2f}s") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|