Traktor/myenv/Lib/site-packages/sympy/concrete/guess.py

474 lines
17 KiB
Python
Raw Permalink Normal View History

2024-05-26 05:12:46 +02:00
"""Various algorithms for helping identifying numbers and sequences."""
from sympy.concrete.products import (Product, product)
from sympy.core import Function, S
from sympy.core.add import Add
from sympy.core.numbers import Integer, Rational
from sympy.core.symbol import Symbol, symbols
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.integers import floor
from sympy.integrals.integrals import integrate
from sympy.polys.polyfuncs import rational_interpolate as rinterp
from sympy.polys.polytools import lcm
from sympy.simplify.radsimp import denom
from sympy.utilities import public
@public
def find_simple_recurrence_vector(l):
"""
This function is used internally by other functions from the
sympy.concrete.guess module. While most users may want to rather use the
function find_simple_recurrence when looking for recurrence relations
among rational numbers, the current function may still be useful when
some post-processing has to be done.
Explanation
===========
The function returns a vector of length n when a recurrence relation of
order n is detected in the sequence of rational numbers v.
If the returned vector has a length 1, then the returned value is always
the list [0], which means that no relation has been found.
While the functions is intended to be used with rational numbers, it should
work for other kinds of real numbers except for some cases involving
quadratic numbers; for that reason it should be used with some caution when
the argument is not a list of rational numbers.
Examples
========
>>> from sympy.concrete.guess import find_simple_recurrence_vector
>>> from sympy import fibonacci
>>> find_simple_recurrence_vector([fibonacci(k) for k in range(12)])
[1, -1, -1]
See Also
========
See the function sympy.concrete.guess.find_simple_recurrence which is more
user-friendly.
"""
q1 = [0]
q2 = [1]
b, z = 0, len(l) >> 1
while len(q2) <= z:
while l[b]==0:
b += 1
if b == len(l):
c = 1
for x in q2:
c = lcm(c, denom(x))
if q2[0]*c < 0: c = -c
for k in range(len(q2)):
q2[k] = int(q2[k]*c)
return q2
a = S.One/l[b]
m = [a]
for k in range(b+1, len(l)):
m.append(-sum(l[j+1]*m[b-j-1] for j in range(b, k))*a)
l, m = m, [0] * max(len(q2), b+len(q1))
for k, q in enumerate(q2):
m[k] = a*q
for k, q in enumerate(q1):
m[k+b] += q
while m[-1]==0: m.pop() # because trailing zeros can occur
q1, q2, b = q2, m, 1
return [0]
@public
def find_simple_recurrence(v, A=Function('a'), N=Symbol('n')):
"""
Detects and returns a recurrence relation from a sequence of several integer
(or rational) terms. The name of the function in the returned expression is
'a' by default; the main variable is 'n' by default. The smallest index in
the returned expression is always n (and never n-1, n-2, etc.).
Examples
========
>>> from sympy.concrete.guess import find_simple_recurrence
>>> from sympy import fibonacci
>>> find_simple_recurrence([fibonacci(k) for k in range(12)])
-a(n) - a(n + 1) + a(n + 2)
>>> from sympy import Function, Symbol
>>> a = [1, 1, 1]
>>> for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3])
>>> find_simple_recurrence(a, A=Function('f'), N=Symbol('i'))
-8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3)
"""
p = find_simple_recurrence_vector(v)
n = len(p)
if n <= 1: return S.Zero
return Add(*[A(N+n-1-k)*p[k] for k in range(n)])
@public
def rationalize(x, maxcoeff=10000):
"""
Helps identifying a rational number from a float (or mpmath.mpf) value by
using a continued fraction. The algorithm stops as soon as a large partial
quotient is detected (greater than 10000 by default).
Examples
========
>>> from sympy.concrete.guess import rationalize
>>> from mpmath import cos, pi
>>> rationalize(cos(pi/3))
1/2
>>> from mpmath import mpf
>>> rationalize(mpf("0.333333333333333"))
1/3
While the function is rather intended to help 'identifying' rational
values, it may be used in some cases for approximating real numbers.
(Though other functions may be more relevant in that case.)
>>> rationalize(pi, maxcoeff = 250)
355/113
See Also
========
Several other methods can approximate a real number as a rational, like:
* fractions.Fraction.from_decimal
* fractions.Fraction.from_float
* mpmath.identify
* mpmath.pslq by using the following syntax: mpmath.pslq([x, 1])
* mpmath.findpoly by using the following syntax: mpmath.findpoly(x, 1)
* sympy.simplify.nsimplify (which is a more general function)
The main difference between the current function and all these variants is
that control focuses on magnitude of partial quotients here rather than on
global precision of the approximation. If the real is "known to be" a
rational number, the current function should be able to detect it correctly
with the default settings even when denominator is great (unless its
expansion contains unusually big partial quotients) which may occur
when studying sequences of increasing numbers. If the user cares more
on getting simple fractions, other methods may be more convenient.
"""
p0, p1 = 0, 1
q0, q1 = 1, 0
a = floor(x)
while a < maxcoeff or q1==0:
p = a*p1 + p0
q = a*q1 + q0
p0, p1 = p1, p
q0, q1 = q1, q
if x==a: break
x = 1/(x-a)
a = floor(x)
return sympify(p) / q
@public
def guess_generating_function_rational(v, X=Symbol('x')):
"""
Tries to "guess" a rational generating function for a sequence of rational
numbers v.
Examples
========
>>> from sympy.concrete.guess import guess_generating_function_rational
>>> from sympy import fibonacci
>>> l = [fibonacci(k) for k in range(5,15)]
>>> guess_generating_function_rational(l)
(3*x + 5)/(-x**2 - x + 1)
See Also
========
sympy.series.approximants
mpmath.pade
"""
# a) compute the denominator as q
q = find_simple_recurrence_vector(v)
n = len(q)
if n <= 1: return None
# b) compute the numerator as p
p = [sum(v[i-k]*q[k] for k in range(min(i+1, n)))
for i in range(len(v)>>1)]
return (sum(p[k]*X**k for k in range(len(p)))
/ sum(q[k]*X**k for k in range(n)))
@public
def guess_generating_function(v, X=Symbol('x'), types=['all'], maxsqrtn=2):
"""
Tries to "guess" a generating function for a sequence of rational numbers v.
Only a few patterns are implemented yet.
Explanation
===========
The function returns a dictionary where keys are the name of a given type of
generating function. Six types are currently implemented:
type | formal definition
-------+----------------------------------------------------------------
ogf | f(x) = Sum( a_k * x^k , k: 0..infinity )
egf | f(x) = Sum( a_k * x^k / k! , k: 0..infinity )
lgf | f(x) = Sum( (-1)^(k+1) a_k * x^k / k , k: 1..infinity )
| (with initial index being hold as 1 rather than 0)
hlgf | f(x) = Sum( a_k * x^k / k , k: 1..infinity )
| (with initial index being hold as 1 rather than 0)
lgdogf | f(x) = derivate( log(Sum( a_k * x^k, k: 0..infinity )), x)
lgdegf | f(x) = derivate( log(Sum( a_k * x^k / k!, k: 0..infinity )), x)
In order to spare time, the user can select only some types of generating
functions (default being ['all']). While forgetting to use a list in the
case of a single type may seem to work most of the time as in: types='ogf'
this (convenient) syntax may lead to unexpected extra results in some cases.
Discarding a type when calling the function does not mean that the type will
not be present in the returned dictionary; it only means that no extra
computation will be performed for that type, but the function may still add
it in the result when it can be easily converted from another type.
Two generating functions (lgdogf and lgdegf) are not even computed if the
initial term of the sequence is 0; it may be useful in that case to try
again after having removed the leading zeros.
Examples
========
>>> from sympy.concrete.guess import guess_generating_function as ggf
>>> ggf([k+1 for k in range(12)], types=['ogf', 'lgf', 'hlgf'])
{'hlgf': 1/(1 - x), 'lgf': 1/(x + 1), 'ogf': 1/(x**2 - 2*x + 1)}
>>> from sympy import sympify
>>> l = sympify("[3/2, 11/2, 0, -121/2, -363/2, 121]")
>>> ggf(l)
{'ogf': (x + 3/2)/(11*x**2 - 3*x + 1)}
>>> from sympy import fibonacci
>>> ggf([fibonacci(k) for k in range(5, 15)], types=['ogf'])
{'ogf': (3*x + 5)/(-x**2 - x + 1)}
>>> from sympy import factorial
>>> ggf([factorial(k) for k in range(12)], types=['ogf', 'egf', 'lgf'])
{'egf': 1/(1 - x)}
>>> ggf([k+1 for k in range(12)], types=['egf'])
{'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)}
N-th root of a rational function can also be detected (below is an example
coming from the sequence A108626 from https://oeis.org).
The greatest n-th root to be tested is specified as maxsqrtn (default 2).
>>> ggf([1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf']
sqrt(1/(x**4 + 2*x**2 - 4*x + 1))
References
==========
.. [1] "Concrete Mathematics", R.L. Graham, D.E. Knuth, O. Patashnik
.. [2] https://oeis.org/wiki/Generating_functions
"""
# List of all types of all g.f. known by the algorithm
if 'all' in types:
types = ('ogf', 'egf', 'lgf', 'hlgf', 'lgdogf', 'lgdegf')
result = {}
# Ordinary Generating Function (ogf)
if 'ogf' in types:
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(v) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*v[i] for i in range(n+1)) for n in range(len(v))]
g = guess_generating_function_rational(t, X=X)
if g:
result['ogf'] = g**Rational(1, d+1)
break
# Exponential Generating Function (egf)
if 'egf' in types:
# Transform sequence (division by factorial)
w, f = [], S.One
for i, k in enumerate(v):
f *= i if i else 1
w.append(k/f)
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(w) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
g = guess_generating_function_rational(t, X=X)
if g:
result['egf'] = g**Rational(1, d+1)
break
# Logarithmic Generating Function (lgf)
if 'lgf' in types:
# Transform sequence (multiplication by (-1)^(n+1) / n)
w, f = [], S.NegativeOne
for i, k in enumerate(v):
f = -f
w.append(f*k/Integer(i+1))
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(w) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
g = guess_generating_function_rational(t, X=X)
if g:
result['lgf'] = g**Rational(1, d+1)
break
# Hyperbolic logarithmic Generating Function (hlgf)
if 'hlgf' in types:
# Transform sequence (division by n+1)
w = []
for i, k in enumerate(v):
w.append(k/Integer(i+1))
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(w) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
g = guess_generating_function_rational(t, X=X)
if g:
result['hlgf'] = g**Rational(1, d+1)
break
# Logarithmic derivative of ordinary generating Function (lgdogf)
if v[0] != 0 and ('lgdogf' in types
or ('ogf' in types and 'ogf' not in result)):
# Transform sequence by computing f'(x)/f(x)
# because log(f(x)) = integrate( f'(x)/f(x) )
a, w = sympify(v[0]), []
for n in range(len(v)-1):
w.append(
(v[n+1]*(n+1) - sum(w[-i-1]*v[i+1] for i in range(n)))/a)
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(w) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
g = guess_generating_function_rational(t, X=X)
if g:
result['lgdogf'] = g**Rational(1, d+1)
if 'ogf' not in result:
result['ogf'] = exp(integrate(result['lgdogf'], X))
break
# Logarithmic derivative of exponential generating Function (lgdegf)
if v[0] != 0 and ('lgdegf' in types
or ('egf' in types and 'egf' not in result)):
# Transform sequence / step 1 (division by factorial)
z, f = [], S.One
for i, k in enumerate(v):
f *= i if i else 1
z.append(k/f)
# Transform sequence / step 2 by computing f'(x)/f(x)
# because log(f(x)) = integrate( f'(x)/f(x) )
a, w = z[0], []
for n in range(len(z)-1):
w.append(
(z[n+1]*(n+1) - sum(w[-i-1]*z[i+1] for i in range(n)))/a)
# Perform some convolutions of the sequence with itself
t = [1] + [0]*(len(w) - 1)
for d in range(max(1, maxsqrtn)):
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
g = guess_generating_function_rational(t, X=X)
if g:
result['lgdegf'] = g**Rational(1, d+1)
if 'egf' not in result:
result['egf'] = exp(integrate(result['lgdegf'], X))
break
return result
@public
def guess(l, all=False, evaluate=True, niter=2, variables=None):
"""
This function is adapted from the Rate.m package for Mathematica
written by Christian Krattenthaler.
It tries to guess a formula from a given sequence of rational numbers.
Explanation
===========
In order to speed up the process, the 'all' variable is set to False by
default, stopping the computation as some results are returned during an
iteration; the variable can be set to True if more iterations are needed
(other formulas may be found; however they may be equivalent to the first
ones).
Another option is the 'evaluate' variable (default is True); setting it
to False will leave the involved products unevaluated.
By default, the number of iterations is set to 2 but a greater value (up
to len(l)-1) can be specified with the optional 'niter' variable.
More and more convoluted results are found when the order of the
iteration gets higher:
* first iteration returns polynomial or rational functions;
* second iteration returns products of rising factorials and their
inverses;
* third iteration returns products of products of rising factorials
and their inverses;
* etc.
The returned formulas contain symbols i0, i1, i2, ... where the main
variables is i0 (and auxiliary variables are i1, i2, ...). A list of
other symbols can be provided in the 'variables' option; the length of
the least should be the value of 'niter' (more is acceptable but only
the first symbols will be used); in this case, the main variable will be
the first symbol in the list.
Examples
========
>>> from sympy.concrete.guess import guess
>>> guess([1,2,6,24,120], evaluate=False)
[Product(i1 + 1, (i1, 1, i0 - 1))]
>>> from sympy import symbols
>>> r = guess([1,2,7,42,429,7436,218348,10850216], niter=4)
>>> i0 = symbols("i0")
>>> [r[0].subs(i0,n).doit() for n in range(1,10)]
[1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460]
"""
if any(a==0 for a in l[:-1]):
return []
N = len(l)
niter = min(N-1, niter)
myprod = product if evaluate else Product
g = []
res = []
if variables is None:
symb = symbols('i:'+str(niter))
else:
symb = variables
for k, s in enumerate(symb):
g.append(l)
n, r = len(l), []
for i in range(n-2-1, -1, -1):
ri = rinterp(enumerate(g[k][:-1], start=1), i, X=s)
if ((denom(ri).subs({s:n}) != 0)
and (ri.subs({s:n}) - g[k][-1] == 0)
and ri not in r):
r.append(ri)
if r:
for i in range(k-1, -1, -1):
r = [g[i][0]
* myprod(v, (symb[i+1], 1, symb[i]-1)) for v in r]
if not all: return r
res += r
l = [Rational(l[i+1], l[i]) for i in range(N-k-1)]
return res