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.
Files changed (201) hide show
  1. solverpilot/__init__.py +250 -0
  2. solverpilot/_immutability.py +27 -0
  3. solverpilot/_version.py +3 -0
  4. solverpilot/applications/__init__.py +3 -0
  5. solverpilot/applications/routing/__init__.py +19 -0
  6. solverpilot/applications/routing/compile_milp.py +321 -0
  7. solverpilot/applications/routing/diagnostics.py +78 -0
  8. solverpilot/applications/routing/errors.py +14 -0
  9. solverpilot/applications/routing/heuristics.py +167 -0
  10. solverpilot/applications/routing/model.py +259 -0
  11. solverpilot/applications/routing/reference.py +81 -0
  12. solverpilot/applications/routing/solution.py +89 -0
  13. solverpilot/applications/routing/timeline.py +79 -0
  14. solverpilot/applications/routing/validation.py +169 -0
  15. solverpilot/applications/tsp/__init__.py +41 -0
  16. solverpilot/applications/tsp/compile_cp.py +121 -0
  17. solverpilot/applications/tsp/compile_milp.py +139 -0
  18. solverpilot/applications/tsp/distance.py +60 -0
  19. solverpilot/applications/tsp/errors.py +17 -0
  20. solverpilot/applications/tsp/heuristics.py +112 -0
  21. solverpilot/applications/tsp/model.py +175 -0
  22. solverpilot/applications/tsp/reference.py +121 -0
  23. solverpilot/applications/tsp/solution.py +65 -0
  24. solverpilot/applications/tsp/validation.py +92 -0
  25. solverpilot/backends/__init__.py +51 -0
  26. solverpilot/backends/_casadi_highs_worker.py +92 -0
  27. solverpilot/backends/base.py +28 -0
  28. solverpilot/backends/bundled_capi.py +910 -0
  29. solverpilot/backends/casadi_conic.py +303 -0
  30. solverpilot/backends/health.py +240 -0
  31. solverpilot/backends/highspy_native.py +217 -0
  32. solverpilot/backends/nlopt_native.py +237 -0
  33. solverpilot/backends/osqp_native.py +297 -0
  34. solverpilot/backends/protocol_v2.py +119 -0
  35. solverpilot/backends/pyscipopt_native.py +200 -0
  36. solverpilot/backends/registry.py +38 -0
  37. solverpilot/backends/scipy_highs.py +138 -0
  38. solverpilot/backends/scipy_highs_lp.py +195 -0
  39. solverpilot/backends/scipy_slsqp_qp.py +133 -0
  40. solverpilot/backends/scipy_vendored_highs_dev.py +363 -0
  41. solverpilot/benchmark/__init__.py +23 -0
  42. solverpilot/benchmark/acquire.py +199 -0
  43. solverpilot/benchmark/baselines.py +70 -0
  44. solverpilot/benchmark/bundle.py +37 -0
  45. solverpilot/benchmark/campaign.py +150 -0
  46. solverpilot/benchmark/environment.py +115 -0
  47. solverpilot/benchmark/integrity.py +46 -0
  48. solverpilot/benchmark/model.py +146 -0
  49. solverpilot/benchmark/policy.py +182 -0
  50. solverpilot/benchmark/registry.py +38 -0
  51. solverpilot/benchmark/report.py +44 -0
  52. solverpilot/benchmark/runner.py +516 -0
  53. solverpilot/benchmark/slurm.py +55 -0
  54. solverpilot/benchmark/specs.py +75 -0
  55. solverpilot/benchmark/splits.py +181 -0
  56. solverpilot/benchmark/summary.py +384 -0
  57. solverpilot/benchmark/verify.py +142 -0
  58. solverpilot/benchmark/worker.py +71 -0
  59. solverpilot/bridges/__init__.py +19 -0
  60. solverpilot/bridges/engine.py +44 -0
  61. solverpilot/bridges/indicator.py +232 -0
  62. solverpilot/bridges/registry.py +78 -0
  63. solverpilot/bridges/transformation-tape.schema.json +53 -0
  64. solverpilot/bridges/types.py +149 -0
  65. solverpilot/bridges/validation.py +110 -0
  66. solverpilot/capabilities/__init__.py +32 -0
  67. solverpilot/capabilities/backend-capability-v2.schema.json +21 -0
  68. solverpilot/capabilities/conformance.py +151 -0
  69. solverpilot/capabilities/enums.py +27 -0
  70. solverpilot/capabilities/manifest.py +19 -0
  71. solverpilot/capabilities/ontology.py +73 -0
  72. solverpilot/capabilities/requirements.py +37 -0
  73. solverpilot/capabilities/resolve.py +30 -0
  74. solverpilot/capabilities/v2.py +711 -0
  75. solverpilot/cli/__init__.py +1 -0
  76. solverpilot/cli/backend_health.py +77 -0
  77. solverpilot/cli/benchmark.py +331 -0
  78. solverpilot/cli/benchmark_worker.py +33 -0
  79. solverpilot/cli/capabilities.py +65 -0
  80. solverpilot/conic/__init__.py +11 -0
  81. solverpilot/conic/backend.py +263 -0
  82. solverpilot/conic/compiler.py +310 -0
  83. solverpilot/conic/conformance.py +60 -0
  84. solverpilot/conic/conic-ir-v1.schema.json +24 -0
  85. solverpilot/conic/ir.py +299 -0
  86. solverpilot/conic/validation.py +149 -0
  87. solverpilot/cp/__init__.py +7 -0
  88. solverpilot/cp/conformance.py +32 -0
  89. solverpilot/cp/cp-ir-v1.schema.json +13 -0
  90. solverpilot/cp/ir.py +170 -0
  91. solverpilot/cp/model.py +143 -0
  92. solverpilot/cp/ortools_backend.py +423 -0
  93. solverpilot/cp/ortools_worker.py +75 -0
  94. solverpilot/cp/reference.py +51 -0
  95. solverpilot/cp/serialization.py +117 -0
  96. solverpilot/cp/validate.py +83 -0
  97. solverpilot/diagnose/__init__.py +33 -0
  98. solverpilot/diagnose/infeasibility.py +463 -0
  99. solverpilot/diagnose/model.py +87 -0
  100. solverpilot/evaluation/__init__.py +53 -0
  101. solverpilot/evaluation/metrics.py +150 -0
  102. solverpilot/evaluation/oracle.py +288 -0
  103. solverpilot/evaluation/proof.py +53 -0
  104. solverpilot/evaluation/references.py +73 -0
  105. solverpilot/evaluation/regret.py +281 -0
  106. solverpilot/exceptions.py +34 -0
  107. solverpilot/experimental/__init__.py +32 -0
  108. solverpilot/extensions/__init__.py +56 -0
  109. solverpilot/extensions/entrypoints.py +34 -0
  110. solverpilot/extensions/errors.py +35 -0
  111. solverpilot/extensions/manager.py +651 -0
  112. solverpilot/extensions/manifest.py +232 -0
  113. solverpilot/extensions/model.py +101 -0
  114. solverpilot/extensions/versioning.py +136 -0
  115. solverpilot/history/__init__.py +22 -0
  116. solverpilot/history/errors.py +15 -0
  117. solverpilot/history/model.py +129 -0
  118. solverpilot/history/sqlite.py +651 -0
  119. solverpilot/inspect/__init__.py +3 -0
  120. solverpilot/inspect/fingerprint.py +199 -0
  121. solverpilot/intelligence/__init__.py +36 -0
  122. solverpilot/intelligence/errors.py +16 -0
  123. solverpilot/intelligence/fingerprint.py +108 -0
  124. solverpilot/intelligence/leakage.py +69 -0
  125. solverpilot/intelligence/schema.py +314 -0
  126. solverpilot/intelligence/shift.py +208 -0
  127. solverpilot/io/__init__.py +41 -0
  128. solverpilot/io/binary64.py +49 -0
  129. solverpilot/io/csv.py +230 -0
  130. solverpilot/io/errors.py +25 -0
  131. solverpilot/io/hashing.py +111 -0
  132. solverpilot/io/json.py +90 -0
  133. solverpilot/io/mapping.py +195 -0
  134. solverpilot/io/model.py +407 -0
  135. solverpilot/io/numeric.py +108 -0
  136. solverpilot/io/provenance.py +114 -0
  137. solverpilot/io/result.py +45 -0
  138. solverpilot/io/source.py +104 -0
  139. solverpilot/io/strict_json.py +133 -0
  140. solverpilot/minlp/__init__.py +6 -0
  141. solverpilot/minlp/compiler.py +66 -0
  142. solverpilot/minlp/conformance.py +34 -0
  143. solverpilot/minlp/convexity.py +85 -0
  144. solverpilot/minlp/ir.py +35 -0
  145. solverpilot/minlp/minlp-ir-v1.schema.json +1 -0
  146. solverpilot/minlp/orchestrator.py +220 -0
  147. solverpilot/minlp/validation.py +28 -0
  148. solverpilot/model/__init__.py +27 -0
  149. solverpilot/model/compiler.py +1263 -0
  150. solverpilot/model/errors.py +22 -0
  151. solverpilot/model/expression.py +373 -0
  152. solverpilot/model/model.py +716 -0
  153. solverpilot/model/sets.py +60 -0
  154. solverpilot/model/types.py +36 -0
  155. solverpilot/nlp/__init__.py +7 -0
  156. solverpilot/nlp/ad.py +166 -0
  157. solverpilot/nlp/backend.py +113 -0
  158. solverpilot/nlp/compiler.py +116 -0
  159. solverpilot/nlp/conformance.py +58 -0
  160. solverpilot/nlp/ir.py +70 -0
  161. solverpilot/nlp/nlp-ir-v1.schema.json +1 -0
  162. solverpilot/nlp/validation.py +60 -0
  163. solverpilot/plan/__init__.py +33 -0
  164. solverpilot/plan/model.py +79 -0
  165. solverpilot/plan/planner.py +201 -0
  166. solverpilot/plan/production.py +627 -0
  167. solverpilot/plan/selective_lp.py +65 -0
  168. solverpilot/problem/__init__.py +16 -0
  169. solverpilot/problem/enums.py +20 -0
  170. solverpilot/problem/hashing.py +88 -0
  171. solverpilot/problem/linear.py +196 -0
  172. solverpilot/problem/mps.py +447 -0
  173. solverpilot/problem/quadratic.py +192 -0
  174. solverpilot/reporting/__init__.py +27 -0
  175. solverpilot/reporting/explain.py +327 -0
  176. solverpilot/reporting/model.py +264 -0
  177. solverpilot/reporting/render.py +55 -0
  178. solverpilot/runtime/__init__.py +19 -0
  179. solverpilot/runtime/auto.py +193 -0
  180. solverpilot/runtime/budgeting.py +43 -0
  181. solverpilot/runtime/executor.py +126 -0
  182. solverpilot/runtime/portfolio.py +197 -0
  183. solverpilot/runtime/result.py +72 -0
  184. solverpilot/session/__init__.py +29 -0
  185. solverpilot/session/conformance_v2.py +239 -0
  186. solverpilot/session/mutations.py +115 -0
  187. solverpilot/session/persistent-session-trace.schema.json +45 -0
  188. solverpilot/session/persistent.py +491 -0
  189. solverpilot/session/reuse.py +81 -0
  190. solverpilot/session/session.py +236 -0
  191. solverpilot/trace/__init__.py +3 -0
  192. solverpilot/trace/schema.py +47 -0
  193. solverpilot/validate/__init__.py +10 -0
  194. solverpilot/validate/core.py +179 -0
  195. solverpilot/validate/result.py +55 -0
  196. solverpilot-0.1.dist-info/METADATA +554 -0
  197. solverpilot-0.1.dist-info/RECORD +201 -0
  198. solverpilot-0.1.dist-info/WHEEL +5 -0
  199. solverpilot-0.1.dist-info/entry_points.txt +4 -0
  200. solverpilot-0.1.dist-info/licenses/LICENSE +202 -0
  201. 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
+ ]