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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

find_closed_form-0.5.0.tar.gz (29.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

find_closed_form-0.5.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

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

Hashes for find_closed_form-0.5.0.tar.gz
Algorithm Hash digest
SHA256 71683e9e2b3f5723d5dbd0a1f03541131fb57d0b923caf212717074e63c5e503
MD5 50e442438428693b41a4785ed70129f4
BLAKE2b-256 e5c0f78a626fceddb752019650dda180d93aa84fcf47cddace7e495fbdd43a3c

See more details on using hashes here.

File details

Details for the file find_closed_form-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for find_closed_form-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b150db06b4217f82c864681975966e28ec9c13dd8f2812d66f900df800f7b22b
MD5 67d0c9b6089729e2c988062eed9cf811
BLAKE2b-256 06f76400f154a9374fe966ae5177c9f3a408570d0aed6b5e6945cfee8a1d2de2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page