find-closed-form
A Python port of the Wolfram Language resource function FindClosedForm, contributed by the same author.
find_closed_form helps solve the fundamental problem of
number recognition,
by searching for a possible closed-form formula for a given number y,
in terms of arbitrary combinations of elementary and higher mathematical
functions.
The fundamental strategy is that, given a callable f, progressively more
complex rational arguments are tried, until a numerical match with the given
value y is found. By default, this match is searched up to linear
combinations with algebraic numbers (rationals or roots). Through the
search_range option the arguments tried can also be exact algebraic or
transcendental numbers, generated by the sibling packages
algebraic-range and
transcendental-range.
When no functional form is specified, for each round of argument search,
a further search goes through the following common mathematical functions:
sin, cos, tan, asin, acos, atan, acot, log, exp,
sinh, cosh, tanh, asinh, acosh, atanh, acoth,
zeta, gamma, polygamma, erf, erfc, erfinv,
elliptic_k, elliptic_e, airyai, airybi, Ei.
In addition, find_closed_form searches among algebraic combinations of the
following mathematical constants:
pi, EulerGamma, Catalan, GoldenRatio.
Installation
pip install find-closed-form
To include the algebraic and transcendental search ranges:
pip install "find-closed-form[ranges]"
Quick start
Find a possible mathematical function for a number:
from find_closed_form import find_closed_form
find_closed_form(0.405465) # log(3/2)
Find possible closed forms in terms of common mathematical functions:
find_closed_form(3.792277) # 1/6 + gamma(1/4)
Find formulae in terms of mathematical constants:
find_closed_form(1.044866) # 1/sqrt(Catalan)
Specify the functional form as a callable:
from sympy import zeta
find_closed_form(1.85653, functions=lambda x: 1/zeta(x)**2)
# zeta(1/5)**(-2)
Scope
The numerical match with the functional form is searched up to addition or multiplication by an algebraic number (that is, a rational or root):
from sympy import asinh, log, exp
find_closed_form(0.780653, functions=lambda x: asinh(x))
# sqrt(5)*asinh(4)/6
find_closed_form(7.443967, functions=lambda x: log(1 + exp(x)))
# 10*log(1 + exp(1/10))
Multi-argument functions are supported:
from sympy import gamma, log
find_closed_form(6.263643,
functions=lambda x, y: log(x)*log(y),
search_range="Integer")
# 2*log(5)*log(7)
find_closed_form(14.911818,
functions=lambda x, y: gamma(x)*gamma(y),
search_range="Plain")
# gamma(1/6)*gamma(1/3)
Search through a list of functional forms:
from sympy import sinh, cosh, sech, csch
find_closed_form(5.550045, functions=[
lambda x: sinh(x), lambda x: cosh(x),
lambda x: sech(x), lambda x: csch(x),
])
# 6*sech(2/5)
Multiple results can be requested through max_results:
find_closed_form(0.405465, functions=lambda x: log(x), max_results=10)
# returns multiple results, first = log(3/2)
Usage forms
The positional forms mirror the Wolfram Language resource function — an integer second argument is the number of results, a callable (or list of callables) the functional forms:
| Python | Wolfram Language |
|---|---|
find_closed_form(y) |
FindClosedForm[y] |
find_closed_form(y, n) |
FindClosedForm[y, n] |
find_closed_form(y, f) |
FindClosedForm[y, f] |
find_closed_form(y, [f1, f2, ...]) |
FindClosedForm[y, {f1, f2, …}] |
find_closed_form(y, f, n) |
FindClosedForm[y, f, n] |
The keyword equivalents functions=f and max_results=n are
interchangeable with the positional forms:
find_closed_form(0.405465, lambda x: log(x), 10) # same as above
find_closed_form(0.405465, 3) # FindClosedForm[y, n]:
# [log(3/2), -log(2/3), 7*airyai(1/2)/4]
Options
algebraic_add
Setting algebraic_add=False restricts the search to the specified functional
form up to multiplication (but not addition) by an algebraic number. This
can speed up the search, since special range properties are exploited for
certain known functions:
from sympy import gamma
find_closed_form(0.1013578,
functions=lambda x, y: 1/(gamma(x)*gamma(y)),
algebraic_add=False)
# 1/(sqrt(pi)*gamma(1/6))
algebraic_factor
Setting algebraic_factor=False restricts the search to the specified
functional form up to addition (but not multiplication) of an algebraic
number.
If both algebraic_add and algebraic_factor are set to False, the
search can be faster but may miss linear combinations of the functional form.
formula_complexity_threshold
If not enough digits are specified, a careful balance between precision and
complexity of the result should be reached through formula_complexity_threshold.
Often the desired formula is the simplest. For example:
from sympy import gamma
find_closed_form(38.94017, functions=lambda x: gamma(x),
formula_complexity_threshold=15)
# 2*gamma(1/20)
The formula complexity is a positive real value which ranks complexity as
follows (matching the bug-fixed WL kernel 1.0.0.4): take all integers
appearing in the formula (expanding rationals, complex numbers and roots —
a root of degree m/n counts its base |m|+|n| times, and a non-positive
integer j counts as −j+1); for each integer, compute
(5*digits + digit_sum + Ω + sqrt(i)) / 8, where Ω is the number of
prime factors counted with multiplicity; then take the total.
max_search_rounds
The maximum number of argument search rounds is 50 by default. This also determines the largest integer argument and rational denominator reachable:
from sympy import gamma
find_closed_form(49.44221, functions=lambda x: gamma(x),
algebraic_add=False, algebraic_factor=False, search_range="Plain")
# gamma(1/50)
By default, larger arguments are not reachable:
find_closed_form(59.43902, functions=lambda x: gamma(x),
algebraic_add=False, algebraic_factor=False, search_range="Plain")
# None
Changing the value of max_search_rounds allows a solution to be found:
find_closed_form(59.43902, functions=lambda x: gamma(x),
max_search_rounds=100, algebraic_add=False, algebraic_factor=False,
search_range="Plain")
# gamma(1/60)
rational_solutions
By default, simple rational solutions are not returned, and more sophisticated
solutions are searched for. If rational_solutions=True, simple exact
rational solutions are allowed:
from sympy import sin, pi
find_closed_form(0.25, functions=lambda x: sin(pi*x),
rational_solutions=True, algebraic_add=False)
# 1/4
If the functional form is the identity, there is no need for this option:
find_closed_form(0.25, functions=lambda x: x)
# 1/4
search_arguments
Through search_arguments you can specify each particular argument
to be tried:
from sympy import gamma
from fractions import Fraction
find_closed_form(4.678938, functions=lambda x: gamma(x),
search_arguments=[Fraction(3), Fraction(1), Fraction(1, 3)])
# 2 + gamma(1/3)
This can speed up the search and serves as a debugging tool.
For multi-argument functions, a dict maps each slot to its own list
(the WL association <|#1 -> list1, #2 -> list2|>), with 1-based
integer or "#1" string keys:
find_closed_form(1.32325, functions=lambda x, y: gamma(x)/gamma(y),
search_arguments={1: [Fraction(1), Fraction(1, 2)],
2: [Fraction(3), Fraction(1), Fraction(1, 3)]})
# 2*sqrt(pi)/gamma(1/3)
Exact symbolic arguments are used as given, so an algebraic_range or
transcendental_range output can be passed directly:
from sympy import exp
from algebraic_range import algebraic_range
find_closed_form(4.1132503787829275, functions=lambda x: exp(x),
search_arguments=algebraic_range(0, 2, Fraction(1, 2)))
# exp(sqrt(2))
search_range
By default, for each search round the arguments span the Farey range
farey_range(-round, round, round), which consists of rationals of
uniform complexity. The following values are supported:
| Value | Range per round | Requires |
|---|---|---|
"Farey" |
farey_range(-cut, cut, cut) — a rational Farey range |
— |
"Plain" |
range(-cut, cut, 1/cut) — the shorter rational range |
— |
"Integer" |
range(-cut, cut) — purely integer arguments |
— |
"Algebraic" |
algebraic_range(-cut, cut, 1/cut) — exact roots |
algebraic-range |
"Transcendental" |
transcendental_range(-cut, cut, 1/cut) — exact transcendental numbers |
transcendental-range |
from sympy import log
find_closed_form(6.263643,
functions=lambda x, y: log(x)*log(y),
search_range="Integer")
# 2*log(5)*log(7)
The "Algebraic" and "Transcendental" ranges search functions of
exact algebraic or transcendental arguments, going in the reverse
direction from a raw machine number to formulae such as exp(sqrt(2))
or atan(log(2)):
find_closed_form(4.1132503787829275, search_range="Algebraic")
# exp(sqrt(2))
find_closed_form(0.606111934732855, search_range="Transcendental",
search_range_options={"method": "log"})
# atan(log(2))
On these two ranges the default function list additionally includes the
identity, since the range elements are closed forms themselves — matched,
as always, up to algebraic factors and addends. Here the identity
recognizes the Gelfond–Schneider constant among the 'power' elements
over algebraic generators:
find_closed_form(2.665144142690225, search_range="Transcendental",
search_range_options={"method": "power", "generators_domain": "algebraics"})
# 2**sqrt(2)
A callable is also accepted (a function of the search round, as in the
WL original), equivalent to search_range_fn:
from fractions import Fraction
from algebraic_range import algebraic_range
find_closed_form(4.1132503787829275, functions=lambda x: exp(x),
search_range=lambda cut: algebraic_range(-cut, cut, Fraction(1, cut)))
# exp(sqrt(2))
search_range_fn
It is possible to specify a custom range function of the search round number:
from sympy import log
from fractions import Fraction
find_closed_form(13.165149, functions=lambda x: log(x),
search_range_fn=lambda cut: [Fraction(i) for i in range(0, 100*cut+1, 25)])
# sqrt(3)*log(2000)
search_range_options
Extra keyword options for the "Algebraic" and "Transcendental" range
generators are forwarded through search_range_options — for example the
root orders of algebraic_range, or the transcendental function family
and multiplicity of transcendental_range:
from sympy import exp
find_closed_form(3.5251431659552352, functions=lambda x: exp(x),
search_range="Algebraic", search_range_options={"root_order": 3})
# exp(2**(1/3))
significant_digits
The precision of the numerical match is automatically set to the number of significant digits in the given number. If you want to ignore some numerical error, you can specify a lower value:
from sympy import zeta
find_closed_form(0.81248057539,
functions=lambda x: 1/zeta(x)**2,
significant_digits=7)
# zeta(11/3)**(-2)
search_time_limit
The maximum time in seconds spent by the search algorithm. Default is 3600.
As in the WL original (TimeConstrained), the limit is enforced as a hard
interrupt — through signal.setitimer/SIGALRM on Unix main threads, with
cooperative clock checks elsewhere — and the results found before the
interrupt are still returned.
Summary table
| Parameter | Default | Description |
|---|---|---|
functions |
None |
Functional forms to search; None uses ~31 common functions. |
max_results |
1 |
Number of results to return. |
significant_digits |
Auto | Precision target; auto-detected from input digits. |
formula_complexity_threshold |
Auto | Maximum formula complexity; auto-scaled per round. |
algebraic_factor |
True |
Search up to multiplication by algebraic numbers. |
algebraic_add |
True |
Search up to addition of algebraic numbers. |
rational_solutions |
False |
Allow simple rational solutions. |
max_search_rounds |
50 |
Maximum argument-range expansion rounds. |
search_range |
"Farey" |
"Farey", "Plain", "Integer", "Algebraic", "Transcendental", or a callable. |
search_range_fn |
None |
Custom f(cut) → list for argument generation. |
search_range_options |
None |
Options forwarded to the "Algebraic"/"Transcendental" generators. |
search_arguments |
None |
Fixed argument list or per-slot dict (bypasses auto ranges). |
search_time_limit |
3600 |
Maximum seconds for the search (hard TimeConstrained). |
monitor_search |
False |
Print each result as it is found. |
Properties and relations
find_closed_form with the identity function generalizes rationalization
and works with fewer digits:
find_closed_form(0.666, functions=lambda x: x) # 2/3
When the given number approximates a simple root, it also generalizes root approximation:
find_closed_form(4.243, functions=lambda x: x) # 3*sqrt(2)
find_closed_form(0.5848, functions=lambda x: x) # 5**(-1/3)
Performance
On representative searches the port runs within ±2× of the Wolfram
Language 1.0.0 timings — substantially faster on the range-based
searches, slower on multi-argument functions (the WL functionChamber
optimization is not yet ported). Seven of ten benchmark cases return
symbolically identical results, the others equally precise alternative
matches. See
benchmark/BENCHMARK.md
for the case-by-case comparison and methodology.
Auxiliary functions
The formula_complexity function is also exported and can be used directly
to compute the complexity of any sympy expression:
from find_closed_form import formula_complexity
from sympy import Rational
formula_complexity(2*gamma(Rational(1, 20)))
The farey_range function generates Farey-based argument ranges:
from find_closed_form import farey_range
farey_range(-3, 3, 3)
# [-3, -8/3, -5/2, ..., 5/2, 8/3, 3]
Dependencies
Optional, for the "Algebraic" and "Transcendental" search ranges
(pip install "find-closed-form[ranges]"):
- algebraic-range ≥ 0.9.0
- transcendental-range ≥ 0.9.0 (Python ≥ 3.10)
License
MIT
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 find_closed_form-0.5.0.tar.gz.
File metadata
- Download URL: find_closed_form-0.5.0.tar.gz
- Upload date:
- Size: 29.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71683e9e2b3f5723d5dbd0a1f03541131fb57d0b923caf212717074e63c5e503
|
|
| MD5 |
50e442438428693b41a4785ed70129f4
|
|
| BLAKE2b-256 |
e5c0f78a626fceddb752019650dda180d93aa84fcf47cddace7e495fbdd43a3c
|
File details
Details for the file find_closed_form-0.5.0-py3-none-any.whl.
File metadata
- Download URL: find_closed_form-0.5.0-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b150db06b4217f82c864681975966e28ec9c13dd8f2812d66f900df800f7b22b
|
|
| MD5 |
67d0c9b6089729e2c988062eed9cf811
|
|
| BLAKE2b-256 |
06f76400f154a9374fe966ae5177c9f3a408570d0aed6b5e6945cfee8a1d2de2
|