mamut_routing_tools.campaign.descriptors¶
Descriptive features of a CVRP instance.
Everywhere else in this project "metric" means an arc-cost metric variant (euclidean / shortest / fastest). This module is the other kind: the statistics that say what an instance is like -- how clustered its customers are, how uneven its demands are, how far off-centre its depot sits, how many customers a vehicle can carry. They exist so a campaign can choose instances that span the feature space instead of hoping a parameter grid happens to.
Two blocks, split by what they cost:
:class:InstanceDescriptors needs only coordinates, demands and capacity, so it
can be computed for a large candidate pool before committing to any shortest
path computation. That is what makes over-generate-then-select affordable.
:class:MetricDivergence needs the three materialized arc-cost matrices and is
therefore computed after generation. It measures how far an instance departs
from the Euclidean assumption -- the independent variable of the metric study.
Degenerate inputs (collinear customers, a constant demand vector) leave some features undefined; those fields come back as NaN rather than a guessed value, and the selector drops non-finite candidates.
InstanceDescriptors
dataclass
¶
InstanceDescriptors(
n: int,
extent_m: float,
clark_evans_r: float,
nnd_cv: float,
radial_dispersion: float,
depot_centrality: float,
depot_eccentricity: float,
demand_cv: float,
demand_gini: float,
demand_max_over_mean: float,
demand_moran_i: float,
lb_cap: int,
route_size: float,
capacity_slack: float,
)
What an instance looks like, from coordinates and demands alone.
MetricDivergence
dataclass
¶
MetricDivergence(
detour_mean: float,
detour_p90: float,
asymmetry_shortest: float,
asymmetry_fastest: float,
rank_tau_eucl_short: float,
rank_tau_eucl_fast: float,
rank_tau_short_fast: float,
)
How far an instance's road metrics depart from its Euclidean one.
selection_features
¶
Where this instance sits in feature space, for the selector.
Not the same thing as the descriptors themselves. A statistic can be formally undefined while the instance still has a perfectly definite position on that axis, and conflating the two silently deletes instances:
demand_moran_i is undefined when every demand is equal, because there is
no variance to correlate. But "are big demands next to big demands?" has an
unambiguous answer for a constant field -- no, there is no such pattern --
and 0 is exactly where such an instance belongs on the autocorrelation axis.
Leaving it NaN made every unit-demand instance unusable, which quietly
removed one of the seven demand distributions from the whole benchmark.
Only that case is imputed. A NaN from a degenerate geometry
(clark_evans_r on collinear customers) is a genuinely unusable instance
and stays NaN, so :meth:EvaluatedCandidate.is_usable still rejects it.
Two features are taken in logs, because they are ratios spanning an order of magnitude and the selector z-scores whatever it is handed. On a linear scale the step from 50 to 200 customers per route counts eleven times the step from 3 to 16, so a max-min spread objective buys most of its score at one end of the axis and parks instances there. In v2 that put 39 of 100 instances in the longest route-size band against a quota floor of 7 -- the selector was not ignoring the axis, it was being paid to sit on its edge. A log scale prices the two ends the same way a reader does, in ratios.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/campaign/descriptors.py
descriptor_values
¶
The selection descriptors in their own units, imputation applied.
Split from :func:selection_features because the two answer different
questions and confusing them misreports the collection. The selector wants
the transformed space; a reader wants customers per route to be a number
of customers. Reporting the transformed value under the descriptor's own
name published a route size of "1.097" for a set whose shortest routes hold
three customers.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/campaign/descriptors.py
compute_descriptors
¶
compute_descriptors(
coordinates: Sequence[Sequence[float]],
demands: Sequence[int],
capacity: int,
) -> InstanceDescriptors
Descriptors of one instance. Index 0 is the depot, in both sequences.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/campaign/descriptors.py
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
compute_divergence
¶
compute_divergence(
euclidean: Sequence[Sequence[float]],
shortest: Sequence[Sequence[float]],
fastest: Sequence[Sequence[float]],
*,
seed: int = 0,
) -> MetricDivergence
How much the road metrics of one instance disagree with its Euclidean one.
seed fixes the pair subsample used for the rank correlations, so the
reported values are reproducible for a given instance.