Exact limit computation for Python functions. Resolve singularities algebraically.
Project description
composite-resolve
Evaluate Python functions at points where they're undefined.
Pass a plain numeric Python function and get exact limits via algebraic infinitesimal arithmetic. To my knowledge, this is the first library that does this directly on plain Python functions - no symbolic expressions, no approximation.
Note however, this is a mathematical correctness and continuity analysis tool, not a general execution safety mechanism. It is usefull for verifying the mathematical correctness of functions before they become production code.
It can only be used as general execution safety mechamism at runtime in some very limited and specific cases like in physics-style modeling and signal processing math (interpolation in DSP is the reason this tool emerged after all), but you have to know what you are doing in such cases.
Do not use use this as a general execution safety mechanism unless you are aware of consequences of using the limit results at the points where function is genuinely undefined.
import math
from composite_resolve import safe
@safe
def sinc(x):
return math.sin(x) / x
sinc(0.5) # → 0.9589 (normal computation)
sinc(0) # → 1.0 (singularity resolved)
Uses composite arithmetic to resolve singularities algebraically. Works with any callable that uses standard Python arithmetic and math module functions. Pure Python, zero dependencies.
Install
pip install composite-resolve
The @safe Decorator
Write your math as-is. The decorator handles singularities automatically:
import math
from composite_resolve import safe
@safe
def f(x):
return (x**2 - 1) / (x - 1)
f(3) # → 4.0 (normal)
f(1) # → 2.0 (resolved - no ZeroDivisionError)
@safe
def entropy(p):
return -p * math.log(p)
entropy(0.5) # → 0.347 (normal)
entropy(0) # → 0.0 (resolved - no ValueError)
Normal inputs run the original function directly with zero overhead. Only when the function fails (ZeroDivisionError, NaN, Inf) does the resolver kick in.
Direct API
For more control, use resolve, limit, and classify directly:
import math
from composite_resolve import resolve, limit, classify, taylor
# Evaluate at removable singularities
resolve(lambda x: math.sin(x) / x, at=0) # → 1.0
resolve(lambda x: (math.exp(x) - 1) / x, at=0) # → 1.0
resolve(lambda x: (x**2 - 1) / (x - 1), at=1) # → 2.0
# Indeterminate forms
limit(lambda x: x * math.log(x), to=0, dir="+") # → 0.0 (0 × ∞)
limit(lambda x: x**x, to=0, dir="+") # → 1.0 (0⁰)
limit(lambda x: (1 + x)**(1/x), to=0) # → e (1^∞)
limit(lambda x: 1/x - 1/math.sin(x), to=0) # → 0.0 (∞ − ∞)
# Limits at infinity
limit(lambda x: (1 + 1/x)**x, to=math.inf) # → e
limit(lambda x: math.sin(x) / x, to=math.inf) # → 0.0
# One-sided limits
limit(lambda x: 1/x, to=0, dir="+") # raises LimitDivergesError (+∞)
limit(lambda x: 1/x, to=0, dir="-") # raises LimitDivergesError (-∞)
limit(lambda x: 1/x, to=0) # raises LimitDoesNotExistError
# Singularity classification
classify(lambda x: math.sin(x)/x, at=0) # → Removable(value=1.0)
classify(lambda x: 1/x, at=0) # → Pole(order=1, residue=1.0)
classify(lambda x: math.exp(x), at=0) # → Regular(value=1.0)
# Taylor coefficients
taylor(lambda x: math.exp(x), at=0, order=4)
# → [1.0, 1.0, 0.5, 0.16667, 0.04167]
How It Works
The library evaluates functions using composite arithmetic. Instead of symbolic manipulation, the library substitutes a concrete algebraic infinitesimal into your function. The result carries enough structure to resolve 0/0, 0×∞, and all other indeterminate forms through ordinary arithmetic.
The function is treated as a black box. No expression tree, no symbolic manipulation.
Evaluation layers
-
Fast path -
f(float(to))evaluated directly. If the point is regular (no singularity), this returns instantly. A continuity check atto ± εprevents silent mis-evaluation at discontinuities. -
Composite arithmetic - the core primitive. Substitutes a seeded composite number at the limit point and evaluates
fthrough the full derivative tower. Handles removable singularities, indeterminate forms, and pole detection algebraically. -
Numerical fallback - when composite arithmetic hits a representation it can't handle (e.g.,
ln(∞),floorat infinity, factorial overflow), falls back to evaluatingfat nearby float probe points and extrapolating convergence/divergence. -
Clear refusal - when none of the above can determine the answer, raises
LimitUndecidableError(notLimitDoesNotExistError) to distinguish "we couldn't determine this" from "the limit genuinely does not exist. (I'm deliberating should I move this above numerical fallback and limit this library only to Composite resolvable limits. TBD.)"
API
safe(f) -> wrapped function
Decorator. Normal inputs run f directly. Singularities are resolved automatically.
resolve(f, at, dir="both", truncation=20) -> float
Evaluate f at a point where it would normally fail. Returns math.inf or -math.inf for divergent limits.
limit(f, to, dir="both", truncation=20) -> float
Compute the limit of f(x) as x → to. Raises:
LimitDivergesError- limit is ±∞ (access.valuefor the sign)LimitDoesNotExistError- limit genuinely does not exist (oscillation, one-sided limits disagree). Carries evidence in.left_limit/.right_limit.LimitUndecidableError- CR could not determine the limit. The mathematical limit may still exist. Typical causes: double-precision overflow in probes, sub-polynomial growth rates the integer-dimension system can't represent, or expressions requiring log-space computation.
evaluate(f, at) -> float
Strict: only returns a value if the singularity is removable. Raises SingularityError otherwise.
taylor(f, at=0, order=10) -> list[float]
Extract Taylor coefficients [f(a), f'(a)/1!, f''(a)/2!, ...].
classify(f, at=0, dir="both") -> SingularityType
Returns Regular, Removable, Pole, or Essential.
residue(f, at=0) -> float
Residue at a pole.
Supported Math Functions
composite_resolve.math provides composite-aware versions of 42 functions. These accept both plain floats and Composite objects - use them in functions passed to limit()/resolve() for guaranteed composite propagation, or use math.* / numpy.* which are patched automatically during evaluation.
Core transcendentals:
sin cos tan exp log ln sqrt
Inverse trig / hyperbolic:
asin acos atan sinh cosh tanh
asinh acosh atanh
Reciprocal trig / hyperbolic:
cot sec csc coth sech csch
Inverse reciprocal:
acot asec acsc asech acsch acoth
Early-cancellation primitives (numerically stable near cancellation points):
expm1 (= exp(x)−1), log1p (= ln(1+x)), cosm1 (= cos(x)−1)
Step / piecewise (direction-aware at discontinuities):
floor ceil ceiling frac
Arithmetic:
cbrt (real cube root, handles negatives), Mod (mathematical modulo)
Error functions:
erf erfc erfi
Fresnel integrals:
fresnels fresnelc
Gamma family:
gamma (with pole handling at 0, −1, −2, …), factorial, binomial
Not yet supported: Bessel functions (J, Y, I, K), exponential integral (Ei), Lambert W, elliptic integrals. These raise NameError if used in expressions passed to limit().
Examples
# Evaluate a function across its full domain, including singularities
from composite_resolve import resolve
f = lambda x: (x**2 - 1) / (x - 1)
for x in range(-5, 6):
print(f"x={x:>2d} f(x)={resolve(f, at=x):.1f}")
# x=1 gives 2.0 - no special case needed
# Cross-entropy loss at boundary
resolve(lambda p: -(0*math.log(p) + 1*math.log(1-p)), at=0, dir="+") # → 0.0
# Continuous compounding
limit(lambda n: (1 + 0.05/n)**n, to=math.inf) # → 1.05127
# Directional limits at discontinuities
from composite_resolve.math import floor, ceiling
limit(lambda x: floor(x), to=2, dir="+") # → 2
limit(lambda x: floor(x), to=2, dir="-") # → 1
limit(lambda x: ceiling(x), to=2, dir="+") # → 3
Math Library Support
Functions can use math, numpy, or composite_resolve.math - all work transparently:
import math
import numpy as np
from composite_resolve import safe
@safe
def f(x):
return math.sin(x) / x # works
@safe
def g(x):
return np.sin(x) / x # also works
f(0) # → 1.0
g(0) # → 1.0
math functions are patched during resolution. numpy functions dispatch via __array_ufunc__.
Important: use import math not from math import sin. The from form captures the original function at import time - patching the module later doesn't reach it.
Error Semantics
composite-resolve distinguishes three failure modes:
| Error | Meaning | User action |
|---|---|---|
LimitDivergesError |
Limit is ±∞ | Access .value for the sign |
LimitDoesNotExistError |
Limit genuinely doesn't exist - positive evidence (oscillation, one-sided limits disagree) | Check .left_limit / .right_limit for the one-sided values |
LimitUndecidableError |
CR couldn't determine the limit - no claim about existence | Try a symbolic engine (SymPy) or higher-precision tool (mpmath) |
LimitUndecidableError is NOT a subclass of LimitDoesNotExistError. They represent fundamentally different situations: evidence of non-existence vs. insufficient machinery.
Limitations
- Single-variable functions only. Multi-variable limits are out of scope.
- Use
import mathnotfrom math import sin- thefromform captures the original function; patching can't reach it. - Float-precision evaluation points.
math.pi/2is not exactly π/2 - limits at transcendental points may lose precision. - Not thread-safe during
limit()/resolve()/@safecalls (themathmodule is temporarily patched). - Integer-dimension limitation. The composite number system uses integer dimensions. Functions whose growth rate sits between polynomial orders (like
log(x), which grows slower thanx^εfor any ε > 0) can't be faithfully represented. Limits involving log/polynomial rate comparisons may fall to numerical extrapolation or raiseLimitUndecidableError. - Double-precision overflow. Numerical fallback probes are plain Python floats. Functions like
factorial(n)overflow at n > 170;exp(x)at x > 709. Limits requiring probe values beyond these ranges may be undecidable. - Unsupported functions (
jax,torch, Bessel, Ei, Lambert W, etc.) raiseUnsupportedFunctionErrororNameError- not silent wrong answers.
License
AGPL-3.0. Commercial licensing available: tmilovan@fwd.hr
Author
Toni Milovan - tmilovan@fwd.hr
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file composite_resolve-0.1.6.tar.gz.
File metadata
- Download URL: composite_resolve-0.1.6.tar.gz
- Upload date:
- Size: 64.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
803ad5587ad57b7931dbb1a09ed83fd4ad9829ec8b192e270f75ff31d81c7d4d
|
|
| MD5 |
0f7b0ede92657dc7ea3206f2b77357d5
|
|
| BLAKE2b-256 |
e3e52cb746be9c51b3ecf2d2a86a662db2122c8a463b85e9b786785aa3e38261
|
File details
Details for the file composite_resolve-0.1.6-py3-none-any.whl.
File metadata
- Download URL: composite_resolve-0.1.6-py3-none-any.whl
- Upload date:
- Size: 74.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2326a8d01ebc4713f475c1c20e41a18879a8c435b7f4a12f8f3f08c8aa10930
|
|
| MD5 |
cf781cdee6366bc56707380612767b05
|
|
| BLAKE2b-256 |
a99c83d99c8c33f1a357dc8f249c4774692c6970e91533c134aa9e4e8ab54f7e
|