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 @@
|
|
|
1
|
+
"""Command-line utilities for auditing an SolverPilot installation."""
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import asdict
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version as package_version
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import scipy
|
|
13
|
+
|
|
14
|
+
from solverpilot import __version__, builtin_backend_candidates
|
|
15
|
+
from solverpilot.backends import probe_backends
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _jsonable(value):
|
|
19
|
+
if isinstance(value, Enum):
|
|
20
|
+
return value.value
|
|
21
|
+
if isinstance(value, dict):
|
|
22
|
+
return {str(k): _jsonable(v) for k, v in value.items()}
|
|
23
|
+
if isinstance(value, (list, tuple)):
|
|
24
|
+
return [_jsonable(v) for v in value]
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_report() -> dict[str, object]:
|
|
29
|
+
reports = probe_backends(builtin_backend_candidates())
|
|
30
|
+
try:
|
|
31
|
+
dist_version = package_version("solverpilot")
|
|
32
|
+
except PackageNotFoundError:
|
|
33
|
+
dist_version = None
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"schema_version": "1.0",
|
|
37
|
+
"solverpilot_version": __version__,
|
|
38
|
+
"distribution_version": dist_version,
|
|
39
|
+
"python_version": platform.python_version(),
|
|
40
|
+
"platform": platform.platform(),
|
|
41
|
+
"numpy_version": np.__version__,
|
|
42
|
+
"scipy_version": scipy.__version__,
|
|
43
|
+
"backends": [_jsonable(asdict(report)) for report in reports],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main(argv: list[str] | None = None) -> int:
|
|
48
|
+
parser = argparse.ArgumentParser(
|
|
49
|
+
prog="solverpilot-backend-health",
|
|
50
|
+
description="Actively smoke-test every built-in SolverPilot backend candidate.",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--compact",
|
|
54
|
+
action="store_true",
|
|
55
|
+
help="emit compact JSON instead of indented JSON",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--require-all",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="return non-zero unless every built-in candidate is healthy",
|
|
61
|
+
)
|
|
62
|
+
args = parser.parse_args(argv)
|
|
63
|
+
|
|
64
|
+
report = build_report()
|
|
65
|
+
print(json.dumps(report, indent=None if args.compact else 2, sort_keys=True))
|
|
66
|
+
|
|
67
|
+
statuses = [entry["status"] for entry in report["backends"]] # type: ignore[index]
|
|
68
|
+
unhealthy = any(status == "unhealthy" for status in statuses)
|
|
69
|
+
if unhealthy:
|
|
70
|
+
return 2
|
|
71
|
+
if args.require_all and any(status != "healthy" for status in statuses):
|
|
72
|
+
return 3
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
sys.exit(main())
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from solverpilot.backends import probe_backends
|
|
10
|
+
from solverpilot.runtime.auto import builtin_backend_candidates
|
|
11
|
+
from solverpilot.benchmark import (
|
|
12
|
+
BUILTIN_DATASETS,
|
|
13
|
+
RunSpec,
|
|
14
|
+
acquire_dataset,
|
|
15
|
+
benchmark_registry,
|
|
16
|
+
capture_environment,
|
|
17
|
+
create_result_bundle,
|
|
18
|
+
get_builtin_dataset,
|
|
19
|
+
load_dataset_spec,
|
|
20
|
+
run_benchmark,
|
|
21
|
+
save_dataset_spec,
|
|
22
|
+
summarize_jsonl,
|
|
23
|
+
write_slurm_array,
|
|
24
|
+
verify_acquired_dataset,
|
|
25
|
+
evaluate_policy_jsonl,
|
|
26
|
+
validate_split_file,
|
|
27
|
+
generate_group_split, save_split_records,
|
|
28
|
+
load_campaign_spec, miplib2017_campaign_template, run_campaign, save_campaign_spec, validate_campaign,
|
|
29
|
+
scientific_report_payload, write_scientific_report,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print(payload) -> None:
|
|
34
|
+
print(json.dumps(payload, indent=2, sort_keys=True, default=str))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cmd_doctor(args) -> int:
|
|
38
|
+
candidates = builtin_backend_candidates()
|
|
39
|
+
reports = probe_backends(candidates)
|
|
40
|
+
payload = {
|
|
41
|
+
"environment": capture_environment(packages=(
|
|
42
|
+
"solverpilot", "numpy", "scipy", "highspy", "osqp", "pyscipopt", "nlopt", "casadi", "benchopt"
|
|
43
|
+
)),
|
|
44
|
+
"backends": [asdict(r) for r in reports],
|
|
45
|
+
}
|
|
46
|
+
if args.output:
|
|
47
|
+
Path(args.output).write_text(json.dumps(payload, indent=2, sort_keys=True, default=str), encoding="utf-8")
|
|
48
|
+
_print(payload)
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def cmd_list_datasets(args) -> int:
|
|
53
|
+
_print({name: asdict(spec) for name, spec in BUILTIN_DATASETS.items()})
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_write_spec(args) -> int:
|
|
58
|
+
spec = get_builtin_dataset(args.dataset)
|
|
59
|
+
save_dataset_spec(spec, args.output)
|
|
60
|
+
_print({"written": str(args.output), "dataset": spec.name})
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_acquire(args) -> int:
|
|
65
|
+
spec = get_builtin_dataset(args.dataset) if args.dataset else load_dataset_spec(args.spec)
|
|
66
|
+
record = acquire_dataset(spec, args.target, extract=not args.no_extract, timeout_s=args.timeout_s, retries=args.retries, force=args.force)
|
|
67
|
+
_print(asdict(record))
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cmd_verify_dataset(args) -> int:
|
|
72
|
+
report = verify_acquired_dataset(args.target)
|
|
73
|
+
_print(report.to_dict())
|
|
74
|
+
return 0 if report.ok else 3
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_run(args) -> int:
|
|
78
|
+
backends = tuple(args.backend)
|
|
79
|
+
if args.thread_env_uncontrolled:
|
|
80
|
+
args.thread_env_limit = None
|
|
81
|
+
registry = benchmark_registry(backends)
|
|
82
|
+
spec = RunSpec(
|
|
83
|
+
dataset_dir=Path(args.dataset_dir),
|
|
84
|
+
manifest=Path(args.manifest),
|
|
85
|
+
reference=None if args.reference is None else Path(args.reference),
|
|
86
|
+
backends=backends,
|
|
87
|
+
output_jsonl=Path(args.output),
|
|
88
|
+
repetitions=args.repetitions,
|
|
89
|
+
time_limit_s=args.time_limit_s,
|
|
90
|
+
hard_timeout_s=args.hard_timeout_s,
|
|
91
|
+
objective_atol=args.objective_atol,
|
|
92
|
+
objective_rtol=args.objective_rtol,
|
|
93
|
+
shard_index=args.shard_index,
|
|
94
|
+
shard_count=args.shard_count,
|
|
95
|
+
resume=not args.no_resume,
|
|
96
|
+
seed=args.seed,
|
|
97
|
+
thread_env_limit=args.thread_env_limit,
|
|
98
|
+
solver_threads=args.solver_threads,
|
|
99
|
+
worker_python_mode=args.worker_python_mode,
|
|
100
|
+
)
|
|
101
|
+
summary = run_benchmark(spec, registry=registry)
|
|
102
|
+
summary_path = Path(args.output).with_suffix(Path(args.output).suffix + ".run.json")
|
|
103
|
+
summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True), encoding="utf-8")
|
|
104
|
+
_print(summary)
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def cmd_summarize(args) -> int:
|
|
109
|
+
payload = summarize_jsonl(
|
|
110
|
+
args.input, cutoff_s=args.cutoff_s, par_penalty=args.par_penalty, cost_field=args.cost_field,
|
|
111
|
+
bootstrap_draws=args.bootstrap_draws, bootstrap_seed=args.bootstrap_seed,
|
|
112
|
+
allow_mixed_environments=args.allow_mixed_environments,
|
|
113
|
+
)
|
|
114
|
+
if args.output:
|
|
115
|
+
Path(args.output).write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
116
|
+
_print(payload)
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cmd_evaluate_policy(args) -> int:
|
|
121
|
+
payload = evaluate_policy_jsonl(
|
|
122
|
+
args.input, args.policy, cutoff_s=args.cutoff_s, par_penalty=args.par_penalty,
|
|
123
|
+
cost_field=args.cost_field, bootstrap_draws=args.bootstrap_draws, bootstrap_seed=args.bootstrap_seed,
|
|
124
|
+
allow_mixed_environments=args.allow_mixed_environments,
|
|
125
|
+
allow_unmeasured_overhead=args.allow_unmeasured_overhead,
|
|
126
|
+
)
|
|
127
|
+
if args.output:
|
|
128
|
+
Path(args.output).write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
129
|
+
_print(payload)
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def cmd_slurm(args) -> int:
|
|
134
|
+
text = write_slurm_array(
|
|
135
|
+
args.output,
|
|
136
|
+
spec_path="",
|
|
137
|
+
dataset_dir=args.dataset_dir,
|
|
138
|
+
manifest=args.manifest,
|
|
139
|
+
reference=args.reference,
|
|
140
|
+
output_dir=args.output_dir,
|
|
141
|
+
backends=tuple(args.backend),
|
|
142
|
+
shards=args.shards,
|
|
143
|
+
repetitions=args.repetitions,
|
|
144
|
+
time_limit_s=args.time_limit_s,
|
|
145
|
+
job_name=args.job_name,
|
|
146
|
+
cpus_per_task=args.cpus_per_task,
|
|
147
|
+
memory=args.memory,
|
|
148
|
+
walltime=args.walltime,
|
|
149
|
+
thread_env_limit=args.thread_env_limit,
|
|
150
|
+
solver_threads=args.solver_threads,
|
|
151
|
+
worker_python_mode=args.worker_python_mode,
|
|
152
|
+
)
|
|
153
|
+
print(text)
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def cmd_validate_split(args) -> int:
|
|
159
|
+
expected = None
|
|
160
|
+
if args.manifest:
|
|
161
|
+
from solverpilot.evaluation import parse_test_manifest
|
|
162
|
+
expected = [Path(x).name.removesuffix(".gz").removesuffix(".mps") for x in parse_test_manifest(Path(args.manifest).read_text(encoding="utf-8"))]
|
|
163
|
+
report = validate_split_file(args.input, expected_instances=expected)
|
|
164
|
+
_print(report.to_dict())
|
|
165
|
+
return 0 if report.ok else 4
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def cmd_write_miplib_campaign(args) -> int:
|
|
169
|
+
spec = miplib2017_campaign_template(tuple(args.backend), repetitions=args.repetitions, cutoff_s=args.cutoff_s, hard_timeout_s=args.hard_timeout_s, seed=args.seed, solver_threads=args.solver_threads)
|
|
170
|
+
save_campaign_spec(spec, args.output)
|
|
171
|
+
_print({"written": str(args.output), "campaign_id": spec.campaign_id, "campaign_sha256": spec.sha256})
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
def cmd_validate_campaign(args) -> int:
|
|
175
|
+
spec=load_campaign_spec(args.campaign); report=validate_campaign(spec,dataset_target=args.dataset_target,split_path=args.split); _print(report.to_dict()); return 0 if report.ok else 5
|
|
176
|
+
|
|
177
|
+
def cmd_run_campaign(args) -> int:
|
|
178
|
+
spec=load_campaign_spec(args.campaign); payload=run_campaign(spec,dataset_target=args.dataset_target,output_jsonl=args.output,split_path=args.split,allow_unverified_dataset=args.allow_unverified_dataset); _print(payload); return 0
|
|
179
|
+
|
|
180
|
+
def cmd_generate_split(args) -> int:
|
|
181
|
+
payload=json.loads(Path(args.groups).read_text(encoding="utf-8"))
|
|
182
|
+
if not isinstance(payload,dict): raise ValueError("groups JSON must be an object mapping instance to group/null")
|
|
183
|
+
records=generate_group_split({str(k):None if v is None else str(v) for k,v in payload.items()},train_fraction=args.train_fraction,validation_fraction=args.validation_fraction,test_fraction=args.test_fraction,seed=args.seed)
|
|
184
|
+
report=save_split_records(records,args.output,seed=args.seed); _print(report.to_dict()); return 0
|
|
185
|
+
|
|
186
|
+
def cmd_scientific_report(args) -> int:
|
|
187
|
+
campaign=load_campaign_spec(args.campaign); summary=json.loads(Path(args.summary).read_text(encoding="utf-8")); campaign_run=None if args.campaign_run is None else json.loads(Path(args.campaign_run).read_text(encoding="utf-8"))
|
|
188
|
+
payload=scientific_report_payload(campaign,summary,campaign_run=campaign_run,limitations=tuple(args.limitation or ())); write_scientific_report(payload,json_path=args.output_json,markdown_path=args.output_md); _print({"json":str(args.output_json),"markdown":str(args.output_md),"deployable_scientific_evidence":payload["deployable_scientific_evidence"]}); return 0
|
|
189
|
+
|
|
190
|
+
def cmd_bundle(args) -> int:
|
|
191
|
+
payload = create_result_bundle(args.output, files=args.file, metadata={"label": args.label} if args.label else {})
|
|
192
|
+
_print(payload)
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
197
|
+
p = argparse.ArgumentParser(prog="solverpilot-bench", description="Portable, auditable optimization benchmark harness")
|
|
198
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
199
|
+
|
|
200
|
+
sp = sub.add_parser("doctor", help="capture environment and backend health")
|
|
201
|
+
sp.add_argument("--output")
|
|
202
|
+
sp.set_defaults(func=cmd_doctor)
|
|
203
|
+
|
|
204
|
+
sp = sub.add_parser("list-datasets", help="list built-in public dataset specs")
|
|
205
|
+
sp.set_defaults(func=cmd_list_datasets)
|
|
206
|
+
|
|
207
|
+
sp = sub.add_parser("write-spec", help="write a built-in dataset spec to JSON")
|
|
208
|
+
sp.add_argument("dataset", choices=sorted(BUILTIN_DATASETS))
|
|
209
|
+
sp.add_argument("--output", required=True, type=Path)
|
|
210
|
+
sp.set_defaults(func=cmd_write_spec)
|
|
211
|
+
|
|
212
|
+
sp = sub.add_parser("acquire", help="download and hash-lock a public dataset")
|
|
213
|
+
group = sp.add_mutually_exclusive_group(required=True)
|
|
214
|
+
group.add_argument("--dataset", choices=sorted(BUILTIN_DATASETS))
|
|
215
|
+
group.add_argument("--spec")
|
|
216
|
+
sp.add_argument("--target", required=True)
|
|
217
|
+
sp.add_argument("--timeout-s", type=float, default=60.0)
|
|
218
|
+
sp.add_argument("--retries", type=int, default=3)
|
|
219
|
+
sp.add_argument("--force", action="store_true")
|
|
220
|
+
sp.add_argument("--no-extract", action="store_true")
|
|
221
|
+
sp.set_defaults(func=cmd_acquire)
|
|
222
|
+
|
|
223
|
+
sp = sub.add_parser("verify-dataset", help="re-hash acquisition files and verify manifest/reference/instances")
|
|
224
|
+
sp.add_argument("--target", required=True)
|
|
225
|
+
sp.set_defaults(func=cmd_verify_dataset)
|
|
226
|
+
|
|
227
|
+
sp = sub.add_parser("run", help="run a resumable rectangular MPS benchmark matrix")
|
|
228
|
+
sp.add_argument("--dataset-dir", required=True)
|
|
229
|
+
sp.add_argument("--manifest", required=True)
|
|
230
|
+
sp.add_argument("--reference")
|
|
231
|
+
sp.add_argument("--backend", action="append", required=True)
|
|
232
|
+
sp.add_argument("--output", required=True)
|
|
233
|
+
sp.add_argument("--repetitions", type=int, default=1)
|
|
234
|
+
sp.add_argument("--time-limit-s", type=float)
|
|
235
|
+
sp.add_argument("--hard-timeout-s", type=float, help="controller-enforced process timeout; enables solver isolation")
|
|
236
|
+
sp.add_argument("--objective-atol", type=float, default=1e-6)
|
|
237
|
+
sp.add_argument("--objective-rtol", type=float, default=1e-7)
|
|
238
|
+
sp.add_argument("--shard-index", type=int, default=0)
|
|
239
|
+
sp.add_argument("--shard-count", type=int, default=1)
|
|
240
|
+
sp.add_argument("--seed", type=int, default=0)
|
|
241
|
+
sp.add_argument("--thread-env-limit", type=int, default=1, help="set common BLAS/OpenMP thread env vars in isolated workers; use 0 only via --thread-env-uncontrolled")
|
|
242
|
+
sp.add_argument("--thread-env-uncontrolled", action="store_true", help="do not set common C/BLAS thread env vars")
|
|
243
|
+
sp.add_argument("--solver-threads", type=int, help="require backend-level solver thread control; unsupported backends fail explicitly")
|
|
244
|
+
sp.add_argument("--worker-python-mode", choices=("normal", "no_site"), default="no_site", help="isolated worker startup mode; no_site disables sitecustomize and uses an explicit import path")
|
|
245
|
+
sp.add_argument("--no-resume", action="store_true")
|
|
246
|
+
sp.set_defaults(func=cmd_run)
|
|
247
|
+
|
|
248
|
+
sp = sub.add_parser("summarize", help="merge JSONL shards and compute PAR/SBS/VBS/performance profiles")
|
|
249
|
+
sp.add_argument("--input", action="append", required=True)
|
|
250
|
+
sp.add_argument("--cutoff-s", type=float, required=True)
|
|
251
|
+
sp.add_argument("--par-penalty", type=float, default=10.0)
|
|
252
|
+
sp.add_argument("--cost-field", default="wall_s", choices=["wall_s", "worker_solve_wall_s", "trace.solve_s", "trace.total_s"])
|
|
253
|
+
sp.add_argument("--bootstrap-draws", type=int, default=10000)
|
|
254
|
+
sp.add_argument("--bootstrap-seed", type=int, default=0)
|
|
255
|
+
sp.add_argument("--allow-mixed-environments", action="store_true", help="override timing comparability guard for heterogeneous environments")
|
|
256
|
+
sp.add_argument("--output")
|
|
257
|
+
sp.set_defaults(func=cmd_summarize)
|
|
258
|
+
|
|
259
|
+
sp = sub.add_parser("evaluate-policy", help="evaluate an external frozen selector policy against direct solver rows")
|
|
260
|
+
sp.add_argument("--input", action="append", required=True)
|
|
261
|
+
sp.add_argument("--policy", required=True)
|
|
262
|
+
sp.add_argument("--cutoff-s", type=float, required=True)
|
|
263
|
+
sp.add_argument("--par-penalty", type=float, default=10.0)
|
|
264
|
+
sp.add_argument("--cost-field", default="wall_s", choices=["wall_s", "worker_solve_wall_s", "trace.solve_s", "trace.total_s"])
|
|
265
|
+
sp.add_argument("--bootstrap-draws", type=int, default=10000)
|
|
266
|
+
sp.add_argument("--bootstrap-seed", type=int, default=0)
|
|
267
|
+
sp.add_argument("--allow-mixed-environments", action="store_true", help="override timing comparability guard for heterogeneous environments")
|
|
268
|
+
sp.add_argument("--allow-unmeasured-overhead", action="store_true", help="treat missing policy decision overhead as zero for non-deployable analysis; default is fail-closed")
|
|
269
|
+
sp.add_argument("--output")
|
|
270
|
+
sp.set_defaults(func=cmd_evaluate_policy)
|
|
271
|
+
|
|
272
|
+
sp = sub.add_parser("emit-slurm", help="emit a deterministic SLURM array launcher")
|
|
273
|
+
sp.add_argument("--dataset-dir", required=True)
|
|
274
|
+
sp.add_argument("--manifest", required=True)
|
|
275
|
+
sp.add_argument("--reference")
|
|
276
|
+
sp.add_argument("--backend", action="append", required=True)
|
|
277
|
+
sp.add_argument("--output-dir", required=True)
|
|
278
|
+
sp.add_argument("--output", required=True)
|
|
279
|
+
sp.add_argument("--shards", type=int, required=True)
|
|
280
|
+
sp.add_argument("--repetitions", type=int, default=1)
|
|
281
|
+
sp.add_argument("--time-limit-s", type=float)
|
|
282
|
+
sp.add_argument("--job-name", default="solverpilot-bench")
|
|
283
|
+
sp.add_argument("--cpus-per-task", type=int, default=1)
|
|
284
|
+
sp.add_argument("--memory", default="8G")
|
|
285
|
+
sp.add_argument("--walltime", default="02:00:00")
|
|
286
|
+
sp.add_argument("--thread-env-limit", type=int, default=1)
|
|
287
|
+
sp.add_argument("--solver-threads", type=int)
|
|
288
|
+
sp.add_argument("--worker-python-mode", choices=("normal", "no_site"), default="no_site")
|
|
289
|
+
sp.set_defaults(func=cmd_slurm)
|
|
290
|
+
|
|
291
|
+
sp = sub.add_parser("validate-split", help="validate train/validation/test split coverage and group leakage")
|
|
292
|
+
sp.add_argument("--input", required=True)
|
|
293
|
+
sp.add_argument("--manifest", help="optional benchmark manifest requiring exact instance coverage")
|
|
294
|
+
sp.set_defaults(func=cmd_validate_split)
|
|
295
|
+
|
|
296
|
+
sp = sub.add_parser("write-miplib-campaign", help="write the frozen MIPLIB-2017 benchmark-v2 campaign protocol")
|
|
297
|
+
sp.add_argument("--backend", action="append", required=True); sp.add_argument("--output", required=True, type=Path); sp.add_argument("--repetitions", type=int, default=1); sp.add_argument("--cutoff-s", type=float, default=3600.0); sp.add_argument("--hard-timeout-s", type=float); sp.add_argument("--seed", type=int, default=0); sp.add_argument("--solver-threads", type=int, default=1); sp.set_defaults(func=cmd_write_miplib_campaign)
|
|
298
|
+
|
|
299
|
+
sp = sub.add_parser("validate-campaign", help="validate campaign identity, split binding and dataset scientific provenance")
|
|
300
|
+
sp.add_argument("--campaign", required=True); sp.add_argument("--dataset-target"); sp.add_argument("--split"); sp.set_defaults(func=cmd_validate_campaign)
|
|
301
|
+
|
|
302
|
+
sp = sub.add_parser("run-campaign", help="run a validated benchmark campaign")
|
|
303
|
+
sp.add_argument("--campaign", required=True); sp.add_argument("--dataset-target", required=True); sp.add_argument("--output", required=True); sp.add_argument("--split"); sp.add_argument("--allow-unverified-dataset", action="store_true"); sp.set_defaults(func=cmd_run_campaign)
|
|
304
|
+
|
|
305
|
+
sp = sub.add_parser("generate-split", help="create a deterministic group-safe train/validation/test split")
|
|
306
|
+
sp.add_argument("--groups", required=True); sp.add_argument("--output", required=True); sp.add_argument("--train-fraction", type=float, default=0.60); sp.add_argument("--validation-fraction", type=float, default=0.20); sp.add_argument("--test-fraction", type=float, default=0.20); sp.add_argument("--seed", type=int, default=0); sp.set_defaults(func=cmd_generate_split)
|
|
307
|
+
|
|
308
|
+
sp = sub.add_parser("scientific-report", help="render a claim-bounded scientific benchmark report")
|
|
309
|
+
sp.add_argument("--campaign", required=True); sp.add_argument("--summary", required=True); sp.add_argument("--campaign-run"); sp.add_argument("--limitation", action="append"); sp.add_argument("--output-json", required=True); sp.add_argument("--output-md", required=True); sp.set_defaults(func=cmd_scientific_report)
|
|
310
|
+
|
|
311
|
+
sp = sub.add_parser("bundle", help="bundle result files with SHA-256 manifest")
|
|
312
|
+
sp.add_argument("--file", action="append", required=True)
|
|
313
|
+
sp.add_argument("--output", required=True)
|
|
314
|
+
sp.add_argument("--label")
|
|
315
|
+
sp.set_defaults(func=cmd_bundle)
|
|
316
|
+
return p
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def main(argv: list[str] | None = None) -> None:
|
|
320
|
+
parser = build_parser()
|
|
321
|
+
args = parser.parse_args(argv)
|
|
322
|
+
try:
|
|
323
|
+
code = args.func(args)
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
print(json.dumps({"error_type": type(exc).__name__, "error": str(exc)}, indent=2), file=sys.stderr)
|
|
326
|
+
raise SystemExit(2) from exc
|
|
327
|
+
raise SystemExit(code)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if __name__ == "__main__":
|
|
331
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from solverpilot.benchmark.worker import solve_file_worker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
p = argparse.ArgumentParser()
|
|
12
|
+
p.add_argument("--path", required=True)
|
|
13
|
+
p.add_argument("--backend", required=True)
|
|
14
|
+
p.add_argument("--portfolio", action="append", required=True)
|
|
15
|
+
p.add_argument("--time-limit-s", type=float)
|
|
16
|
+
p.add_argument("--solver-threads", type=int)
|
|
17
|
+
args = p.parse_args()
|
|
18
|
+
try:
|
|
19
|
+
payload = solve_file_worker(
|
|
20
|
+
path=args.path,
|
|
21
|
+
backend=args.backend,
|
|
22
|
+
portfolio=tuple(args.portfolio),
|
|
23
|
+
time_limit_s=args.time_limit_s,
|
|
24
|
+
solver_threads=args.solver_threads,
|
|
25
|
+
)
|
|
26
|
+
print(json.dumps(payload, sort_keys=True))
|
|
27
|
+
except Exception as exc:
|
|
28
|
+
print(json.dumps({"error_type": type(exc).__name__, "error": str(exc)}, sort_keys=True), file=sys.stderr)
|
|
29
|
+
raise SystemExit(2) from exc
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from solverpilot.capabilities import conform_backends, project_legacy_manifest
|
|
7
|
+
from solverpilot.runtime import builtin_backend_candidates
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _parser() -> argparse.ArgumentParser:
|
|
11
|
+
p = argparse.ArgumentParser(description="Inspect SolverPilot backend capability protocol v2 manifests")
|
|
12
|
+
p.add_argument("--verify", action="store_true", help="run native problem-class conformance smokes")
|
|
13
|
+
p.add_argument("--p0-schema", action="store_true", help="emit the P0 frozen capability schema projection")
|
|
14
|
+
p.add_argument("--backend", action="append", default=[], help="restrict output to one or more backend names")
|
|
15
|
+
return p
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv: list[str] | None = None) -> int:
|
|
19
|
+
args = _parser().parse_args(argv)
|
|
20
|
+
backends = list(builtin_backend_candidates())
|
|
21
|
+
if args.backend:
|
|
22
|
+
wanted = set(args.backend)
|
|
23
|
+
backends = [b for b in backends if b.manifest.name in wanted]
|
|
24
|
+
missing = sorted(wanted - {b.manifest.name for b in backends})
|
|
25
|
+
if missing:
|
|
26
|
+
raise SystemExit(f"unknown backend(s): {', '.join(missing)}")
|
|
27
|
+
|
|
28
|
+
if args.verify:
|
|
29
|
+
reports = conform_backends(backends)
|
|
30
|
+
payload = []
|
|
31
|
+
for report in reports:
|
|
32
|
+
manifest = report.manifest
|
|
33
|
+
payload.append({
|
|
34
|
+
"backend": report.backend,
|
|
35
|
+
"available": report.available,
|
|
36
|
+
"conformance_passed": report.passed,
|
|
37
|
+
"checks": [
|
|
38
|
+
{
|
|
39
|
+
"capability": c.capability.value,
|
|
40
|
+
"passed": c.passed,
|
|
41
|
+
"evidence_id": c.evidence_id,
|
|
42
|
+
"detail": c.detail,
|
|
43
|
+
}
|
|
44
|
+
for c in report.checks
|
|
45
|
+
],
|
|
46
|
+
"manifest": manifest.to_p0_schema_dict() if args.p0_schema else manifest.to_canonical_dict(),
|
|
47
|
+
"signature": manifest.signature,
|
|
48
|
+
})
|
|
49
|
+
else:
|
|
50
|
+
payload = []
|
|
51
|
+
for backend in backends:
|
|
52
|
+
import solverpilot
|
|
53
|
+
manifest = project_legacy_manifest(backend.manifest, adapter_version=solverpilot.__version__)
|
|
54
|
+
payload.append({
|
|
55
|
+
"backend": backend.manifest.name,
|
|
56
|
+
"available": backend.is_available(),
|
|
57
|
+
"manifest": manifest.to_p0_schema_dict() if args.p0_schema else manifest.to_canonical_dict(),
|
|
58
|
+
"signature": manifest.signature,
|
|
59
|
+
})
|
|
60
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__": # pragma: no cover
|
|
65
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .conformance import ConicConformanceCheck, ConicConformanceReport, conform_casadi_superscs_backend
|
|
2
|
+
from .backend import CasadiSuperSCSBackend, ConicSolveResult, solve_conic
|
|
3
|
+
from .ir import ConeAffineBlock, ConeKind, ConicProblem
|
|
4
|
+
from .validation import ConeCheck, ConicValidationReport, validate_conic_solution
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"CasadiSuperSCSBackend", "ConicSolveResult", "solve_conic",
|
|
8
|
+
"ConeAffineBlock", "ConeKind", "ConicProblem",
|
|
9
|
+
"ConeCheck", "ConicValidationReport", "validate_conic_solution",
|
|
10
|
+
"ConicConformanceCheck", "ConicConformanceReport", "conform_casadi_superscs_backend",
|
|
11
|
+
]
|