funcprops 0.1.0__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.
- funcprops/__init__.py +234 -0
- funcprops/_semialg.py +246 -0
- funcprops/api.py +70 -0
- funcprops/branchcuts/__init__.py +141 -0
- funcprops/branchcuts/computation.py +664 -0
- funcprops/branchcuts/core.py +1204 -0
- funcprops/branchcuts/post_simplify.py +461 -0
- funcprops/conditions.py +124 -0
- funcprops/data/catalogue.json +127449 -0
- funcprops/data/dlmf_audit.json +3382 -0
- funcprops/dlmf.py +29 -0
- funcprops/engine.py +324 -0
- funcprops/exceptions.py +29 -0
- funcprops/proof_cache.py +373 -0
- funcprops/real_analysis/__init__.py +52 -0
- funcprops/real_analysis/advanced_convexity.py +217 -0
- funcprops/real_analysis/boundedness.py +168 -0
- funcprops/real_analysis/common.py +244 -0
- funcprops/real_analysis/convexity.py +698 -0
- funcprops/real_analysis/differentiability.py +112 -0
- funcprops/real_analysis/domain.py +188 -0
- funcprops/real_analysis/mapping.py +374 -0
- funcprops/real_analysis/monotonic_certificates.py +867 -0
- funcprops/real_analysis/monotonicity.py +673 -0
- funcprops/real_analysis/oscillation.py +151 -0
- funcprops/real_analysis/piecewise.py +174 -0
- funcprops/real_analysis/range.py +552 -0
- funcprops/real_analysis/sign.py +561 -0
- funcprops/real_analysis/smoothness.py +268 -0
- funcprops/real_analysis/special_functions.py +515 -0
- funcprops/real_analysis/structural_properties.py +558 -0
- funcprops/real_analysis/zeros.py +327 -0
- funcprops/registry.py +274 -0
- funcprops/results.py +239 -0
- funcprops/singularities.py +1767 -0
- funcprops/stratification.py +494 -0
- funcprops/sympy_compare.py +63 -0
- funcprops/transcendental/__init__.py +140 -0
- funcprops/transcendental/algebraize.py +201 -0
- funcprops/transcendental/analytic.py +261 -0
- funcprops/transcendental/complex_roots.py +542 -0
- funcprops/transcendental/exp_fourier.py +221 -0
- funcprops/transcendental/extrema.py +821 -0
- funcprops/transcendental/periodic.py +222 -0
- funcprops/transcendental/proof.py +104 -0
- funcprops/transcendental/real_interval.py +128 -0
- funcprops/transcendental/real_sign.py +117 -0
- funcprops/transcendental/reduce.py +288 -0
- funcprops/transcendental/roots.py +1380 -0
- funcprops/transcendental/zero_geometry.py +1197 -0
- funcprops/verification.py +410 -0
- funcprops-0.1.0.dist-info/METADATA +208 -0
- funcprops-0.1.0.dist-info/RECORD +56 -0
- funcprops-0.1.0.dist-info/WHEEL +5 -0
- funcprops-0.1.0.dist-info/licenses/LICENSE +674 -0
- funcprops-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sympy as sp
|
|
4
|
+
from sympy import S
|
|
5
|
+
|
|
6
|
+
from .. import _semialg
|
|
7
|
+
from ..engine import evaluate_property
|
|
8
|
+
from ..exceptions import SYMBOLIC_QUERY_ERRORS
|
|
9
|
+
from ..results import ConditionalValue, Convexity, Monotonicity, PropertyResult, Sign
|
|
10
|
+
from ..stratification import (
|
|
11
|
+
evaluate_parameter_strata,
|
|
12
|
+
normalize_cases,
|
|
13
|
+
semantic_condition,
|
|
14
|
+
)
|
|
15
|
+
from ..transcendental.zero_geometry import property_interval_decomposition
|
|
16
|
+
from .advanced_convexity import (
|
|
17
|
+
ConvexityCertificate,
|
|
18
|
+
convexity_implications,
|
|
19
|
+
parameterized_exp_convexity,
|
|
20
|
+
parameterized_power_convexity,
|
|
21
|
+
secondary_convexity_properties,
|
|
22
|
+
special_function_convexity,
|
|
23
|
+
strong_convexity,
|
|
24
|
+
)
|
|
25
|
+
from .common import (
|
|
26
|
+
combine_natural_domain_condition,
|
|
27
|
+
condition_to_set,
|
|
28
|
+
normalize_variables,
|
|
29
|
+
)
|
|
30
|
+
from .domain import function_domain
|
|
31
|
+
from .monotonicity import function_monotonicity
|
|
32
|
+
from .piecewise import piecewise_branches, piecewise_interface_limits
|
|
33
|
+
from .sign import function_sign
|
|
34
|
+
from .special_functions import parameter_guard, positive_axis_convexity_strata
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _from_code(value):
|
|
38
|
+
try:
|
|
39
|
+
code = int(str(value))
|
|
40
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
41
|
+
return Convexity.UNKNOWN
|
|
42
|
+
return {
|
|
43
|
+
1: Convexity.STRICTLY_CONVEX,
|
|
44
|
+
2: Convexity.CONVEX,
|
|
45
|
+
-1: Convexity.STRICTLY_CONCAVE,
|
|
46
|
+
-2: Convexity.CONCAVE,
|
|
47
|
+
0: Convexity.NEITHER,
|
|
48
|
+
}.get(code, Convexity.UNKNOWN)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _registry_convexity(expr, var):
|
|
52
|
+
r = evaluate_property(expr, "GlobalConvexity", var=var, mode="real")
|
|
53
|
+
pguard = parameter_guard(expr, var, "GlobalConvexity")
|
|
54
|
+
if r.primary is not None:
|
|
55
|
+
v = _from_code(r.primary)
|
|
56
|
+
if pguard is S.true:
|
|
57
|
+
return v
|
|
58
|
+
return normalize_cases(
|
|
59
|
+
(ConditionalValue(v, pguard),), source="registry-convexity-stratification"
|
|
60
|
+
)
|
|
61
|
+
if r.cases:
|
|
62
|
+
return normalize_cases(
|
|
63
|
+
(
|
|
64
|
+
ConditionalValue(
|
|
65
|
+
_from_code(c.value), sp.And(semantic_condition(c.condition), pguard)
|
|
66
|
+
)
|
|
67
|
+
for c in r.cases
|
|
68
|
+
),
|
|
69
|
+
source="registry-convexity-stratification",
|
|
70
|
+
)
|
|
71
|
+
return Convexity.UNKNOWN
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _normalize_semialg(value):
|
|
75
|
+
return _semialg.normalize_convexity(value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _sum_closure(expr, var, domain, strict):
|
|
79
|
+
classes = [
|
|
80
|
+
function_convexity(a, var, domain=domain, strict_inequalities=strict)
|
|
81
|
+
for a in expr.args
|
|
82
|
+
if a.has(var)
|
|
83
|
+
]
|
|
84
|
+
if not classes:
|
|
85
|
+
return Convexity.AFFINE
|
|
86
|
+
cv = (Convexity.STRICTLY_CONVEX, Convexity.CONVEX, Convexity.AFFINE)
|
|
87
|
+
cc = (Convexity.STRICTLY_CONCAVE, Convexity.CONCAVE, Convexity.AFFINE)
|
|
88
|
+
if all(c in cv for c in classes):
|
|
89
|
+
return (
|
|
90
|
+
Convexity.STRICTLY_CONVEX
|
|
91
|
+
if any(c is Convexity.STRICTLY_CONVEX for c in classes)
|
|
92
|
+
else Convexity.CONVEX
|
|
93
|
+
)
|
|
94
|
+
if all(c in cc for c in classes):
|
|
95
|
+
return (
|
|
96
|
+
Convexity.STRICTLY_CONCAVE
|
|
97
|
+
if any(c is Convexity.STRICTLY_CONCAVE for c in classes)
|
|
98
|
+
else Convexity.CONCAVE
|
|
99
|
+
)
|
|
100
|
+
return Convexity.UNKNOWN
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _composition(expr, var, domain, strict):
|
|
104
|
+
varying = [(i, a) for i, a in enumerate(expr.args) if a.has(var)]
|
|
105
|
+
if len(varying) != 1 or expr.func in (sp.Add, sp.Mul, sp.Pow, sp.Piecewise):
|
|
106
|
+
return Convexity.UNKNOWN
|
|
107
|
+
i, inner = varying[0]
|
|
108
|
+
X = sp.Dummy("X", real=True)
|
|
109
|
+
args = list(expr.args)
|
|
110
|
+
args[i] = X
|
|
111
|
+
outer = expr.func(*args)
|
|
112
|
+
oc = _registry_convexity(outer, X)
|
|
113
|
+
if outer.func is sp.exp and oc is Convexity.UNKNOWN:
|
|
114
|
+
oc = Convexity.STRICTLY_CONVEX
|
|
115
|
+
om = function_monotonicity(outer, X)
|
|
116
|
+
ic = function_convexity(inner, var, domain=domain, strict_inequalities=strict)
|
|
117
|
+
inc = om in (Monotonicity.STRICTLY_INCREASING, Monotonicity.NONDECREASING)
|
|
118
|
+
dec = om in (Monotonicity.STRICTLY_DECREASING, Monotonicity.NONINCREASING)
|
|
119
|
+
if (
|
|
120
|
+
oc in (Convexity.STRICTLY_CONVEX, Convexity.CONVEX)
|
|
121
|
+
and inc
|
|
122
|
+
and ic in (Convexity.STRICTLY_CONVEX, Convexity.CONVEX, Convexity.AFFINE)
|
|
123
|
+
):
|
|
124
|
+
return (
|
|
125
|
+
Convexity.STRICTLY_CONVEX
|
|
126
|
+
if oc is Convexity.STRICTLY_CONVEX or ic is Convexity.STRICTLY_CONVEX
|
|
127
|
+
else Convexity.CONVEX
|
|
128
|
+
)
|
|
129
|
+
if (
|
|
130
|
+
oc in (Convexity.STRICTLY_CONCAVE, Convexity.CONCAVE)
|
|
131
|
+
and inc
|
|
132
|
+
and ic in (Convexity.STRICTLY_CONCAVE, Convexity.CONCAVE, Convexity.AFFINE)
|
|
133
|
+
):
|
|
134
|
+
return (
|
|
135
|
+
Convexity.STRICTLY_CONCAVE
|
|
136
|
+
if oc is Convexity.STRICTLY_CONCAVE or ic is Convexity.STRICTLY_CONCAVE
|
|
137
|
+
else Convexity.CONCAVE
|
|
138
|
+
)
|
|
139
|
+
if (
|
|
140
|
+
oc in (Convexity.STRICTLY_CONVEX, Convexity.CONVEX)
|
|
141
|
+
and dec
|
|
142
|
+
and ic in (Convexity.STRICTLY_CONCAVE, Convexity.CONCAVE)
|
|
143
|
+
):
|
|
144
|
+
return Convexity.CONVEX
|
|
145
|
+
if (
|
|
146
|
+
oc in (Convexity.STRICTLY_CONCAVE, Convexity.CONCAVE)
|
|
147
|
+
and dec
|
|
148
|
+
and ic in (Convexity.STRICTLY_CONVEX, Convexity.CONVEX)
|
|
149
|
+
):
|
|
150
|
+
return Convexity.CONCAVE
|
|
151
|
+
return Convexity.UNKNOWN
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _convexity_from_second_sign(v):
|
|
155
|
+
if v is Sign.POSITIVE:
|
|
156
|
+
return Convexity.STRICTLY_CONVEX
|
|
157
|
+
if v is Sign.NEGATIVE:
|
|
158
|
+
return Convexity.STRICTLY_CONCAVE
|
|
159
|
+
if v is Sign.NONNEGATIVE:
|
|
160
|
+
return Convexity.CONVEX
|
|
161
|
+
if v is Sign.NONPOSITIVE:
|
|
162
|
+
return Convexity.CONCAVE
|
|
163
|
+
if v is Sign.ZERO:
|
|
164
|
+
return Convexity.AFFINE
|
|
165
|
+
if v is Sign.SIGN_CHANGING:
|
|
166
|
+
return Convexity.NEITHER
|
|
167
|
+
return Convexity.UNKNOWN
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _flip_convexity(v):
|
|
171
|
+
return {
|
|
172
|
+
Convexity.STRONGLY_CONVEX: Convexity.STRONGLY_CONCAVE,
|
|
173
|
+
Convexity.STRICTLY_CONVEX: Convexity.STRICTLY_CONCAVE,
|
|
174
|
+
Convexity.CONVEX: Convexity.CONCAVE,
|
|
175
|
+
Convexity.AFFINE: Convexity.AFFINE,
|
|
176
|
+
Convexity.STRONGLY_CONCAVE: Convexity.STRONGLY_CONVEX,
|
|
177
|
+
Convexity.STRICTLY_CONCAVE: Convexity.STRICTLY_CONVEX,
|
|
178
|
+
Convexity.CONCAVE: Convexity.CONVEX,
|
|
179
|
+
}.get(v, Convexity.UNKNOWN)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _multivariate_constant_hessian_convexity(expr, variables):
|
|
183
|
+
"""Exact classification for expressions with a constant symmetric Hessian.
|
|
184
|
+
|
|
185
|
+
This intentionally handles only the zero-cost exact case. Variable
|
|
186
|
+
Hessians remain on the established scalar/semialgebraic paths rather than
|
|
187
|
+
triggering expensive multivariate inequality solving here.
|
|
188
|
+
"""
|
|
189
|
+
vv = tuple(variables)
|
|
190
|
+
try:
|
|
191
|
+
H = sp.hessian(expr, vv)
|
|
192
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
193
|
+
return Convexity.UNKNOWN
|
|
194
|
+
if any(entry.has(*vv) for entry in H):
|
|
195
|
+
return Convexity.UNKNOWN
|
|
196
|
+
if sp.zeros(len(vv)) == H:
|
|
197
|
+
return Convexity.AFFINE
|
|
198
|
+
try:
|
|
199
|
+
eigs = H.eigenvals()
|
|
200
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
201
|
+
eigs = {}
|
|
202
|
+
if eigs:
|
|
203
|
+
vals = list(eigs)
|
|
204
|
+
if all(v.is_positive is True for v in vals):
|
|
205
|
+
return Convexity.STRONGLY_CONVEX
|
|
206
|
+
if all(v.is_negative is True for v in vals):
|
|
207
|
+
return Convexity.STRONGLY_CONCAVE
|
|
208
|
+
if all(v.is_nonnegative is True for v in vals):
|
|
209
|
+
return Convexity.CONVEX
|
|
210
|
+
if all(v.is_nonpositive is True for v in vals):
|
|
211
|
+
return Convexity.CONCAVE
|
|
212
|
+
if any(v.is_positive is True for v in vals) and any(
|
|
213
|
+
v.is_negative is True for v in vals
|
|
214
|
+
):
|
|
215
|
+
return Convexity.NEITHER
|
|
216
|
+
# Matrix definiteness predicates are exact/tri-state; never use floating
|
|
217
|
+
# eigenvalue signs to turn an undecided symbolic proof into a certificate.
|
|
218
|
+
try:
|
|
219
|
+
if H.is_positive_definite is True:
|
|
220
|
+
return Convexity.STRONGLY_CONVEX
|
|
221
|
+
if H.is_negative_definite is True:
|
|
222
|
+
return Convexity.STRONGLY_CONCAVE
|
|
223
|
+
if H.is_positive_semidefinite is True:
|
|
224
|
+
return Convexity.CONVEX
|
|
225
|
+
if H.is_negative_semidefinite is True:
|
|
226
|
+
return Convexity.CONCAVE
|
|
227
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
228
|
+
pass
|
|
229
|
+
return Convexity.UNKNOWN
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _piecewise_convexity(expr, var, domain, strict):
|
|
233
|
+
"""Combine convex branches after checking value and slope interfaces."""
|
|
234
|
+
if not isinstance(expr, sp.Piecewise):
|
|
235
|
+
return Convexity.UNKNOWN
|
|
236
|
+
branches = piecewise_branches(expr, var, domain)
|
|
237
|
+
if not branches:
|
|
238
|
+
return Convexity.UNKNOWN
|
|
239
|
+
classes = [
|
|
240
|
+
function_convexity(
|
|
241
|
+
branch.expression,
|
|
242
|
+
var,
|
|
243
|
+
domain=branch.condition,
|
|
244
|
+
strict_inequalities=strict,
|
|
245
|
+
)
|
|
246
|
+
for branch in branches
|
|
247
|
+
]
|
|
248
|
+
if any(isinstance(value, PropertyResult) for value in classes):
|
|
249
|
+
return Convexity.UNKNOWN
|
|
250
|
+
convex = {
|
|
251
|
+
Convexity.STRONGLY_CONVEX,
|
|
252
|
+
Convexity.STRICTLY_CONVEX,
|
|
253
|
+
Convexity.CONVEX,
|
|
254
|
+
Convexity.AFFINE,
|
|
255
|
+
}
|
|
256
|
+
concave = {
|
|
257
|
+
Convexity.STRONGLY_CONCAVE,
|
|
258
|
+
Convexity.STRICTLY_CONCAVE,
|
|
259
|
+
Convexity.CONCAVE,
|
|
260
|
+
Convexity.AFFINE,
|
|
261
|
+
}
|
|
262
|
+
orientation = (
|
|
263
|
+
Convexity.CONVEX
|
|
264
|
+
if all(value in convex for value in classes)
|
|
265
|
+
else Convexity.CONCAVE
|
|
266
|
+
if all(value in concave for value in classes)
|
|
267
|
+
else Convexity.UNKNOWN
|
|
268
|
+
)
|
|
269
|
+
if orientation is Convexity.UNKNOWN:
|
|
270
|
+
return orientation
|
|
271
|
+
values = piecewise_interface_limits(branches, var)
|
|
272
|
+
slopes = piecewise_interface_limits(branches, var, derivative_order=1)
|
|
273
|
+
if values is None or slopes is None:
|
|
274
|
+
return orientation if len(branches) == 1 else Convexity.UNKNOWN
|
|
275
|
+
for (_, left_value, right_value), (_, left_slope, right_slope) in zip(
|
|
276
|
+
values, slopes, strict=False
|
|
277
|
+
):
|
|
278
|
+
if sp.simplify(left_value - right_value) != 0:
|
|
279
|
+
return Convexity.NEITHER
|
|
280
|
+
jump = sp.simplify(right_slope - left_slope)
|
|
281
|
+
if orientation is Convexity.CONVEX:
|
|
282
|
+
if jump.is_nonnegative is not True:
|
|
283
|
+
return (
|
|
284
|
+
Convexity.NEITHER if jump.is_negative is True else Convexity.UNKNOWN
|
|
285
|
+
)
|
|
286
|
+
elif jump.is_nonpositive is not True:
|
|
287
|
+
return Convexity.NEITHER if jump.is_positive is True else Convexity.UNKNOWN
|
|
288
|
+
return orientation
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _convexity_property_result(
|
|
292
|
+
primary,
|
|
293
|
+
domain,
|
|
294
|
+
source,
|
|
295
|
+
*,
|
|
296
|
+
all_properties=(),
|
|
297
|
+
evidence=(),
|
|
298
|
+
):
|
|
299
|
+
"""Build one normalized detailed convexity result and proof certificate."""
|
|
300
|
+
proof = tuple(evidence)
|
|
301
|
+
if primary is not Convexity.UNKNOWN and not any(
|
|
302
|
+
isinstance(item, ConvexityCertificate) for item in proof
|
|
303
|
+
):
|
|
304
|
+
proof = (
|
|
305
|
+
ConvexityCertificate(primary, domain, source, proof=proof),
|
|
306
|
+
*proof,
|
|
307
|
+
)
|
|
308
|
+
return PropertyResult(
|
|
309
|
+
primary,
|
|
310
|
+
all_properties=tuple(all_properties),
|
|
311
|
+
source=source,
|
|
312
|
+
evidence=proof,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _semialgebraic_convexity_stage(
|
|
317
|
+
expr, var, assumptions, *, strict_inequalities, options
|
|
318
|
+
):
|
|
319
|
+
"""Return a decisive exact semialgebraic convexity class or ``None``."""
|
|
320
|
+
if not _semialg.eligible_problem(expr, var, domain=assumptions):
|
|
321
|
+
return None
|
|
322
|
+
try:
|
|
323
|
+
raw = _semialg.function_convexity(
|
|
324
|
+
expr,
|
|
325
|
+
var,
|
|
326
|
+
domain=assumptions,
|
|
327
|
+
strict_inequalities=strict_inequalities,
|
|
328
|
+
**options,
|
|
329
|
+
)
|
|
330
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
331
|
+
return None
|
|
332
|
+
value = _normalize_semialg(raw)
|
|
333
|
+
return value if value is not Convexity.UNKNOWN else None
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def function_convexity(
|
|
337
|
+
expr,
|
|
338
|
+
var,
|
|
339
|
+
*,
|
|
340
|
+
domain=True,
|
|
341
|
+
strict_inequalities=True,
|
|
342
|
+
all_properties=False,
|
|
343
|
+
details=False,
|
|
344
|
+
intervals=False,
|
|
345
|
+
_domain_ready=False,
|
|
346
|
+
**kwargs,
|
|
347
|
+
):
|
|
348
|
+
"""Classify real convexity with strong/log/quasi/pseudo refinements.
|
|
349
|
+
|
|
350
|
+
The default is the strongest canonical ordinary convexity/concavity class.
|
|
351
|
+
With ``all_properties=True`` a PropertyResult includes additional proved
|
|
352
|
+
log-, quasi-, and pseudo-convexity/concavity properties.
|
|
353
|
+
"""
|
|
354
|
+
expr = sp.sympify(expr)
|
|
355
|
+
if isinstance(var, (tuple, list, sp.Tuple)):
|
|
356
|
+
variables = normalize_variables(var)
|
|
357
|
+
if not any(expr.has(v) for v in variables):
|
|
358
|
+
primary = Convexity.AFFINE
|
|
359
|
+
else:
|
|
360
|
+
primary = _multivariate_constant_hessian_convexity(expr, variables)
|
|
361
|
+
if primary is not Convexity.UNKNOWN:
|
|
362
|
+
extra = convexity_implications(primary)
|
|
363
|
+
return (
|
|
364
|
+
_convexity_property_result(
|
|
365
|
+
primary,
|
|
366
|
+
S.Reals,
|
|
367
|
+
"constant-hessian",
|
|
368
|
+
all_properties=extra,
|
|
369
|
+
)
|
|
370
|
+
if (all_properties or details or intervals)
|
|
371
|
+
else primary
|
|
372
|
+
)
|
|
373
|
+
return (
|
|
374
|
+
_convexity_property_result(
|
|
375
|
+
Convexity.UNKNOWN, S.Reals, "multivariate-variable-hessian"
|
|
376
|
+
)
|
|
377
|
+
if (all_properties or details or intervals)
|
|
378
|
+
else Convexity.UNKNOWN
|
|
379
|
+
)
|
|
380
|
+
var = sp.sympify(var)
|
|
381
|
+
if not expr.has(var):
|
|
382
|
+
primary = Convexity.AFFINE
|
|
383
|
+
return (
|
|
384
|
+
_convexity_property_result(
|
|
385
|
+
primary,
|
|
386
|
+
S.Reals,
|
|
387
|
+
"constant",
|
|
388
|
+
all_properties=(Convexity.CONVEX, Convexity.CONCAVE),
|
|
389
|
+
)
|
|
390
|
+
if (all_properties or details)
|
|
391
|
+
else primary
|
|
392
|
+
)
|
|
393
|
+
natural = function_domain(expr, var)
|
|
394
|
+
assumptions = (
|
|
395
|
+
sp.sympify(domain)
|
|
396
|
+
if _domain_ready
|
|
397
|
+
else combine_natural_domain_condition(natural, domain, var)
|
|
398
|
+
)
|
|
399
|
+
if not _domain_ready:
|
|
400
|
+
split = evaluate_parameter_strata(
|
|
401
|
+
assumptions,
|
|
402
|
+
var,
|
|
403
|
+
lambda branch_domain: function_convexity(
|
|
404
|
+
expr,
|
|
405
|
+
var,
|
|
406
|
+
domain=branch_domain,
|
|
407
|
+
strict_inequalities=strict_inequalities,
|
|
408
|
+
all_properties=all_properties,
|
|
409
|
+
details=details,
|
|
410
|
+
intervals=intervals,
|
|
411
|
+
_domain_ready=True,
|
|
412
|
+
**kwargs,
|
|
413
|
+
),
|
|
414
|
+
source="convexity-parameter-stratification",
|
|
415
|
+
)
|
|
416
|
+
if split is not None:
|
|
417
|
+
return split
|
|
418
|
+
ds_for_geometry = condition_to_set(assumptions, var)
|
|
419
|
+
requested_set = (
|
|
420
|
+
condition_to_set(domain, var)
|
|
421
|
+
if domain not in (True, None, S.true, S.Reals)
|
|
422
|
+
else None
|
|
423
|
+
)
|
|
424
|
+
# Global convexity/concavity is defined on a convex domain. Validate an
|
|
425
|
+
# explicit requested domain independently of natural-domain bookkeeping so
|
|
426
|
+
# recognized convex restrictions (for example x > 0 for gamma(x)) keep
|
|
427
|
+
# their stronger catalogue/special-function proofs.
|
|
428
|
+
if isinstance(requested_set, sp.Union) or isinstance(ds_for_geometry, sp.Union):
|
|
429
|
+
primary = Convexity.NEITHER
|
|
430
|
+
return (
|
|
431
|
+
_convexity_property_result(primary, assumptions, "nonconvex-domain")
|
|
432
|
+
if (all_properties or details or intervals)
|
|
433
|
+
else primary
|
|
434
|
+
)
|
|
435
|
+
if domain not in (True, None, S.true, S.Reals) and not isinstance(
|
|
436
|
+
requested_set, sp.Set
|
|
437
|
+
):
|
|
438
|
+
primary = Convexity.UNKNOWN
|
|
439
|
+
return (
|
|
440
|
+
_convexity_property_result(
|
|
441
|
+
primary, assumptions, "unresolved-domain-convexity"
|
|
442
|
+
)
|
|
443
|
+
if (all_properties or details or intervals)
|
|
444
|
+
else primary
|
|
445
|
+
)
|
|
446
|
+
geom_domain = (
|
|
447
|
+
ds_for_geometry
|
|
448
|
+
if isinstance(ds_for_geometry, sp.Set)
|
|
449
|
+
else requested_set
|
|
450
|
+
if isinstance(requested_set, sp.Set)
|
|
451
|
+
else S.Reals
|
|
452
|
+
)
|
|
453
|
+
piecewise = _piecewise_convexity(expr, var, assumptions, strict_inequalities)
|
|
454
|
+
if piecewise is not Convexity.UNKNOWN:
|
|
455
|
+
if all_properties or details or intervals:
|
|
456
|
+
return _convexity_property_result(
|
|
457
|
+
piecewise,
|
|
458
|
+
assumptions,
|
|
459
|
+
"piecewise-analysis",
|
|
460
|
+
all_properties=convexity_implications(piecewise),
|
|
461
|
+
)
|
|
462
|
+
return piecewise
|
|
463
|
+
curvature_decomposition = (
|
|
464
|
+
property_interval_decomposition(
|
|
465
|
+
expr,
|
|
466
|
+
var,
|
|
467
|
+
derivative_order=2,
|
|
468
|
+
domain=geom_domain,
|
|
469
|
+
positive_value=Convexity.STRICTLY_CONVEX,
|
|
470
|
+
negative_value=Convexity.STRICTLY_CONCAVE,
|
|
471
|
+
)
|
|
472
|
+
if (intervals or details)
|
|
473
|
+
else None
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Certified second-derivative zero geometry can establish alternating
|
|
477
|
+
# convex/concave cells even when symbolic sign simplification cannot.
|
|
478
|
+
raw_curv = property_interval_decomposition(
|
|
479
|
+
expr,
|
|
480
|
+
var,
|
|
481
|
+
derivative_order=2,
|
|
482
|
+
domain=geom_domain,
|
|
483
|
+
positive_value=Convexity.STRICTLY_CONVEX,
|
|
484
|
+
negative_value=Convexity.STRICTLY_CONCAVE,
|
|
485
|
+
)
|
|
486
|
+
if (
|
|
487
|
+
raw_curv.complete
|
|
488
|
+
and raw_curv.alternating
|
|
489
|
+
and raw_curv.boundary_geometry
|
|
490
|
+
and geom_domain is S.Reals
|
|
491
|
+
):
|
|
492
|
+
if intervals or details or all_properties:
|
|
493
|
+
return _convexity_property_result(
|
|
494
|
+
Convexity.NEITHER,
|
|
495
|
+
assumptions,
|
|
496
|
+
"second-derivative-zero-geometry",
|
|
497
|
+
evidence=(raw_curv,),
|
|
498
|
+
)
|
|
499
|
+
return Convexity.NEITHER
|
|
500
|
+
|
|
501
|
+
# Parameter strata are returned rather than collapsed to Unknown.
|
|
502
|
+
strat = parameterized_power_convexity(
|
|
503
|
+
expr, var, assumptions
|
|
504
|
+
) or parameterized_exp_convexity(expr, var)
|
|
505
|
+
if strat is not None:
|
|
506
|
+
return strat
|
|
507
|
+
try:
|
|
508
|
+
positive_axis = (
|
|
509
|
+
sp.simplify_logic(sp.Implies(semantic_condition(assumptions), var > 0))
|
|
510
|
+
is S.true
|
|
511
|
+
)
|
|
512
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
513
|
+
positive_axis = False
|
|
514
|
+
if positive_axis:
|
|
515
|
+
sfacts = positive_axis_convexity_strata(expr, var)
|
|
516
|
+
if sfacts:
|
|
517
|
+
out = normalize_cases(
|
|
518
|
+
(ConditionalValue(v, g) for v, g, *_ in sfacts),
|
|
519
|
+
source="special-function-convexity-stratification",
|
|
520
|
+
)
|
|
521
|
+
if out.primary is None:
|
|
522
|
+
return out
|
|
523
|
+
|
|
524
|
+
registry_hint = Convexity.UNKNOWN
|
|
525
|
+
primary = Convexity.UNKNOWN
|
|
526
|
+
source = "insufficient-evidence"
|
|
527
|
+
evidence = ()
|
|
528
|
+
|
|
529
|
+
backend = _semialgebraic_convexity_stage(
|
|
530
|
+
expr,
|
|
531
|
+
var,
|
|
532
|
+
assumptions,
|
|
533
|
+
strict_inequalities=strict_inequalities,
|
|
534
|
+
options=kwargs,
|
|
535
|
+
)
|
|
536
|
+
if backend is not None:
|
|
537
|
+
primary = backend
|
|
538
|
+
source = "semialg"
|
|
539
|
+
|
|
540
|
+
if primary is Convexity.UNKNOWN and domain is True:
|
|
541
|
+
registry_hint = _registry_convexity(expr, var)
|
|
542
|
+
if isinstance(registry_hint, PropertyResult):
|
|
543
|
+
# Parameterized registry facts are already exact strata.
|
|
544
|
+
return registry_hint
|
|
545
|
+
if registry_hint not in (Convexity.UNKNOWN, Convexity.NEITHER):
|
|
546
|
+
primary = registry_hint
|
|
547
|
+
source = "registry"
|
|
548
|
+
elif expr.func is sp.exp:
|
|
549
|
+
primary = Convexity.STRICTLY_CONVEX
|
|
550
|
+
source = "primitive"
|
|
551
|
+
|
|
552
|
+
if primary is Convexity.UNKNOWN and expr.func is sp.Add:
|
|
553
|
+
c = _sum_closure(expr, var, assumptions, strict_inequalities)
|
|
554
|
+
if c is not Convexity.UNKNOWN:
|
|
555
|
+
primary = c
|
|
556
|
+
source = "sum-closure"
|
|
557
|
+
if primary is Convexity.UNKNOWN and expr.func is sp.Mul:
|
|
558
|
+
const, dep = expr.as_independent(var, as_Add=False)
|
|
559
|
+
if const != 1 and dep != expr:
|
|
560
|
+
c = function_convexity(
|
|
561
|
+
dep, var, domain=assumptions, strict_inequalities=strict_inequalities
|
|
562
|
+
)
|
|
563
|
+
sg = function_sign(const, var)
|
|
564
|
+
if isinstance(c, PropertyResult) or isinstance(sg, PropertyResult):
|
|
565
|
+
ccases = (
|
|
566
|
+
c.cases
|
|
567
|
+
if isinstance(c, PropertyResult)
|
|
568
|
+
else (ConditionalValue(c, S.true),)
|
|
569
|
+
)
|
|
570
|
+
scases = (
|
|
571
|
+
sg.cases
|
|
572
|
+
if isinstance(sg, PropertyResult)
|
|
573
|
+
else (ConditionalValue(sg, S.true),)
|
|
574
|
+
)
|
|
575
|
+
cases = []
|
|
576
|
+
for cv in ccases:
|
|
577
|
+
for sv in scases:
|
|
578
|
+
val = (
|
|
579
|
+
cv.value
|
|
580
|
+
if sv.value is Sign.POSITIVE
|
|
581
|
+
else _flip_convexity(cv.value)
|
|
582
|
+
if sv.value is Sign.NEGATIVE
|
|
583
|
+
else Convexity.AFFINE
|
|
584
|
+
if sv.value is Sign.ZERO
|
|
585
|
+
else Convexity.UNKNOWN
|
|
586
|
+
)
|
|
587
|
+
if val is not Convexity.UNKNOWN:
|
|
588
|
+
cases.append(
|
|
589
|
+
ConditionalValue(
|
|
590
|
+
val, sp.And(cv.condition, sv.condition)
|
|
591
|
+
)
|
|
592
|
+
)
|
|
593
|
+
if cases:
|
|
594
|
+
return normalize_cases(
|
|
595
|
+
cases, source="scalar-convexity-stratification"
|
|
596
|
+
)
|
|
597
|
+
else:
|
|
598
|
+
if sg is Sign.POSITIVE:
|
|
599
|
+
primary = c
|
|
600
|
+
source = "scalar-closure"
|
|
601
|
+
elif sg is Sign.NEGATIVE:
|
|
602
|
+
primary = _flip_convexity(c)
|
|
603
|
+
source = "scalar-closure"
|
|
604
|
+
elif sg is Sign.ZERO:
|
|
605
|
+
primary = Convexity.AFFINE
|
|
606
|
+
source = "scalar-closure"
|
|
607
|
+
|
|
608
|
+
if primary is Convexity.UNKNOWN:
|
|
609
|
+
c = _composition(expr, var, assumptions, strict_inequalities)
|
|
610
|
+
if c is not Convexity.UNKNOWN:
|
|
611
|
+
primary = c
|
|
612
|
+
source = "composition"
|
|
613
|
+
|
|
614
|
+
if primary is Convexity.UNKNOWN:
|
|
615
|
+
try:
|
|
616
|
+
from ..proof_cache import cached_derivative
|
|
617
|
+
|
|
618
|
+
d2 = cached_derivative(expr, var, order=2)
|
|
619
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
620
|
+
d2 = None
|
|
621
|
+
if d2 == 0:
|
|
622
|
+
primary = Convexity.AFFINE
|
|
623
|
+
source = "second-derivative"
|
|
624
|
+
elif d2 is not None:
|
|
625
|
+
sg = function_sign(d2, var, domain=assumptions, strict_inequalities=True)
|
|
626
|
+
if isinstance(sg, PropertyResult):
|
|
627
|
+
out = normalize_cases(
|
|
628
|
+
(
|
|
629
|
+
ConditionalValue(
|
|
630
|
+
_convexity_from_second_sign(c.value), c.condition
|
|
631
|
+
)
|
|
632
|
+
for c in sg.cases
|
|
633
|
+
if _convexity_from_second_sign(c.value) is not Convexity.UNKNOWN
|
|
634
|
+
),
|
|
635
|
+
source="second-derivative-stratification",
|
|
636
|
+
)
|
|
637
|
+
if out.cases:
|
|
638
|
+
return out
|
|
639
|
+
primary = _convexity_from_second_sign(sg)
|
|
640
|
+
if primary is not Convexity.UNKNOWN:
|
|
641
|
+
source = "second-derivative"
|
|
642
|
+
if primary is Convexity.UNKNOWN and registry_hint is Convexity.NEITHER:
|
|
643
|
+
primary = Convexity.NEITHER
|
|
644
|
+
source = "registry"
|
|
645
|
+
|
|
646
|
+
# Upgrade an ordinary classification when a positive curvature bound is
|
|
647
|
+
# available. If an upstream backend already proved strong convexity, compute
|
|
648
|
+
# the local certificate only when the caller requested proof details.
|
|
649
|
+
ordinary = (
|
|
650
|
+
Convexity.STRICTLY_CONVEX,
|
|
651
|
+
Convexity.CONVEX,
|
|
652
|
+
Convexity.STRICTLY_CONCAVE,
|
|
653
|
+
Convexity.CONCAVE,
|
|
654
|
+
)
|
|
655
|
+
strong = (Convexity.STRONGLY_CONVEX, Convexity.STRONGLY_CONCAVE)
|
|
656
|
+
need_certificate = (all_properties or details or intervals) and primary in strong
|
|
657
|
+
if primary in ordinary or need_certificate:
|
|
658
|
+
strong_result = strong_convexity(expr, var, assumptions)
|
|
659
|
+
if strong_result is not None:
|
|
660
|
+
proved_class, certificate = strong_result
|
|
661
|
+
if primary in ordinary or proved_class is primary:
|
|
662
|
+
primary = proved_class
|
|
663
|
+
evidence = (certificate,)
|
|
664
|
+
source = "strong-convexity-bound"
|
|
665
|
+
|
|
666
|
+
specials = special_function_convexity(expr, var, assumptions)
|
|
667
|
+
if primary is Convexity.UNKNOWN:
|
|
668
|
+
for q in specials:
|
|
669
|
+
if q in (
|
|
670
|
+
Convexity.STRONGLY_CONVEX,
|
|
671
|
+
Convexity.STRICTLY_CONVEX,
|
|
672
|
+
Convexity.CONVEX,
|
|
673
|
+
Convexity.STRONGLY_CONCAVE,
|
|
674
|
+
Convexity.STRICTLY_CONCAVE,
|
|
675
|
+
Convexity.CONCAVE,
|
|
676
|
+
):
|
|
677
|
+
primary = q
|
|
678
|
+
source = "special-function"
|
|
679
|
+
break
|
|
680
|
+
|
|
681
|
+
if all_properties or details or intervals:
|
|
682
|
+
extra, _ = secondary_convexity_properties(expr, var, assumptions, primary)
|
|
683
|
+
for q in specials:
|
|
684
|
+
if q is not primary and q not in extra:
|
|
685
|
+
extra = (*extra, q)
|
|
686
|
+
if (
|
|
687
|
+
curvature_decomposition is not None
|
|
688
|
+
and curvature_decomposition.boundary_geometry
|
|
689
|
+
):
|
|
690
|
+
evidence = (*evidence, curvature_decomposition)
|
|
691
|
+
return _convexity_property_result(
|
|
692
|
+
primary,
|
|
693
|
+
assumptions,
|
|
694
|
+
source,
|
|
695
|
+
all_properties=extra,
|
|
696
|
+
evidence=evidence,
|
|
697
|
+
)
|
|
698
|
+
return primary
|