"""Instance feature extraction for meta-model input.""" from __future__ import annotations import math from typing import Any from solvbench.models import InstanceFeatures, ProblemInstance def extract_features(problem_type: str, data: dict[str, Any], size: str) -> InstanceFeatures: extractors = { "knapsack": _knapsack_features, "tsp": _tsp_features, "vrp": _vrp_features, "job_shop": _job_shop_features, "bin_packing": _bin_packing_features, "facility_location": _facility_location_features, "set_cover": _set_cover_features, "assignment": _assignment_features, "max_independent_set": _mis_features, } fn = extractors.get(problem_type, _default_features) return fn(data, size) def _scale(size: str) -> float: return {"small": 0.6, "medium": 1.0, "large": 1.5}.get(size, 1.0) def _knapsack_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = len(data["weights"]) capacity = data["capacity"] total_w = sum(data["weights"]) return InstanceFeatures( n_variables=n, n_constraints=1, density=round(n / max(capacity, 1), 4), symmetry=0.15, pct_integer=1.0, graph_sparsity=0.0, constraint_tightness=round(total_w / max(capacity * 2, 1), 4), ) def _tsp_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = data["n_cities"] return InstanceFeatures( n_variables=n * n, n_constraints=2 * n, density=round(1.0 / n, 4), symmetry=0.95, pct_integer=1.0, graph_sparsity=0.0, constraint_tightness=0.5, ) def _vrp_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = data["n_customers"] vehicles = data["n_vehicles"] return InstanceFeatures( n_variables=n * vehicles + n, n_constraints=n + vehicles, density=round(vehicles / max(n, 1), 4), symmetry=0.4, pct_integer=0.85, graph_sparsity=0.3, constraint_tightness=round(data.get("demand_ratio", 0.7), 4), ) def _job_shop_features(data: dict[str, Any], size: str) -> InstanceFeatures: n_jobs = data["n_jobs"] n_machines = data["n_machines"] n_ops = n_jobs * n_machines return InstanceFeatures( n_variables=n_ops * 2, n_constraints=n_ops + n_jobs, density=round(n_machines / max(n_ops, 1), 4), symmetry=0.25, pct_integer=0.9, graph_sparsity=0.6, constraint_tightness=0.65, ) def _bin_packing_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = len(data["item_sizes"]) cap = data["bin_capacity"] return InstanceFeatures( n_variables=n * 2, n_constraints=n, density=round(sum(data["item_sizes"]) / (n * cap), 4), symmetry=0.1, pct_integer=1.0, graph_sparsity=0.0, constraint_tightness=round(sum(data["item_sizes"]) / (n * cap * 1.2), 4), ) def _facility_location_features(data: dict[str, Any], size: str) -> InstanceFeatures: nf = data["n_facilities"] nc = data["n_customers"] return InstanceFeatures( n_variables=nf + nf * nc, n_constraints=nc + 1, density=round(nf / max(nc, 1), 4), symmetry=0.2, pct_integer=0.5, graph_sparsity=0.4, constraint_tightness=0.55, ) def _set_cover_features(data: dict[str, Any], size: str) -> InstanceFeatures: ne = data["n_elements"] ns = data["n_sets"] avg_cov = sum(len(s) for s in data["sets"]) / max(ns, 1) return InstanceFeatures( n_variables=ns, n_constraints=ne, density=round(avg_cov / max(ne, 1), 4), symmetry=0.05, pct_integer=1.0, graph_sparsity=round(1 - avg_cov / ne, 4), constraint_tightness=round(ne / (ns * avg_cov), 4), ) def _assignment_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = data["n_agents"] return InstanceFeatures( n_variables=n * n, n_constraints=2 * n, density=round(1.0 / n, 4), symmetry=0.8, pct_integer=1.0, graph_sparsity=0.0, constraint_tightness=0.5, ) def _mis_features(data: dict[str, Any], size: str) -> InstanceFeatures: n = data["n_vertices"] m = len(data["edges"]) max_edges = n * (n - 1) / 2 return InstanceFeatures( n_variables=n, n_constraints=m, density=round(m / max(max_edges, 1), 4), symmetry=0.3, pct_integer=1.0, graph_sparsity=round(1 - m / max(max_edges, 1), 4), constraint_tightness=round(m / max(n * 3, 1), 4), ) def _default_features(data: dict[str, Any], size: str) -> InstanceFeatures: return InstanceFeatures( n_variables=int(50 * _scale(size)), n_constraints=int(30 * _scale(size)), density=0.3, symmetry=0.3, pct_integer=0.8, graph_sparsity=0.5, constraint_tightness=0.5, ) def features_from_instance(instance: ProblemInstance) -> InstanceFeatures: return instance.features