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,263 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import importlib.util
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .ir import ConeKind, ConicProblem
|
|
11
|
+
from .validation import ConicValidationReport, validate_conic_solution
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _casadi_version() -> str | None:
|
|
15
|
+
try:
|
|
16
|
+
return version("casadi")
|
|
17
|
+
except PackageNotFoundError:
|
|
18
|
+
try:
|
|
19
|
+
import casadi as ca
|
|
20
|
+
return getattr(ca, "__version__", None)
|
|
21
|
+
except Exception:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _superscs_available() -> bool:
|
|
26
|
+
if importlib.util.find_spec("casadi") is None:
|
|
27
|
+
return False
|
|
28
|
+
try:
|
|
29
|
+
import casadi as ca
|
|
30
|
+
return bool(ca.has_conic("superscs"))
|
|
31
|
+
except Exception:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, slots=True)
|
|
36
|
+
class ConicSolveResult:
|
|
37
|
+
backend: str
|
|
38
|
+
backend_status: str
|
|
39
|
+
x: np.ndarray | None
|
|
40
|
+
objective_reported: float | None
|
|
41
|
+
validation: ConicValidationReport
|
|
42
|
+
raw_statistics: dict[str, Any]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def validated(self) -> bool:
|
|
46
|
+
return bool(self.validation.valid)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(slots=True)
|
|
50
|
+
class CasadiSuperSCSBackend:
|
|
51
|
+
"""Verified P6 bridge to the SuperSCS plugin bundled by CasADi 3.7.2.
|
|
52
|
+
|
|
53
|
+
This adapter is intentionally labeled a bridge, not a public SuperSCS Python
|
|
54
|
+
binding. It is used because dedicated Clarabel/SCS packages are unavailable in
|
|
55
|
+
the current release environment. Verified P6 solve scope is linear-objective SOC
|
|
56
|
+
and rotated SOC (the latter via an exact linear map to SOC). PSD is represented
|
|
57
|
+
and independently validated by P6 but intentionally solve-disabled for this adapter
|
|
58
|
+
after negative objective-accuracy conformance.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
max_iter: int = 20_000
|
|
62
|
+
eps: float = 1e-8
|
|
63
|
+
verbose: bool = False
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def name(self) -> str:
|
|
67
|
+
return "casadi-superscs-conic-bridge"
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def capability_manifest_v2(self):
|
|
71
|
+
from solverpilot import __version__
|
|
72
|
+
from solverpilot.capabilities.v2 import (
|
|
73
|
+
BackendCapabilityManifestV2, CapabilityClaim, CapabilityEvidence, CapabilityKey,
|
|
74
|
+
CapabilityMode, CapabilityStatus, VerificationLevel,
|
|
75
|
+
)
|
|
76
|
+
binding_version = self.binding_version
|
|
77
|
+
backend_version = f"bundled-with-casadi-{binding_version or 'unknown'}"
|
|
78
|
+
evidence = CapabilityEvidence(
|
|
79
|
+
evidence_id="p6-casadi-superscs-conformance-v1",
|
|
80
|
+
kind="native-runtime-conformance",
|
|
81
|
+
backend_versions=(backend_version,),
|
|
82
|
+
binding_versions=(() if binding_version is None else (binding_version,)),
|
|
83
|
+
adapter_versions=(__version__,),
|
|
84
|
+
verified_on="2026-09-06",
|
|
85
|
+
notes=(
|
|
86
|
+
"CasADi SuperSCS plugin exercised on SOC/rotated-SOC models; PSD negative conformance retained",
|
|
87
|
+
"rotated SOC is exact adapter transformation to SOC",
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
def verified(mode=CapabilityMode.NATIVE, restrictions=None):
|
|
91
|
+
restrictions = {} if restrictions is None else restrictions
|
|
92
|
+
status = CapabilityStatus.SUPPORTED if not restrictions else CapabilityStatus.RESTRICTED
|
|
93
|
+
return CapabilityClaim(status=status, mode=mode, verification=VerificationLevel.VERIFIED, restrictions=restrictions, evidence=(evidence,))
|
|
94
|
+
claims = {
|
|
95
|
+
CapabilityKey.PROBLEM_CONIC: verified(CapabilityMode.EMULATED_SAFE, {"binding": "CasADi SuperSCS bridge", "continuous_only": True, "verified_objective": "linear"}),
|
|
96
|
+
CapabilityKey.PROBLEM_CONIC_QUADRATIC: CapabilityClaim(status=CapabilityStatus.UNVERIFIED, mode=CapabilityMode.UNKNOWN, verification=VerificationLevel.UNVERIFIED, notes=("P6 generic quadratic-SOCP conformance failed on feasible analytic projection cases",)),
|
|
97
|
+
CapabilityKey.CONSTRAINT_LINEAR: verified(CapabilityMode.NATIVE),
|
|
98
|
+
CapabilityKey.CONSTRAINT_SOC: verified(CapabilityMode.EMULATED_SAFE, {"transport": "PSD Schur-complement representation via casadi.soc"}),
|
|
99
|
+
CapabilityKey.CONSTRAINT_ROTATED_SOC: verified(CapabilityMode.EMULATED_SAFE, {"transport": "exact linear map to SOC then PSD representation"}),
|
|
100
|
+
CapabilityKey.CONSTRAINT_PSD: CapabilityClaim(status=CapabilityStatus.UNVERIFIED, mode=CapabilityMode.UNKNOWN, verification=VerificationLevel.UNVERIFIED, notes=("P6 PSD semantic IR is implemented, but bundled SuperSCS failed objective-accuracy conformance for generic PSD programs",)),
|
|
101
|
+
CapabilityKey.RESULT_PRIMAL: verified(CapabilityMode.NATIVE),
|
|
102
|
+
CapabilityKey.RESULT_OBJECTIVE: verified(CapabilityMode.NATIVE),
|
|
103
|
+
}
|
|
104
|
+
return BackendCapabilityManifestV2(
|
|
105
|
+
backend=self.name,
|
|
106
|
+
backend_version=backend_version,
|
|
107
|
+
binding="casadi",
|
|
108
|
+
binding_version=binding_version,
|
|
109
|
+
adapter_version=__version__,
|
|
110
|
+
claims=claims,
|
|
111
|
+
numeric_limits={"p6_tolerance": self.eps},
|
|
112
|
+
metadata={"verification_only_bridge": True, "plugin": "superscs"},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def binding_version(self) -> str | None:
|
|
117
|
+
return _casadi_version()
|
|
118
|
+
|
|
119
|
+
def is_available(self) -> bool:
|
|
120
|
+
return _superscs_available()
|
|
121
|
+
|
|
122
|
+
def _build_solver(self, problem: ConicProblem):
|
|
123
|
+
import casadi as ca
|
|
124
|
+
|
|
125
|
+
n = problem.n_variables
|
|
126
|
+
x = ca.MX.sym("x", n)
|
|
127
|
+
P = ca.DM(problem.P.toarray())
|
|
128
|
+
q = ca.DM(problem.q)
|
|
129
|
+
f = 0.5 * ca.mtimes([x.T, P, x]) + ca.dot(q, x) + float(problem.objective_offset)
|
|
130
|
+
|
|
131
|
+
g_exprs = []
|
|
132
|
+
lbg: list[float] = []
|
|
133
|
+
ubg: list[float] = []
|
|
134
|
+
if problem.n_linear_constraints:
|
|
135
|
+
Ad = ca.DM(problem.A.toarray())
|
|
136
|
+
lin = ca.mtimes(Ad, x)
|
|
137
|
+
g_exprs.append(lin)
|
|
138
|
+
lbg.extend(problem.constraint_lower.tolist())
|
|
139
|
+
ubg.extend(problem.constraint_upper.tolist())
|
|
140
|
+
|
|
141
|
+
psd_blocks = []
|
|
142
|
+
for block in problem.cones:
|
|
143
|
+
F = ca.DM(block.F.toarray())
|
|
144
|
+
g = ca.DM(block.g)
|
|
145
|
+
y = ca.mtimes(F, x) + g
|
|
146
|
+
if block.kind is ConeKind.SECOND_ORDER:
|
|
147
|
+
psd_blocks.append(ca.soc(y[1:], y[0]))
|
|
148
|
+
elif block.kind is ConeKind.ROTATED_SECOND_ORDER:
|
|
149
|
+
# 2*u*v >= ||z||^2, u,v>=0 <=>
|
|
150
|
+
# ||[sqrt(2)z, u-v]|| <= u+v.
|
|
151
|
+
u = y[0]; v = y[1]; z = y[2:]
|
|
152
|
+
soc_vec = ca.vertcat(np.sqrt(2.0) * z, u - v)
|
|
153
|
+
psd_blocks.append(ca.soc(soc_vec, u + v))
|
|
154
|
+
# Explicit nonnegativity keeps the semantic boundary obvious and
|
|
155
|
+
# protects against numerical interpretations of the transformed SOC.
|
|
156
|
+
g_exprs.append(ca.vertcat(u, v))
|
|
157
|
+
lbg.extend([0.0, 0.0]); ubg.extend([np.inf, np.inf])
|
|
158
|
+
elif block.kind is ConeKind.POSITIVE_SEMIDEFINITE:
|
|
159
|
+
rows, cols = block.output_shape
|
|
160
|
+
M = ca.reshape(y, cols, rows).T # row-major semantic flattening
|
|
161
|
+
psd_blocks.append(M)
|
|
162
|
+
else: # pragma: no cover
|
|
163
|
+
raise ValueError(block.kind)
|
|
164
|
+
|
|
165
|
+
h = None
|
|
166
|
+
if psd_blocks:
|
|
167
|
+
h = psd_blocks[0]
|
|
168
|
+
for blk in psd_blocks[1:]:
|
|
169
|
+
r1, c1 = h.shape; r2, c2 = blk.shape
|
|
170
|
+
h = ca.blockcat([[h, ca.MX.zeros(r1, c2)], [ca.MX.zeros(r2, c1), blk]])
|
|
171
|
+
|
|
172
|
+
qp: dict[str, Any] = {"x": x, "f": f}
|
|
173
|
+
if g_exprs:
|
|
174
|
+
qp["g"] = ca.vertcat(*g_exprs)
|
|
175
|
+
if h is not None:
|
|
176
|
+
qp["h"] = h
|
|
177
|
+
|
|
178
|
+
opts: dict[str, Any] = {"error_on_fail": False}
|
|
179
|
+
# SuperSCS option names follow its native interface. Keep the set minimal;
|
|
180
|
+
# unsupported verbosity options have differed across CasADi builds.
|
|
181
|
+
opts["superscs"] = {
|
|
182
|
+
"max_iters": int(self.max_iter),
|
|
183
|
+
"eps": float(self.eps),
|
|
184
|
+
"verbose": 1 if self.verbose else 0,
|
|
185
|
+
}
|
|
186
|
+
try:
|
|
187
|
+
solver = ca.qpsol("solverpilot_p6_superscs", "superscs", qp, opts)
|
|
188
|
+
except Exception:
|
|
189
|
+
# Some bundled SuperSCS builds reject the verbose key. Retry with only
|
|
190
|
+
# convergence controls rather than silently abandoning the backend.
|
|
191
|
+
opts["superscs"] = {"max_iters": int(self.max_iter), "eps": float(self.eps)}
|
|
192
|
+
solver = ca.qpsol("solverpilot_p6_superscs", "superscs", qp, opts)
|
|
193
|
+
return solver, np.asarray(lbg, dtype=float), np.asarray(ubg, dtype=float)
|
|
194
|
+
|
|
195
|
+
def solve(self, problem: ConicProblem) -> ConicSolveResult:
|
|
196
|
+
if not self.is_available():
|
|
197
|
+
raise RuntimeError("CasADi SuperSCS conic plugin is unavailable")
|
|
198
|
+
if problem.P.nnz:
|
|
199
|
+
raise RuntimeError("P6 fail-closed: quadratic conic objective conformance did not pass for the bundled CasADi SuperSCS plugin")
|
|
200
|
+
if any(block.kind is ConeKind.POSITIVE_SEMIDEFINITE for block in problem.cones):
|
|
201
|
+
raise RuntimeError("P6 fail-closed: generic PSD solve conformance did not pass for the bundled CasADi SuperSCS plugin; install a future verified PSD backend instead")
|
|
202
|
+
solver, lbg, ubg = self._build_solver(problem)
|
|
203
|
+
kwargs: dict[str, Any] = {
|
|
204
|
+
"lbx": problem.variable_lower,
|
|
205
|
+
"ubx": problem.variable_upper,
|
|
206
|
+
}
|
|
207
|
+
if lbg.size:
|
|
208
|
+
kwargs["lbg"] = lbg
|
|
209
|
+
kwargs["ubg"] = ubg
|
|
210
|
+
error = None
|
|
211
|
+
result = None
|
|
212
|
+
try:
|
|
213
|
+
result = solver(**kwargs)
|
|
214
|
+
except Exception as exc:
|
|
215
|
+
error = f"{type(exc).__name__}: {exc}"
|
|
216
|
+
try:
|
|
217
|
+
stats = dict(solver.stats())
|
|
218
|
+
except Exception:
|
|
219
|
+
stats = {}
|
|
220
|
+
x = None
|
|
221
|
+
objective = None
|
|
222
|
+
if result is not None and "x" in result:
|
|
223
|
+
arr = np.asarray(result["x"], dtype=float).reshape(-1)
|
|
224
|
+
if arr.shape == (problem.n_variables,) and np.all(np.isfinite(arr)):
|
|
225
|
+
x = arr
|
|
226
|
+
objective = problem.objective_value(arr)
|
|
227
|
+
raw = " ".join(str(stats.get(k, "")) for k in ("return_status", "unified_return_status")).lower()
|
|
228
|
+
if bool(stats.get("success")) and x is not None:
|
|
229
|
+
status = "optimal_candidate"
|
|
230
|
+
elif "infeasible" in raw:
|
|
231
|
+
status = "infeasible"
|
|
232
|
+
elif "unbounded" in raw:
|
|
233
|
+
status = "unbounded"
|
|
234
|
+
elif x is not None:
|
|
235
|
+
status = "candidate"
|
|
236
|
+
else:
|
|
237
|
+
status = "solver_error"
|
|
238
|
+
validation_tol = max(self.eps * 25.0, 5e-7)
|
|
239
|
+
validation = validate_conic_solution(problem, x, atol=validation_tol, rtol=validation_tol)
|
|
240
|
+
stats["independent_validation_tolerance"] = validation_tol
|
|
241
|
+
stats.update({
|
|
242
|
+
"casadi_version": _casadi_version(),
|
|
243
|
+
"conic_plugin": "superscs",
|
|
244
|
+
"error": error,
|
|
245
|
+
"verification_only_bridge": True,
|
|
246
|
+
"cone_families": sorted({c.kind.value for c in problem.cones}),
|
|
247
|
+
"adapter_transformations": (["rotated_second_order->second_order_exact_linear_map"] if any(c.kind is ConeKind.ROTATED_SECOND_ORDER for c in problem.cones) else []),
|
|
248
|
+
})
|
|
249
|
+
return ConicSolveResult(self.name, status, x, objective, validation, stats)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def solve_conic(problem: ConicProblem, *, backend: CasadiSuperSCSBackend | None = None) -> ConicSolveResult:
|
|
253
|
+
backend = CasadiSuperSCSBackend() if backend is None else backend
|
|
254
|
+
from solverpilot.capabilities.v2 import compatible_v2, requirements_v2_for
|
|
255
|
+
manifest = backend.capability_manifest_v2
|
|
256
|
+
ok, checks = compatible_v2(
|
|
257
|
+
manifest, requirements_v2_for(problem),
|
|
258
|
+
allow_safe_emulation=True, allow_risky_emulation=False, require_verified=True,
|
|
259
|
+
)
|
|
260
|
+
if not ok:
|
|
261
|
+
reasons = "; ".join(f"{c.key.value}: {c.reason}" for c in checks if not c.usable)
|
|
262
|
+
raise RuntimeError(f"P6 conic backend capability gate rejected solve: {reasons}")
|
|
263
|
+
return backend.solve(problem)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from time import perf_counter
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy import sparse
|
|
12
|
+
|
|
13
|
+
from solverpilot.problem import ObjectiveSense, VariableDomain
|
|
14
|
+
from solverpilot.model.errors import CompileError
|
|
15
|
+
from solverpilot.model.sets import (
|
|
16
|
+
EqualTo, GreaterThan, Interval, LessThan,
|
|
17
|
+
PositiveSemidefiniteCone, RotatedSecondOrderCone, SecondOrderCone,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .ir import ConeAffineBlock, ConeKind, ConicProblem
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_P6_CACHE_KEY = "conic-ir:p6:v1"
|
|
24
|
+
_P6_CAPABILITY_SIGNATURE = "conic-ir:p6:v1:casadi-superscs-bridge-verified-soc-rsoc-psd-unverified"
|
|
25
|
+
_P6_POLICY = "p0-rfc-012:p6-convex-conic-v1"
|
|
26
|
+
_MAX_SNAPSHOTS = 8
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(slots=True)
|
|
30
|
+
class _ConicCacheEntry:
|
|
31
|
+
semantic_hash: str
|
|
32
|
+
current: Any
|
|
33
|
+
parameter_versions: dict[str, int]
|
|
34
|
+
snapshots: OrderedDict[str, Any]
|
|
35
|
+
full_compiles: int = 1
|
|
36
|
+
snapshot_hits: int = 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _compilation_hash(model) -> str:
|
|
40
|
+
payload = {
|
|
41
|
+
"semantic_hash": model.semantic_hash,
|
|
42
|
+
"target": "conic-ir-v1",
|
|
43
|
+
"capability_signature": _P6_CAPABILITY_SIGNATURE,
|
|
44
|
+
"numerical_policy": _P6_POLICY,
|
|
45
|
+
"cone_semantics": ["soc", "rotated_soc", "psd"],
|
|
46
|
+
}
|
|
47
|
+
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _poly_to_affine(poly, n: int, *, context: str) -> tuple[dict[int, float], float]:
|
|
51
|
+
if poly.degree > 1:
|
|
52
|
+
raise CompileError(f"{context} must be affine for P6 conic lowering")
|
|
53
|
+
return dict(poly.linear), float(poly.constant)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _affine_map(evaluator, node, n: int, *, context: str) -> tuple[sparse.csr_matrix, np.ndarray, tuple[int, ...]]:
|
|
57
|
+
arr = np.asarray(evaluator.evaluate(node), dtype=object)
|
|
58
|
+
shape = tuple(arr.shape)
|
|
59
|
+
flat = arr.reshape(-1)
|
|
60
|
+
rr: list[int] = []
|
|
61
|
+
cc: list[int] = []
|
|
62
|
+
vv: list[float] = []
|
|
63
|
+
g = np.zeros(flat.size, dtype=float)
|
|
64
|
+
for r, poly in enumerate(flat):
|
|
65
|
+
coeffs, const = _poly_to_affine(poly, n, context=context)
|
|
66
|
+
g[r] = const
|
|
67
|
+
for c, value in coeffs.items():
|
|
68
|
+
if value != 0.0:
|
|
69
|
+
rr.append(r); cc.append(int(c)); vv.append(float(value))
|
|
70
|
+
F = sparse.csr_matrix((vv, (rr, cc)), shape=(flat.size, n), dtype=float)
|
|
71
|
+
return F, g, shape
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _assert_psd_affine_symmetry(evaluator, node, *, context: str) -> None:
|
|
75
|
+
arr = np.asarray(evaluator.evaluate(node), dtype=object)
|
|
76
|
+
if arr.ndim != 2 or arr.shape[0] != arr.shape[1]:
|
|
77
|
+
raise CompileError(f"{context} must be a square matrix")
|
|
78
|
+
for i in range(arr.shape[0]):
|
|
79
|
+
for j in range(i + 1, arr.shape[1]):
|
|
80
|
+
if arr[i, j] != arr[j, i]:
|
|
81
|
+
raise CompileError(
|
|
82
|
+
f"{context} is not structurally symmetric; P6 refuses to silently symmetrize PSD semantics"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _build_conic(model, *, cache_status: str, changed_parameters: tuple[str, ...]):
|
|
87
|
+
# Import P2/P4 compiler internals only at execution time to avoid module init cycles.
|
|
88
|
+
from solverpilot.model.compiler import (
|
|
89
|
+
CompiledModel, CompilationReport, _Evaluator, _constraint_block,
|
|
90
|
+
_objective_data, _rows_to_csr, _variable_layout,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
total_t0 = perf_counter()
|
|
94
|
+
if model.objective is None:
|
|
95
|
+
raise CompileError("model has no objective")
|
|
96
|
+
if model.objective.sense is not ObjectiveSense.MINIMIZE:
|
|
97
|
+
raise CompileError("P6 conic compilation supports convex minimization only")
|
|
98
|
+
if any(data.domain is not VariableDomain.CONTINUOUS for data in model._variables.values()):
|
|
99
|
+
raise CompileError("P6 conic execution currently supports continuous variables only")
|
|
100
|
+
if model.objective.expression.polynomial_degree is None or model.objective.expression.polynomial_degree > 2:
|
|
101
|
+
raise CompileError("P6 objective must be affine or convex quadratic")
|
|
102
|
+
|
|
103
|
+
analysis_t0 = perf_counter()
|
|
104
|
+
offsets, n, vl, vu, domains, source_vars = _variable_layout(model)
|
|
105
|
+
semantic_analysis_s = perf_counter() - analysis_t0
|
|
106
|
+
evaluator = _Evaluator(model, offsets)
|
|
107
|
+
|
|
108
|
+
lower_t0 = perf_counter()
|
|
109
|
+
rows: list[dict[int, float]] = []
|
|
110
|
+
cl: list[float] = []
|
|
111
|
+
cu: list[float] = []
|
|
112
|
+
cones: list[ConeAffineBlock] = []
|
|
113
|
+
source_constraints: dict[str, Any] = {}
|
|
114
|
+
cone_dependencies: set[str] = set()
|
|
115
|
+
|
|
116
|
+
for constraint in model.constraints:
|
|
117
|
+
cid = constraint.entity_id.value
|
|
118
|
+
set_ = constraint.set
|
|
119
|
+
if isinstance(set_, (LessThan, GreaterThan, EqualTo, Interval)):
|
|
120
|
+
block_rows, block_cl, block_cu = _constraint_block(evaluator, constraint)
|
|
121
|
+
start = len(rows)
|
|
122
|
+
rows.extend(block_rows); cl.extend(block_cl); cu.extend(block_cu)
|
|
123
|
+
source_constraints[cid] = {
|
|
124
|
+
"semantic_kind": "function_in_scalar_set",
|
|
125
|
+
"execution_rows": list(range(start, len(rows))),
|
|
126
|
+
"shape": list(constraint.function.shape),
|
|
127
|
+
}
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
F, g, shape = _affine_map(evaluator, constraint.function._node, n, context=f"cone constraint {cid}")
|
|
131
|
+
deps = tuple(sorted(constraint.function.parameter_dependencies))
|
|
132
|
+
cone_dependencies.update(deps)
|
|
133
|
+
if isinstance(set_, SecondOrderCone):
|
|
134
|
+
kind = ConeKind.SECOND_ORDER
|
|
135
|
+
expected = (set_.dimension,)
|
|
136
|
+
elif isinstance(set_, RotatedSecondOrderCone):
|
|
137
|
+
kind = ConeKind.ROTATED_SECOND_ORDER
|
|
138
|
+
expected = (set_.dimension,)
|
|
139
|
+
elif isinstance(set_, PositiveSemidefiniteCone):
|
|
140
|
+
_assert_psd_affine_symmetry(evaluator, constraint.function._node, context=f"PSD constraint {cid}")
|
|
141
|
+
kind = ConeKind.POSITIVE_SEMIDEFINITE
|
|
142
|
+
expected = (set_.dimension, set_.dimension)
|
|
143
|
+
else:
|
|
144
|
+
raise CompileError(f"unsupported P6 constraint set {type(set_).__name__}")
|
|
145
|
+
if shape != expected:
|
|
146
|
+
raise CompileError(f"cone shape mismatch for {cid}: expected {expected}, got {shape}")
|
|
147
|
+
index = len(cones)
|
|
148
|
+
cones.append(ConeAffineBlock(kind, F, g, expected, source_id=cid, metadata={"parameter_dependencies": deps}))
|
|
149
|
+
source_constraints[cid] = {
|
|
150
|
+
"semantic_kind": kind.value,
|
|
151
|
+
"cone_block": index,
|
|
152
|
+
"shape": list(shape),
|
|
153
|
+
"parameter_dependencies": list(deps),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if not cones:
|
|
157
|
+
raise CompileError("P6 conic compiler received a model with no cone constraints")
|
|
158
|
+
A = _rows_to_csr(rows, n)
|
|
159
|
+
# Reuse the already verified P2 quadratic convention 0.5*x'P*x + q'x + offset.
|
|
160
|
+
target = "legacy-qp-v1" if model.objective.expression.polynomial_degree == 2 else "legacy-linear-v1"
|
|
161
|
+
q, offset, P = _objective_data(evaluator, model, n, target)
|
|
162
|
+
if P is None:
|
|
163
|
+
P = sparse.csr_matrix((n, n), dtype=float)
|
|
164
|
+
try:
|
|
165
|
+
problem = ConicProblem.from_data(
|
|
166
|
+
P=P,
|
|
167
|
+
q=q,
|
|
168
|
+
A=A,
|
|
169
|
+
variable_lower=vl,
|
|
170
|
+
variable_upper=vu,
|
|
171
|
+
constraint_lower=np.asarray(cl, dtype=float),
|
|
172
|
+
constraint_upper=np.asarray(cu, dtype=float),
|
|
173
|
+
cones=cones,
|
|
174
|
+
objective_offset=offset,
|
|
175
|
+
name=model.name,
|
|
176
|
+
metadata={
|
|
177
|
+
"semantic_hash": model.semantic_hash,
|
|
178
|
+
"compiled_by": "solverpilot-p6",
|
|
179
|
+
"compiler_policy": _P6_POLICY,
|
|
180
|
+
"cone_families": sorted({c.kind.value for c in cones}),
|
|
181
|
+
},
|
|
182
|
+
verify_convexity=True,
|
|
183
|
+
)
|
|
184
|
+
except ValueError as exc:
|
|
185
|
+
raise CompileError(str(exc)) from exc
|
|
186
|
+
|
|
187
|
+
pmap: dict[str, Any] = {}
|
|
188
|
+
for pid, pdata in model._parameters.items():
|
|
189
|
+
regions: list[str] = []
|
|
190
|
+
for cid, info in source_constraints.items():
|
|
191
|
+
if pid in info.get("parameter_dependencies", []):
|
|
192
|
+
regions.append(f"cone:{cid}")
|
|
193
|
+
if model.objective is not None and pid in model.objective.expression.parameter_dependencies:
|
|
194
|
+
regions.append("objective")
|
|
195
|
+
for c in model.constraints:
|
|
196
|
+
if pid in c.function.parameter_dependencies and f"cone:{c.entity_id.value}" not in regions:
|
|
197
|
+
regions.append(f"constraint:{c.entity_id.value}")
|
|
198
|
+
pmap[pid] = {
|
|
199
|
+
"shape": list(pdata.shape),
|
|
200
|
+
"patchable": False,
|
|
201
|
+
"parameter_compile_class": "p6_full_conic_relower",
|
|
202
|
+
"plan_sensitive": True if regions else False,
|
|
203
|
+
"execution_regions": regions,
|
|
204
|
+
}
|
|
205
|
+
comp_hash = _compilation_hash(model)
|
|
206
|
+
source_map = {
|
|
207
|
+
"variables": source_vars,
|
|
208
|
+
"constraints": source_constraints,
|
|
209
|
+
"objective": {model.objective.entity_id.value: {"execution_objective": True}},
|
|
210
|
+
}
|
|
211
|
+
lowering_s = perf_counter() - lower_t0
|
|
212
|
+
report = CompilationReport(
|
|
213
|
+
target="conic-ir-v1",
|
|
214
|
+
semantic_analysis_s=semantic_analysis_s,
|
|
215
|
+
lowering_s=lowering_s,
|
|
216
|
+
total_s=perf_counter() - total_t0,
|
|
217
|
+
n_semantic_variables=len(model._variables),
|
|
218
|
+
n_execution_variables=n,
|
|
219
|
+
n_semantic_constraints=len(model._constraints),
|
|
220
|
+
n_execution_constraints=len(rows) + len(cones),
|
|
221
|
+
cache_status=cache_status,
|
|
222
|
+
changed_parameters=changed_parameters,
|
|
223
|
+
reused_execution_rows=0,
|
|
224
|
+
recompiled_execution_rows=len(rows) + len(cones),
|
|
225
|
+
objective_recompiled=True,
|
|
226
|
+
execution_mutations=(),
|
|
227
|
+
n_transformations=0,
|
|
228
|
+
)
|
|
229
|
+
return CompiledModel(
|
|
230
|
+
execution_ir=problem,
|
|
231
|
+
semantic_hash=model.semantic_hash,
|
|
232
|
+
data_hash=model.data_hash,
|
|
233
|
+
compilation_hash=comp_hash,
|
|
234
|
+
parameter_map=pmap,
|
|
235
|
+
source_map=source_map,
|
|
236
|
+
transformation_tape=(),
|
|
237
|
+
capability_signature=_P6_CAPABILITY_SIGNATURE,
|
|
238
|
+
mutation_plan={
|
|
239
|
+
"mode": "p6_full_conic_relower",
|
|
240
|
+
"incremental_parameter_patching": False,
|
|
241
|
+
"backend_session_patch": False,
|
|
242
|
+
"changed_parameters": list(changed_parameters),
|
|
243
|
+
"affected_regions": [r for pid in changed_parameters for r in pmap.get(pid, {}).get("execution_regions", [])],
|
|
244
|
+
"execution_mutations": [],
|
|
245
|
+
"reason": "P6 cone maps are rebuilt from semantic affine expressions; persistent conic mutation is outside P6 scope",
|
|
246
|
+
},
|
|
247
|
+
plan_sensitive_dependencies=tuple(sorted(cone_dependencies)),
|
|
248
|
+
precondition_certificate_hashes=(),
|
|
249
|
+
compilation_report=report,
|
|
250
|
+
reconstruction_contract={
|
|
251
|
+
"primal": "identity_by_source_map",
|
|
252
|
+
"dual": "backend_specific_not_standardized_p6",
|
|
253
|
+
"certificate": "none_p6",
|
|
254
|
+
"original_space_validation": "solverpilot.conic.validate_conic_solution + semantic validator",
|
|
255
|
+
},
|
|
256
|
+
schema_version="solverpilot.compiled-model.p6.v1",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def compile_conic_model(model, *, use_cache: bool = True):
|
|
261
|
+
entry = model._compiler_cache.get(_P6_CACHE_KEY)
|
|
262
|
+
changed: tuple[str, ...] = ()
|
|
263
|
+
if isinstance(entry, _ConicCacheEntry):
|
|
264
|
+
changed = tuple(
|
|
265
|
+
pid for pid in model._parameters
|
|
266
|
+
if model._parameter_versions.get(pid, 0) != entry.parameter_versions.get(pid, 0)
|
|
267
|
+
)
|
|
268
|
+
if not use_cache:
|
|
269
|
+
return _build_conic(model, cache_status="disabled", changed_parameters=changed)
|
|
270
|
+
if not isinstance(entry, _ConicCacheEntry) or entry.semantic_hash != model.semantic_hash:
|
|
271
|
+
compiled = _build_conic(model, cache_status="miss", changed_parameters=())
|
|
272
|
+
snapshots = OrderedDict([(compiled.data_hash, compiled)])
|
|
273
|
+
model._compiler_cache[_P6_CACHE_KEY] = _ConicCacheEntry(
|
|
274
|
+
semantic_hash=model.semantic_hash,
|
|
275
|
+
current=compiled,
|
|
276
|
+
parameter_versions=dict(model._parameter_versions),
|
|
277
|
+
snapshots=snapshots,
|
|
278
|
+
)
|
|
279
|
+
return compiled
|
|
280
|
+
if model.data_hash in entry.snapshots:
|
|
281
|
+
entry.snapshot_hits += 1
|
|
282
|
+
snap = entry.snapshots[model.data_hash]
|
|
283
|
+
hit = replace(
|
|
284
|
+
snap,
|
|
285
|
+
compilation_report=replace(
|
|
286
|
+
snap.compilation_report,
|
|
287
|
+
semantic_analysis_s=0.0,
|
|
288
|
+
lowering_s=0.0,
|
|
289
|
+
total_s=0.0,
|
|
290
|
+
cache_status="snapshot_hit",
|
|
291
|
+
changed_parameters=changed,
|
|
292
|
+
reused_execution_rows=snap.compilation_report.n_execution_constraints,
|
|
293
|
+
recompiled_execution_rows=0,
|
|
294
|
+
objective_recompiled=False,
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
entry.current = hit
|
|
298
|
+
entry.parameter_versions = dict(model._parameter_versions)
|
|
299
|
+
entry.snapshots[model.data_hash] = hit
|
|
300
|
+
entry.snapshots.move_to_end(model.data_hash)
|
|
301
|
+
return hit
|
|
302
|
+
compiled = _build_conic(model, cache_status="full_conic_relower", changed_parameters=changed)
|
|
303
|
+
entry.full_compiles += 1
|
|
304
|
+
entry.current = compiled
|
|
305
|
+
entry.parameter_versions = dict(model._parameter_versions)
|
|
306
|
+
entry.snapshots[compiled.data_hash] = compiled
|
|
307
|
+
entry.snapshots.move_to_end(compiled.data_hash)
|
|
308
|
+
while len(entry.snapshots) > _MAX_SNAPSHOTS:
|
|
309
|
+
entry.snapshots.popitem(last=False)
|
|
310
|
+
return compiled
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .backend import CasadiSuperSCSBackend
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ConicConformanceCheck:
|
|
11
|
+
name: str
|
|
12
|
+
passed: bool
|
|
13
|
+
detail: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class ConicConformanceReport:
|
|
18
|
+
backend: str
|
|
19
|
+
binding_version: str | None
|
|
20
|
+
checks: tuple[ConicConformanceCheck, ...]
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def passed(self) -> bool:
|
|
24
|
+
return all(c.passed for c in self.checks)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def conform_casadi_superscs_backend(backend: CasadiSuperSCSBackend | None = None) -> ConicConformanceReport:
|
|
28
|
+
import solverpilot as om
|
|
29
|
+
backend = CasadiSuperSCSBackend(eps=1e-9, max_iter=100000) if backend is None else backend
|
|
30
|
+
checks: list[ConicConformanceCheck] = []
|
|
31
|
+
if not backend.is_available():
|
|
32
|
+
return ConicConformanceReport(backend.name, backend.binding_version, (ConicConformanceCheck("availability", False, "plugin unavailable"),))
|
|
33
|
+
|
|
34
|
+
m=om.Model(); x=m.variable(2,lower=[3,4],upper=[3,4]); t=m.variable(lower=0,upper=10); m.soc(t,x); m.minimize(t)
|
|
35
|
+
r=backend.solve(m.compile(use_cache=False).execution_ir)
|
|
36
|
+
ok=r.validation.valid and r.objective_reported is not None and abs(r.objective_reported-5.0) <= 2e-6
|
|
37
|
+
checks.append(ConicConformanceCheck("soc-3-4-5", ok, f"objective={r.objective_reported}, valid={r.validation.valid}"))
|
|
38
|
+
|
|
39
|
+
m=om.Model(); z=m.variable(1,lower=[1],upper=[1]); u=m.variable(lower=0,upper=5); v=m.variable(lower=0,upper=5); m.rotated_soc(u,v,z); m.minimize(u+v)
|
|
40
|
+
r=backend.solve(m.compile(use_cache=False).execution_ir)
|
|
41
|
+
expected=float(np.sqrt(2.0)); ok=r.validation.valid and r.objective_reported is not None and abs(r.objective_reported-expected) <= 5e-6
|
|
42
|
+
checks.append(ConicConformanceCheck("rotated-soc-analytic", ok, f"objective={r.objective_reported}, expected={expected}, valid={r.validation.valid}"))
|
|
43
|
+
|
|
44
|
+
# Negative evidence is part of conformance: generic PSD must not be silently accepted.
|
|
45
|
+
m=om.Model(); tt=m.variable(lower=0,upper=5); m.psd(tt*m.constant(np.eye(2))+m.constant([[0.,1.],[1.,0.]])); m.minimize(tt)
|
|
46
|
+
try:
|
|
47
|
+
backend.solve(m.compile(use_cache=False).execution_ir)
|
|
48
|
+
refused=False; detail="PSD unexpectedly solved"
|
|
49
|
+
except RuntimeError as exc:
|
|
50
|
+
refused="PSD solve conformance did not pass" in str(exc); detail=str(exc)
|
|
51
|
+
checks.append(ConicConformanceCheck("psd-fail-closed", refused, detail))
|
|
52
|
+
|
|
53
|
+
m=om.Model(); xx=m.variable(3,lower=-10,upper=10); a=np.array([0.4322322767575165,-0.861140634080589,0.06823807914880933]); m.soc(2.848110356815995,xx); m.minimize(0.5*((xx-m.constant(a))**2).sum())
|
|
54
|
+
try:
|
|
55
|
+
backend.solve(m.compile(use_cache=False).execution_ir)
|
|
56
|
+
qrefused=False; qdetail="quadratic conic objective unexpectedly solved"
|
|
57
|
+
except RuntimeError as exc:
|
|
58
|
+
qrefused="quadratic conic objective conformance did not pass" in str(exc); qdetail=str(exc)
|
|
59
|
+
checks.append(ConicConformanceCheck("quadratic-conic-fail-closed", qrefused, qdetail))
|
|
60
|
+
return ConicConformanceReport(backend.name, backend.binding_version, tuple(checks))
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "solverpilot.conic-ir.v1",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["schema", "P", "q", "A", "variable_lower", "variable_upper", "constraint_lower", "constraint_upper", "cones", "objective_sense", "objective_offset", "structural_hash", "data_hash"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"schema": {"const": "solverpilot.conic-ir.v1"},
|
|
8
|
+
"P": {"type": "object"},
|
|
9
|
+
"q": {"type": "array", "items": {"type": "number"}},
|
|
10
|
+
"A": {"type": "object"},
|
|
11
|
+
"variable_lower": {"type": "array", "items": {"anyOf": [{"type": "number"}, {"enum": ["+inf", "-inf"]}]}},
|
|
12
|
+
"variable_upper": {"type": "array", "items": {"anyOf": [{"type": "number"}, {"enum": ["+inf", "-inf"]}]}},
|
|
13
|
+
"constraint_lower": {"type": "array", "items": {"anyOf": [{"type": "number"}, {"enum": ["+inf", "-inf"]}]}},
|
|
14
|
+
"constraint_upper": {"type": "array", "items": {"anyOf": [{"type": "number"}, {"enum": ["+inf", "-inf"]}]}},
|
|
15
|
+
"cones": {"type": "array", "minItems": 1, "items": {"type": "object", "required": ["kind", "F", "g", "output_shape", "structural_hash", "data_hash"], "properties": {"kind": {"enum": ["second_order", "rotated_second_order", "positive_semidefinite"]}}}},
|
|
16
|
+
"objective_sense": {"const": "minimize"},
|
|
17
|
+
"objective_offset": {"type": "number"},
|
|
18
|
+
"name": {"type": ["string", "null"]},
|
|
19
|
+
"metadata": {"type": "object"},
|
|
20
|
+
"convexity_status": {"type": "string"},
|
|
21
|
+
"structural_hash": {"type": "string"},
|
|
22
|
+
"data_hash": {"type": "string"}
|
|
23
|
+
}
|
|
24
|
+
}
|