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
funcprops/__init__.py ADDED
@@ -0,0 +1,234 @@
1
+ """Symbolic function properties for SymPy expressions."""
2
+
3
+ from .api import (
4
+ function_analytic,
5
+ function_bijective,
6
+ function_boundedness,
7
+ function_branchcuts,
8
+ function_branchpoints,
9
+ function_complex_zeros,
10
+ function_continuous,
11
+ function_convexity,
12
+ function_differentiability,
13
+ function_discontinuities,
14
+ function_domain,
15
+ function_homogeneity,
16
+ function_injective,
17
+ function_meromorphic,
18
+ function_monotonicity,
19
+ function_oscillation,
20
+ function_periodicity,
21
+ function_poles,
22
+ function_range,
23
+ function_sign,
24
+ function_singularities,
25
+ function_singularity_analysis,
26
+ function_smoothness,
27
+ function_surjective,
28
+ function_symmetry,
29
+ function_zeros,
30
+ )
31
+ from .dlmf import DLMFFact, dlmf_audit_report, dlmf_facts
32
+ from .real_analysis.advanced_convexity import ConvexityCertificate
33
+ from .real_analysis.monotonic_certificates import (
34
+ IndexedMonotonicPartition,
35
+ InverseSolutionCertificate,
36
+ MonotonicDecomposition,
37
+ MonotonicInterval,
38
+ MonotonicPartition,
39
+ NonMonotonicityWitness,
40
+ PartitionRange,
41
+ certified_monotonic_inverse,
42
+ clear_monotonic_partition_cache,
43
+ injectivity_from_monotonic_partition,
44
+ monotonic_decomposition,
45
+ monotonic_partition,
46
+ range_from_monotonic_partition,
47
+ solve_monotonic_inverse,
48
+ )
49
+ from .real_analysis.oscillation import OscillationCertificate
50
+ from .real_analysis.smoothness import SmoothnessResult
51
+ from .real_analysis.structural_properties import (
52
+ HomogeneityResult,
53
+ PeriodicityResult,
54
+ SymmetryResult,
55
+ )
56
+ from .registry import Family, FunctionRegistry, RuleCase, registry
57
+ from .results import *
58
+ from .singularities import SingularityAnalysis
59
+ from .transcendental import (
60
+ AlgebraizationResult,
61
+ AnalyticNeighborhoodCertificate,
62
+ ArgumentPrincipleCertificate,
63
+ CertifiedComplexRootRectangle,
64
+ CertifiedIntegerBracket,
65
+ CertifiedRootInterval,
66
+ ComplexRectangle,
67
+ ComplexRootIsolationResult,
68
+ ExponentialFourierCount,
69
+ ExtremumEnvelope,
70
+ ExtremumFamilyCertificate,
71
+ ExtremumOrderingTheorem,
72
+ ExtremumRange,
73
+ IndexedExtremumValue,
74
+ IndexedTailIndex,
75
+ IndexedTailInversionCertificate,
76
+ IndexedZero,
77
+ IntervalPropertyCell,
78
+ ParametricZeroFamily,
79
+ PeriodicCondition,
80
+ PeriodicSet,
81
+ PropertyIntervalDecomposition,
82
+ RootCertificate,
83
+ RootIsolationResult,
84
+ UnivariateReductionResult,
85
+ ZeroGeometryCertificate,
86
+ ZeroInterlacing,
87
+ ZeroOrdering,
88
+ algebraize,
89
+ analytic_neighborhood,
90
+ argument_principle_count,
91
+ exponential_fourier_count,
92
+ exponential_polynomial_terms,
93
+ extremum_geometry,
94
+ extremum_ordering_theorems,
95
+ holomorphic_on_rectangle,
96
+ indexed_tail_integer_bracket,
97
+ indexed_tail_inversion,
98
+ isolate_complex_roots,
99
+ property_interval_decomposition,
100
+ reduce_univariate_condition,
101
+ robust_real_sign,
102
+ simplify_extremum_range,
103
+ symbolic_extremum_range,
104
+ transcendental_roots,
105
+ zero_geometry,
106
+ zero_ordering_bounds,
107
+ )
108
+ from .verification import (
109
+ verify_certificate,
110
+ verify_convexity_certificate,
111
+ verify_extremum_family_certificate,
112
+ verify_extremum_ordering_theorem,
113
+ verify_extremum_range,
114
+ verify_indexed_tail_inversion,
115
+ verify_integer_bracket,
116
+ verify_monotonic_decomposition,
117
+ verify_monotonic_partition,
118
+ verify_root_certificate,
119
+ verify_zero_geometry,
120
+ )
121
+
122
+ __all__ = [
123
+ name
124
+ for name in globals()
125
+ if name.startswith("function_")
126
+ or name
127
+ in {
128
+ "registry",
129
+ "FunctionRegistry",
130
+ "Family",
131
+ "RuleCase",
132
+ "Analyticity",
133
+ "Meromorphicity",
134
+ "Monotonicity",
135
+ "Convexity",
136
+ "Sign",
137
+ "Boundedness",
138
+ "Continuity",
139
+ "Differentiability",
140
+ "TruthValue",
141
+ "Periodicity",
142
+ "Symmetry",
143
+ "Homogeneity",
144
+ "PropertyResult",
145
+ "DetailedPropertyResult",
146
+ "SupportsDetailedPropertyResult",
147
+ "as_detailed_result",
148
+ "ConditionalValue",
149
+ "Singularity",
150
+ "SingularityKind",
151
+ "SingularityAnalysis",
152
+ "PeriodicSet",
153
+ "PeriodicCondition",
154
+ "RootCertificate",
155
+ "CertifiedRootInterval",
156
+ "ComplexRectangle",
157
+ "ArgumentPrincipleCertificate",
158
+ "CertifiedComplexRootRectangle",
159
+ "ComplexRootIsolationResult",
160
+ "ParametricZeroFamily",
161
+ "RootIsolationResult",
162
+ "IndexedZero",
163
+ "ZeroOrdering",
164
+ "ZeroInterlacing",
165
+ "ZeroGeometryCertificate",
166
+ "IntervalPropertyCell",
167
+ "PropertyIntervalDecomposition",
168
+ "zero_geometry",
169
+ "property_interval_decomposition",
170
+ "zero_ordering_bounds",
171
+ "IndexedExtremumValue",
172
+ "ExtremumEnvelope",
173
+ "ExtremumFamilyCertificate",
174
+ "ExtremumOrderingTheorem",
175
+ "ExtremumRange",
176
+ "ExponentialFourierCount",
177
+ "extremum_geometry",
178
+ "extremum_ordering_theorems",
179
+ "simplify_extremum_range",
180
+ "symbolic_extremum_range",
181
+ "AnalyticNeighborhoodCertificate",
182
+ "UnivariateReductionResult",
183
+ "PeriodicityResult",
184
+ "SymmetryResult",
185
+ "HomogeneityResult",
186
+ "MonotonicDecomposition",
187
+ "MonotonicInterval",
188
+ "IndexedMonotonicPartition",
189
+ "MonotonicPartition",
190
+ "NonMonotonicityWitness",
191
+ "InverseSolutionCertificate",
192
+ "PartitionRange",
193
+ "OscillationCertificate",
194
+ "monotonic_decomposition",
195
+ "monotonic_partition",
196
+ "solve_monotonic_inverse",
197
+ "range_from_monotonic_partition",
198
+ "injectivity_from_monotonic_partition",
199
+ "certified_monotonic_inverse",
200
+ "clear_monotonic_partition_cache",
201
+ "IndexedTailIndex",
202
+ "IndexedTailInversionCertificate",
203
+ "CertifiedIntegerBracket",
204
+ "indexed_tail_inversion",
205
+ "indexed_tail_integer_bracket",
206
+ "SmoothnessResult",
207
+ "ConvexityCertificate",
208
+ "DLMFFact",
209
+ "dlmf_facts",
210
+ "dlmf_audit_report",
211
+ "verify_certificate",
212
+ "verify_monotonic_decomposition",
213
+ "verify_monotonic_partition",
214
+ "verify_zero_geometry",
215
+ "verify_convexity_certificate",
216
+ "verify_extremum_ordering_theorem",
217
+ "verify_indexed_tail_inversion",
218
+ "verify_root_certificate",
219
+ "verify_extremum_family_certificate",
220
+ "verify_extremum_range",
221
+ "verify_integer_bracket",
222
+ "AlgebraizationResult",
223
+ "transcendental_roots",
224
+ "robust_real_sign",
225
+ "exponential_fourier_count",
226
+ "exponential_polynomial_terms",
227
+ "reduce_univariate_condition",
228
+ "algebraize",
229
+ "argument_principle_count",
230
+ "isolate_complex_roots",
231
+ "holomorphic_on_rectangle",
232
+ "analytic_neighborhood",
233
+ }
234
+ ]
funcprops/_semialg.py ADDED
@@ -0,0 +1,246 @@
1
+ """Adapter to semialg's public exact real-function API.
2
+
3
+ funcprops owns the public property vocabulary and evidence objects. semialg
4
+ provides the exact backend for semialgebraic functions on semialgebraic real
5
+ domains, including domain, range, sign, monotonicity, convexity, smoothness,
6
+ and mapping-property calculations. Imports remain lazy so catalogue and
7
+ complex-analysis queries do not load the backend until a real-domain proof
8
+ needs it.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Callable
14
+ from functools import cache
15
+
16
+ import sympy as sp
17
+
18
+
19
+ class SemialgUnavailable(ImportError):
20
+ """Raised when the required semialg public API cannot be loaded."""
21
+
22
+
23
+ @cache
24
+ def _public_function(name: str) -> Callable:
25
+ """Return one function from semialg's documented root API."""
26
+ try:
27
+ import semialg
28
+ except ImportError as exc:
29
+ raise SemialgUnavailable(
30
+ "funcprops real-analysis queries require semialg>=1.1.0"
31
+ ) from exc
32
+
33
+ try:
34
+ function = getattr(semialg, name)
35
+ except AttributeError as exc:
36
+ raise SemialgUnavailable(
37
+ f"semialg>=1.1.0 does not provide the required public function {name!r}"
38
+ ) from exc
39
+ if not callable(function):
40
+ raise SemialgUnavailable(f"semialg public attribute {name!r} is not callable")
41
+ return function
42
+
43
+
44
+ def function_domain(expr, variable):
45
+ """Return semialg's recognized real-domain condition."""
46
+ function = _public_function("function_domain")
47
+ return function(sp.sympify(expr), [variable])
48
+
49
+
50
+ def function_range(
51
+ expr, variable, value_symbol, *, constraints=True, method="qe", **kwargs
52
+ ):
53
+ """Return an exact real-range condition from semialg."""
54
+ function = _public_function("function_range")
55
+ return function(
56
+ sp.sympify(expr),
57
+ constraints,
58
+ [variable],
59
+ value_symbol=value_symbol,
60
+ method=method,
61
+ **kwargs,
62
+ )
63
+
64
+
65
+ def function_convexity(
66
+ expr, variable, *, domain=True, strict_inequalities=True, **kwargs
67
+ ):
68
+ """Return semialg's strongest certified convexity classification."""
69
+ properties = kwargs.pop("properties", "primary")
70
+ return_result = kwargs.pop("return_result", False)
71
+ parameters = kwargs.pop("parameters", None)
72
+ if kwargs:
73
+ names = ", ".join(sorted(kwargs))
74
+ raise TypeError(f"unsupported semialg convexity options: {names}")
75
+
76
+ # semialg computes the strongest exact class. funcprops weakens the class
77
+ # afterwards when callers request non-strict semantics.
78
+ del strict_inequalities
79
+ function = _public_function("function_convexity")
80
+ return function(
81
+ sp.sympify(expr),
82
+ [variable],
83
+ domain=domain,
84
+ parameters=parameters,
85
+ return_result=return_result,
86
+ properties=properties,
87
+ )
88
+
89
+
90
+ def function_monotonicity(expr, variable, *, domain=True, return_result=False):
91
+ """Return semialg's exact monotonicity classification."""
92
+ function = _public_function("function_monotonicity")
93
+ return function(
94
+ sp.sympify(expr),
95
+ variable,
96
+ domain=domain,
97
+ return_result=return_result,
98
+ )
99
+
100
+
101
+ def function_smoothness(expr, variable, *, domain=True, max_order=6):
102
+ """Return semialg's exact regularity result on a semialgebraic domain."""
103
+ function = _public_function("function_smoothness")
104
+ return function(
105
+ sp.sympify(expr),
106
+ variable,
107
+ domain=domain,
108
+ max_order=max_order,
109
+ )
110
+
111
+
112
+ def function_mapping_properties(
113
+ expr, variable, *, domain=True, codomain=True, image_variable=None
114
+ ):
115
+ """Return semialg's exact mapping properties for a semialgebraic map."""
116
+ function = _public_function("function_mapping_properties")
117
+ image_variables = None if image_variable is None else (image_variable,)
118
+ return function(
119
+ expr,
120
+ variable,
121
+ domain=domain,
122
+ codomain=codomain,
123
+ image_variables=image_variables,
124
+ )
125
+
126
+
127
+ def eligible_problem(expr, variable, *, domain=True) -> bool:
128
+ """Return whether an expression/domain pair is safe for semialg delegation."""
129
+ from .real_analysis.common import (
130
+ is_semialgebraic_condition,
131
+ is_semialgebraic_expression,
132
+ )
133
+
134
+ return is_semialgebraic_expression(expr, variable) and is_semialgebraic_condition(
135
+ domain, variable
136
+ )
137
+
138
+
139
+ def is_satisfiable(condition, variables):
140
+ """Decide satisfiability of a semialgebraic parameter guard exactly."""
141
+ function = _public_function("is_satisfiable")
142
+ normalized = tuple(sp.sympify(v) for v in variables)
143
+ return bool(function(sp.sympify(condition), normalized))
144
+
145
+
146
+ def function_sign(expr, variable, *, assumptions=True, return_result=False):
147
+ """Return semialg's canonical exact sign classification."""
148
+ function = _public_function("function_sign")
149
+ return function(
150
+ sp.sympify(expr),
151
+ [variable],
152
+ assumptions=assumptions,
153
+ return_result=return_result,
154
+ )
155
+
156
+
157
+ def function_sign_partition(expr, variable, *, domain=True):
158
+ """Return semialg's exact univariate sign partition."""
159
+ function = _public_function("function_sign_partition")
160
+ return function(sp.sympify(expr), variable, domain=domain)
161
+
162
+
163
+ def normalize_sign(value):
164
+ """Translate a semialg sign classification into funcprops' Sign enum."""
165
+ from .results import Sign
166
+
167
+ classification = getattr(value, "classification", value)
168
+ return {
169
+ "positive": Sign.POSITIVE,
170
+ "negative": Sign.NEGATIVE,
171
+ "zero": Sign.ZERO,
172
+ "nonnegative": Sign.NONNEGATIVE,
173
+ "nonpositive": Sign.NONPOSITIVE,
174
+ "mixed": Sign.SIGN_CHANGING,
175
+ }.get(str(classification), Sign.UNKNOWN)
176
+
177
+
178
+ def normalize_convexity(value):
179
+ """Translate semialg convexity vocabulary into funcprops' enum."""
180
+ from .results import Convexity
181
+
182
+ if isinstance(value, Convexity):
183
+ return value
184
+ if hasattr(value, "primary"):
185
+ value = value.primary
186
+ if isinstance(value, int):
187
+ return {
188
+ 1: Convexity.STRICTLY_CONVEX,
189
+ 2: Convexity.CONVEX,
190
+ -1: Convexity.STRICTLY_CONCAVE,
191
+ -2: Convexity.CONCAVE,
192
+ }.get(int(value), Convexity.UNKNOWN)
193
+ key = str(getattr(value, "name", value)).lower().replace("_", "").replace(" ", "")
194
+ return {
195
+ "stronglyconvex": Convexity.STRONGLY_CONVEX,
196
+ "strictlyconvex": Convexity.STRICTLY_CONVEX,
197
+ "convex": Convexity.CONVEX,
198
+ "affine": Convexity.AFFINE,
199
+ "strictlyconcave": Convexity.STRICTLY_CONCAVE,
200
+ "concave": Convexity.CONCAVE,
201
+ "stronglyconcave": Convexity.STRONGLY_CONCAVE,
202
+ "neither": Convexity.NEITHER,
203
+ }.get(key, Convexity.UNKNOWN)
204
+
205
+
206
+ def normalize_monotonicity(value):
207
+ """Translate semialg monotonicity vocabulary into funcprops' enum."""
208
+ from .results import Monotonicity
209
+
210
+ classification = getattr(value, "classification", value)
211
+ return {
212
+ "strictly_increasing": Monotonicity.STRICTLY_INCREASING,
213
+ "increasing": Monotonicity.NONDECREASING,
214
+ "constant": Monotonicity.CONSTANT,
215
+ "decreasing": Monotonicity.NONINCREASING,
216
+ "strictly_decreasing": Monotonicity.STRICTLY_DECREASING,
217
+ "nonmonotonic": Monotonicity.NOT_MONOTONE,
218
+ }.get(str(classification), Monotonicity.UNKNOWN)
219
+
220
+
221
+ def normalize_truth(value):
222
+ """Translate a backend boolean into funcprops' three-valued truth type."""
223
+ from .results import TruthValue
224
+
225
+ if value is True:
226
+ return TruthValue.TRUE
227
+ if value is False:
228
+ return TruthValue.FALSE
229
+ return TruthValue.UNKNOWN
230
+
231
+
232
+ def prove_sign(expr, variable, relation, *, assumptions=True):
233
+ """Prove one exact sign relation with semialg's public sign provers."""
234
+ prover_names = {
235
+ "positive": "prove_positive",
236
+ "negative": "prove_negative",
237
+ "nonnegative": "prove_nonnegative",
238
+ "nonpositive": "prove_nonpositive",
239
+ }
240
+ try:
241
+ prover_name = prover_names[relation]
242
+ except KeyError as exc:
243
+ raise ValueError(f"unknown sign relation: {relation!r}") from exc
244
+
245
+ prover = _public_function(prover_name)
246
+ return prover(sp.sympify(expr), [variable], assumptions=assumptions)
funcprops/api.py ADDED
@@ -0,0 +1,70 @@
1
+ """Public routing layer for function-property queries.
2
+
3
+ Each public query has a single implementation in either :mod:`funcprops.singularities`
4
+ or :mod:`funcprops.real_analysis`. This module only re-exports those implementations
5
+ so callers have one stable import surface without duplicating property logic.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .real_analysis.boundedness import function_boundedness
11
+ from .real_analysis.convexity import function_convexity
12
+ from .real_analysis.differentiability import function_differentiability
13
+ from .real_analysis.domain import function_domain
14
+ from .real_analysis.mapping import (
15
+ function_bijective,
16
+ function_injective,
17
+ function_surjective,
18
+ )
19
+ from .real_analysis.monotonicity import function_monotonicity
20
+ from .real_analysis.oscillation import function_oscillation
21
+ from .real_analysis.range import function_range
22
+ from .real_analysis.sign import function_sign
23
+ from .real_analysis.smoothness import function_smoothness
24
+ from .real_analysis.structural_properties import (
25
+ function_homogeneity,
26
+ function_periodicity,
27
+ function_symmetry,
28
+ )
29
+ from .real_analysis.zeros import function_zeros
30
+ from .singularities import (
31
+ function_analytic,
32
+ function_branchcuts,
33
+ function_branchpoints,
34
+ function_continuous,
35
+ function_discontinuities,
36
+ function_meromorphic,
37
+ function_poles,
38
+ function_singularities,
39
+ function_singularity_analysis,
40
+ )
41
+ from .transcendental.complex_roots import function_complex_zeros
42
+
43
+ __all__ = [
44
+ "function_analytic",
45
+ "function_bijective",
46
+ "function_boundedness",
47
+ "function_branchcuts",
48
+ "function_branchpoints",
49
+ "function_complex_zeros",
50
+ "function_continuous",
51
+ "function_convexity",
52
+ "function_differentiability",
53
+ "function_discontinuities",
54
+ "function_domain",
55
+ "function_homogeneity",
56
+ "function_injective",
57
+ "function_meromorphic",
58
+ "function_monotonicity",
59
+ "function_oscillation",
60
+ "function_periodicity",
61
+ "function_poles",
62
+ "function_range",
63
+ "function_sign",
64
+ "function_singularities",
65
+ "function_singularity_analysis",
66
+ "function_smoothness",
67
+ "function_surjective",
68
+ "function_symmetry",
69
+ "function_zeros",
70
+ ]
@@ -0,0 +1,141 @@
1
+ """Symbolic principal-branch cuts, boundary jumps, and restrictions.
2
+
3
+ The low-level API computes cut conditions and jumps. The property-level
4
+ :func:`funcprops.function_branchcuts` query converts those loci into the common
5
+ funcprops singularity vocabulary.
6
+ """
7
+
8
+ from ..engine import simplify_condition
9
+ from .computation import (
10
+ branch_cut_jumps,
11
+ branch_cut_jumps_pairs,
12
+ condition_false_q,
13
+ condition_simplify,
14
+ differentiate_pairs,
15
+ jump_pairs_from_table,
16
+ numeric_branch_cut_jumps,
17
+ unsatisfiable_q,
18
+ )
19
+ from .core import (
20
+ IMPLEMENTED_RULE_NAMES,
21
+ BranchRule,
22
+ ConditionalJump,
23
+ JumpTerm,
24
+ RestrictedExpr,
25
+ Undefined,
26
+ apply_restrictions,
27
+ branch_jump,
28
+ get_branch_rule,
29
+ propagate_undefined,
30
+ restrict,
31
+ simplify_restricted,
32
+ )
33
+ from .post_simplify import (
34
+ merge_duplicate_pairs,
35
+ post_simplify_pairs,
36
+ post_simplify_restricted,
37
+ rewrite_branch_expr,
38
+ simplify_branch_condition,
39
+ )
40
+
41
+
42
+ def branch_cut_conditions(expr, variable, *, assumptions=True):
43
+ """Return unique symbolic principal-cut conditions for ``expr``.
44
+
45
+ This property query is intentionally cheaper than full jump propagation.
46
+ It pulls primitive cut rules back through arguments and recursively includes
47
+ inner cuts, but does not form plus/minus boundary-value products.
48
+ """
49
+ from sympy import Dummy, S, sympify
50
+
51
+ expr = sympify(expr)
52
+ variable = sympify(variable)
53
+ out = []
54
+ seen = set()
55
+
56
+ def add(cond):
57
+ cond = simplify_condition(sympify(cond), assumptions=assumptions)
58
+ if cond is S.false:
59
+ return
60
+ key = repr(cond)
61
+ if key not in seen:
62
+ seen.add(key)
63
+ out.append(cond)
64
+
65
+ def visit(e):
66
+ e = sympify(e)
67
+ if not e.has(variable):
68
+ return
69
+ # Exact primitive rule first.
70
+ try:
71
+ rule = get_branch_rule(e, variable)
72
+ except (NotImplementedError, ValueError, TypeError, AttributeError):
73
+ rule = None
74
+ if rule is not None:
75
+ for term in rule.jump_terms:
76
+ add(term.cond)
77
+ else:
78
+ # Pull a one-variable primitive rule back through each varying argument.
79
+ for i, arg in enumerate(getattr(e, "args", ())):
80
+ if not getattr(arg, "has", lambda *_: False)(variable):
81
+ continue
82
+ u = Dummy("funcprops_branch_arg")
83
+ args = list(e.args)
84
+ args[i] = u
85
+ try:
86
+ template = e.func(*args)
87
+ rule = get_branch_rule(template, u)
88
+ except (NotImplementedError, ValueError, TypeError, AttributeError):
89
+ continue
90
+ for term in rule.jump_terms:
91
+ add(term.cond.xreplace({u: arg}))
92
+ # Branches inside arguments remain branches of the composite expression.
93
+ for arg in getattr(e, "args", ()):
94
+ if getattr(arg, "has", lambda *_: False)(variable):
95
+ visit(arg)
96
+
97
+ visit(expr)
98
+ return tuple(out)
99
+
100
+
101
+ def coverage_summary():
102
+ """Return BranchCuts-rule coverage against the funcprops catalogue."""
103
+ from ..registry import registry
104
+
105
+ families = list(registry())
106
+ return {
107
+ "implemented_rules": {"total": len(IMPLEMENTED_RULE_NAMES)},
108
+ "sympy": {"total": sum(bool(f.sympy_names) for f in families)},
109
+ "mpmath": {"total": sum(bool(f.mpmath_names) for f in families)},
110
+ }
111
+
112
+
113
+ __all__ = [
114
+ "IMPLEMENTED_RULE_NAMES",
115
+ "BranchRule",
116
+ "ConditionalJump",
117
+ "JumpTerm",
118
+ "RestrictedExpr",
119
+ "Undefined",
120
+ "apply_restrictions",
121
+ "branch_cut_conditions",
122
+ "branch_cut_jumps",
123
+ "branch_cut_jumps_pairs",
124
+ "branch_jump",
125
+ "condition_false_q",
126
+ "condition_simplify",
127
+ "coverage_summary",
128
+ "differentiate_pairs",
129
+ "get_branch_rule",
130
+ "jump_pairs_from_table",
131
+ "merge_duplicate_pairs",
132
+ "numeric_branch_cut_jumps",
133
+ "post_simplify_pairs",
134
+ "post_simplify_restricted",
135
+ "propagate_undefined",
136
+ "restrict",
137
+ "rewrite_branch_expr",
138
+ "simplify_branch_condition",
139
+ "simplify_restricted",
140
+ "unsatisfiable_q",
141
+ ]