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,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import sympy as sp
|
|
7
|
+
from sympy import S
|
|
8
|
+
|
|
9
|
+
from ..exceptions import SYMBOLIC_QUERY_ERRORS
|
|
10
|
+
from ..real_analysis.common import condition_to_set, set_to_condition
|
|
11
|
+
from .periodic import PeriodicCondition, PeriodicSet, common_positive_period
|
|
12
|
+
from .roots import transcendental_roots
|
|
13
|
+
|
|
14
|
+
_REL = (sp.Eq, sp.Ne, sp.Lt, sp.Le, sp.Gt, sp.Ge)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class UnivariateReductionResult:
|
|
19
|
+
condition: object
|
|
20
|
+
complete: bool
|
|
21
|
+
cells: tuple[sp.Set, ...] = ()
|
|
22
|
+
roots: tuple[sp.Expr, ...] = ()
|
|
23
|
+
method: str = "univariate_cells"
|
|
24
|
+
evidence: tuple[object, ...] = ()
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def certified(self):
|
|
28
|
+
return self.complete
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _normalize_relations(condition):
|
|
32
|
+
condition = sp.sympify(condition)
|
|
33
|
+
replacements = {}
|
|
34
|
+
for rel in condition.atoms(sp.core.relational.Relational):
|
|
35
|
+
lhs = sp.simplify(rel.lhs - rel.rhs)
|
|
36
|
+
replacements[rel] = rel.func(lhs, S.Zero)
|
|
37
|
+
return condition.xreplace(replacements)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _lhs_expressions(condition):
|
|
41
|
+
return tuple(
|
|
42
|
+
sorted(
|
|
43
|
+
{
|
|
44
|
+
sp.simplify(rel.lhs)
|
|
45
|
+
for rel in condition.atoms(sp.core.relational.Relational)
|
|
46
|
+
},
|
|
47
|
+
key=sp.default_sort_key,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _bool_at(condition, var, value):
|
|
53
|
+
try:
|
|
54
|
+
q = sp.simplify(condition.subs(var, value))
|
|
55
|
+
if q in (S.true, S.false):
|
|
56
|
+
return bool(q)
|
|
57
|
+
q = sp.refine(q)
|
|
58
|
+
if q in (S.true, S.false):
|
|
59
|
+
return bool(q)
|
|
60
|
+
return bool(q) if q.is_Boolean and q in (S.true, S.false) else None
|
|
61
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sample(a, b):
|
|
66
|
+
if a is -sp.oo and b is sp.oo:
|
|
67
|
+
return S.Zero
|
|
68
|
+
if a is -sp.oo:
|
|
69
|
+
return sp.simplify(b - 1)
|
|
70
|
+
if b is sp.oo:
|
|
71
|
+
return sp.simplify(a + 1)
|
|
72
|
+
return sp.simplify((a + b) / 2)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _singular_points(expr, var, interval):
|
|
76
|
+
"""Certified finite real discontinuity/singularity points in an interval."""
|
|
77
|
+
try:
|
|
78
|
+
from sympy.calculus.util import continuous_domain
|
|
79
|
+
|
|
80
|
+
cd = continuous_domain(expr, var, interval)
|
|
81
|
+
missing = sp.Complement(interval, cd)
|
|
82
|
+
if missing is S.EmptySet:
|
|
83
|
+
return (), True
|
|
84
|
+
if isinstance(missing, sp.FiniteSet):
|
|
85
|
+
return tuple(missing), True
|
|
86
|
+
# Complements/unions that simplify to finitely many points.
|
|
87
|
+
simp = sp.simplify(missing)
|
|
88
|
+
if isinstance(simp, sp.FiniteSet):
|
|
89
|
+
return tuple(simp), True
|
|
90
|
+
return (), False
|
|
91
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
92
|
+
# Rational denominators give exact poles even when continuous_domain
|
|
93
|
+
# declines a more complicated transcendental expression.
|
|
94
|
+
try:
|
|
95
|
+
den = sp.denom(sp.together(expr))
|
|
96
|
+
if den != 1:
|
|
97
|
+
sol = sp.solveset(sp.Eq(den, 0), var, domain=interval)
|
|
98
|
+
if isinstance(sol, sp.FiniteSet):
|
|
99
|
+
return tuple(sol), True
|
|
100
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
101
|
+
pass
|
|
102
|
+
return (), False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _reduce_interval(condition, var, interval, allow_numerical):
|
|
106
|
+
"""Reduce a Boolean condition on one interval using certified event points.
|
|
107
|
+
|
|
108
|
+
Roots and singularities partition the interval into cells. A result is marked
|
|
109
|
+
complete only when every event search is complete and every cell truth value
|
|
110
|
+
is certified.
|
|
111
|
+
"""
|
|
112
|
+
lhs = _lhs_expressions(condition)
|
|
113
|
+
roots = []
|
|
114
|
+
evidence = []
|
|
115
|
+
for f in lhs:
|
|
116
|
+
singular, sing_complete = _singular_points(f, var, interval)
|
|
117
|
+
roots.extend(singular)
|
|
118
|
+
evidence.append(("singularities", f, singular, sing_complete))
|
|
119
|
+
if not sing_complete:
|
|
120
|
+
return UnivariateReductionResult(
|
|
121
|
+
condition,
|
|
122
|
+
False,
|
|
123
|
+
evidence=tuple(evidence),
|
|
124
|
+
method="incomplete_singularities",
|
|
125
|
+
)
|
|
126
|
+
rr = transcendental_roots(f, var, interval, allow_numerical=allow_numerical)
|
|
127
|
+
evidence.append(rr)
|
|
128
|
+
if not rr.complete:
|
|
129
|
+
return UnivariateReductionResult(
|
|
130
|
+
condition, False, evidence=tuple(evidence), method="incomplete_roots"
|
|
131
|
+
)
|
|
132
|
+
if rr.periodic is not None:
|
|
133
|
+
# Bounded interval: let solveset/intersection enumerate the exact points.
|
|
134
|
+
try:
|
|
135
|
+
s = sp.solveset(sp.Eq(f, 0), var, domain=interval)
|
|
136
|
+
if isinstance(s, sp.FiniteSet):
|
|
137
|
+
roots.extend(s)
|
|
138
|
+
else:
|
|
139
|
+
return UnivariateReductionResult(
|
|
140
|
+
condition,
|
|
141
|
+
False,
|
|
142
|
+
evidence=tuple(evidence),
|
|
143
|
+
method="periodic_unexpanded",
|
|
144
|
+
)
|
|
145
|
+
except SYMBOLIC_QUERY_ERRORS:
|
|
146
|
+
return UnivariateReductionResult(
|
|
147
|
+
condition,
|
|
148
|
+
False,
|
|
149
|
+
evidence=tuple(evidence),
|
|
150
|
+
method="periodic_unexpanded",
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
roots.extend(rr.roots)
|
|
154
|
+
roots = tuple(sorted(set(map(sp.simplify, roots)), key=sp.default_sort_key))
|
|
155
|
+
interior = [
|
|
156
|
+
r
|
|
157
|
+
for r in roots
|
|
158
|
+
if r != interval.start
|
|
159
|
+
and r != interval.end
|
|
160
|
+
and sp.Contains(r, interval) is not S.false
|
|
161
|
+
]
|
|
162
|
+
boundaries = [interval.start, *interior, interval.end]
|
|
163
|
+
cells = []
|
|
164
|
+
true_parts = []
|
|
165
|
+
for a, b in itertools.pairwise(boundaries):
|
|
166
|
+
if a == b:
|
|
167
|
+
continue
|
|
168
|
+
sample = _sample(a, b)
|
|
169
|
+
val = _bool_at(condition, var, sample)
|
|
170
|
+
if val is None:
|
|
171
|
+
return UnivariateReductionResult(
|
|
172
|
+
condition,
|
|
173
|
+
False,
|
|
174
|
+
tuple(cells),
|
|
175
|
+
roots,
|
|
176
|
+
"undecidable_sample",
|
|
177
|
+
tuple(evidence),
|
|
178
|
+
)
|
|
179
|
+
cell = sp.Interval.open(a, b)
|
|
180
|
+
cells.append(cell)
|
|
181
|
+
if val:
|
|
182
|
+
true_parts.append(cell)
|
|
183
|
+
for r in roots:
|
|
184
|
+
if sp.Contains(r, interval) is S.false:
|
|
185
|
+
continue
|
|
186
|
+
val = _bool_at(condition, var, r)
|
|
187
|
+
if val is None:
|
|
188
|
+
continue
|
|
189
|
+
cell = sp.FiniteSet(r)
|
|
190
|
+
cells.append(cell)
|
|
191
|
+
if val:
|
|
192
|
+
true_parts.append(cell)
|
|
193
|
+
# Include finite closed endpoints that are not roots of a relation.
|
|
194
|
+
for endpoint, is_open in (
|
|
195
|
+
(interval.start, interval.left_open),
|
|
196
|
+
(interval.end, interval.right_open),
|
|
197
|
+
):
|
|
198
|
+
if endpoint in (-sp.oo, sp.oo) or is_open or endpoint in roots:
|
|
199
|
+
continue
|
|
200
|
+
val = _bool_at(condition, var, endpoint)
|
|
201
|
+
if val:
|
|
202
|
+
true_parts.append(sp.FiniteSet(endpoint))
|
|
203
|
+
solution = sp.Union(*true_parts) if true_parts else S.EmptySet
|
|
204
|
+
return UnivariateReductionResult(
|
|
205
|
+
set_to_condition(solution, var),
|
|
206
|
+
True,
|
|
207
|
+
tuple(cells),
|
|
208
|
+
roots,
|
|
209
|
+
"univariate_cells",
|
|
210
|
+
tuple(evidence),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def reduce_univariate_condition(
|
|
215
|
+
condition, var, domain=S.Reals, *, allow_numerical=False
|
|
216
|
+
):
|
|
217
|
+
"""Certified real-univariate reduction using exact real-univariate reduction.
|
|
218
|
+
|
|
219
|
+
Every relational boundary and discontinuity supplied by the caller should be
|
|
220
|
+
represented as a relation in ``condition``. The reducer isolates all roots,
|
|
221
|
+
partitions the domain, samples each open cell and each boundary point, then
|
|
222
|
+
reconstructs the exact solution. It refuses certification if any root set
|
|
223
|
+
is incomplete.
|
|
224
|
+
"""
|
|
225
|
+
condition = _normalize_relations(condition)
|
|
226
|
+
var = sp.sympify(var)
|
|
227
|
+
dset = domain if isinstance(domain, sp.Set) else condition_to_set(domain, var)
|
|
228
|
+
if dset is None:
|
|
229
|
+
return UnivariateReductionResult(condition, False, method="unsupported_domain")
|
|
230
|
+
|
|
231
|
+
lhs = _lhs_expressions(condition)
|
|
232
|
+
if dset is S.Reals and lhs:
|
|
233
|
+
p = common_positive_period(lhs, var)
|
|
234
|
+
if p is not None:
|
|
235
|
+
fi = sp.Interval.Ropen(0, p)
|
|
236
|
+
base = _reduce_interval(condition, var, fi, allow_numerical)
|
|
237
|
+
if base.complete:
|
|
238
|
+
bset = condition_to_set(base.condition, var)
|
|
239
|
+
if bset is not None:
|
|
240
|
+
ps = PeriodicSet(sp.Intersection(bset, fi), p, var, S.Zero)
|
|
241
|
+
return UnivariateReductionResult(
|
|
242
|
+
PeriodicCondition(ps),
|
|
243
|
+
True,
|
|
244
|
+
base.cells,
|
|
245
|
+
base.roots,
|
|
246
|
+
"periodic_cells",
|
|
247
|
+
base.evidence,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
intervals = []
|
|
251
|
+
if isinstance(dset, sp.Interval):
|
|
252
|
+
intervals = [dset]
|
|
253
|
+
elif isinstance(dset, sp.Union) and all(
|
|
254
|
+
isinstance(a, sp.Interval) for a in dset.args
|
|
255
|
+
):
|
|
256
|
+
intervals = list(dset.args)
|
|
257
|
+
else:
|
|
258
|
+
return UnivariateReductionResult(
|
|
259
|
+
condition, False, method="unsupported_domain_set"
|
|
260
|
+
)
|
|
261
|
+
parts = []
|
|
262
|
+
allcells = []
|
|
263
|
+
allroots = []
|
|
264
|
+
evidence = []
|
|
265
|
+
for interval in intervals:
|
|
266
|
+
r = _reduce_interval(condition, var, interval, allow_numerical)
|
|
267
|
+
evidence.extend(r.evidence)
|
|
268
|
+
if not r.complete:
|
|
269
|
+
return UnivariateReductionResult(
|
|
270
|
+
condition,
|
|
271
|
+
False,
|
|
272
|
+
tuple(allcells),
|
|
273
|
+
tuple(allroots),
|
|
274
|
+
r.method,
|
|
275
|
+
tuple(evidence),
|
|
276
|
+
)
|
|
277
|
+
parts.append(r.condition)
|
|
278
|
+
allcells.extend(r.cells)
|
|
279
|
+
allroots.extend(r.roots)
|
|
280
|
+
out = sp.Or(*parts) if parts else S.false
|
|
281
|
+
return UnivariateReductionResult(
|
|
282
|
+
sp.simplify(out),
|
|
283
|
+
True,
|
|
284
|
+
tuple(allcells),
|
|
285
|
+
tuple(allroots),
|
|
286
|
+
"univariate_cells",
|
|
287
|
+
tuple(evidence),
|
|
288
|
+
)
|