| """Optimization problem: existing stations + candidates + objective + how many to add.""" | |
| from dataclasses import dataclass | |
| from functools import cached_property | |
| import numpy as np | |
| from .objective import SeparableObjective | |
| from .placement import PlacementSet | |
| class Problem: | |
| existing: PlacementSet # fixed ψ⁰ | |
| candidates: PlacementSet # 𝒞 — choose m of these | |
| objective: SeparableObjective | |
| m: int | |
| def __post_init__(self): | |
| if self.existing.travel_times.shape[1] != self.candidates.travel_times.shape[1]: | |
| raise ValueError("existing and candidates must share the same grid (N)") | |
| if self.objective.weights.shape[0] != self.candidates.travel_times.shape[1]: | |
| raise ValueError("objective.weights length must match grid size N") | |
| if self.m < 0 or self.m > self.candidates.K: | |
| raise ValueError(f"m must be in [0, K]; got m={self.m}, K={self.candidates.K}") | |
| def base_field(self) -> np.ndarray: | |
| """t⁰ — minimum arrival time over all existing stations.""" | |
| if self.existing.K == 0: | |
| return np.full(self.candidates.N, np.inf, dtype=np.float64) | |
| return self.existing.travel_times.min(axis=0) | |
| def base_value(self) -> float: | |
| return self.objective.value(self.base_field) | |