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,22 @@
|
|
|
1
|
+
class ModelingError(Exception):
|
|
2
|
+
"""Base error for the semantic modeling layer."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ShapeError(ModelingError, ValueError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DomainError(ModelingError, ValueError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OwnershipError(ModelingError, ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SymbolicTruthValueError(ModelingError, TypeError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CompileError(ModelingError, ValueError):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Iterable
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .errors import DomainError, OwnershipError, ShapeError, SymbolicTruthValueError
|
|
9
|
+
from .types import Curvature, SignDomain
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .model import Model
|
|
13
|
+
from .sets import ScalarSet
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _shape_tuple(shape: int | Iterable[int] | tuple[int, ...] | None) -> tuple[int, ...]:
|
|
17
|
+
if shape is None:
|
|
18
|
+
return ()
|
|
19
|
+
if isinstance(shape, int):
|
|
20
|
+
if shape < 0:
|
|
21
|
+
raise ShapeError("shape dimensions must be nonnegative")
|
|
22
|
+
return (shape,)
|
|
23
|
+
out = tuple(int(x) for x in shape)
|
|
24
|
+
if any(x < 0 for x in out):
|
|
25
|
+
raise ShapeError("shape dimensions must be nonnegative")
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _sign_of_constant(value: np.ndarray) -> SignDomain:
|
|
30
|
+
if value.size == 0 or np.all(value == 0):
|
|
31
|
+
return SignDomain.ZERO
|
|
32
|
+
if np.all(value >= 0):
|
|
33
|
+
return SignDomain.NONNEGATIVE
|
|
34
|
+
if np.all(value <= 0):
|
|
35
|
+
return SignDomain.NONPOSITIVE
|
|
36
|
+
return SignDomain.UNKNOWN
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _neg_sign(sign: SignDomain) -> SignDomain:
|
|
40
|
+
if sign is SignDomain.NONNEGATIVE:
|
|
41
|
+
return SignDomain.NONPOSITIVE
|
|
42
|
+
if sign is SignDomain.NONPOSITIVE:
|
|
43
|
+
return SignDomain.NONNEGATIVE
|
|
44
|
+
return sign
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _add_sign(a: SignDomain, b: SignDomain) -> SignDomain:
|
|
48
|
+
if a is SignDomain.ZERO:
|
|
49
|
+
return b
|
|
50
|
+
if b is SignDomain.ZERO:
|
|
51
|
+
return a
|
|
52
|
+
if a is b and a in {SignDomain.NONNEGATIVE, SignDomain.NONPOSITIVE}:
|
|
53
|
+
return a
|
|
54
|
+
return SignDomain.UNKNOWN
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _mul_sign(a: SignDomain, b: SignDomain) -> SignDomain:
|
|
58
|
+
if SignDomain.ZERO in {a, b}:
|
|
59
|
+
return SignDomain.ZERO
|
|
60
|
+
if a is SignDomain.UNKNOWN or b is SignDomain.UNKNOWN:
|
|
61
|
+
return SignDomain.UNKNOWN
|
|
62
|
+
if a is b:
|
|
63
|
+
return SignDomain.NONNEGATIVE
|
|
64
|
+
return SignDomain.NONPOSITIVE
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _broadcast_shape(a: tuple[int, ...], b: tuple[int, ...]) -> tuple[int, ...]:
|
|
68
|
+
try:
|
|
69
|
+
return tuple(np.broadcast_shapes(a, b))
|
|
70
|
+
except ValueError as exc:
|
|
71
|
+
raise ShapeError(f"cannot broadcast shapes {a} and {b}") from exc
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _matmul_shape(a: tuple[int, ...], b: tuple[int, ...]) -> tuple[int, ...]:
|
|
75
|
+
if len(a) == 0 or len(b) == 0:
|
|
76
|
+
raise ShapeError("matrix multiplication requires non-scalar operands")
|
|
77
|
+
if len(a) > 2 or len(b) > 2:
|
|
78
|
+
raise ShapeError("P1 matrix multiplication supports at most 2-D operands")
|
|
79
|
+
if len(a) == 1 and len(b) == 1:
|
|
80
|
+
if a[0] != b[0]:
|
|
81
|
+
raise ShapeError(f"matmul dimension mismatch: {a} @ {b}")
|
|
82
|
+
return ()
|
|
83
|
+
if len(a) == 2 and len(b) == 1:
|
|
84
|
+
if a[1] != b[0]:
|
|
85
|
+
raise ShapeError(f"matmul dimension mismatch: {a} @ {b}")
|
|
86
|
+
return (a[0],)
|
|
87
|
+
if len(a) == 1 and len(b) == 2:
|
|
88
|
+
if a[0] != b[0]:
|
|
89
|
+
raise ShapeError(f"matmul dimension mismatch: {a} @ {b}")
|
|
90
|
+
return (b[1],)
|
|
91
|
+
if a[1] != b[0]:
|
|
92
|
+
raise ShapeError(f"matmul dimension mismatch: {a} @ {b}")
|
|
93
|
+
return (a[0], b[1])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _slice_length(sl: slice, n: int) -> int:
|
|
97
|
+
start, stop, step = sl.indices(n)
|
|
98
|
+
return len(range(start, stop, step))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _index_shape(shape: tuple[int, ...], key: Any) -> tuple[int, ...]:
|
|
102
|
+
if not isinstance(key, tuple):
|
|
103
|
+
key = (key,)
|
|
104
|
+
if len(key) > len(shape):
|
|
105
|
+
raise ShapeError("too many indices for expression")
|
|
106
|
+
out: list[int] = []
|
|
107
|
+
for dim, item in zip(shape, key):
|
|
108
|
+
if isinstance(item, (int, np.integer)):
|
|
109
|
+
idx = int(item)
|
|
110
|
+
if idx < -dim or idx >= dim:
|
|
111
|
+
raise ShapeError("expression index out of range")
|
|
112
|
+
elif isinstance(item, slice):
|
|
113
|
+
out.append(_slice_length(item, dim))
|
|
114
|
+
else:
|
|
115
|
+
raise ShapeError("P1 supports integer and slice indexing only")
|
|
116
|
+
out.extend(shape[len(key):])
|
|
117
|
+
return tuple(out)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
121
|
+
class ExprNode:
|
|
122
|
+
kind: str
|
|
123
|
+
shape: tuple[int, ...]
|
|
124
|
+
args: tuple["ExprNode", ...] = ()
|
|
125
|
+
payload: Any = None
|
|
126
|
+
variable_dependencies: frozenset[str] = frozenset()
|
|
127
|
+
parameter_dependencies: frozenset[str] = frozenset()
|
|
128
|
+
sign: SignDomain = SignDomain.UNKNOWN
|
|
129
|
+
degree: int | None = None
|
|
130
|
+
curvature: Curvature = Curvature.UNKNOWN
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Expression:
|
|
134
|
+
__slots__ = ("_model", "_node")
|
|
135
|
+
__array_priority__ = 10000
|
|
136
|
+
|
|
137
|
+
def __init__(self, model: "Model", node: ExprNode) -> None:
|
|
138
|
+
self._model = model
|
|
139
|
+
self._node = node
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def shape(self) -> tuple[int, ...]:
|
|
143
|
+
return self._node.shape
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def ndim(self) -> int:
|
|
147
|
+
return len(self.shape)
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def size(self) -> int:
|
|
151
|
+
return int(np.prod(self.shape, dtype=np.int64)) if self.shape else 1
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def variable_dependencies(self) -> frozenset[str]:
|
|
155
|
+
return self._node.variable_dependencies
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def parameter_dependencies(self) -> frozenset[str]:
|
|
159
|
+
return self._node.parameter_dependencies
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def sign(self) -> SignDomain:
|
|
163
|
+
return self._node.sign
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def polynomial_degree(self) -> int | None:
|
|
167
|
+
return self._node.degree
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def curvature(self) -> Curvature:
|
|
171
|
+
return self._node.curvature
|
|
172
|
+
|
|
173
|
+
def _coerce(self, other: Any) -> "Expression":
|
|
174
|
+
if isinstance(other, Expression):
|
|
175
|
+
if other._model is not self._model:
|
|
176
|
+
raise OwnershipError("cross-model expression reference is not allowed")
|
|
177
|
+
return other
|
|
178
|
+
return self._model.constant(other)
|
|
179
|
+
|
|
180
|
+
def _binary(self, other: Any, kind: str) -> "Expression":
|
|
181
|
+
rhs = self._coerce(other)
|
|
182
|
+
if kind in {"add", "mul"}:
|
|
183
|
+
shape = _broadcast_shape(self.shape, rhs.shape)
|
|
184
|
+
elif kind == "matmul":
|
|
185
|
+
shape = _matmul_shape(self.shape, rhs.shape)
|
|
186
|
+
else:
|
|
187
|
+
raise AssertionError(kind)
|
|
188
|
+
deps_v = self.variable_dependencies | rhs.variable_dependencies
|
|
189
|
+
deps_p = self.parameter_dependencies | rhs.parameter_dependencies
|
|
190
|
+
if kind == "add":
|
|
191
|
+
degree = None if None in {self.polynomial_degree, rhs.polynomial_degree} else max(self.polynomial_degree, rhs.polynomial_degree)
|
|
192
|
+
sign = _add_sign(self.sign, rhs.sign)
|
|
193
|
+
elif kind == "mul":
|
|
194
|
+
degree = None if None in {self.polynomial_degree, rhs.polynomial_degree} else self.polynomial_degree + rhs.polynomial_degree
|
|
195
|
+
sign = _mul_sign(self.sign, rhs.sign)
|
|
196
|
+
else:
|
|
197
|
+
degree = None if None in {self.polynomial_degree, rhs.polynomial_degree} else self.polynomial_degree + rhs.polynomial_degree
|
|
198
|
+
sign = SignDomain.UNKNOWN
|
|
199
|
+
curvature = Curvature.CONSTANT if degree == 0 else Curvature.AFFINE if degree is not None and degree <= 1 else Curvature.UNKNOWN
|
|
200
|
+
args = (self._node, rhs._node)
|
|
201
|
+
if kind in {"add", "mul"}:
|
|
202
|
+
args = tuple(sorted(args, key=self._model._node_sort_key))
|
|
203
|
+
return Expression(self._model, ExprNode(kind, shape, args=args, variable_dependencies=deps_v, parameter_dependencies=deps_p, sign=sign, degree=degree, curvature=curvature))
|
|
204
|
+
|
|
205
|
+
def __add__(self, other: Any) -> "Expression":
|
|
206
|
+
return self._binary(other, "add")
|
|
207
|
+
|
|
208
|
+
def __radd__(self, other: Any) -> "Expression":
|
|
209
|
+
return self._coerce(other)._binary(self, "add")
|
|
210
|
+
|
|
211
|
+
def __sub__(self, other: Any) -> "Expression":
|
|
212
|
+
return self + (-self._coerce(other))
|
|
213
|
+
|
|
214
|
+
def __rsub__(self, other: Any) -> "Expression":
|
|
215
|
+
return self._coerce(other) + (-self)
|
|
216
|
+
|
|
217
|
+
def __neg__(self) -> "Expression":
|
|
218
|
+
degree = self.polynomial_degree
|
|
219
|
+
curvature = Curvature.CONSTANT if degree == 0 else Curvature.AFFINE if degree is not None and degree <= 1 else Curvature.UNKNOWN
|
|
220
|
+
return Expression(self._model, ExprNode("neg", self.shape, args=(self._node,), variable_dependencies=self.variable_dependencies, parameter_dependencies=self.parameter_dependencies, sign=_neg_sign(self.sign), degree=degree, curvature=curvature))
|
|
221
|
+
|
|
222
|
+
def __mul__(self, other: Any) -> "Expression":
|
|
223
|
+
return self._binary(other, "mul")
|
|
224
|
+
|
|
225
|
+
def __rmul__(self, other: Any) -> "Expression":
|
|
226
|
+
return self._coerce(other)._binary(self, "mul")
|
|
227
|
+
|
|
228
|
+
def __truediv__(self, other: Any) -> "Expression":
|
|
229
|
+
rhs = self._coerce(other)
|
|
230
|
+
if not rhs.variable_dependencies and not rhs.parameter_dependencies and rhs.polynomial_degree == 0:
|
|
231
|
+
value = self._model._constant_value(rhs._node)
|
|
232
|
+
if np.any(value == 0):
|
|
233
|
+
raise DomainError("division by zero")
|
|
234
|
+
return self * self._model.constant(1.0 / value)
|
|
235
|
+
shape = _broadcast_shape(self.shape, rhs.shape)
|
|
236
|
+
return Expression(self._model, ExprNode("div", shape, args=(self._node, rhs._node), variable_dependencies=self.variable_dependencies | rhs.variable_dependencies, parameter_dependencies=self.parameter_dependencies | rhs.parameter_dependencies, sign=SignDomain.UNKNOWN, degree=None, curvature=Curvature.UNKNOWN))
|
|
237
|
+
|
|
238
|
+
def __rtruediv__(self, other: Any) -> "Expression":
|
|
239
|
+
return self._coerce(other).__truediv__(self)
|
|
240
|
+
|
|
241
|
+
def __matmul__(self, other: Any) -> "Expression":
|
|
242
|
+
return self._binary(other, "matmul")
|
|
243
|
+
|
|
244
|
+
def __rmatmul__(self, other: Any) -> "Expression":
|
|
245
|
+
return self._coerce(other)._binary(self, "matmul")
|
|
246
|
+
|
|
247
|
+
def __pow__(self, exponent: int) -> "Expression":
|
|
248
|
+
exponent = int(exponent)
|
|
249
|
+
if exponent < 0:
|
|
250
|
+
raise DomainError("negative symbolic powers are outside P7 smooth-core scope")
|
|
251
|
+
if exponent == 0:
|
|
252
|
+
return self._model.constant(np.ones(self.shape if self.shape else (), dtype=float))
|
|
253
|
+
if exponent == 1:
|
|
254
|
+
return self
|
|
255
|
+
if exponent == 2:
|
|
256
|
+
return self * self
|
|
257
|
+
deps_v = self.variable_dependencies
|
|
258
|
+
deps_p = self.parameter_dependencies
|
|
259
|
+
degree = None if self.polynomial_degree is None else self.polynomial_degree * exponent
|
|
260
|
+
sign = SignDomain.NONNEGATIVE if exponent % 2 == 0 else self.sign
|
|
261
|
+
return Expression(self._model, ExprNode("pow", self.shape, args=(self._node,), payload=exponent, variable_dependencies=deps_v, parameter_dependencies=deps_p, sign=sign, degree=degree, curvature=Curvature.UNKNOWN))
|
|
262
|
+
|
|
263
|
+
def _smooth_unary(self, kind: str) -> "Expression":
|
|
264
|
+
sign = SignDomain.UNKNOWN
|
|
265
|
+
if kind in {"exp", "sqrt"}:
|
|
266
|
+
sign = SignDomain.NONNEGATIVE
|
|
267
|
+
return Expression(self._model, ExprNode(kind, self.shape, args=(self._node,), variable_dependencies=self.variable_dependencies, parameter_dependencies=self.parameter_dependencies, sign=sign, degree=None, curvature=Curvature.UNKNOWN))
|
|
268
|
+
|
|
269
|
+
def sin(self) -> "Expression": return self._smooth_unary("sin")
|
|
270
|
+
def cos(self) -> "Expression": return self._smooth_unary("cos")
|
|
271
|
+
def exp(self) -> "Expression": return self._smooth_unary("exp")
|
|
272
|
+
def log(self) -> "Expression": return self._smooth_unary("log")
|
|
273
|
+
def sqrt(self) -> "Expression": return self._smooth_unary("sqrt")
|
|
274
|
+
def tanh(self) -> "Expression": return self._smooth_unary("tanh")
|
|
275
|
+
|
|
276
|
+
def __getitem__(self, key: Any) -> "Expression":
|
|
277
|
+
shape = _index_shape(self.shape, key)
|
|
278
|
+
sign = self.sign
|
|
279
|
+
if self._node.kind in {"variable", "parameter", "constant"}:
|
|
280
|
+
sign = self._model._indexed_sign(self._node, key)
|
|
281
|
+
return Expression(self._model, ExprNode("index", shape, args=(self._node,), payload=key, variable_dependencies=self.variable_dependencies, parameter_dependencies=self.parameter_dependencies, sign=sign, degree=self.polynomial_degree, curvature=self.curvature))
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def T(self) -> "Expression":
|
|
285
|
+
if self.ndim < 2:
|
|
286
|
+
return self
|
|
287
|
+
if self.ndim != 2:
|
|
288
|
+
raise ShapeError("P1 transpose supports at most 2-D expressions")
|
|
289
|
+
return Expression(self._model, ExprNode("transpose", (self.shape[1], self.shape[0]), args=(self._node,), variable_dependencies=self.variable_dependencies, parameter_dependencies=self.parameter_dependencies, sign=self.sign, degree=self.polynomial_degree, curvature=self.curvature))
|
|
290
|
+
|
|
291
|
+
def sum(self, axis: int | None = None) -> "Expression":
|
|
292
|
+
if axis is not None:
|
|
293
|
+
axis = int(axis)
|
|
294
|
+
if axis < 0:
|
|
295
|
+
axis += self.ndim
|
|
296
|
+
if axis < 0 or axis >= self.ndim:
|
|
297
|
+
raise ShapeError("sum axis out of range")
|
|
298
|
+
shape = self.shape[:axis] + self.shape[axis + 1:]
|
|
299
|
+
else:
|
|
300
|
+
shape = ()
|
|
301
|
+
return Expression(self._model, ExprNode("sum", shape, args=(self._node,), payload=axis, variable_dependencies=self.variable_dependencies, parameter_dependencies=self.parameter_dependencies, sign=self.sign, degree=self.polynomial_degree, curvature=self.curvature))
|
|
302
|
+
|
|
303
|
+
def __le__(self, other: Any):
|
|
304
|
+
from .model import PendingConstraint
|
|
305
|
+
rhs = self._coerce(other)
|
|
306
|
+
return PendingConstraint(self - rhs, "le")
|
|
307
|
+
|
|
308
|
+
def __ge__(self, other: Any):
|
|
309
|
+
from .model import PendingConstraint
|
|
310
|
+
rhs = self._coerce(other)
|
|
311
|
+
return PendingConstraint(self - rhs, "ge")
|
|
312
|
+
|
|
313
|
+
def __eq__(self, other: Any): # type: ignore[override]
|
|
314
|
+
from .model import PendingConstraint
|
|
315
|
+
rhs = self._coerce(other)
|
|
316
|
+
return PendingConstraint(self - rhs, "eq")
|
|
317
|
+
|
|
318
|
+
def __bool__(self) -> bool:
|
|
319
|
+
raise SymbolicTruthValueError("symbolic expressions/relations cannot be converted to bool")
|
|
320
|
+
|
|
321
|
+
def __float__(self) -> float:
|
|
322
|
+
raise TypeError("symbolic expressions cannot be converted to float")
|
|
323
|
+
|
|
324
|
+
def __array__(self, dtype=None):
|
|
325
|
+
raise TypeError("symbolic expressions cannot be converted to NumPy arrays")
|
|
326
|
+
|
|
327
|
+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
|
328
|
+
if method != "__call__" or kwargs.get("out") is not None:
|
|
329
|
+
return NotImplemented
|
|
330
|
+
if ufunc is np.add:
|
|
331
|
+
return _as_expression(inputs[0], self._model) + inputs[1]
|
|
332
|
+
if ufunc is np.subtract:
|
|
333
|
+
return _as_expression(inputs[0], self._model) - inputs[1]
|
|
334
|
+
if ufunc is np.multiply:
|
|
335
|
+
return _as_expression(inputs[0], self._model) * inputs[1]
|
|
336
|
+
if ufunc is np.divide:
|
|
337
|
+
return _as_expression(inputs[0], self._model) / inputs[1]
|
|
338
|
+
if ufunc is np.matmul:
|
|
339
|
+
return _as_expression(inputs[0], self._model) @ inputs[1]
|
|
340
|
+
if ufunc is np.negative:
|
|
341
|
+
return -_as_expression(inputs[0], self._model)
|
|
342
|
+
if ufunc is np.sin:
|
|
343
|
+
return _as_expression(inputs[0], self._model).sin()
|
|
344
|
+
if ufunc is np.cos:
|
|
345
|
+
return _as_expression(inputs[0], self._model).cos()
|
|
346
|
+
if ufunc is np.exp:
|
|
347
|
+
return _as_expression(inputs[0], self._model).exp()
|
|
348
|
+
if ufunc is np.log:
|
|
349
|
+
return _as_expression(inputs[0], self._model).log()
|
|
350
|
+
if ufunc is np.sqrt:
|
|
351
|
+
return _as_expression(inputs[0], self._model).sqrt()
|
|
352
|
+
if ufunc is np.tanh:
|
|
353
|
+
return _as_expression(inputs[0], self._model).tanh()
|
|
354
|
+
return NotImplemented
|
|
355
|
+
|
|
356
|
+
def __array_function__(self, func, types, args, kwargs):
|
|
357
|
+
if func is np.sum:
|
|
358
|
+
expr = _as_expression(args[0], self._model)
|
|
359
|
+
return expr.sum(axis=kwargs.get("axis"))
|
|
360
|
+
if func is np.dot:
|
|
361
|
+
return _as_expression(args[0], self._model) @ args[1]
|
|
362
|
+
return NotImplemented
|
|
363
|
+
|
|
364
|
+
def __repr__(self) -> str:
|
|
365
|
+
return f"Expression(kind={self._node.kind!r}, shape={self.shape}, degree={self.polynomial_degree})"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _as_expression(value: Any, model: "Model") -> Expression:
|
|
369
|
+
if isinstance(value, Expression):
|
|
370
|
+
if value._model is not model:
|
|
371
|
+
raise OwnershipError("cross-model expression reference is not allowed")
|
|
372
|
+
return value
|
|
373
|
+
return model.constant(value)
|