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,515 @@
|
|
|
1
|
+
"""DLMF-audited real-property facts for parameterized special functions.
|
|
2
|
+
|
|
3
|
+
The rules here are intentionally conservative. Each fact has provenance:
|
|
4
|
+
``direct`` means DLMF states the property under essentially the same
|
|
5
|
+
hypotheses; ``derived`` means funcprops combines DLMF identities, endpoint
|
|
6
|
+
limits, or elementary differentiation without weakening the hypotheses.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
import sympy as sp
|
|
14
|
+
from sympy import S
|
|
15
|
+
|
|
16
|
+
from ..exceptions import SYMBOLIC_QUERY_ERRORS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class DLMFFact:
|
|
21
|
+
family: str
|
|
22
|
+
property: str
|
|
23
|
+
section: str
|
|
24
|
+
url: str
|
|
25
|
+
status: str = "direct" # direct | derived
|
|
26
|
+
note: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
DLMF_FACTS = (
|
|
30
|
+
DLMFFact(
|
|
31
|
+
"besseli",
|
|
32
|
+
"positive/increasing",
|
|
33
|
+
"10.37",
|
|
34
|
+
"https://dlmf.nist.gov/10.37",
|
|
35
|
+
note="fixed nu>=0, x>0",
|
|
36
|
+
),
|
|
37
|
+
DLMFFact(
|
|
38
|
+
"besselk",
|
|
39
|
+
"positive/decreasing",
|
|
40
|
+
"10.37",
|
|
41
|
+
"https://dlmf.nist.gov/10.37",
|
|
42
|
+
note="fixed nu>=0, x>0",
|
|
43
|
+
),
|
|
44
|
+
DLMFFact(
|
|
45
|
+
"besselk",
|
|
46
|
+
"order symmetry",
|
|
47
|
+
"10.27.3",
|
|
48
|
+
"https://dlmf.nist.gov/10.27.E3",
|
|
49
|
+
note="K_-nu(z)=K_nu(z)",
|
|
50
|
+
),
|
|
51
|
+
DLMFFact(
|
|
52
|
+
"lowergamma",
|
|
53
|
+
"integral/domain",
|
|
54
|
+
"8.2.1",
|
|
55
|
+
"https://dlmf.nist.gov/8.2.E1",
|
|
56
|
+
note="Re(a)>0",
|
|
57
|
+
),
|
|
58
|
+
DLMFFact("uppergamma", "integral/domain", "8.2.2", "https://dlmf.nist.gov/8.2.E2"),
|
|
59
|
+
DLMFFact(
|
|
60
|
+
"beta",
|
|
61
|
+
"Euler integral/domain",
|
|
62
|
+
"5.12.1",
|
|
63
|
+
"https://dlmf.nist.gov/5.12.E1",
|
|
64
|
+
note="Re(a)>0, Re(b)>0",
|
|
65
|
+
),
|
|
66
|
+
DLMFFact(
|
|
67
|
+
"betainc",
|
|
68
|
+
"integral/domain/range",
|
|
69
|
+
"8.17.1",
|
|
70
|
+
"https://dlmf.nist.gov/8.17.E1",
|
|
71
|
+
note="a>0, b>0, 0<=x<=1",
|
|
72
|
+
),
|
|
73
|
+
DLMFFact(
|
|
74
|
+
"betainc_regularized",
|
|
75
|
+
"normalization/domain/range",
|
|
76
|
+
"8.17.2",
|
|
77
|
+
"https://dlmf.nist.gov/8.17.E2",
|
|
78
|
+
note="a>0, b>0, 0<=x<=1",
|
|
79
|
+
),
|
|
80
|
+
DLMFFact("gamma", "meromorphic/no zeros/poles", "5.2", "https://dlmf.nist.gov/5.2"),
|
|
81
|
+
DLMFFact("digamma", "meromorphic/poles", "5.2.2", "https://dlmf.nist.gov/5.2.E2"),
|
|
82
|
+
DLMFFact(
|
|
83
|
+
"hyper",
|
|
84
|
+
"series/termination/principal branch",
|
|
85
|
+
"16.2.1",
|
|
86
|
+
"https://dlmf.nist.gov/16.2.E1",
|
|
87
|
+
note="p<=q entire; p=q+1 principal branch cut [1,oo) unless terminating",
|
|
88
|
+
),
|
|
89
|
+
DLMFFact(
|
|
90
|
+
"assoc_legendre",
|
|
91
|
+
"Ferrers zero distribution",
|
|
92
|
+
"14.16",
|
|
93
|
+
"https://dlmf.nist.gov/14.16",
|
|
94
|
+
note="integer-degree/order specialization gives n-|m| interior zeros on (-1,1)",
|
|
95
|
+
),
|
|
96
|
+
DLMFFact(
|
|
97
|
+
"assoc_legendre",
|
|
98
|
+
"principal complex branch",
|
|
99
|
+
"14.21",
|
|
100
|
+
"https://dlmf.nist.gov/14.21",
|
|
101
|
+
note="principal branches use the standard cut to 1, with polynomial/degenerate exceptions handled separately",
|
|
102
|
+
),
|
|
103
|
+
DLMFFact(
|
|
104
|
+
"elliptic_f",
|
|
105
|
+
"incomplete integral branch radicand",
|
|
106
|
+
"19.2.4",
|
|
107
|
+
"https://dlmf.nist.gov/19.2.E4",
|
|
108
|
+
note="principal sqrt of 1-k^2 sin^2(theta)",
|
|
109
|
+
),
|
|
110
|
+
DLMFFact(
|
|
111
|
+
"elliptic_e",
|
|
112
|
+
"incomplete integral branch radicand",
|
|
113
|
+
"19.2.5",
|
|
114
|
+
"https://dlmf.nist.gov/19.2.E5",
|
|
115
|
+
note="principal sqrt of 1-k^2 sin^2(theta)",
|
|
116
|
+
),
|
|
117
|
+
DLMFFact(
|
|
118
|
+
"elliptic_pi",
|
|
119
|
+
"incomplete third-kind path singularity",
|
|
120
|
+
"19.2.7",
|
|
121
|
+
"https://dlmf.nist.gov/19.2.E7",
|
|
122
|
+
note="parameter singularity requires path-aware treatment; endpoint-only tests are insufficient",
|
|
123
|
+
),
|
|
124
|
+
DLMFFact(
|
|
125
|
+
"jacobi",
|
|
126
|
+
"zeros/interlacing",
|
|
127
|
+
"18.16.1",
|
|
128
|
+
"https://dlmf.nist.gov/18.16.E1",
|
|
129
|
+
note="n simple zeros in (-1,1) for alpha,beta>-1; consecutive degrees interlace by 18.2(vi)",
|
|
130
|
+
),
|
|
131
|
+
DLMFFact(
|
|
132
|
+
"assoc_laguerre",
|
|
133
|
+
"zeros/interlacing",
|
|
134
|
+
"18.16.9",
|
|
135
|
+
"https://dlmf.nist.gov/18.16.E9",
|
|
136
|
+
note="n simple positive zeros for alpha>-1",
|
|
137
|
+
),
|
|
138
|
+
DLMFFact(
|
|
139
|
+
"chebyshevt",
|
|
140
|
+
"trigonometric representation/zeros",
|
|
141
|
+
"18.5.1",
|
|
142
|
+
"https://dlmf.nist.gov/18.5.E1",
|
|
143
|
+
),
|
|
144
|
+
DLMFFact(
|
|
145
|
+
"chebyshevu",
|
|
146
|
+
"trigonometric representation/zeros",
|
|
147
|
+
"18.5.2",
|
|
148
|
+
"https://dlmf.nist.gov/18.5.E2",
|
|
149
|
+
),
|
|
150
|
+
DLMFFact(
|
|
151
|
+
"jn",
|
|
152
|
+
"positive zeros",
|
|
153
|
+
"10.58.1",
|
|
154
|
+
"https://dlmf.nist.gov/10.58.E1",
|
|
155
|
+
note="spherical Bessel zeros inherit ordinary Bessel zero structure",
|
|
156
|
+
),
|
|
157
|
+
DLMFFact(
|
|
158
|
+
"yn",
|
|
159
|
+
"positive zeros",
|
|
160
|
+
"10.58.1",
|
|
161
|
+
"https://dlmf.nist.gov/10.58.E1",
|
|
162
|
+
note="spherical Bessel zeros inherit ordinary Bessel zero structure",
|
|
163
|
+
),
|
|
164
|
+
DLMFFact(
|
|
165
|
+
"multigamma",
|
|
166
|
+
"domain/product",
|
|
167
|
+
"35.3.1,35.3.4",
|
|
168
|
+
"https://dlmf.nist.gov/35.3.E1",
|
|
169
|
+
note="Re(a)>(p-1)/2; product formula 35.3.E4",
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def dlmf_facts(family: str | None = None):
|
|
175
|
+
"""Return executable and formula-audit DLMF facts.
|
|
176
|
+
|
|
177
|
+
The generated audit contributes one provenance record per exact formula or
|
|
178
|
+
narrowly scoped DLMF section. Helper/wrapper families are marked
|
|
179
|
+
``derived`` rather than being misrepresented as DLMF-native functions.
|
|
180
|
+
"""
|
|
181
|
+
facts = list(DLMF_FACTS)
|
|
182
|
+
try:
|
|
183
|
+
import json
|
|
184
|
+
from importlib.resources import files
|
|
185
|
+
|
|
186
|
+
data = json.loads(
|
|
187
|
+
files("funcprops")
|
|
188
|
+
.joinpath("data/dlmf_audit.json")
|
|
189
|
+
.read_text(encoding="utf-8")
|
|
190
|
+
)
|
|
191
|
+
seen = {(f.family, f.section) for f in facts}
|
|
192
|
+
for row in data.get("families", ()):
|
|
193
|
+
if row.get("status") not in {
|
|
194
|
+
"formula-verified-core",
|
|
195
|
+
"formula-verified-derived",
|
|
196
|
+
}:
|
|
197
|
+
continue
|
|
198
|
+
status = (
|
|
199
|
+
"derived" if row.get("verification_kind") == "derived" else "direct"
|
|
200
|
+
)
|
|
201
|
+
for ref in row.get("formula_refs", ()):
|
|
202
|
+
key = (row["family_id"], ref)
|
|
203
|
+
if key in seen:
|
|
204
|
+
continue
|
|
205
|
+
facts.append(
|
|
206
|
+
DLMFFact(
|
|
207
|
+
row["family_id"],
|
|
208
|
+
row.get("formula_verified_properties", "formula audit"),
|
|
209
|
+
ref,
|
|
210
|
+
f"https://dlmf.nist.gov/{ref}",
|
|
211
|
+
status=status,
|
|
212
|
+
note="formula-level catalogue audit",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
seen.add(key)
|
|
216
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
217
|
+
pass
|
|
218
|
+
return tuple(f for f in facts if family is None or f.family == family)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _real_guard(a):
|
|
222
|
+
return S.true if a.is_real is True else sp.Contains(a, S.Reals)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _positive_axis(domain, var):
|
|
226
|
+
try:
|
|
227
|
+
from .common import condition_to_set
|
|
228
|
+
|
|
229
|
+
ds = condition_to_set(domain, var)
|
|
230
|
+
if (
|
|
231
|
+
isinstance(ds, sp.Interval)
|
|
232
|
+
and ds.start == 0
|
|
233
|
+
and ds.left_open
|
|
234
|
+
and ds.end is S.Infinity
|
|
235
|
+
):
|
|
236
|
+
return True
|
|
237
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
238
|
+
pass
|
|
239
|
+
try:
|
|
240
|
+
return sp.simplify_logic(sp.Implies(domain, var > 0)) is S.true
|
|
241
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def parameter_guard(expr, var, prop):
|
|
246
|
+
expr = sp.sympify(expr)
|
|
247
|
+
var = sp.sympify(var)
|
|
248
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
249
|
+
nu = expr.args[0]
|
|
250
|
+
if prop in {
|
|
251
|
+
"Positive",
|
|
252
|
+
"NonNegative",
|
|
253
|
+
"Range",
|
|
254
|
+
"GlobalMonotonicity",
|
|
255
|
+
"GlobalConvexity",
|
|
256
|
+
}:
|
|
257
|
+
# 10.37 gives nu>=0; 10.27.3 extends K to all real order.
|
|
258
|
+
return _real_guard(nu)
|
|
259
|
+
if expr.func is sp.besseli and len(expr.args) == 2 and expr.args[1] == var:
|
|
260
|
+
nu = expr.args[0]
|
|
261
|
+
if prop in {"Positive", "NonNegative", "Range", "GlobalMonotonicity"}:
|
|
262
|
+
return sp.And(_real_guard(nu), nu >= 0)
|
|
263
|
+
return S.true
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def positive_axis_domain_strata(expr, var):
|
|
267
|
+
"""Sufficient exact real-domain strata on x>0.
|
|
268
|
+
|
|
269
|
+
These are not used to claim maximal domains outside the stated guards;
|
|
270
|
+
they provide parameter-conditioned branches that can be composed safely.
|
|
271
|
+
"""
|
|
272
|
+
expr = sp.sympify(expr)
|
|
273
|
+
var = sp.sympify(var)
|
|
274
|
+
if (
|
|
275
|
+
expr.func in (sp.lowergamma, sp.uppergamma)
|
|
276
|
+
and len(expr.args) == 2
|
|
277
|
+
and expr.args[1] == var
|
|
278
|
+
):
|
|
279
|
+
a = expr.args[0]
|
|
280
|
+
return ((var > 0, a > 0, "DLMF 8.2.1-8.2.2"),)
|
|
281
|
+
if expr.func is sp.beta and len(expr.args) == 2 and expr.args[1] == var:
|
|
282
|
+
a = expr.args[0]
|
|
283
|
+
return ((var > 0, a > 0, "DLMF 5.12.1 + Gamma quotient"),)
|
|
284
|
+
if expr.func is sp.besseli and len(expr.args) == 2 and expr.args[1] == var:
|
|
285
|
+
nu = expr.args[0]
|
|
286
|
+
return ((var > 0, sp.And(_real_guard(nu), nu >= 0), "DLMF 10.37"),)
|
|
287
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
288
|
+
nu = expr.args[0]
|
|
289
|
+
return ((var > 0, _real_guard(nu), "DLMF 10.37 + 10.27.3"),)
|
|
290
|
+
if (
|
|
291
|
+
expr.func in (sp.betainc, sp.betainc_regularized)
|
|
292
|
+
and len(expr.args) == 4
|
|
293
|
+
and expr.args[2] == 0
|
|
294
|
+
and expr.args[3] == var
|
|
295
|
+
):
|
|
296
|
+
a, b = expr.args[:2]
|
|
297
|
+
return (
|
|
298
|
+
(sp.And(var >= 0, var <= 1), sp.And(a > 0, b > 0), "DLMF 8.17.1-8.17.2"),
|
|
299
|
+
)
|
|
300
|
+
return ()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def positive_axis_range_strata(expr, var, y):
|
|
304
|
+
"""Exact ranges on x>0, including parameter-dependent endpoints."""
|
|
305
|
+
expr = sp.sympify(expr)
|
|
306
|
+
var = sp.sympify(var)
|
|
307
|
+
y = sp.sympify(y)
|
|
308
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
309
|
+
nu = expr.args[0]
|
|
310
|
+
return ((y > 0, _real_guard(nu), "DLMF 10.37 + 10.27.3 + endpoint limits"),)
|
|
311
|
+
if expr.func is sp.besseli and len(expr.args) == 2 and expr.args[1] == var:
|
|
312
|
+
nu = expr.args[0]
|
|
313
|
+
rg = _real_guard(nu)
|
|
314
|
+
# I_0(x) -> 1 as x->0+, while I_nu(x)->0 for nu>0; all diverge to
|
|
315
|
+
# +oo at +oo. The endpoint is not attained because x>0.
|
|
316
|
+
return (
|
|
317
|
+
(y > 1, sp.And(rg, sp.Eq(nu, 0)), "DLMF 10.37 + 10.30 limiting forms"),
|
|
318
|
+
(y > 0, sp.And(rg, nu > 0), "DLMF 10.37 + 10.30 limiting forms"),
|
|
319
|
+
)
|
|
320
|
+
if expr.func is sp.beta and len(expr.args) == 2 and expr.args[1] == var:
|
|
321
|
+
a = expr.args[0]
|
|
322
|
+
return ((y > 0, a > 0, "DLMF 5.12.1 + Gamma quotient limits"),)
|
|
323
|
+
if expr.func is sp.lowergamma and len(expr.args) == 2 and expr.args[1] == var:
|
|
324
|
+
a = expr.args[0]
|
|
325
|
+
return (
|
|
326
|
+
(sp.And(y > 0, y < sp.gamma(a)), a > 0, "DLMF 8.2.1 + endpoint limits"),
|
|
327
|
+
)
|
|
328
|
+
if expr.func is sp.uppergamma and len(expr.args) == 2 and expr.args[1] == var:
|
|
329
|
+
a = expr.args[0]
|
|
330
|
+
return (
|
|
331
|
+
(sp.And(y > 0, y < sp.gamma(a)), a > 0, "DLMF 8.2.2 + endpoint limits"),
|
|
332
|
+
)
|
|
333
|
+
return ()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def special_function_range_strata(expr, var, y, domain):
|
|
337
|
+
"""Exact DLMF-backed ranges with domain-sensitive endpoint attainment."""
|
|
338
|
+
from .common import condition_to_set
|
|
339
|
+
|
|
340
|
+
expr = sp.sympify(expr)
|
|
341
|
+
var = sp.sympify(var)
|
|
342
|
+
y = sp.sympify(y)
|
|
343
|
+
ds = condition_to_set(domain, var)
|
|
344
|
+
pos_open = (
|
|
345
|
+
isinstance(ds, sp.Interval)
|
|
346
|
+
and ds.start == 0
|
|
347
|
+
and ds.left_open
|
|
348
|
+
and ds.end is S.Infinity
|
|
349
|
+
)
|
|
350
|
+
pos_closed = (
|
|
351
|
+
isinstance(ds, sp.Interval)
|
|
352
|
+
and ds.start == 0
|
|
353
|
+
and not ds.left_open
|
|
354
|
+
and ds.end is S.Infinity
|
|
355
|
+
)
|
|
356
|
+
unit_open = (
|
|
357
|
+
isinstance(ds, sp.Interval)
|
|
358
|
+
and ds.start == 0
|
|
359
|
+
and ds.end == 1
|
|
360
|
+
and ds.left_open
|
|
361
|
+
and ds.right_open
|
|
362
|
+
)
|
|
363
|
+
unit_closed = (
|
|
364
|
+
isinstance(ds, sp.Interval)
|
|
365
|
+
and ds.start == 0
|
|
366
|
+
and ds.end == 1
|
|
367
|
+
and not ds.left_open
|
|
368
|
+
and not ds.right_open
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
expr.func is sp.besseli
|
|
373
|
+
and len(expr.args) == 2
|
|
374
|
+
and expr.args[1] == var
|
|
375
|
+
and (pos_open or pos_closed)
|
|
376
|
+
):
|
|
377
|
+
nu = expr.args[0]
|
|
378
|
+
rg = _real_guard(nu)
|
|
379
|
+
if pos_open:
|
|
380
|
+
return (
|
|
381
|
+
(y > 1, sp.And(rg, sp.Eq(nu, 0)), "DLMF 10.37, 10.30, 10.40"),
|
|
382
|
+
(y > 0, sp.And(rg, nu > 0), "DLMF 10.37, 10.30, 10.40"),
|
|
383
|
+
)
|
|
384
|
+
return (
|
|
385
|
+
(y >= 1, sp.And(rg, sp.Eq(nu, 0)), "DLMF 10.37, 10.30, 10.40"),
|
|
386
|
+
(y >= 0, sp.And(rg, nu > 0), "DLMF 10.37, 10.30, 10.40"),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if (
|
|
390
|
+
expr.func in (sp.lowergamma, sp.uppergamma)
|
|
391
|
+
and len(expr.args) == 2
|
|
392
|
+
and expr.args[1] == var
|
|
393
|
+
and (pos_open or pos_closed)
|
|
394
|
+
):
|
|
395
|
+
a = expr.args[0]
|
|
396
|
+
G = sp.gamma(a)
|
|
397
|
+
if expr.func is sp.lowergamma:
|
|
398
|
+
cond = sp.And(y > 0, y < G) if pos_open else sp.And(y >= 0, y < G)
|
|
399
|
+
else:
|
|
400
|
+
cond = sp.And(y > 0, y < G) if pos_open else sp.And(y > 0, y <= G)
|
|
401
|
+
return ((cond, a > 0, "DLMF 8.2.1-8.2.2 + endpoint limits"),)
|
|
402
|
+
|
|
403
|
+
if (
|
|
404
|
+
expr.func in (sp.betainc, sp.betainc_regularized)
|
|
405
|
+
and len(expr.args) == 4
|
|
406
|
+
and expr.args[2] == 0
|
|
407
|
+
and expr.args[3] == var
|
|
408
|
+
and (unit_open or unit_closed)
|
|
409
|
+
):
|
|
410
|
+
a, b = expr.args[:2]
|
|
411
|
+
guard = sp.And(a > 0, b > 0)
|
|
412
|
+
endpoint = S.One if expr.func is sp.betainc_regularized else sp.beta(a, b)
|
|
413
|
+
cond = (
|
|
414
|
+
sp.And(y > 0, y < endpoint) if unit_open else sp.And(y >= 0, y <= endpoint)
|
|
415
|
+
)
|
|
416
|
+
return ((cond, guard, "DLMF 8.17.1-8.17.3"),)
|
|
417
|
+
return ()
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def positive_axis_monotonicity_strata(expr, var):
|
|
421
|
+
from ..results import Monotonicity
|
|
422
|
+
|
|
423
|
+
expr = sp.sympify(expr)
|
|
424
|
+
var = sp.sympify(var)
|
|
425
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
426
|
+
return (
|
|
427
|
+
(
|
|
428
|
+
Monotonicity.STRICTLY_DECREASING,
|
|
429
|
+
_real_guard(expr.args[0]),
|
|
430
|
+
"DLMF 10.37 + 10.27.3",
|
|
431
|
+
),
|
|
432
|
+
)
|
|
433
|
+
if expr.func is sp.besseli and len(expr.args) == 2 and expr.args[1] == var:
|
|
434
|
+
nu = expr.args[0]
|
|
435
|
+
return (
|
|
436
|
+
(
|
|
437
|
+
Monotonicity.STRICTLY_INCREASING,
|
|
438
|
+
sp.And(_real_guard(nu), nu >= 0),
|
|
439
|
+
"DLMF 10.37",
|
|
440
|
+
),
|
|
441
|
+
)
|
|
442
|
+
if expr.func is sp.beta and len(expr.args) == 2 and expr.args[1] == var:
|
|
443
|
+
return (
|
|
444
|
+
(
|
|
445
|
+
Monotonicity.STRICTLY_DECREASING,
|
|
446
|
+
expr.args[0] > 0,
|
|
447
|
+
"derived from digamma monotonicity / beta quotient",
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
if expr.func is sp.lowergamma and len(expr.args) == 2 and expr.args[1] == var:
|
|
451
|
+
return (
|
|
452
|
+
(
|
|
453
|
+
Monotonicity.STRICTLY_INCREASING,
|
|
454
|
+
expr.args[0] > 0,
|
|
455
|
+
"DLMF 8.2.1 integrand > 0",
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
if expr.func is sp.uppergamma and len(expr.args) == 2 and expr.args[1] == var:
|
|
459
|
+
return (
|
|
460
|
+
(
|
|
461
|
+
Monotonicity.STRICTLY_DECREASING,
|
|
462
|
+
expr.args[0] > 0,
|
|
463
|
+
"DLMF 8.2.2 integrand > 0",
|
|
464
|
+
),
|
|
465
|
+
)
|
|
466
|
+
return ()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def positive_axis_sign_strata(expr, var):
|
|
470
|
+
from ..results import Sign
|
|
471
|
+
|
|
472
|
+
expr = sp.sympify(expr)
|
|
473
|
+
var = sp.sympify(var)
|
|
474
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
475
|
+
return ((Sign.POSITIVE, _real_guard(expr.args[0]), "DLMF 10.37 + 10.27.3"),)
|
|
476
|
+
if expr.func is sp.besseli and len(expr.args) == 2 and expr.args[1] == var:
|
|
477
|
+
nu = expr.args[0]
|
|
478
|
+
return ((Sign.POSITIVE, sp.And(_real_guard(nu), nu >= 0), "DLMF 10.37"),)
|
|
479
|
+
if expr.func is sp.beta and len(expr.args) == 2 and expr.args[1] == var:
|
|
480
|
+
return ((Sign.POSITIVE, expr.args[0] > 0, "DLMF 5.12.1"),)
|
|
481
|
+
if (
|
|
482
|
+
expr.func in (sp.lowergamma, sp.uppergamma)
|
|
483
|
+
and len(expr.args) == 2
|
|
484
|
+
and expr.args[1] == var
|
|
485
|
+
):
|
|
486
|
+
return ((Sign.POSITIVE, expr.args[0] > 0, "DLMF 8.2.1-8.2.2"),)
|
|
487
|
+
if expr.func is sp.polygamma and len(expr.args) == 2 and expr.args[1] == var:
|
|
488
|
+
n = expr.args[0]
|
|
489
|
+
pos = sp.And(sp.Contains(n, S.Integers), n >= 1, sp.Eq(sp.Mod(n, 2), 1))
|
|
490
|
+
neg = sp.And(sp.Contains(n, S.Integers), n >= 1, sp.Eq(sp.Mod(n, 2), 0))
|
|
491
|
+
return (
|
|
492
|
+
(Sign.POSITIVE, pos, "polygamma series"),
|
|
493
|
+
(Sign.NEGATIVE, neg, "polygamma series"),
|
|
494
|
+
)
|
|
495
|
+
return ()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def positive_axis_convexity_strata(expr, var):
|
|
499
|
+
from ..results import Convexity
|
|
500
|
+
|
|
501
|
+
expr = sp.sympify(expr)
|
|
502
|
+
var = sp.sympify(var)
|
|
503
|
+
if expr.func is sp.besselk and len(expr.args) == 2 and expr.args[1] == var:
|
|
504
|
+
return (
|
|
505
|
+
(
|
|
506
|
+
Convexity.STRICTLY_CONVEX,
|
|
507
|
+
_real_guard(expr.args[0]),
|
|
508
|
+
"DLMF-backed integral/complete monotonicity",
|
|
509
|
+
),
|
|
510
|
+
)
|
|
511
|
+
if expr.func is sp.beta and len(expr.args) == 2 and expr.args[1] == var:
|
|
512
|
+
return (
|
|
513
|
+
(Convexity.STRICTLY_CONVEX, expr.args[0] > 0, "derived from beta quotient"),
|
|
514
|
+
)
|
|
515
|
+
return ()
|