You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
11 KiB

5 months ago
"""Recurrence Operators"""
from sympy.core.singleton import S
from sympy.core.symbol import (Symbol, symbols)
from sympy.printing import sstr
from sympy.core.sympify import sympify
def RecurrenceOperators(base, generator):
"""
Returns an Algebra of Recurrence Operators and the operator for
shifting i.e. the `Sn` operator.
The first argument needs to be the base polynomial ring for the algebra
and the second argument must be a generator which can be either a
noncommutative Symbol or a string.
Examples
========
>>> from sympy import ZZ
>>> from sympy import symbols
>>> from sympy.holonomic.recurrence import RecurrenceOperators
>>> n = symbols('n', integer=True)
>>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n), 'Sn')
"""
ring = RecurrenceOperatorAlgebra(base, generator)
return (ring, ring.shift_operator)
class RecurrenceOperatorAlgebra:
"""
A Recurrence Operator Algebra is a set of noncommutative polynomials
in intermediate `Sn` and coefficients in a base ring A. It follows the
commutation rule:
Sn * a(n) = a(n + 1) * Sn
This class represents a Recurrence Operator Algebra and serves as the parent ring
for Recurrence Operators.
Examples
========
>>> from sympy import ZZ
>>> from sympy import symbols
>>> from sympy.holonomic.recurrence import RecurrenceOperators
>>> n = symbols('n', integer=True)
>>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n), 'Sn')
>>> R
Univariate Recurrence Operator Algebra in intermediate Sn over the base ring
ZZ[n]
See Also
========
RecurrenceOperator
"""
def __init__(self, base, generator):
# the base ring for the algebra
self.base = base
# the operator representing shift i.e. `Sn`
self.shift_operator = RecurrenceOperator(
[base.zero, base.one], self)
if generator is None:
self.gen_symbol = symbols('Sn', commutative=False)
else:
if isinstance(generator, str):
self.gen_symbol = symbols(generator, commutative=False)
elif isinstance(generator, Symbol):
self.gen_symbol = generator
def __str__(self):
string = 'Univariate Recurrence Operator Algebra in intermediate '\
+ sstr(self.gen_symbol) + ' over the base ring ' + \
(self.base).__str__()
return string
__repr__ = __str__
def __eq__(self, other):
if self.base == other.base and self.gen_symbol == other.gen_symbol:
return True
else:
return False
def _add_lists(list1, list2):
if len(list1) <= len(list2):
sol = [a + b for a, b in zip(list1, list2)] + list2[len(list1):]
else:
sol = [a + b for a, b in zip(list1, list2)] + list1[len(list2):]
return sol
class RecurrenceOperator:
"""
The Recurrence Operators are defined by a list of polynomials
in the base ring and the parent ring of the Operator.
Explanation
===========
Takes a list of polynomials for each power of Sn and the
parent ring which must be an instance of RecurrenceOperatorAlgebra.
A Recurrence Operator can be created easily using
the operator `Sn`. See examples below.
Examples
========
>>> from sympy.holonomic.recurrence import RecurrenceOperator, RecurrenceOperators
>>> from sympy import ZZ
>>> from sympy import symbols
>>> n = symbols('n', integer=True)
>>> R, Sn = RecurrenceOperators(ZZ.old_poly_ring(n),'Sn')
>>> RecurrenceOperator([0, 1, n**2], R)
(1)Sn + (n**2)Sn**2
>>> Sn*n
(n + 1)Sn
>>> n*Sn*n + 1 - Sn**2*n
(1) + (n**2 + n)Sn + (-n - 2)Sn**2
See Also
========
DifferentialOperatorAlgebra
"""
_op_priority = 20
def __init__(self, list_of_poly, parent):
# the parent ring for this operator
# must be an RecurrenceOperatorAlgebra object
self.parent = parent
# sequence of polynomials in n for each power of Sn
# represents the operator
# convert the expressions into ring elements using from_sympy
if isinstance(list_of_poly, list):
for i, j in enumerate(list_of_poly):
if isinstance(j, int):
list_of_poly[i] = self.parent.base.from_sympy(S(j))
elif not isinstance(j, self.parent.base.dtype):
list_of_poly[i] = self.parent.base.from_sympy(j)
self.listofpoly = list_of_poly
self.order = len(self.listofpoly) - 1
def __mul__(self, other):
"""
Multiplies two Operators and returns another
RecurrenceOperator instance using the commutation rule
Sn * a(n) = a(n + 1) * Sn
"""
listofself = self.listofpoly
base = self.parent.base
if not isinstance(other, RecurrenceOperator):
if not isinstance(other, self.parent.base.dtype):
listofother = [self.parent.base.from_sympy(sympify(other))]
else:
listofother = [other]
else:
listofother = other.listofpoly
# multiply a polynomial `b` with a list of polynomials
def _mul_dmp_diffop(b, listofother):
if isinstance(listofother, list):
sol = []
for i in listofother:
sol.append(i * b)
return sol
else:
return [b * listofother]
sol = _mul_dmp_diffop(listofself[0], listofother)
# compute Sn^i * b
def _mul_Sni_b(b):
sol = [base.zero]
if isinstance(b, list):
for i in b:
j = base.to_sympy(i).subs(base.gens[0], base.gens[0] + S.One)
sol.append(base.from_sympy(j))
else:
j = b.subs(base.gens[0], base.gens[0] + S.One)
sol.append(base.from_sympy(j))
return sol
for i in range(1, len(listofself)):
# find Sn^i * b in ith iteration
listofother = _mul_Sni_b(listofother)
# solution = solution + listofself[i] * (Sn^i * b)
sol = _add_lists(sol, _mul_dmp_diffop(listofself[i], listofother))
return RecurrenceOperator(sol, self.parent)
def __rmul__(self, other):
if not isinstance(other, RecurrenceOperator):
if isinstance(other, int):
other = S(other)
if not isinstance(other, self.parent.base.dtype):
other = (self.parent.base).from_sympy(other)
sol = []
for j in self.listofpoly:
sol.append(other * j)
return RecurrenceOperator(sol, self.parent)
def __add__(self, other):
if isinstance(other, RecurrenceOperator):
sol = _add_lists(self.listofpoly, other.listofpoly)
return RecurrenceOperator(sol, self.parent)
else:
if isinstance(other, int):
other = S(other)
list_self = self.listofpoly
if not isinstance(other, self.parent.base.dtype):
list_other = [((self.parent).base).from_sympy(other)]
else:
list_other = [other]
sol = []
sol.append(list_self[0] + list_other[0])
sol += list_self[1:]
return RecurrenceOperator(sol, self.parent)
__radd__ = __add__
def __sub__(self, other):
return self + (-1) * other
def __rsub__(self, other):
return (-1) * self + other
def __pow__(self, n):
if n == 1:
return self
if n == 0:
return RecurrenceOperator([self.parent.base.one], self.parent)
# if self is `Sn`
if self.listofpoly == self.parent.shift_operator.listofpoly:
sol = []
for i in range(0, n):
sol.append(self.parent.base.zero)
sol.append(self.parent.base.one)
return RecurrenceOperator(sol, self.parent)
else:
if n % 2 == 1:
powreduce = self**(n - 1)
return powreduce * self
elif n % 2 == 0:
powreduce = self**(n / 2)
return powreduce * powreduce
def __str__(self):
listofpoly = self.listofpoly
print_str = ''
for i, j in enumerate(listofpoly):
if j == self.parent.base.zero:
continue
if i == 0:
print_str += '(' + sstr(j) + ')'
continue
if print_str:
print_str += ' + '
if i == 1:
print_str += '(' + sstr(j) + ')Sn'
continue
print_str += '(' + sstr(j) + ')' + 'Sn**' + sstr(i)
return print_str
__repr__ = __str__
def __eq__(self, other):
if isinstance(other, RecurrenceOperator):
if self.listofpoly == other.listofpoly and self.parent == other.parent:
return True
else:
return False
else:
if self.listofpoly[0] == other:
for i in self.listofpoly[1:]:
if i is not self.parent.base.zero:
return False
return True
else:
return False
class HolonomicSequence:
"""
A Holonomic Sequence is a type of sequence satisfying a linear homogeneous
recurrence relation with Polynomial coefficients. Alternatively, A sequence
is Holonomic if and only if its generating function is a Holonomic Function.
"""
def __init__(self, recurrence, u0=[]):
self.recurrence = recurrence
if not isinstance(u0, list):
self.u0 = [u0]
else:
self.u0 = u0
if len(self.u0) == 0:
self._have_init_cond = False
else:
self._have_init_cond = True
self.n = recurrence.parent.base.gens[0]
def __repr__(self):
str_sol = 'HolonomicSequence(%s, %s)' % ((self.recurrence).__repr__(), sstr(self.n))
if not self._have_init_cond:
return str_sol
else:
cond_str = ''
seq_str = 0
for i in self.u0:
cond_str += ', u(%s) = %s' % (sstr(seq_str), sstr(i))
seq_str += 1
sol = str_sol + cond_str
return sol
__str__ = __repr__
def __eq__(self, other):
if self.recurrence == other.recurrence:
if self.n == other.n:
if self._have_init_cond and other._have_init_cond:
if self.u0 == other.u0:
return True
else:
return False
else:
return True
else:
return False
else:
return False