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
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
|