Skip to main content

find-closed-form

find-closed-form PyPI version Python

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]"):

License

MIT

Metadata

Release files for find-closed-form 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for find-closed-form 0.5.0
File Size Uploaded
find_closed_form-0.5.0.tar.gz 29.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for find-closed-form 0.5.0
File Interpreter ABI Platform
find_closed_form-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 48.2 kB

Release files / find_closed_form-0.5.0.tar.gz

Download URL find_closed_form-0.5.0.tar.gz
Size 29.7 kB
Tags Source
SHA-256 checksum
How to use checksums
71683e9e2b3f5723d5dbd0a1f03541131fb57d0b923caf212717074e63c5e503
BLAKE2b-256 checksum
How to use checksums
e5c0f78a626fceddb752019650dda180d93aa84fcf47cddace7e495fbdd43a3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.2

Release files / find_closed_form-0.5.0-py3-none-any.whl

Download URL find_closed_form-0.5.0-py3-none-any.whl
Size 18.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b150db06b4217f82c864681975966e28ec9c13dd8f2812d66f900df800f7b22b
BLAKE2b-256 checksum
How to use checksums
06f76400f154a9374fe966ae5177c9f3a408570d0aed6b5e6945cfee8a1d2de2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.2

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page