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,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
+ )