123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- """Various algorithms for helping identifying numbers and sequences."""
- from sympy.utilities import public
- from sympy.core import Function, Symbol, S
- from sympy.core.numbers import Zero
- from sympy.concrete.products import (Product, product)
- from sympy.core.numbers import (Integer, Rational)
- from sympy.core.symbol import 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.polytools import lcm
- from sympy.simplify.radsimp import denom
- from sympy.polys.polyfuncs import rational_interpolate as rinterp
- @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 = [Integer(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 = Integer(1)/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 in range(len(q2)):
- m[k] = a*q2[k]
- for k in range(b, b+len(q1)):
- m[k] += q1[k-b]
- 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 Zero()
- rel = Zero()
- for k in range(n):
- rel += A(N+n-1-k)*p[k]
- return rel
- @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 http://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 if k==0 else 0 for k in range(len(v))]
- 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 if k==0 else 0 for k in range(len(w))]
- 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 if k==0 else 0 for k in range(len(w))]
- 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 if k==0 else 0 for k in range(len(w))]
- 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 if k==0 else 0 for k in range(len(w))]
- 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 = [], Integer(1)
- 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 if k==0 else 0 for k in range(len(w))]
- 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 = list(map(lambda v: g[i][0]
- * myprod(v, (symb[i+1], 1, symb[i]-1)), r))
- if not all: return r
- res += r
- l = [Rational(l[i+1], l[i]) for i in range(N-k-1)]
- return res
|