recurrences.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. Recurrences
  3. """
  4. from sympy.core import S, sympify
  5. from sympy.utilities.iterables import iterable
  6. from sympy.utilities.misc import as_int
  7. def linrec(coeffs, init, n):
  8. r"""
  9. Evaluation of univariate linear recurrences of homogeneous type
  10. having coefficients independent of the recurrence variable.
  11. Parameters
  12. ==========
  13. coeffs : iterable
  14. Coefficients of the recurrence
  15. init : iterable
  16. Initial values of the recurrence
  17. n : Integer
  18. Point of evaluation for the recurrence
  19. Notes
  20. =====
  21. Let `y(n)` be the recurrence of given type, ``c`` be the sequence
  22. of coefficients, ``b`` be the sequence of initial/base values of the
  23. recurrence and ``k`` (equal to ``len(c)``) be the order of recurrence.
  24. Then,
  25. .. math :: y(n) = \begin{cases} b_n & 0 \le n < k \\
  26. c_0 y(n-1) + c_1 y(n-2) + \cdots + c_{k-1} y(n-k) & n \ge k
  27. \end{cases}
  28. Let `x_0, x_1, \ldots, x_n` be a sequence and consider the transformation
  29. that maps each polynomial `f(x)` to `T(f(x))` where each power `x^i` is
  30. replaced by the corresponding value `x_i`. The sequence is then a solution
  31. of the recurrence if and only if `T(x^i p(x)) = 0` for each `i \ge 0` where
  32. `p(x) = x^k - c_0 x^(k-1) - \cdots - c_{k-1}` is the characteristic
  33. polynomial.
  34. Then `T(f(x)p(x)) = 0` for each polynomial `f(x)` (as it is a linear
  35. combination of powers `x^i`). Now, if `x^n` is congruent to
  36. `g(x) = a_0 x^0 + a_1 x^1 + \cdots + a_{k-1} x^{k-1}` modulo `p(x)`, then
  37. `T(x^n) = x_n` is equal to
  38. `T(g(x)) = a_0 x_0 + a_1 x_1 + \cdots + a_{k-1} x_{k-1}`.
  39. Computation of `x^n`,
  40. given `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`
  41. is performed using exponentiation by squaring (refer to [1_]) with
  42. an additional reduction step performed to retain only first `k` powers
  43. of `x` in the representation of `x^n`.
  44. Examples
  45. ========
  46. >>> from sympy.discrete.recurrences import linrec
  47. >>> from sympy.abc import x, y, z
  48. >>> linrec(coeffs=[1, 1], init=[0, 1], n=10)
  49. 55
  50. >>> linrec(coeffs=[1, 1], init=[x, y], n=10)
  51. 34*x + 55*y
  52. >>> linrec(coeffs=[x, y], init=[0, 1], n=5)
  53. x**2*y + x*(x**3 + 2*x*y) + y**2
  54. >>> linrec(coeffs=[1, 2, 3, 0, 0, 4], init=[x, y, z], n=16)
  55. 13576*x + 5676*y + 2356*z
  56. References
  57. ==========
  58. .. [1] https://en.wikipedia.org/wiki/Exponentiation_by_squaring
  59. .. [2] https://en.wikipedia.org/w/index.php?title=Modular_exponentiation&section=6#Matrices
  60. See Also
  61. ========
  62. sympy.polys.agca.extensions.ExtensionElement.__pow__
  63. """
  64. if not coeffs:
  65. return S.Zero
  66. if not iterable(coeffs):
  67. raise TypeError("Expected a sequence of coefficients for"
  68. " the recurrence")
  69. if not iterable(init):
  70. raise TypeError("Expected a sequence of values for the initialization"
  71. " of the recurrence")
  72. n = as_int(n)
  73. if n < 0:
  74. raise ValueError("Point of evaluation of recurrence must be a "
  75. "non-negative integer")
  76. c = [sympify(arg) for arg in coeffs]
  77. b = [sympify(arg) for arg in init]
  78. k = len(c)
  79. if len(b) > k:
  80. raise TypeError("Count of initial values should not exceed the "
  81. "order of the recurrence")
  82. else:
  83. b += [S.Zero]*(k - len(b)) # remaining initial values default to zero
  84. if n < k:
  85. return b[n]
  86. terms = [u*v for u, v in zip(linrec_coeffs(c, n), b)]
  87. return sum(terms[:-1], terms[-1])
  88. def linrec_coeffs(c, n):
  89. r"""
  90. Compute the coefficients of n'th term in linear recursion
  91. sequence defined by c.
  92. `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`.
  93. It computes the coefficients by using binary exponentiation.
  94. This function is used by `linrec` and `_eval_pow_by_cayley`.
  95. Parameters
  96. ==========
  97. c = coefficients of the divisor polynomial
  98. n = exponent of x, so dividend is x^n
  99. """
  100. k = len(c)
  101. def _square_and_reduce(u, offset):
  102. # squares `(u_0 + u_1 x + u_2 x^2 + \cdots + u_{k-1} x^k)` (and
  103. # multiplies by `x` if offset is 1) and reduces the above result of
  104. # length upto `2k` to `k` using the characteristic equation of the
  105. # recurrence given by, `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`
  106. w = [S.Zero]*(2*len(u) - 1 + offset)
  107. for i, p in enumerate(u):
  108. for j, q in enumerate(u):
  109. w[offset + i + j] += p*q
  110. for j in range(len(w) - 1, k - 1, -1):
  111. for i in range(k):
  112. w[j - i - 1] += w[j]*c[i]
  113. return w[:k]
  114. def _final_coeffs(n):
  115. # computes the final coefficient list - `cf` corresponding to the
  116. # point at which recurrence is to be evalauted - `n`, such that,
  117. # `y(n) = cf_0 y(k-1) + cf_1 y(k-2) + \cdots + cf_{k-1} y(0)`
  118. if n < k:
  119. return [S.Zero]*n + [S.One] + [S.Zero]*(k - n - 1)
  120. else:
  121. return _square_and_reduce(_final_coeffs(n // 2), n % 2)
  122. return _final_coeffs(n)