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,268 @@
|
|
|
1
|
+
"""Joint continuity, differentiability, smoothness, and analyticity analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import sympy as sp
|
|
8
|
+
from sympy import S
|
|
9
|
+
|
|
10
|
+
from .. import _semialg
|
|
11
|
+
from ..exceptions import SYMBOLIC_QUERY_ERRORS
|
|
12
|
+
from ..results import Analyticity, Continuity
|
|
13
|
+
from ..singularities import (
|
|
14
|
+
function_analytic,
|
|
15
|
+
function_continuous,
|
|
16
|
+
function_discontinuities,
|
|
17
|
+
)
|
|
18
|
+
from ..stratification import evaluate_parameter_strata
|
|
19
|
+
from .common import (
|
|
20
|
+
combine_natural_domain_condition,
|
|
21
|
+
condition_to_set,
|
|
22
|
+
)
|
|
23
|
+
from .domain import function_domain
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class SmoothnessResult:
|
|
28
|
+
"""Certified regularity information on a requested real domain."""
|
|
29
|
+
|
|
30
|
+
continuity: Continuity
|
|
31
|
+
differentiability_order: int | sp.Expr | None
|
|
32
|
+
smooth: bool | None
|
|
33
|
+
analytic: Analyticity
|
|
34
|
+
exceptional_loci: tuple = ()
|
|
35
|
+
evidence: tuple = ()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _exception_loci(records):
|
|
39
|
+
"""Return unique singularity conditions while preserving source order."""
|
|
40
|
+
loci = []
|
|
41
|
+
for record in records:
|
|
42
|
+
condition = getattr(record, "condition", record)
|
|
43
|
+
if condition not in loci:
|
|
44
|
+
loci.append(condition)
|
|
45
|
+
return tuple(loci)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _locus_intersects_domain(locus, domain, var):
|
|
49
|
+
"""Conservatively decide whether a locus can meet a requested domain."""
|
|
50
|
+
domain_set = condition_to_set(domain, var)
|
|
51
|
+
locus_set = condition_to_set(locus, var)
|
|
52
|
+
if domain_set is not None and locus_set is not None:
|
|
53
|
+
try:
|
|
54
|
+
overlap = sp.Intersection(domain_set, locus_set)
|
|
55
|
+
except (TypeError, ValueError, NotImplementedError):
|
|
56
|
+
overlap = None
|
|
57
|
+
if overlap is not None:
|
|
58
|
+
if overlap is S.EmptySet or overlap == S.EmptySet:
|
|
59
|
+
return False
|
|
60
|
+
is_empty = getattr(overlap, "is_empty", None)
|
|
61
|
+
if is_empty is True:
|
|
62
|
+
return False
|
|
63
|
+
if is_empty is False:
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
satisfiable = sp.satisfiable(sp.And(sp.sympify(domain), sp.sympify(locus)))
|
|
68
|
+
except (TypeError, ValueError, NotImplementedError):
|
|
69
|
+
return True
|
|
70
|
+
return satisfiable is not False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _domain_loci(records, domain, var):
|
|
74
|
+
return tuple(
|
|
75
|
+
locus
|
|
76
|
+
for locus in _exception_loci(records)
|
|
77
|
+
if _locus_intersects_domain(locus, domain, var)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _structurally_smooth(expr, var):
|
|
82
|
+
"""Recognize globally smooth expression trees without derivative probing.
|
|
83
|
+
|
|
84
|
+
The rule set intentionally contains only closure operations that cannot
|
|
85
|
+
introduce a real singularity: polynomial arithmetic, nonnegative integer
|
|
86
|
+
powers, and composition with entire elementary functions.
|
|
87
|
+
"""
|
|
88
|
+
expr = sp.sympify(expr)
|
|
89
|
+
if not expr.has(var):
|
|
90
|
+
return True
|
|
91
|
+
if expr == var:
|
|
92
|
+
return True
|
|
93
|
+
if expr.is_Add or expr.is_Mul:
|
|
94
|
+
return all(_structurally_smooth(arg, var) for arg in expr.args)
|
|
95
|
+
if expr.is_Pow:
|
|
96
|
+
base, exponent = expr.args
|
|
97
|
+
return bool(exponent.is_Integer and exponent.is_nonnegative) and (
|
|
98
|
+
_structurally_smooth(base, var)
|
|
99
|
+
)
|
|
100
|
+
entire_heads = {sp.exp, sp.sin, sp.cos, sp.sinh, sp.cosh, sp.erf}
|
|
101
|
+
if expr.func in entire_heads and len(expr.args) == 1:
|
|
102
|
+
return _structurally_smooth(expr.args[0], var)
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def function_smoothness(expr, var, *, domain=True, max_order=6, _domain_ready=False):
|
|
107
|
+
"""Analyze regularity up to ``max_order`` on the requested real domain.
|
|
108
|
+
|
|
109
|
+
Finite derivative probing never certifies :math:`C^\\infty`. Smoothness is
|
|
110
|
+
true only when analyticity proves it; it is false only after a witnessed
|
|
111
|
+
finite-order failure, and otherwise remains unknown.
|
|
112
|
+
"""
|
|
113
|
+
expr = sp.sympify(expr)
|
|
114
|
+
var = sp.sympify(var)
|
|
115
|
+
if isinstance(max_order, bool) or not isinstance(max_order, int) or max_order < 1:
|
|
116
|
+
raise ValueError("max_order must be a positive integer")
|
|
117
|
+
|
|
118
|
+
natural_domain = function_domain(expr, var)
|
|
119
|
+
requested_domain = (
|
|
120
|
+
sp.sympify(domain)
|
|
121
|
+
if _domain_ready
|
|
122
|
+
else combine_natural_domain_condition(natural_domain, domain, var)
|
|
123
|
+
)
|
|
124
|
+
if not _domain_ready:
|
|
125
|
+
split = evaluate_parameter_strata(
|
|
126
|
+
requested_domain,
|
|
127
|
+
var,
|
|
128
|
+
lambda branch_domain: function_smoothness(
|
|
129
|
+
expr,
|
|
130
|
+
var,
|
|
131
|
+
domain=branch_domain,
|
|
132
|
+
max_order=max_order,
|
|
133
|
+
_domain_ready=True,
|
|
134
|
+
),
|
|
135
|
+
source="smoothness-parameter-stratification",
|
|
136
|
+
)
|
|
137
|
+
if split is not None:
|
|
138
|
+
return split
|
|
139
|
+
analyticity = function_analytic(expr, var, domain=requested_domain)
|
|
140
|
+
if analyticity is Analyticity.ANALYTIC:
|
|
141
|
+
continuity = function_continuous(expr, var, domain=requested_domain)
|
|
142
|
+
exceptions = list(
|
|
143
|
+
_domain_loci(function_discontinuities(expr, var), requested_domain, var)
|
|
144
|
+
)
|
|
145
|
+
return SmoothnessResult(
|
|
146
|
+
continuity,
|
|
147
|
+
sp.oo,
|
|
148
|
+
True,
|
|
149
|
+
analyticity,
|
|
150
|
+
tuple(exceptions),
|
|
151
|
+
("analytic-implies-smooth",),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
backend = None
|
|
155
|
+
if _semialg.eligible_problem(expr, var, domain=requested_domain):
|
|
156
|
+
try:
|
|
157
|
+
backend = _semialg.function_smoothness(
|
|
158
|
+
expr, var, domain=requested_domain, max_order=max_order
|
|
159
|
+
)
|
|
160
|
+
# Negative/finite regularity results are safe to use immediately.
|
|
161
|
+
# A backend claim of C-infinity is validated below through the
|
|
162
|
+
# requested finite derivative order before it can strengthen the
|
|
163
|
+
# public result.
|
|
164
|
+
if backend.smooth is not True:
|
|
165
|
+
continuity = (
|
|
166
|
+
Continuity.CONTINUOUS
|
|
167
|
+
if backend.continuous is True
|
|
168
|
+
else Continuity.DISCONTINUOUS
|
|
169
|
+
if backend.continuous is False
|
|
170
|
+
else Continuity.UNKNOWN
|
|
171
|
+
)
|
|
172
|
+
exceptions = [
|
|
173
|
+
backend.continuity_exceptions,
|
|
174
|
+
*backend.derivative_exceptions.values(),
|
|
175
|
+
]
|
|
176
|
+
exceptions = [
|
|
177
|
+
locus for locus in exceptions if locus not in (S.false, False)
|
|
178
|
+
]
|
|
179
|
+
return SmoothnessResult(
|
|
180
|
+
continuity,
|
|
181
|
+
backend.differentiability_order,
|
|
182
|
+
backend.smooth,
|
|
183
|
+
analyticity,
|
|
184
|
+
tuple(exceptions),
|
|
185
|
+
(("semialg", backend),),
|
|
186
|
+
)
|
|
187
|
+
except (_semialg.SemialgUnavailable, *SYMBOLIC_QUERY_ERRORS):
|
|
188
|
+
backend = None
|
|
189
|
+
|
|
190
|
+
continuity = function_continuous(expr, var, domain=requested_domain)
|
|
191
|
+
exceptions = list(
|
|
192
|
+
_domain_loci(function_discontinuities(expr, var), requested_domain, var)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if continuity is Continuity.CONTINUOUS and _structurally_smooth(expr, var):
|
|
196
|
+
return SmoothnessResult(
|
|
197
|
+
continuity,
|
|
198
|
+
sp.oo,
|
|
199
|
+
True,
|
|
200
|
+
analyticity,
|
|
201
|
+
tuple(exceptions),
|
|
202
|
+
("smooth-structural-closure",),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
order = 0 if continuity is Continuity.CONTINUOUS else None
|
|
206
|
+
failure_proved = False
|
|
207
|
+
domain_set = condition_to_set(requested_domain, var)
|
|
208
|
+
if domain_set is None:
|
|
209
|
+
return SmoothnessResult(
|
|
210
|
+
continuity, order, None, analyticity, tuple(exceptions), ()
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
from ..proof_cache import property_proof_context
|
|
214
|
+
|
|
215
|
+
context = property_proof_context(expr, var, domain=requested_domain)
|
|
216
|
+
for derivative_order in range(1, max_order + 1):
|
|
217
|
+
try:
|
|
218
|
+
derivative = context.derivative(derivative_order)
|
|
219
|
+
except (TypeError, ValueError, NotImplementedError):
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
loci = _domain_loci(
|
|
223
|
+
function_discontinuities(derivative, var), requested_domain, var
|
|
224
|
+
)
|
|
225
|
+
for locus in loci:
|
|
226
|
+
if locus not in exceptions:
|
|
227
|
+
exceptions.append(locus)
|
|
228
|
+
if loci:
|
|
229
|
+
failure_proved = True
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
continuous_domain = sp.calculus.util.continuous_domain(
|
|
234
|
+
derivative, var, domain_set
|
|
235
|
+
)
|
|
236
|
+
except (TypeError, ValueError, NotImplementedError):
|
|
237
|
+
# An unsupported continuity query is uncertainty, not evidence of
|
|
238
|
+
# either continuity or discontinuity.
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
if continuous_domain == domain_set:
|
|
242
|
+
order = derivative_order
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
missing = sp.Complement(domain_set, continuous_domain)
|
|
246
|
+
if missing is not S.EmptySet and missing != S.EmptySet:
|
|
247
|
+
failure_proved = True
|
|
248
|
+
break
|
|
249
|
+
|
|
250
|
+
smooth = False if failure_proved else None
|
|
251
|
+
evidence = (("verified-derivatives-through", order),) if order is not None else ()
|
|
252
|
+
if (
|
|
253
|
+
not failure_proved
|
|
254
|
+
and backend is not None
|
|
255
|
+
and backend.smooth is True
|
|
256
|
+
and order == max_order
|
|
257
|
+
):
|
|
258
|
+
order = sp.oo
|
|
259
|
+
smooth = True
|
|
260
|
+
evidence = (*evidence, ("semialg", backend))
|
|
261
|
+
return SmoothnessResult(
|
|
262
|
+
continuity,
|
|
263
|
+
order,
|
|
264
|
+
smooth,
|
|
265
|
+
analyticity,
|
|
266
|
+
tuple(exceptions),
|
|
267
|
+
evidence,
|
|
268
|
+
)
|