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.
Files changed (56) hide show
  1. funcprops/__init__.py +234 -0
  2. funcprops/_semialg.py +246 -0
  3. funcprops/api.py +70 -0
  4. funcprops/branchcuts/__init__.py +141 -0
  5. funcprops/branchcuts/computation.py +664 -0
  6. funcprops/branchcuts/core.py +1204 -0
  7. funcprops/branchcuts/post_simplify.py +461 -0
  8. funcprops/conditions.py +124 -0
  9. funcprops/data/catalogue.json +127449 -0
  10. funcprops/data/dlmf_audit.json +3382 -0
  11. funcprops/dlmf.py +29 -0
  12. funcprops/engine.py +324 -0
  13. funcprops/exceptions.py +29 -0
  14. funcprops/proof_cache.py +373 -0
  15. funcprops/real_analysis/__init__.py +52 -0
  16. funcprops/real_analysis/advanced_convexity.py +217 -0
  17. funcprops/real_analysis/boundedness.py +168 -0
  18. funcprops/real_analysis/common.py +244 -0
  19. funcprops/real_analysis/convexity.py +698 -0
  20. funcprops/real_analysis/differentiability.py +112 -0
  21. funcprops/real_analysis/domain.py +188 -0
  22. funcprops/real_analysis/mapping.py +374 -0
  23. funcprops/real_analysis/monotonic_certificates.py +867 -0
  24. funcprops/real_analysis/monotonicity.py +673 -0
  25. funcprops/real_analysis/oscillation.py +151 -0
  26. funcprops/real_analysis/piecewise.py +174 -0
  27. funcprops/real_analysis/range.py +552 -0
  28. funcprops/real_analysis/sign.py +561 -0
  29. funcprops/real_analysis/smoothness.py +268 -0
  30. funcprops/real_analysis/special_functions.py +515 -0
  31. funcprops/real_analysis/structural_properties.py +558 -0
  32. funcprops/real_analysis/zeros.py +327 -0
  33. funcprops/registry.py +274 -0
  34. funcprops/results.py +239 -0
  35. funcprops/singularities.py +1767 -0
  36. funcprops/stratification.py +494 -0
  37. funcprops/sympy_compare.py +63 -0
  38. funcprops/transcendental/__init__.py +140 -0
  39. funcprops/transcendental/algebraize.py +201 -0
  40. funcprops/transcendental/analytic.py +261 -0
  41. funcprops/transcendental/complex_roots.py +542 -0
  42. funcprops/transcendental/exp_fourier.py +221 -0
  43. funcprops/transcendental/extrema.py +821 -0
  44. funcprops/transcendental/periodic.py +222 -0
  45. funcprops/transcendental/proof.py +104 -0
  46. funcprops/transcendental/real_interval.py +128 -0
  47. funcprops/transcendental/real_sign.py +117 -0
  48. funcprops/transcendental/reduce.py +288 -0
  49. funcprops/transcendental/roots.py +1380 -0
  50. funcprops/transcendental/zero_geometry.py +1197 -0
  51. funcprops/verification.py +410 -0
  52. funcprops-0.1.0.dist-info/METADATA +208 -0
  53. funcprops-0.1.0.dist-info/RECORD +56 -0
  54. funcprops-0.1.0.dist-info/WHEEL +5 -0
  55. funcprops-0.1.0.dist-info/licenses/LICENSE +674 -0
  56. 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
+ )