solverpilot 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- solverpilot/__init__.py +250 -0
- solverpilot/_immutability.py +27 -0
- solverpilot/_version.py +3 -0
- solverpilot/applications/__init__.py +3 -0
- solverpilot/applications/routing/__init__.py +19 -0
- solverpilot/applications/routing/compile_milp.py +321 -0
- solverpilot/applications/routing/diagnostics.py +78 -0
- solverpilot/applications/routing/errors.py +14 -0
- solverpilot/applications/routing/heuristics.py +167 -0
- solverpilot/applications/routing/model.py +259 -0
- solverpilot/applications/routing/reference.py +81 -0
- solverpilot/applications/routing/solution.py +89 -0
- solverpilot/applications/routing/timeline.py +79 -0
- solverpilot/applications/routing/validation.py +169 -0
- solverpilot/applications/tsp/__init__.py +41 -0
- solverpilot/applications/tsp/compile_cp.py +121 -0
- solverpilot/applications/tsp/compile_milp.py +139 -0
- solverpilot/applications/tsp/distance.py +60 -0
- solverpilot/applications/tsp/errors.py +17 -0
- solverpilot/applications/tsp/heuristics.py +112 -0
- solverpilot/applications/tsp/model.py +175 -0
- solverpilot/applications/tsp/reference.py +121 -0
- solverpilot/applications/tsp/solution.py +65 -0
- solverpilot/applications/tsp/validation.py +92 -0
- solverpilot/backends/__init__.py +51 -0
- solverpilot/backends/_casadi_highs_worker.py +92 -0
- solverpilot/backends/base.py +28 -0
- solverpilot/backends/bundled_capi.py +910 -0
- solverpilot/backends/casadi_conic.py +303 -0
- solverpilot/backends/health.py +240 -0
- solverpilot/backends/highspy_native.py +217 -0
- solverpilot/backends/nlopt_native.py +237 -0
- solverpilot/backends/osqp_native.py +297 -0
- solverpilot/backends/protocol_v2.py +119 -0
- solverpilot/backends/pyscipopt_native.py +200 -0
- solverpilot/backends/registry.py +38 -0
- solverpilot/backends/scipy_highs.py +138 -0
- solverpilot/backends/scipy_highs_lp.py +195 -0
- solverpilot/backends/scipy_slsqp_qp.py +133 -0
- solverpilot/backends/scipy_vendored_highs_dev.py +363 -0
- solverpilot/benchmark/__init__.py +23 -0
- solverpilot/benchmark/acquire.py +199 -0
- solverpilot/benchmark/baselines.py +70 -0
- solverpilot/benchmark/bundle.py +37 -0
- solverpilot/benchmark/campaign.py +150 -0
- solverpilot/benchmark/environment.py +115 -0
- solverpilot/benchmark/integrity.py +46 -0
- solverpilot/benchmark/model.py +146 -0
- solverpilot/benchmark/policy.py +182 -0
- solverpilot/benchmark/registry.py +38 -0
- solverpilot/benchmark/report.py +44 -0
- solverpilot/benchmark/runner.py +516 -0
- solverpilot/benchmark/slurm.py +55 -0
- solverpilot/benchmark/specs.py +75 -0
- solverpilot/benchmark/splits.py +181 -0
- solverpilot/benchmark/summary.py +384 -0
- solverpilot/benchmark/verify.py +142 -0
- solverpilot/benchmark/worker.py +71 -0
- solverpilot/bridges/__init__.py +19 -0
- solverpilot/bridges/engine.py +44 -0
- solverpilot/bridges/indicator.py +232 -0
- solverpilot/bridges/registry.py +78 -0
- solverpilot/bridges/transformation-tape.schema.json +53 -0
- solverpilot/bridges/types.py +149 -0
- solverpilot/bridges/validation.py +110 -0
- solverpilot/capabilities/__init__.py +32 -0
- solverpilot/capabilities/backend-capability-v2.schema.json +21 -0
- solverpilot/capabilities/conformance.py +151 -0
- solverpilot/capabilities/enums.py +27 -0
- solverpilot/capabilities/manifest.py +19 -0
- solverpilot/capabilities/ontology.py +73 -0
- solverpilot/capabilities/requirements.py +37 -0
- solverpilot/capabilities/resolve.py +30 -0
- solverpilot/capabilities/v2.py +711 -0
- solverpilot/cli/__init__.py +1 -0
- solverpilot/cli/backend_health.py +77 -0
- solverpilot/cli/benchmark.py +331 -0
- solverpilot/cli/benchmark_worker.py +33 -0
- solverpilot/cli/capabilities.py +65 -0
- solverpilot/conic/__init__.py +11 -0
- solverpilot/conic/backend.py +263 -0
- solverpilot/conic/compiler.py +310 -0
- solverpilot/conic/conformance.py +60 -0
- solverpilot/conic/conic-ir-v1.schema.json +24 -0
- solverpilot/conic/ir.py +299 -0
- solverpilot/conic/validation.py +149 -0
- solverpilot/cp/__init__.py +7 -0
- solverpilot/cp/conformance.py +32 -0
- solverpilot/cp/cp-ir-v1.schema.json +13 -0
- solverpilot/cp/ir.py +170 -0
- solverpilot/cp/model.py +143 -0
- solverpilot/cp/ortools_backend.py +423 -0
- solverpilot/cp/ortools_worker.py +75 -0
- solverpilot/cp/reference.py +51 -0
- solverpilot/cp/serialization.py +117 -0
- solverpilot/cp/validate.py +83 -0
- solverpilot/diagnose/__init__.py +33 -0
- solverpilot/diagnose/infeasibility.py +463 -0
- solverpilot/diagnose/model.py +87 -0
- solverpilot/evaluation/__init__.py +53 -0
- solverpilot/evaluation/metrics.py +150 -0
- solverpilot/evaluation/oracle.py +288 -0
- solverpilot/evaluation/proof.py +53 -0
- solverpilot/evaluation/references.py +73 -0
- solverpilot/evaluation/regret.py +281 -0
- solverpilot/exceptions.py +34 -0
- solverpilot/experimental/__init__.py +32 -0
- solverpilot/extensions/__init__.py +56 -0
- solverpilot/extensions/entrypoints.py +34 -0
- solverpilot/extensions/errors.py +35 -0
- solverpilot/extensions/manager.py +651 -0
- solverpilot/extensions/manifest.py +232 -0
- solverpilot/extensions/model.py +101 -0
- solverpilot/extensions/versioning.py +136 -0
- solverpilot/history/__init__.py +22 -0
- solverpilot/history/errors.py +15 -0
- solverpilot/history/model.py +129 -0
- solverpilot/history/sqlite.py +651 -0
- solverpilot/inspect/__init__.py +3 -0
- solverpilot/inspect/fingerprint.py +199 -0
- solverpilot/intelligence/__init__.py +36 -0
- solverpilot/intelligence/errors.py +16 -0
- solverpilot/intelligence/fingerprint.py +108 -0
- solverpilot/intelligence/leakage.py +69 -0
- solverpilot/intelligence/schema.py +314 -0
- solverpilot/intelligence/shift.py +208 -0
- solverpilot/io/__init__.py +41 -0
- solverpilot/io/binary64.py +49 -0
- solverpilot/io/csv.py +230 -0
- solverpilot/io/errors.py +25 -0
- solverpilot/io/hashing.py +111 -0
- solverpilot/io/json.py +90 -0
- solverpilot/io/mapping.py +195 -0
- solverpilot/io/model.py +407 -0
- solverpilot/io/numeric.py +108 -0
- solverpilot/io/provenance.py +114 -0
- solverpilot/io/result.py +45 -0
- solverpilot/io/source.py +104 -0
- solverpilot/io/strict_json.py +133 -0
- solverpilot/minlp/__init__.py +6 -0
- solverpilot/minlp/compiler.py +66 -0
- solverpilot/minlp/conformance.py +34 -0
- solverpilot/minlp/convexity.py +85 -0
- solverpilot/minlp/ir.py +35 -0
- solverpilot/minlp/minlp-ir-v1.schema.json +1 -0
- solverpilot/minlp/orchestrator.py +220 -0
- solverpilot/minlp/validation.py +28 -0
- solverpilot/model/__init__.py +27 -0
- solverpilot/model/compiler.py +1263 -0
- solverpilot/model/errors.py +22 -0
- solverpilot/model/expression.py +373 -0
- solverpilot/model/model.py +716 -0
- solverpilot/model/sets.py +60 -0
- solverpilot/model/types.py +36 -0
- solverpilot/nlp/__init__.py +7 -0
- solverpilot/nlp/ad.py +166 -0
- solverpilot/nlp/backend.py +113 -0
- solverpilot/nlp/compiler.py +116 -0
- solverpilot/nlp/conformance.py +58 -0
- solverpilot/nlp/ir.py +70 -0
- solverpilot/nlp/nlp-ir-v1.schema.json +1 -0
- solverpilot/nlp/validation.py +60 -0
- solverpilot/plan/__init__.py +33 -0
- solverpilot/plan/model.py +79 -0
- solverpilot/plan/planner.py +201 -0
- solverpilot/plan/production.py +627 -0
- solverpilot/plan/selective_lp.py +65 -0
- solverpilot/problem/__init__.py +16 -0
- solverpilot/problem/enums.py +20 -0
- solverpilot/problem/hashing.py +88 -0
- solverpilot/problem/linear.py +196 -0
- solverpilot/problem/mps.py +447 -0
- solverpilot/problem/quadratic.py +192 -0
- solverpilot/reporting/__init__.py +27 -0
- solverpilot/reporting/explain.py +327 -0
- solverpilot/reporting/model.py +264 -0
- solverpilot/reporting/render.py +55 -0
- solverpilot/runtime/__init__.py +19 -0
- solverpilot/runtime/auto.py +193 -0
- solverpilot/runtime/budgeting.py +43 -0
- solverpilot/runtime/executor.py +126 -0
- solverpilot/runtime/portfolio.py +197 -0
- solverpilot/runtime/result.py +72 -0
- solverpilot/session/__init__.py +29 -0
- solverpilot/session/conformance_v2.py +239 -0
- solverpilot/session/mutations.py +115 -0
- solverpilot/session/persistent-session-trace.schema.json +45 -0
- solverpilot/session/persistent.py +491 -0
- solverpilot/session/reuse.py +81 -0
- solverpilot/session/session.py +236 -0
- solverpilot/trace/__init__.py +3 -0
- solverpilot/trace/schema.py +47 -0
- solverpilot/validate/__init__.py +10 -0
- solverpilot/validate/core.py +179 -0
- solverpilot/validate/result.py +55 -0
- solverpilot-0.1.dist-info/METADATA +554 -0
- solverpilot-0.1.dist-info/RECORD +201 -0
- solverpilot-0.1.dist-info/WHEEL +5 -0
- solverpilot-0.1.dist-info/entry_points.txt +4 -0
- solverpilot-0.1.dist-info/licenses/LICENSE +202 -0
- solverpilot-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from math import isclose, isfinite
|
|
6
|
+
from statistics import fmean, median, pstdev
|
|
7
|
+
from typing import Iterable, Mapping, Any
|
|
8
|
+
|
|
9
|
+
from .oracle import ObjectiveSense, OracleEntry, OracleStatus, OracleTable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RegretStatus(str, Enum):
|
|
13
|
+
COMPARED = "compared"
|
|
14
|
+
ORACLE_UNAVAILABLE = "oracle_unavailable"
|
|
15
|
+
INSTANCE_MISMATCH = "instance_mismatch"
|
|
16
|
+
SELECTED_RUN_MISSING = "selected_run_missing"
|
|
17
|
+
SELECTED_RUN_INELIGIBLE = "selected_run_ineligible"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class SelectionDecision:
|
|
22
|
+
decision_id: str
|
|
23
|
+
instance: str
|
|
24
|
+
selected_run_id: str | None
|
|
25
|
+
selector_id: str = "policy"
|
|
26
|
+
selector_version: str = "unknown"
|
|
27
|
+
selection_overhead_s: float = 0.0
|
|
28
|
+
|
|
29
|
+
def __post_init__(self) -> None:
|
|
30
|
+
for name in ("decision_id", "instance", "selector_id", "selector_version"):
|
|
31
|
+
value = str(getattr(self, name)).strip()
|
|
32
|
+
if not value:
|
|
33
|
+
raise ValueError(f"{name} must be non-empty")
|
|
34
|
+
object.__setattr__(self, name, value)
|
|
35
|
+
if self.selected_run_id is not None:
|
|
36
|
+
value = str(self.selected_run_id).strip()
|
|
37
|
+
if not value:
|
|
38
|
+
raise ValueError("selected_run_id must be non-empty when provided")
|
|
39
|
+
object.__setattr__(self, "selected_run_id", value)
|
|
40
|
+
overhead = float(self.selection_overhead_s)
|
|
41
|
+
if not isfinite(overhead) or overhead < 0:
|
|
42
|
+
raise ValueError("selection_overhead_s must be finite and non-negative")
|
|
43
|
+
object.__setattr__(self, "selection_overhead_s", overhead)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class RegretVector:
|
|
48
|
+
decision_id: str
|
|
49
|
+
selector_id: str
|
|
50
|
+
selector_version: str
|
|
51
|
+
instance: str
|
|
52
|
+
selected_run_id: str | None
|
|
53
|
+
oracle_run_ids: tuple[str, ...]
|
|
54
|
+
status: RegretStatus
|
|
55
|
+
comparable: bool
|
|
56
|
+
quality_regret: float | None
|
|
57
|
+
relative_quality_regret: float | None
|
|
58
|
+
runtime_delta_s: float | None
|
|
59
|
+
selection_overhead_s: float
|
|
60
|
+
runtime_regret_s: float | None
|
|
61
|
+
failure_regret: int
|
|
62
|
+
timeout_regret: int
|
|
63
|
+
limit_regret: int
|
|
64
|
+
infeasibility_regret: int
|
|
65
|
+
reasons: tuple[str, ...] = ()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True, slots=True)
|
|
69
|
+
class MetricSummary:
|
|
70
|
+
observed_count: int
|
|
71
|
+
missing_count: int
|
|
72
|
+
minimum: float | None
|
|
73
|
+
maximum: float | None
|
|
74
|
+
mean: float | None
|
|
75
|
+
median: float | None
|
|
76
|
+
population_stddev: float | None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class SelectorRegretSummary:
|
|
81
|
+
selector_id: str
|
|
82
|
+
selector_version: str
|
|
83
|
+
decision_count: int
|
|
84
|
+
compared_count: int
|
|
85
|
+
incomparable_count: int
|
|
86
|
+
quality_regret: MetricSummary
|
|
87
|
+
relative_quality_regret: MetricSummary
|
|
88
|
+
runtime_delta_s: MetricSummary
|
|
89
|
+
runtime_regret_s: MetricSummary
|
|
90
|
+
failure_regret_count: int
|
|
91
|
+
timeout_regret_count: int
|
|
92
|
+
limit_regret_count: int
|
|
93
|
+
infeasibility_regret_count: int
|
|
94
|
+
significance_claim_supported: bool = False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _summary(values: list[float | None]) -> MetricSummary:
|
|
98
|
+
observed = [float(v) for v in values if v is not None]
|
|
99
|
+
missing = len(values) - len(observed)
|
|
100
|
+
if not observed:
|
|
101
|
+
return MetricSummary(0, missing, None, None, None, None, None)
|
|
102
|
+
return MetricSummary(
|
|
103
|
+
observed_count=len(observed),
|
|
104
|
+
missing_count=missing,
|
|
105
|
+
minimum=min(observed),
|
|
106
|
+
maximum=max(observed),
|
|
107
|
+
mean=fmean(observed),
|
|
108
|
+
median=float(median(observed)),
|
|
109
|
+
population_stddev=pstdev(observed) if len(observed) > 1 else 0.0,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _row_runtime(row: Mapping[str, Any], field: str) -> float | None:
|
|
114
|
+
current: Any = row
|
|
115
|
+
for part in field.split("."):
|
|
116
|
+
if not isinstance(current, Mapping) or part not in current:
|
|
117
|
+
return None
|
|
118
|
+
current = current[part]
|
|
119
|
+
try:
|
|
120
|
+
value = float(current)
|
|
121
|
+
except (TypeError, ValueError):
|
|
122
|
+
return None
|
|
123
|
+
return value if isfinite(value) and value >= 0 else None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _binary_failure_dimensions(row: Mapping[str, Any]) -> tuple[int, int, int, int]:
|
|
127
|
+
state = str(row.get("state") or "")
|
|
128
|
+
public_status = str(row.get("public_status") or "")
|
|
129
|
+
timeout = int(state == "hard_timeout")
|
|
130
|
+
limit = int(public_status == "feasible_limit")
|
|
131
|
+
infeasible = int(public_status == "infeasible")
|
|
132
|
+
failure = int(
|
|
133
|
+
state in {"missing_instance", "parse_error", "solve_error", "worker_error"}
|
|
134
|
+
or public_status in {"invalid_solution", "error", "unknown", "infeasible_or_unbounded"}
|
|
135
|
+
)
|
|
136
|
+
return failure, timeout, limit, infeasible
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _directional_regret(selected: float, oracle: float, sense: ObjectiveSense, *, atol: float, rtol: float) -> float:
|
|
140
|
+
if isclose(selected, oracle, abs_tol=atol, rel_tol=rtol):
|
|
141
|
+
return 0.0
|
|
142
|
+
raw = selected - oracle if sense is ObjectiveSense.MINIMIZE else oracle - selected
|
|
143
|
+
# An eligible oracle is extremal, but clamping tiny negative numerical drift keeps the
|
|
144
|
+
# metric non-negative if values sit just outside the explicit tolerance boundary.
|
|
145
|
+
return max(0.0, float(raw))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def evaluate_selection_decisions(
|
|
149
|
+
decisions: Iterable[SelectionDecision],
|
|
150
|
+
rows: Iterable[Mapping[str, Any]],
|
|
151
|
+
oracle_table: OracleTable,
|
|
152
|
+
) -> tuple[RegretVector, ...]:
|
|
153
|
+
decision_rows = tuple(decisions)
|
|
154
|
+
benchmark_rows = tuple(rows)
|
|
155
|
+
if len({d.decision_id for d in decision_rows}) != len(decision_rows):
|
|
156
|
+
raise ValueError("decision_id values must be unique")
|
|
157
|
+
by_run: dict[str, Mapping[str, Any]] = {}
|
|
158
|
+
for row in benchmark_rows:
|
|
159
|
+
run_id = str(row.get("run_id") or "")
|
|
160
|
+
if not run_id:
|
|
161
|
+
raise ValueError("every benchmark row must provide run_id")
|
|
162
|
+
if run_id in by_run:
|
|
163
|
+
raise ValueError(f"duplicate benchmark run_id: {run_id}")
|
|
164
|
+
by_run[run_id] = row
|
|
165
|
+
entries = oracle_table.by_instance()
|
|
166
|
+
vectors: list[RegretVector] = []
|
|
167
|
+
|
|
168
|
+
def emit(decision: SelectionDecision, entry: OracleEntry | None, status: RegretStatus, reasons: tuple[str, ...], *, selected_row: Mapping[str, Any] | None = None) -> None:
|
|
169
|
+
failure = timeout = limit = infeasible = 0
|
|
170
|
+
if selected_row is not None:
|
|
171
|
+
failure, timeout, limit, infeasible = _binary_failure_dimensions(selected_row)
|
|
172
|
+
vectors.append(RegretVector(
|
|
173
|
+
decision_id=decision.decision_id,
|
|
174
|
+
selector_id=decision.selector_id,
|
|
175
|
+
selector_version=decision.selector_version,
|
|
176
|
+
instance=decision.instance,
|
|
177
|
+
selected_run_id=decision.selected_run_id,
|
|
178
|
+
oracle_run_ids=() if entry is None else entry.oracle_run_ids,
|
|
179
|
+
status=status,
|
|
180
|
+
comparable=False,
|
|
181
|
+
quality_regret=None,
|
|
182
|
+
relative_quality_regret=None,
|
|
183
|
+
runtime_delta_s=None,
|
|
184
|
+
selection_overhead_s=decision.selection_overhead_s,
|
|
185
|
+
runtime_regret_s=None,
|
|
186
|
+
failure_regret=failure,
|
|
187
|
+
timeout_regret=timeout,
|
|
188
|
+
limit_regret=limit,
|
|
189
|
+
infeasibility_regret=infeasible,
|
|
190
|
+
reasons=reasons,
|
|
191
|
+
))
|
|
192
|
+
|
|
193
|
+
for decision in sorted(decision_rows, key=lambda d: d.decision_id):
|
|
194
|
+
entry = entries.get(decision.instance)
|
|
195
|
+
if entry is None:
|
|
196
|
+
emit(decision, None, RegretStatus.INSTANCE_MISMATCH, ("decision instance is absent from oracle table",))
|
|
197
|
+
continue
|
|
198
|
+
if entry.status is not OracleStatus.AVAILABLE:
|
|
199
|
+
emit(decision, entry, RegretStatus.ORACLE_UNAVAILABLE, entry.reasons or (f"oracle status is {entry.status.value}",))
|
|
200
|
+
continue
|
|
201
|
+
if decision.selected_run_id is None:
|
|
202
|
+
emit(decision, entry, RegretStatus.SELECTED_RUN_MISSING, ("selector abstained or did not provide a run_id",))
|
|
203
|
+
continue
|
|
204
|
+
selected = by_run.get(decision.selected_run_id)
|
|
205
|
+
if selected is None:
|
|
206
|
+
emit(decision, entry, RegretStatus.SELECTED_RUN_MISSING, ("selected run is absent from benchmark rows",))
|
|
207
|
+
continue
|
|
208
|
+
if str(selected.get("instance") or "") != decision.instance:
|
|
209
|
+
emit(decision, entry, RegretStatus.INSTANCE_MISMATCH, ("selected run belongs to a different instance",), selected_row=selected)
|
|
210
|
+
continue
|
|
211
|
+
candidate_by_id = {c.run_id: c for c in entry.candidates}
|
|
212
|
+
candidate = candidate_by_id.get(decision.selected_run_id)
|
|
213
|
+
if candidate is None:
|
|
214
|
+
exclusion = next((x for x in entry.exclusions if x.run_id == decision.selected_run_id), None)
|
|
215
|
+
reason = "selected run is not oracle-eligible" if exclusion is None else f"selected run excluded: {exclusion.reason.value}: {exclusion.detail}"
|
|
216
|
+
emit(decision, entry, RegretStatus.SELECTED_RUN_INELIGIBLE, (reason,), selected_row=selected)
|
|
217
|
+
continue
|
|
218
|
+
if entry.oracle_objective is None or entry.fastest_oracle_runtime_s is None:
|
|
219
|
+
raise RuntimeError("oracle-eligible candidate is missing objective/runtime invariants")
|
|
220
|
+
quality = _directional_regret(
|
|
221
|
+
candidate.objective,
|
|
222
|
+
entry.oracle_objective,
|
|
223
|
+
entry.objective_sense,
|
|
224
|
+
atol=oracle_table.policy.objective_atol,
|
|
225
|
+
rtol=oracle_table.policy.objective_rtol,
|
|
226
|
+
)
|
|
227
|
+
if abs(entry.oracle_objective) <= oracle_table.policy.objective_atol:
|
|
228
|
+
relative = None
|
|
229
|
+
reasons = ("relative quality regret is undefined because oracle objective is zero",)
|
|
230
|
+
else:
|
|
231
|
+
relative = quality / abs(entry.oracle_objective)
|
|
232
|
+
reasons = ()
|
|
233
|
+
runtime_delta = candidate.runtime_s - entry.fastest_oracle_runtime_s
|
|
234
|
+
runtime_regret = max(0.0, candidate.runtime_s + decision.selection_overhead_s - entry.fastest_oracle_runtime_s)
|
|
235
|
+
vectors.append(RegretVector(
|
|
236
|
+
decision_id=decision.decision_id,
|
|
237
|
+
selector_id=decision.selector_id,
|
|
238
|
+
selector_version=decision.selector_version,
|
|
239
|
+
instance=decision.instance,
|
|
240
|
+
selected_run_id=decision.selected_run_id,
|
|
241
|
+
oracle_run_ids=entry.oracle_run_ids,
|
|
242
|
+
status=RegretStatus.COMPARED,
|
|
243
|
+
comparable=True,
|
|
244
|
+
quality_regret=quality,
|
|
245
|
+
relative_quality_regret=relative,
|
|
246
|
+
runtime_delta_s=runtime_delta,
|
|
247
|
+
selection_overhead_s=decision.selection_overhead_s,
|
|
248
|
+
runtime_regret_s=runtime_regret,
|
|
249
|
+
failure_regret=0,
|
|
250
|
+
timeout_regret=0,
|
|
251
|
+
limit_regret=0,
|
|
252
|
+
infeasibility_regret=0,
|
|
253
|
+
reasons=reasons,
|
|
254
|
+
))
|
|
255
|
+
return tuple(vectors)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def summarize_regret_vectors(vectors: Iterable[RegretVector]) -> tuple[SelectorRegretSummary, ...]:
|
|
259
|
+
rows = tuple(vectors)
|
|
260
|
+
grouped: dict[tuple[str, str], list[RegretVector]] = {}
|
|
261
|
+
for row in rows:
|
|
262
|
+
grouped.setdefault((row.selector_id, row.selector_version), []).append(row)
|
|
263
|
+
summaries: list[SelectorRegretSummary] = []
|
|
264
|
+
for (selector_id, selector_version), group in sorted(grouped.items()):
|
|
265
|
+
summaries.append(SelectorRegretSummary(
|
|
266
|
+
selector_id=selector_id,
|
|
267
|
+
selector_version=selector_version,
|
|
268
|
+
decision_count=len(group),
|
|
269
|
+
compared_count=sum(int(v.comparable) for v in group),
|
|
270
|
+
incomparable_count=sum(int(not v.comparable) for v in group),
|
|
271
|
+
quality_regret=_summary([v.quality_regret for v in group]),
|
|
272
|
+
relative_quality_regret=_summary([v.relative_quality_regret for v in group]),
|
|
273
|
+
runtime_delta_s=_summary([v.runtime_delta_s for v in group]),
|
|
274
|
+
runtime_regret_s=_summary([v.runtime_regret_s for v in group]),
|
|
275
|
+
failure_regret_count=sum(v.failure_regret for v in group),
|
|
276
|
+
timeout_regret_count=sum(v.timeout_regret for v in group),
|
|
277
|
+
limit_regret_count=sum(v.limit_regret for v in group),
|
|
278
|
+
infeasibility_regret_count=sum(v.infeasibility_regret for v in group),
|
|
279
|
+
significance_claim_supported=False,
|
|
280
|
+
))
|
|
281
|
+
return tuple(summaries)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Public exception hierarchy for SolverPilot.
|
|
2
|
+
|
|
3
|
+
M30 freezes these exception classes as the stable error surface for callers that
|
|
4
|
+
need to distinguish environment/setup failures from model-capability failures.
|
|
5
|
+
Solver termination itself remains represented by :class:`solverpilot.PublicStatus`
|
|
6
|
+
inside ``SolveResult`` rather than by exceptions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SolverPilotError(Exception):
|
|
13
|
+
"""Base class for stable SolverPilot runtime/planning exceptions."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BackendUnavailableError(SolverPilotError, RuntimeError):
|
|
17
|
+
"""Raised when an explicitly requested backend cannot be used in this environment."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CapabilityMismatchError(SolverPilotError, ValueError):
|
|
21
|
+
"""Raised when an explicitly chosen backend cannot represent the problem safely."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UnknownBackendError(SolverPilotError, KeyError):
|
|
25
|
+
"""Raised when a backend identifier is not registered."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BudgetNotSupportedError(SolverPilotError, NotImplementedError):
|
|
29
|
+
"""Raised when a requested hard budget cannot be enforced safely by a backend."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NoCompatibleBackendError(SolverPilotError, RuntimeError):
|
|
33
|
+
"""Raised when planning finds no available backend satisfying all hard requirements."""
|
|
34
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Experimental and research-only SolverPilot APIs.
|
|
2
|
+
|
|
3
|
+
Everything in this namespace may change or disappear without the compatibility
|
|
4
|
+
policy applied to the stable top-level ``solverpilot`` API. M30 deliberately moves
|
|
5
|
+
milestone-specific evidence translators and the rejected LP selector evaluator
|
|
6
|
+
here so research artifacts cannot be mistaken for supported product features.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from solverpilot.plan.production import (
|
|
10
|
+
M22_OFFICIAL_EVIDENCE,
|
|
11
|
+
production_evidence_from_m22_gate,
|
|
12
|
+
production_evidence_from_m24_public_ood,
|
|
13
|
+
production_evidence_from_m25_opportunity,
|
|
14
|
+
production_evidence_from_m26_validation,
|
|
15
|
+
production_evidence_from_m27_heldout,
|
|
16
|
+
production_evidence_from_m28_native_choose,
|
|
17
|
+
production_evidence_from_m29_value_audit,
|
|
18
|
+
)
|
|
19
|
+
from solverpilot.plan.selective_lp import SelectiveLPDecision, decide_selective_lp_backend
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"M22_OFFICIAL_EVIDENCE",
|
|
23
|
+
"SelectiveLPDecision",
|
|
24
|
+
"decide_selective_lp_backend",
|
|
25
|
+
"production_evidence_from_m22_gate",
|
|
26
|
+
"production_evidence_from_m24_public_ood",
|
|
27
|
+
"production_evidence_from_m25_opportunity",
|
|
28
|
+
"production_evidence_from_m26_validation",
|
|
29
|
+
"production_evidence_from_m27_heldout",
|
|
30
|
+
"production_evidence_from_m28_native_choose",
|
|
31
|
+
"production_evidence_from_m29_value_audit",
|
|
32
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Explicit extension infrastructure for trusted in-process SolverPilot integrations.
|
|
2
|
+
|
|
3
|
+
Extensions are never auto-discovered or auto-executed merely by importing this package.
|
|
4
|
+
Backends and bridges continue to use SolverPilot's canonical registries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .entrypoints import resolve_entrypoint, resolve_manifest_entrypoint
|
|
8
|
+
from .errors import (
|
|
9
|
+
ExtensionActivationError,
|
|
10
|
+
ExtensionDependencyError,
|
|
11
|
+
ExtensionError,
|
|
12
|
+
ExtensionManifestError,
|
|
13
|
+
ExtensionRegistrationError,
|
|
14
|
+
ExtensionRegistryFrozenError,
|
|
15
|
+
ExtensionResolutionError,
|
|
16
|
+
ExtensionValidationError,
|
|
17
|
+
)
|
|
18
|
+
from .manager import ExtensionManager
|
|
19
|
+
from .manifest import ExtensionDependency, ExtensionKind, ExtensionLifecycle, ExtensionManifest
|
|
20
|
+
from .model import (
|
|
21
|
+
ExportKind,
|
|
22
|
+
Extension,
|
|
23
|
+
ExtensionActivationPlan,
|
|
24
|
+
ExtensionContributions,
|
|
25
|
+
ExtensionSelfCheck,
|
|
26
|
+
NamedExtensionExport,
|
|
27
|
+
StagedExtensionContribution,
|
|
28
|
+
)
|
|
29
|
+
from .versioning import SemanticVersion, version_satisfies
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"ExportKind",
|
|
33
|
+
"Extension",
|
|
34
|
+
"ExtensionActivationError",
|
|
35
|
+
"ExtensionActivationPlan",
|
|
36
|
+
"ExtensionContributions",
|
|
37
|
+
"ExtensionDependency",
|
|
38
|
+
"ExtensionDependencyError",
|
|
39
|
+
"ExtensionError",
|
|
40
|
+
"ExtensionKind",
|
|
41
|
+
"ExtensionLifecycle",
|
|
42
|
+
"ExtensionManager",
|
|
43
|
+
"ExtensionManifest",
|
|
44
|
+
"ExtensionManifestError",
|
|
45
|
+
"ExtensionRegistrationError",
|
|
46
|
+
"ExtensionRegistryFrozenError",
|
|
47
|
+
"ExtensionResolutionError",
|
|
48
|
+
"ExtensionSelfCheck",
|
|
49
|
+
"ExtensionValidationError",
|
|
50
|
+
"NamedExtensionExport",
|
|
51
|
+
"SemanticVersion",
|
|
52
|
+
"StagedExtensionContribution",
|
|
53
|
+
"resolve_entrypoint",
|
|
54
|
+
"resolve_manifest_entrypoint",
|
|
55
|
+
"version_satisfies",
|
|
56
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .errors import ExtensionResolutionError
|
|
7
|
+
from .manifest import ExtensionManifest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resolve_entrypoint(entrypoint: str) -> Any:
|
|
11
|
+
"""Resolve ``package.module:Attribute`` only when explicitly requested by the caller."""
|
|
12
|
+
|
|
13
|
+
raw = str(entrypoint).strip()
|
|
14
|
+
if ":" not in raw:
|
|
15
|
+
raise ExtensionResolutionError("entrypoint must use the form 'package.module:Attribute'")
|
|
16
|
+
module_name, attribute_path = raw.split(":", 1)
|
|
17
|
+
try:
|
|
18
|
+
value: Any = importlib.import_module(module_name)
|
|
19
|
+
except Exception as exc:
|
|
20
|
+
raise ExtensionResolutionError(f"could not import extension module {module_name!r}: {exc}") from exc
|
|
21
|
+
for attribute in attribute_path.split("."):
|
|
22
|
+
try:
|
|
23
|
+
value = getattr(value, attribute)
|
|
24
|
+
except AttributeError as exc:
|
|
25
|
+
raise ExtensionResolutionError(
|
|
26
|
+
f"entrypoint attribute {attribute_path!r} was not found in {module_name!r}"
|
|
27
|
+
) from exc
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resolve_manifest_entrypoint(manifest: ExtensionManifest) -> Any:
|
|
32
|
+
if manifest.entrypoint is None:
|
|
33
|
+
raise ExtensionResolutionError(f"extension {manifest.extension_id!r} declares no entrypoint")
|
|
34
|
+
return resolve_entrypoint(manifest.entrypoint)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from solverpilot.exceptions import SolverPilotError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExtensionError(SolverPilotError):
|
|
7
|
+
"""Base class for SolverPilot extension-system errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExtensionManifestError(ExtensionError, ValueError):
|
|
11
|
+
"""Raised when an extension manifest is malformed or non-canonical."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExtensionRegistrationError(ExtensionError, ValueError):
|
|
15
|
+
"""Raised when extension registration would violate registry invariants."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExtensionDependencyError(ExtensionError, ValueError):
|
|
19
|
+
"""Raised when extension dependencies are missing, incompatible, or cyclic."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExtensionRegistryFrozenError(ExtensionError, RuntimeError):
|
|
23
|
+
"""Raised when a frozen extension manager is mutated."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExtensionValidationError(ExtensionError, RuntimeError):
|
|
27
|
+
"""Raised when an extension fails a self-check or contribution validation."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ExtensionActivationError(ExtensionError, RuntimeError):
|
|
31
|
+
"""Raised when a staged activation plan cannot be safely applied."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ExtensionResolutionError(ExtensionError, ImportError):
|
|
35
|
+
"""Raised when an explicitly requested extension entry point cannot be resolved."""
|