I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,8 @@
from .products import product, Product
from .summations import summation, Sum
__all__ = [
'product', 'Product',
'summation', 'Sum',
]

View File

@ -0,0 +1,330 @@
"""
This module implements sums and products containing the Kronecker Delta function.
References
==========
.. [1] https://mathworld.wolfram.com/KroneckerDelta.html
"""
from .products import product
from .summations import Sum, summation
from sympy.core import Add, Mul, S, Dummy
from sympy.core.cache import cacheit
from sympy.core.sorting import default_sort_key
from sympy.functions import KroneckerDelta, Piecewise, piecewise_fold
from sympy.polys.polytools import factor
from sympy.sets.sets import Interval
from sympy.solvers.solvers import solve
@cacheit
def _expand_delta(expr, index):
"""
Expand the first Add containing a simple KroneckerDelta.
"""
if not expr.is_Mul:
return expr
delta = None
func = Add
terms = [S.One]
for h in expr.args:
if delta is None and h.is_Add and _has_simple_delta(h, index):
delta = True
func = h.func
terms = [terms[0]*t for t in h.args]
else:
terms = [t*h for t in terms]
return func(*terms)
@cacheit
def _extract_delta(expr, index):
"""
Extract a simple KroneckerDelta from the expression.
Explanation
===========
Returns the tuple ``(delta, newexpr)`` where:
- ``delta`` is a simple KroneckerDelta expression if one was found,
or ``None`` if no simple KroneckerDelta expression was found.
- ``newexpr`` is a Mul containing the remaining terms; ``expr`` is
returned unchanged if no simple KroneckerDelta expression was found.
Examples
========
>>> from sympy import KroneckerDelta
>>> from sympy.concrete.delta import _extract_delta
>>> from sympy.abc import x, y, i, j, k
>>> _extract_delta(4*x*y*KroneckerDelta(i, j), i)
(KroneckerDelta(i, j), 4*x*y)
>>> _extract_delta(4*x*y*KroneckerDelta(i, j), k)
(None, 4*x*y*KroneckerDelta(i, j))
See Also
========
sympy.functions.special.tensor_functions.KroneckerDelta
deltaproduct
deltasummation
"""
if not _has_simple_delta(expr, index):
return (None, expr)
if isinstance(expr, KroneckerDelta):
return (expr, S.One)
if not expr.is_Mul:
raise ValueError("Incorrect expr")
delta = None
terms = []
for arg in expr.args:
if delta is None and _is_simple_delta(arg, index):
delta = arg
else:
terms.append(arg)
return (delta, expr.func(*terms))
@cacheit
def _has_simple_delta(expr, index):
"""
Returns True if ``expr`` is an expression that contains a KroneckerDelta
that is simple in the index ``index``, meaning that this KroneckerDelta
is nonzero for a single value of the index ``index``.
"""
if expr.has(KroneckerDelta):
if _is_simple_delta(expr, index):
return True
if expr.is_Add or expr.is_Mul:
for arg in expr.args:
if _has_simple_delta(arg, index):
return True
return False
@cacheit
def _is_simple_delta(delta, index):
"""
Returns True if ``delta`` is a KroneckerDelta and is nonzero for a single
value of the index ``index``.
"""
if isinstance(delta, KroneckerDelta) and delta.has(index):
p = (delta.args[0] - delta.args[1]).as_poly(index)
if p:
return p.degree() == 1
return False
@cacheit
def _remove_multiple_delta(expr):
"""
Evaluate products of KroneckerDelta's.
"""
if expr.is_Add:
return expr.func(*list(map(_remove_multiple_delta, expr.args)))
if not expr.is_Mul:
return expr
eqs = []
newargs = []
for arg in expr.args:
if isinstance(arg, KroneckerDelta):
eqs.append(arg.args[0] - arg.args[1])
else:
newargs.append(arg)
if not eqs:
return expr
solns = solve(eqs, dict=True)
if len(solns) == 0:
return S.Zero
elif len(solns) == 1:
for key in solns[0].keys():
newargs.append(KroneckerDelta(key, solns[0][key]))
expr2 = expr.func(*newargs)
if expr != expr2:
return _remove_multiple_delta(expr2)
return expr
@cacheit
def _simplify_delta(expr):
"""
Rewrite a KroneckerDelta's indices in its simplest form.
"""
if isinstance(expr, KroneckerDelta):
try:
slns = solve(expr.args[0] - expr.args[1], dict=True)
if slns and len(slns) == 1:
return Mul(*[KroneckerDelta(*(key, value))
for key, value in slns[0].items()])
except NotImplementedError:
pass
return expr
@cacheit
def deltaproduct(f, limit):
"""
Handle products containing a KroneckerDelta.
See Also
========
deltasummation
sympy.functions.special.tensor_functions.KroneckerDelta
sympy.concrete.products.product
"""
if ((limit[2] - limit[1]) < 0) == True:
return S.One
if not f.has(KroneckerDelta):
return product(f, limit)
if f.is_Add:
# Identify the term in the Add that has a simple KroneckerDelta
delta = None
terms = []
for arg in sorted(f.args, key=default_sort_key):
if delta is None and _has_simple_delta(arg, limit[0]):
delta = arg
else:
terms.append(arg)
newexpr = f.func(*terms)
k = Dummy("kprime", integer=True)
if isinstance(limit[1], int) and isinstance(limit[2], int):
result = deltaproduct(newexpr, limit) + sum(deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) *
delta.subs(limit[0], ik) *
deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1))
)
else:
result = deltaproduct(newexpr, limit) + deltasummation(
deltaproduct(newexpr, (limit[0], limit[1], k - 1)) *
delta.subs(limit[0], k) *
deltaproduct(newexpr, (limit[0], k + 1, limit[2])),
(k, limit[1], limit[2]),
no_piecewise=_has_simple_delta(newexpr, limit[0])
)
return _remove_multiple_delta(result)
delta, _ = _extract_delta(f, limit[0])
if not delta:
g = _expand_delta(f, limit[0])
if f != g:
try:
return factor(deltaproduct(g, limit))
except AssertionError:
return deltaproduct(g, limit)
return product(f, limit)
return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \
S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1))
@cacheit
def deltasummation(f, limit, no_piecewise=False):
"""
Handle summations containing a KroneckerDelta.
Explanation
===========
The idea for summation is the following:
- If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j),
we try to simplify it.
If we could simplify it, then we sum the resulting expression.
We already know we can sum a simplified expression, because only
simple KroneckerDelta expressions are involved.
If we could not simplify it, there are two cases:
1) The expression is a simple expression: we return the summation,
taking care if we are dealing with a Derivative or with a proper
KroneckerDelta.
2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do
nothing at all.
- If the expr is a multiplication expr having a KroneckerDelta term:
First we expand it.
If the expansion did work, then we try to sum the expansion.
If not, we try to extract a simple KroneckerDelta term, then we have two
cases:
1) We have a simple KroneckerDelta term, so we return the summation.
2) We did not have a simple term, but we do have an expression with
simplified KroneckerDelta terms, so we sum this expression.
Examples
========
>>> from sympy import oo, symbols
>>> from sympy.abc import k
>>> i, j = symbols('i, j', integer=True, finite=True)
>>> from sympy.concrete.delta import deltasummation
>>> from sympy import KroneckerDelta
>>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo))
1
>>> deltasummation(KroneckerDelta(i, k), (k, 0, oo))
Piecewise((1, i >= 0), (0, True))
>>> deltasummation(KroneckerDelta(i, k), (k, 1, 3))
Piecewise((1, (i >= 1) & (i <= 3)), (0, True))
>>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo))
j*KroneckerDelta(i, j)
>>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo))
i
>>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo))
j
See Also
========
deltaproduct
sympy.functions.special.tensor_functions.KroneckerDelta
sympy.concrete.sums.summation
"""
if ((limit[2] - limit[1]) < 0) == True:
return S.Zero
if not f.has(KroneckerDelta):
return summation(f, limit)
x = limit[0]
g = _expand_delta(f, x)
if g.is_Add:
return piecewise_fold(
g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args]))
# try to extract a simple KroneckerDelta term
delta, expr = _extract_delta(g, x)
if (delta is not None) and (delta.delta_range is not None):
dinf, dsup = delta.delta_range
if (limit[1] - dinf <= 0) == True and (limit[2] - dsup >= 0) == True:
no_piecewise = True
if not delta:
return summation(f, limit)
solns = solve(delta.args[0] - delta.args[1], x)
if len(solns) == 0:
return S.Zero
elif len(solns) != 1:
return Sum(f, limit)
value = solns[0]
if no_piecewise:
return expr.subs(x, value)
return Piecewise(
(expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)),
(S.Zero, True)
)

View File

@ -0,0 +1,354 @@
from sympy.concrete.expr_with_limits import ExprWithLimits
from sympy.core.singleton import S
from sympy.core.relational import Eq
class ReorderError(NotImplementedError):
"""
Exception raised when trying to reorder dependent limits.
"""
def __init__(self, expr, msg):
super().__init__(
"%s could not be reordered: %s." % (expr, msg))
class ExprWithIntLimits(ExprWithLimits):
"""
Superclass for Product and Sum.
See Also
========
sympy.concrete.expr_with_limits.ExprWithLimits
sympy.concrete.products.Product
sympy.concrete.summations.Sum
"""
__slots__ = ()
def change_index(self, var, trafo, newvar=None):
r"""
Change index of a Sum or Product.
Perform a linear transformation `x \mapsto a x + b` on the index variable
`x`. For `a` the only values allowed are `\pm 1`. A new variable to be used
after the change of index can also be specified.
Explanation
===========
``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the
index variable `x` to transform. The transformation ``trafo`` must be linear
and given in terms of ``var``. If the optional argument ``newvar`` is
provided then ``var`` gets replaced by ``newvar`` in the final expression.
Examples
========
>>> from sympy import Sum, Product, simplify
>>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l
>>> S = Sum(x, (x, a, b))
>>> S.doit()
-a**2/2 + a/2 + b**2/2 + b/2
>>> Sn = S.change_index(x, x + 1, y)
>>> Sn
Sum(y - 1, (y, a + 1, b + 1))
>>> Sn.doit()
-a**2/2 + a/2 + b**2/2 + b/2
>>> Sn = S.change_index(x, -x, y)
>>> Sn
Sum(-y, (y, -b, -a))
>>> Sn.doit()
-a**2/2 + a/2 + b**2/2 + b/2
>>> Sn = S.change_index(x, x+u)
>>> Sn
Sum(-u + x, (x, a + u, b + u))
>>> Sn.doit()
-a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
>>> simplify(Sn.doit())
-a**2/2 + a/2 + b**2/2 + b/2
>>> Sn = S.change_index(x, -x - u, y)
>>> Sn
Sum(-u - y, (y, -b - u, -a - u))
>>> Sn.doit()
-a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
>>> simplify(Sn.doit())
-a**2/2 + a/2 + b**2/2 + b/2
>>> P = Product(i*j**2, (i, a, b), (j, c, d))
>>> P
Product(i*j**2, (i, a, b), (j, c, d))
>>> P2 = P.change_index(i, i+3, k)
>>> P2
Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d))
>>> P3 = P2.change_index(j, -j, l)
>>> P3
Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c))
When dealing with symbols only, we can make a
general linear transformation:
>>> Sn = S.change_index(x, u*x+v, y)
>>> Sn
Sum((-v + y)/u, (y, b*u + v, a*u + v))
>>> Sn.doit()
-v*(a*u - b*u + 1)/u + (a**2*u**2/2 + a*u*v + a*u/2 - b**2*u**2/2 - b*u*v + b*u/2 + v)/u
>>> simplify(Sn.doit())
a**2*u/2 + a/2 - b**2*u/2 + b/2
However, the last result can be inconsistent with usual
summation where the index increment is always 1. This is
obvious as we get back the original value only for ``u``
equal +1 or -1.
See Also
========
sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
reorder_limit,
sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder,
sympy.concrete.summations.Sum.reverse_order,
sympy.concrete.products.Product.reverse_order
"""
if newvar is None:
newvar = var
limits = []
for limit in self.limits:
if limit[0] == var:
p = trafo.as_poly(var)
if p.degree() != 1:
raise ValueError("Index transformation is not linear")
alpha = p.coeff_monomial(var)
beta = p.coeff_monomial(S.One)
if alpha.is_number:
if alpha == S.One:
limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta))
elif alpha == S.NegativeOne:
limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
else:
raise ValueError("Linear transformation results in non-linear summation stepsize")
else:
# Note that the case of alpha being symbolic can give issues if alpha < 0.
limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
else:
limits.append(limit)
function = self.function.subs(var, (var - beta)/alpha)
function = function.subs(var, newvar)
return self.func(function, *limits)
def index(expr, x):
"""
Return the index of a dummy variable in the list of limits.
Explanation
===========
``index(expr, x)`` returns the index of the dummy variable ``x`` in the
limits of ``expr``. Note that we start counting with 0 at the inner-most
limits tuple.
Examples
========
>>> from sympy.abc import x, y, a, b, c, d
>>> from sympy import Sum, Product
>>> Sum(x*y, (x, a, b), (y, c, d)).index(x)
0
>>> Sum(x*y, (x, a, b), (y, c, d)).index(y)
1
>>> Product(x*y, (x, a, b), (y, c, d)).index(x)
0
>>> Product(x*y, (x, a, b), (y, c, d)).index(y)
1
See Also
========
reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order,
sympy.concrete.products.Product.reverse_order
"""
variables = [limit[0] for limit in expr.limits]
if variables.count(x) != 1:
raise ValueError(expr, "Number of instances of variable not equal to one")
else:
return variables.index(x)
def reorder(expr, *arg):
"""
Reorder limits in a expression containing a Sum or a Product.
Explanation
===========
``expr.reorder(*arg)`` reorders the limits in the expression ``expr``
according to the list of tuples given by ``arg``. These tuples can
contain numerical indices or index variable names or involve both.
Examples
========
>>> from sympy import Sum, Product
>>> from sympy.abc import x, y, z, a, b, c, d, e, f
>>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y))
Sum(x*y, (y, c, d), (x, a, b))
>>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z))
Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
>>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f))
>>> P.reorder((x, y), (x, z), (y, z))
Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
We can also select the index variables by counting them, starting
with the inner-most one:
>>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1))
Sum(x**2, (x, c, d), (x, a, b))
And of course we can mix both schemes:
>>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x))
Sum(x*y, (y, c, d), (x, a, b))
>>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0))
Sum(x*y, (y, c, d), (x, a, b))
See Also
========
reorder_limit, index, sympy.concrete.summations.Sum.reverse_order,
sympy.concrete.products.Product.reverse_order
"""
new_expr = expr
for r in arg:
if len(r) != 2:
raise ValueError(r, "Invalid number of arguments")
index1 = r[0]
index2 = r[1]
if not isinstance(r[0], int):
index1 = expr.index(r[0])
if not isinstance(r[1], int):
index2 = expr.index(r[1])
new_expr = new_expr.reorder_limit(index1, index2)
return new_expr
def reorder_limit(expr, x, y):
"""
Interchange two limit tuples of a Sum or Product expression.
Explanation
===========
``expr.reorder_limit(x, y)`` interchanges two limit tuples. The
arguments ``x`` and ``y`` are integers corresponding to the index
variables of the two limits which are to be interchanged. The
expression ``expr`` has to be either a Sum or a Product.
Examples
========
>>> from sympy.abc import x, y, z, a, b, c, d, e, f
>>> from sympy import Sum, Product
>>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
>>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0)
Sum(x**2, (x, c, d), (x, a, b))
>>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
See Also
========
index, reorder, sympy.concrete.summations.Sum.reverse_order,
sympy.concrete.products.Product.reverse_order
"""
var = {limit[0] for limit in expr.limits}
limit_x = expr.limits[x]
limit_y = expr.limits[y]
if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and
len(set(limit_x[2].free_symbols).intersection(var)) == 0 and
len(set(limit_y[1].free_symbols).intersection(var)) == 0 and
len(set(limit_y[2].free_symbols).intersection(var)) == 0):
limits = []
for i, limit in enumerate(expr.limits):
if i == x:
limits.append(limit_y)
elif i == y:
limits.append(limit_x)
else:
limits.append(limit)
return type(expr)(expr.function, *limits)
else:
raise ReorderError(expr, "could not interchange the two limits specified")
@property
def has_empty_sequence(self):
"""
Returns True if the Sum or Product is computed for an empty sequence.
Examples
========
>>> from sympy import Sum, Product, Symbol
>>> m = Symbol('m')
>>> Sum(m, (m, 1, 0)).has_empty_sequence
True
>>> Sum(m, (m, 1, 1)).has_empty_sequence
False
>>> M = Symbol('M', integer=True, positive=True)
>>> Product(m, (m, 1, M)).has_empty_sequence
False
>>> Product(m, (m, 2, M)).has_empty_sequence
>>> Product(m, (m, M + 1, M)).has_empty_sequence
True
>>> N = Symbol('N', integer=True, positive=True)
>>> Sum(m, (m, N, M)).has_empty_sequence
>>> N = Symbol('N', integer=True, negative=True)
>>> Sum(m, (m, N, M)).has_empty_sequence
False
See Also
========
has_reversed_limits
has_finite_limits
"""
ret_None = False
for lim in self.limits:
dif = lim[1] - lim[2]
eq = Eq(dif, 1)
if eq == True:
return True
elif eq == False:
continue
else:
ret_None = True
if ret_None:
return None
return False

View File

@ -0,0 +1,603 @@
from sympy.core.add import Add
from sympy.core.containers import Tuple
from sympy.core.expr import Expr
from sympy.core.function import AppliedUndef, UndefinedFunction
from sympy.core.mul import Mul
from sympy.core.relational import Equality, Relational
from sympy.core.singleton import S
from sympy.core.symbol import Symbol, Dummy
from sympy.core.sympify import sympify
from sympy.functions.elementary.piecewise import (piecewise_fold,
Piecewise)
from sympy.logic.boolalg import BooleanFunction
from sympy.matrices.matrixbase import MatrixBase
from sympy.sets.sets import Interval, Set
from sympy.sets.fancysets import Range
from sympy.tensor.indexed import Idx
from sympy.utilities import flatten
from sympy.utilities.iterables import sift, is_sequence
from sympy.utilities.exceptions import sympy_deprecation_warning
def _common_new(cls, function, *symbols, discrete, **assumptions):
"""Return either a special return value or the tuple,
(function, limits, orientation). This code is common to
both ExprWithLimits and AddWithLimits."""
function = sympify(function)
if isinstance(function, Equality):
# This transforms e.g. Integral(Eq(x, y)) to Eq(Integral(x), Integral(y))
# but that is only valid for definite integrals.
limits, orientation = _process_limits(*symbols, discrete=discrete)
if not (limits and all(len(limit) == 3 for limit in limits)):
sympy_deprecation_warning(
"""
Creating a indefinite integral with an Eq() argument is
deprecated.
This is because indefinite integrals do not preserve equality
due to the arbitrary constants. If you want an equality of
indefinite integrals, use Eq(Integral(a, x), Integral(b, x))
explicitly.
""",
deprecated_since_version="1.6",
active_deprecations_target="deprecated-indefinite-integral-eq",
stacklevel=5,
)
lhs = function.lhs
rhs = function.rhs
return Equality(cls(lhs, *symbols, **assumptions), \
cls(rhs, *symbols, **assumptions))
if function is S.NaN:
return S.NaN
if symbols:
limits, orientation = _process_limits(*symbols, discrete=discrete)
for i, li in enumerate(limits):
if len(li) == 4:
function = function.subs(li[0], li[-1])
limits[i] = Tuple(*li[:-1])
else:
# symbol not provided -- we can still try to compute a general form
free = function.free_symbols
if len(free) != 1:
raise ValueError(
"specify dummy variables for %s" % function)
limits, orientation = [Tuple(s) for s in free], 1
# denest any nested calls
while cls == type(function):
limits = list(function.limits) + limits
function = function.function
# Any embedded piecewise functions need to be brought out to the
# top level. We only fold Piecewise that contain the integration
# variable.
reps = {}
symbols_of_integration = {i[0] for i in limits}
for p in function.atoms(Piecewise):
if not p.has(*symbols_of_integration):
reps[p] = Dummy()
# mask off those that don't
function = function.xreplace(reps)
# do the fold
function = piecewise_fold(function)
# remove the masking
function = function.xreplace({v: k for k, v in reps.items()})
return function, limits, orientation
def _process_limits(*symbols, discrete=None):
"""Process the list of symbols and convert them to canonical limits,
storing them as Tuple(symbol, lower, upper). The orientation of
the function is also returned when the upper limit is missing
so (x, 1, None) becomes (x, None, 1) and the orientation is changed.
In the case that a limit is specified as (symbol, Range), a list of
length 4 may be returned if a change of variables is needed; the
expression that should replace the symbol in the expression is
the fourth element in the list.
"""
limits = []
orientation = 1
if discrete is None:
err_msg = 'discrete must be True or False'
elif discrete:
err_msg = 'use Range, not Interval or Relational'
else:
err_msg = 'use Interval or Relational, not Range'
for V in symbols:
if isinstance(V, (Relational, BooleanFunction)):
if discrete:
raise TypeError(err_msg)
variable = V.atoms(Symbol).pop()
V = (variable, V.as_set())
elif isinstance(V, Symbol) or getattr(V, '_diff_wrt', False):
if isinstance(V, Idx):
if V.lower is None or V.upper is None:
limits.append(Tuple(V))
else:
limits.append(Tuple(V, V.lower, V.upper))
else:
limits.append(Tuple(V))
continue
if is_sequence(V) and not isinstance(V, Set):
if len(V) == 2 and isinstance(V[1], Set):
V = list(V)
if isinstance(V[1], Interval): # includes Reals
if discrete:
raise TypeError(err_msg)
V[1:] = V[1].inf, V[1].sup
elif isinstance(V[1], Range):
if not discrete:
raise TypeError(err_msg)
lo = V[1].inf
hi = V[1].sup
dx = abs(V[1].step) # direction doesn't matter
if dx == 1:
V[1:] = [lo, hi]
else:
if lo is not S.NegativeInfinity:
V = [V[0]] + [0, (hi - lo)//dx, dx*V[0] + lo]
else:
V = [V[0]] + [0, S.Infinity, -dx*V[0] + hi]
else:
# more complicated sets would require splitting, e.g.
# Union(Interval(1, 3), interval(6,10))
raise NotImplementedError(
'expecting Range' if discrete else
'Relational or single Interval' )
V = sympify(flatten(V)) # list of sympified elements/None
if isinstance(V[0], (Symbol, Idx)) or getattr(V[0], '_diff_wrt', False):
newsymbol = V[0]
if len(V) == 3:
# general case
if V[2] is None and V[1] is not None:
orientation *= -1
V = [newsymbol] + [i for i in V[1:] if i is not None]
lenV = len(V)
if not isinstance(newsymbol, Idx) or lenV == 3:
if lenV == 4:
limits.append(Tuple(*V))
continue
if lenV == 3:
if isinstance(newsymbol, Idx):
# Idx represents an integer which may have
# specified values it can take on; if it is
# given such a value, an error is raised here
# if the summation would try to give it a larger
# or smaller value than permitted. None and Symbolic
# values will not raise an error.
lo, hi = newsymbol.lower, newsymbol.upper
try:
if lo is not None and not bool(V[1] >= lo):
raise ValueError("Summation will set Idx value too low.")
except TypeError:
pass
try:
if hi is not None and not bool(V[2] <= hi):
raise ValueError("Summation will set Idx value too high.")
except TypeError:
pass
limits.append(Tuple(*V))
continue
if lenV == 1 or (lenV == 2 and V[1] is None):
limits.append(Tuple(newsymbol))
continue
elif lenV == 2:
limits.append(Tuple(newsymbol, V[1]))
continue
raise ValueError('Invalid limits given: %s' % str(symbols))
return limits, orientation
class ExprWithLimits(Expr):
__slots__ = ('is_commutative',)
def __new__(cls, function, *symbols, **assumptions):
from sympy.concrete.products import Product
pre = _common_new(cls, function, *symbols,
discrete=issubclass(cls, Product), **assumptions)
if isinstance(pre, tuple):
function, limits, _ = pre
else:
return pre
# limits must have upper and lower bounds; the indefinite form
# is not supported. This restriction does not apply to AddWithLimits
if any(len(l) != 3 or None in l for l in limits):
raise ValueError('ExprWithLimits requires values for lower and upper bounds.')
obj = Expr.__new__(cls, **assumptions)
arglist = [function]
arglist.extend(limits)
obj._args = tuple(arglist)
obj.is_commutative = function.is_commutative # limits already checked
return obj
@property
def function(self):
"""Return the function applied across limits.
Examples
========
>>> from sympy import Integral
>>> from sympy.abc import x
>>> Integral(x**2, (x,)).function
x**2
See Also
========
limits, variables, free_symbols
"""
return self._args[0]
@property
def kind(self):
return self.function.kind
@property
def limits(self):
"""Return the limits of expression.
Examples
========
>>> from sympy import Integral
>>> from sympy.abc import x, i
>>> Integral(x**i, (i, 1, 3)).limits
((i, 1, 3),)
See Also
========
function, variables, free_symbols
"""
return self._args[1:]
@property
def variables(self):
"""Return a list of the limit variables.
>>> from sympy import Sum
>>> from sympy.abc import x, i
>>> Sum(x**i, (i, 1, 3)).variables
[i]
See Also
========
function, limits, free_symbols
as_dummy : Rename dummy variables
sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
"""
return [l[0] for l in self.limits]
@property
def bound_symbols(self):
"""Return only variables that are dummy variables.
Examples
========
>>> from sympy import Integral
>>> from sympy.abc import x, i, j, k
>>> Integral(x**i, (i, 1, 3), (j, 2), k).bound_symbols
[i, j]
See Also
========
function, limits, free_symbols
as_dummy : Rename dummy variables
sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
"""
return [l[0] for l in self.limits if len(l) != 1]
@property
def free_symbols(self):
"""
This method returns the symbols in the object, excluding those
that take on a specific value (i.e. the dummy symbols).
Examples
========
>>> from sympy import Sum
>>> from sympy.abc import x, y
>>> Sum(x, (x, y, 1)).free_symbols
{y}
"""
# don't test for any special values -- nominal free symbols
# should be returned, e.g. don't return set() if the
# function is zero -- treat it like an unevaluated expression.
function, limits = self.function, self.limits
# mask off non-symbol integration variables that have
# more than themself as a free symbol
reps = {i[0]: i[0] if i[0].free_symbols == {i[0]} else Dummy()
for i in self.limits}
function = function.xreplace(reps)
isyms = function.free_symbols
for xab in limits:
v = reps[xab[0]]
if len(xab) == 1:
isyms.add(v)
continue
# take out the target symbol
if v in isyms:
isyms.remove(v)
# add in the new symbols
for i in xab[1:]:
isyms.update(i.free_symbols)
reps = {v: k for k, v in reps.items()}
return {reps.get(_, _) for _ in isyms}
@property
def is_number(self):
"""Return True if the Sum has no free symbols, else False."""
return not self.free_symbols
def _eval_interval(self, x, a, b):
limits = [(i if i[0] != x else (x, a, b)) for i in self.limits]
integrand = self.function
return self.func(integrand, *limits)
def _eval_subs(self, old, new):
"""
Perform substitutions over non-dummy variables
of an expression with limits. Also, can be used
to specify point-evaluation of an abstract antiderivative.
Examples
========
>>> from sympy import Sum, oo
>>> from sympy.abc import s, n
>>> Sum(1/n**s, (n, 1, oo)).subs(s, 2)
Sum(n**(-2), (n, 1, oo))
>>> from sympy import Integral
>>> from sympy.abc import x, a
>>> Integral(a*x**2, x).subs(x, 4)
Integral(a*x**2, (x, 4))
See Also
========
variables : Lists the integration variables
transform : Perform mapping on the dummy variable for integrals
change_index : Perform mapping on the sum and product dummy variables
"""
func, limits = self.function, list(self.limits)
# If one of the expressions we are replacing is used as a func index
# one of two things happens.
# - the old variable first appears as a free variable
# so we perform all free substitutions before it becomes
# a func index.
# - the old variable first appears as a func index, in
# which case we ignore. See change_index.
# Reorder limits to match standard mathematical practice for scoping
limits.reverse()
if not isinstance(old, Symbol) or \
old.free_symbols.intersection(self.free_symbols):
sub_into_func = True
for i, xab in enumerate(limits):
if 1 == len(xab) and old == xab[0]:
if new._diff_wrt:
xab = (new,)
else:
xab = (old, old)
limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
if len(xab[0].free_symbols.intersection(old.free_symbols)) != 0:
sub_into_func = False
break
if isinstance(old, (AppliedUndef, UndefinedFunction)):
sy2 = set(self.variables).intersection(set(new.atoms(Symbol)))
sy1 = set(self.variables).intersection(set(old.args))
if not sy2.issubset(sy1):
raise ValueError(
"substitution cannot create dummy dependencies")
sub_into_func = True
if sub_into_func:
func = func.subs(old, new)
else:
# old is a Symbol and a dummy variable of some limit
for i, xab in enumerate(limits):
if len(xab) == 3:
limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
if old == xab[0]:
break
# simplify redundant limits (x, x) to (x, )
for i, xab in enumerate(limits):
if len(xab) == 2 and (xab[0] - xab[1]).is_zero:
limits[i] = Tuple(xab[0], )
# Reorder limits back to representation-form
limits.reverse()
return self.func(func, *limits)
@property
def has_finite_limits(self):
"""
Returns True if the limits are known to be finite, either by the
explicit bounds, assumptions on the bounds, or assumptions on the
variables. False if known to be infinite, based on the bounds.
None if not enough information is available to determine.
Examples
========
>>> from sympy import Sum, Integral, Product, oo, Symbol
>>> x = Symbol('x')
>>> Sum(x, (x, 1, 8)).has_finite_limits
True
>>> Integral(x, (x, 1, oo)).has_finite_limits
False
>>> M = Symbol('M')
>>> Sum(x, (x, 1, M)).has_finite_limits
>>> N = Symbol('N', integer=True)
>>> Product(x, (x, 1, N)).has_finite_limits
True
See Also
========
has_reversed_limits
"""
ret_None = False
for lim in self.limits:
if len(lim) == 3:
if any(l.is_infinite for l in lim[1:]):
# Any of the bounds are +/-oo
return False
elif any(l.is_infinite is None for l in lim[1:]):
# Maybe there are assumptions on the variable?
if lim[0].is_infinite is None:
ret_None = True
else:
if lim[0].is_infinite is None:
ret_None = True
if ret_None:
return None
return True
@property
def has_reversed_limits(self):
"""
Returns True if the limits are known to be in reversed order, either
by the explicit bounds, assumptions on the bounds, or assumptions on the
variables. False if known to be in normal order, based on the bounds.
None if not enough information is available to determine.
Examples
========
>>> from sympy import Sum, Integral, Product, oo, Symbol
>>> x = Symbol('x')
>>> Sum(x, (x, 8, 1)).has_reversed_limits
True
>>> Sum(x, (x, 1, oo)).has_reversed_limits
False
>>> M = Symbol('M')
>>> Integral(x, (x, 1, M)).has_reversed_limits
>>> N = Symbol('N', integer=True, positive=True)
>>> Sum(x, (x, 1, N)).has_reversed_limits
False
>>> Product(x, (x, 2, N)).has_reversed_limits
>>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits
False
See Also
========
sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence
"""
ret_None = False
for lim in self.limits:
if len(lim) == 3:
var, a, b = lim
dif = b - a
if dif.is_extended_negative:
return True
elif dif.is_extended_nonnegative:
continue
else:
ret_None = True
else:
return None
if ret_None:
return None
return False
class AddWithLimits(ExprWithLimits):
r"""Represents unevaluated oriented additions.
Parent class for Integral and Sum.
"""
__slots__ = ()
def __new__(cls, function, *symbols, **assumptions):
from sympy.concrete.summations import Sum
pre = _common_new(cls, function, *symbols,
discrete=issubclass(cls, Sum), **assumptions)
if isinstance(pre, tuple):
function, limits, orientation = pre
else:
return pre
obj = Expr.__new__(cls, **assumptions)
arglist = [orientation*function] # orientation not used in ExprWithLimits
arglist.extend(limits)
obj._args = tuple(arglist)
obj.is_commutative = function.is_commutative # limits already checked
return obj
def _eval_adjoint(self):
if all(x.is_real for x in flatten(self.limits)):
return self.func(self.function.adjoint(), *self.limits)
return None
def _eval_conjugate(self):
if all(x.is_real for x in flatten(self.limits)):
return self.func(self.function.conjugate(), *self.limits)
return None
def _eval_transpose(self):
if all(x.is_real for x in flatten(self.limits)):
return self.func(self.function.transpose(), *self.limits)
return None
def _eval_factor(self, **hints):
if 1 == len(self.limits):
summand = self.function.factor(**hints)
if summand.is_Mul:
out = sift(summand.args, lambda w: w.is_commutative \
and not set(self.variables) & w.free_symbols)
return Mul(*out[True])*self.func(Mul(*out[False]), \
*self.limits)
else:
summand = self.func(self.function, *self.limits[0:-1]).factor()
if not summand.has(self.variables[-1]):
return self.func(1, [self.limits[-1]]).doit()*summand
elif isinstance(summand, Mul):
return self.func(summand, self.limits[-1]).factor()
return self
def _eval_expand_basic(self, **hints):
summand = self.function.expand(**hints)
force = hints.get('force', False)
if (summand.is_Add and (force or summand.is_commutative and
self.has_finite_limits is not False)):
return Add(*[self.func(i, *self.limits) for i in summand.args])
elif isinstance(summand, MatrixBase):
return summand.applyfunc(lambda x: self.func(x, *self.limits))
elif summand != self.function:
return self.func(summand, *self.limits)
return self

View File

@ -0,0 +1,227 @@
"""Gosper's algorithm for hypergeometric summation. """
from sympy.core import S, Dummy, symbols
from sympy.polys import Poly, parallel_poly_from_expr, factor
from sympy.utilities.iterables import is_sequence
def gosper_normal(f, g, n, polys=True):
r"""
Compute the Gosper's normal form of ``f`` and ``g``.
Explanation
===========
Given relatively prime univariate polynomials ``f`` and ``g``,
rewrite their quotient to a normal form defined as follows:
.. math::
\frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)}
where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are
monic polynomials in ``n`` with the following properties:
1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}`
2. `\gcd(B(n), C(n+1)) = 1`
3. `\gcd(A(n), C(n)) = 1`
This normal form, or rational factorization in other words, is a
crucial step in Gosper's algorithm and in solving of difference
equations. It can be also used to decide if two hypergeometric
terms are similar or not.
This procedure will return a tuple containing elements of this
factorization in the form ``(Z*A, B, C)``.
Examples
========
>>> from sympy.concrete.gosper import gosper_normal
>>> from sympy.abc import n
>>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False)
(1/4, n + 3/2, n + 1/4)
"""
(p, q), opt = parallel_poly_from_expr(
(f, g), n, field=True, extension=True)
a, A = p.LC(), p.monic()
b, B = q.LC(), q.monic()
C, Z = A.one, a/b
h = Dummy('h')
D = Poly(n + h, n, h, domain=opt.domain)
R = A.resultant(B.compose(D))
roots = set(R.ground_roots().keys())
for r in set(roots):
if not r.is_Integer or r < 0:
roots.remove(r)
for i in sorted(roots):
d = A.gcd(B.shift(+i))
A = A.quo(d)
B = B.quo(d.shift(-i))
for j in range(1, i + 1):
C *= d.shift(-j)
A = A.mul_ground(Z)
if not polys:
A = A.as_expr()
B = B.as_expr()
C = C.as_expr()
return A, B, C
def gosper_term(f, n):
r"""
Compute Gosper's hypergeometric term for ``f``.
Explanation
===========
Suppose ``f`` is a hypergeometric term such that:
.. math::
s_n = \sum_{k=0}^{n-1} f_k
and `f_k` does not depend on `n`. Returns a hypergeometric
term `g_n` such that `g_{n+1} - g_n = f_n`.
Examples
========
>>> from sympy.concrete.gosper import gosper_term
>>> from sympy import factorial
>>> from sympy.abc import n
>>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n)
(-n - 1/2)/(n + 1/4)
"""
from sympy.simplify import hypersimp
r = hypersimp(f, n)
if r is None:
return None # 'f' is *not* a hypergeometric term
p, q = r.as_numer_denom()
A, B, C = gosper_normal(p, q, n)
B = B.shift(-1)
N = S(A.degree())
M = S(B.degree())
K = S(C.degree())
if (N != M) or (A.LC() != B.LC()):
D = {K - max(N, M)}
elif not N:
D = {K - N + 1, S.Zero}
else:
D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()}
for d in set(D):
if not d.is_Integer or d < 0:
D.remove(d)
if not D:
return None # 'f(n)' is *not* Gosper-summable
d = max(D)
coeffs = symbols('c:%s' % (d + 1), cls=Dummy)
domain = A.get_domain().inject(*coeffs)
x = Poly(coeffs, n, domain=domain)
H = A*x.shift(1) - B*x - C
from sympy.solvers.solvers import solve
solution = solve(H.coeffs(), coeffs)
if solution is None:
return None # 'f(n)' is *not* Gosper-summable
x = x.as_expr().subs(solution)
for coeff in coeffs:
if coeff not in solution:
x = x.subs(coeff, 0)
if x.is_zero:
return None # 'f(n)' is *not* Gosper-summable
else:
return B.as_expr()*x/C.as_expr()
def gosper_sum(f, k):
r"""
Gosper's hypergeometric summation algorithm.
Explanation
===========
Given a hypergeometric term ``f`` such that:
.. math ::
s_n = \sum_{k=0}^{n-1} f_k
and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where
`g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed
in closed form as a sum of hypergeometric terms.
Examples
========
>>> from sympy.concrete.gosper import gosper_sum
>>> from sympy import factorial
>>> from sympy.abc import n, k
>>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1)
>>> gosper_sum(f, (k, 0, n))
(-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1)
>>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2])
True
>>> gosper_sum(f, (k, 3, n))
(-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1))
>>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5])
True
References
==========
.. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B,
AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100
"""
indefinite = False
if is_sequence(k):
k, a, b = k
else:
indefinite = True
g = gosper_term(f, k)
if g is None:
return None
if indefinite:
result = f*g
else:
result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a)
if result is S.NaN:
try:
result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a)
except NotImplementedError:
result = None
return factor(result)

View File

@ -0,0 +1,473 @@
"""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

View File

@ -0,0 +1,610 @@
from typing import Tuple as tTuple
from .expr_with_intlimits import ExprWithIntLimits
from .summations import Sum, summation, _dummy_with_inherited_properties_concrete
from sympy.core.expr import Expr
from sympy.core.exprtools import factor_terms
from sympy.core.function import Derivative
from sympy.core.mul import Mul
from sympy.core.singleton import S
from sympy.core.symbol import Dummy, Symbol
from sympy.functions.combinatorial.factorials import RisingFactorial
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.special.tensor_functions import KroneckerDelta
from sympy.polys import quo, roots
class Product(ExprWithIntLimits):
r"""
Represents unevaluated products.
Explanation
===========
``Product`` represents a finite or infinite product, with the first
argument being the general form of terms in the series, and the second
argument being ``(dummy_variable, start, end)``, with ``dummy_variable``
taking all integer values from ``start`` through ``end``. In accordance
with long-standing mathematical convention, the end term is included in
the product.
Finite products
===============
For finite products (and products with symbolic limits assumed to be finite)
we follow the analogue of the summation convention described by Karr [1],
especially definition 3 of section 1.4. The product:
.. math::
\prod_{m \leq i < n} f(i)
has *the obvious meaning* for `m < n`, namely:
.. math::
\prod_{m \leq i < n} f(i) = f(m) f(m+1) \cdot \ldots \cdot f(n-2) f(n-1)
with the upper limit value `f(n)` excluded. The product over an empty set is
one if and only if `m = n`:
.. math::
\prod_{m \leq i < n} f(i) = 1 \quad \mathrm{for} \quad m = n
Finally, for all other products over empty sets we assume the following
definition:
.. math::
\prod_{m \leq i < n} f(i) = \frac{1}{\prod_{n \leq i < m} f(i)} \quad \mathrm{for} \quad m > n
It is important to note that above we define all products with the upper
limit being exclusive. This is in contrast to the usual mathematical notation,
but does not affect the product convention. Indeed we have:
.. math::
\prod_{m \leq i < n} f(i) = \prod_{i = m}^{n - 1} f(i)
where the difference in notation is intentional to emphasize the meaning,
with limits typeset on the top being inclusive.
Examples
========
>>> from sympy.abc import a, b, i, k, m, n, x
>>> from sympy import Product, oo
>>> Product(k, (k, 1, m))
Product(k, (k, 1, m))
>>> Product(k, (k, 1, m)).doit()
factorial(m)
>>> Product(k**2,(k, 1, m))
Product(k**2, (k, 1, m))
>>> Product(k**2,(k, 1, m)).doit()
factorial(m)**2
Wallis' product for pi:
>>> W = Product(2*i/(2*i-1) * 2*i/(2*i+1), (i, 1, oo))
>>> W
Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
Direct computation currently fails:
>>> W.doit()
Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
But we can approach the infinite product by a limit of finite products:
>>> from sympy import limit
>>> W2 = Product(2*i/(2*i-1)*2*i/(2*i+1), (i, 1, n))
>>> W2
Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, n))
>>> W2e = W2.doit()
>>> W2e
4**n*factorial(n)**2/(2**(2*n)*RisingFactorial(1/2, n)*RisingFactorial(3/2, n))
>>> limit(W2e, n, oo)
pi/2
By the same formula we can compute sin(pi/2):
>>> from sympy import combsimp, pi, gamma, simplify
>>> P = pi * x * Product(1 - x**2/k**2, (k, 1, n))
>>> P = P.subs(x, pi/2)
>>> P
pi**2*Product(1 - pi**2/(4*k**2), (k, 1, n))/2
>>> Pe = P.doit()
>>> Pe
pi**2*RisingFactorial(1 - pi/2, n)*RisingFactorial(1 + pi/2, n)/(2*factorial(n)**2)
>>> limit(Pe, n, oo).gammasimp()
sin(pi**2/2)
>>> Pe.rewrite(gamma)
(-1)**n*pi**2*gamma(pi/2)*gamma(n + 1 + pi/2)/(2*gamma(1 + pi/2)*gamma(-n + pi/2)*gamma(n + 1)**2)
Products with the lower limit being larger than the upper one:
>>> Product(1/i, (i, 6, 1)).doit()
120
>>> Product(i, (i, 2, 5)).doit()
120
The empty product:
>>> Product(i, (i, n, n-1)).doit()
1
An example showing that the symbolic result of a product is still
valid for seemingly nonsensical values of the limits. Then the Karr
convention allows us to give a perfectly valid interpretation to
those products by interchanging the limits according to the above rules:
>>> P = Product(2, (i, 10, n)).doit()
>>> P
2**(n - 9)
>>> P.subs(n, 5)
1/16
>>> Product(2, (i, 10, 5)).doit()
1/16
>>> 1/Product(2, (i, 6, 9)).doit()
1/16
An explicit example of the Karr summation convention applied to products:
>>> P1 = Product(x, (i, a, b)).doit()
>>> P1
x**(-a + b + 1)
>>> P2 = Product(x, (i, b+1, a-1)).doit()
>>> P2
x**(a - b - 1)
>>> simplify(P1 * P2)
1
And another one:
>>> P1 = Product(i, (i, b, a)).doit()
>>> P1
RisingFactorial(b, a - b + 1)
>>> P2 = Product(i, (i, a+1, b-1)).doit()
>>> P2
RisingFactorial(a + 1, -a + b - 1)
>>> P1 * P2
RisingFactorial(b, a - b + 1)*RisingFactorial(a + 1, -a + b - 1)
>>> combsimp(P1 * P2)
1
See Also
========
Sum, summation
product
References
==========
.. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
Volume 28 Issue 2, April 1981, Pages 305-350
https://dl.acm.org/doi/10.1145/322248.322255
.. [2] https://en.wikipedia.org/wiki/Multiplication#Capital_Pi_notation
.. [3] https://en.wikipedia.org/wiki/Empty_product
"""
__slots__ = ()
limits: tTuple[tTuple[Symbol, Expr, Expr]]
def __new__(cls, function, *symbols, **assumptions):
obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions)
return obj
def _eval_rewrite_as_Sum(self, *args, **kwargs):
return exp(Sum(log(self.function), *self.limits))
@property
def term(self):
return self._args[0]
function = term
def _eval_is_zero(self):
if self.has_empty_sequence:
return False
z = self.term.is_zero
if z is True:
return True
if self.has_finite_limits:
# A Product is zero only if its term is zero assuming finite limits.
return z
def _eval_is_extended_real(self):
if self.has_empty_sequence:
return True
return self.function.is_extended_real
def _eval_is_positive(self):
if self.has_empty_sequence:
return True
if self.function.is_positive and self.has_finite_limits:
return True
def _eval_is_nonnegative(self):
if self.has_empty_sequence:
return True
if self.function.is_nonnegative and self.has_finite_limits:
return True
def _eval_is_extended_nonnegative(self):
if self.has_empty_sequence:
return True
if self.function.is_extended_nonnegative:
return True
def _eval_is_extended_nonpositive(self):
if self.has_empty_sequence:
return True
def _eval_is_finite(self):
if self.has_finite_limits and self.function.is_finite:
return True
def doit(self, **hints):
# first make sure any definite limits have product
# variables with matching assumptions
reps = {}
for xab in self.limits:
d = _dummy_with_inherited_properties_concrete(xab)
if d:
reps[xab[0]] = d
if reps:
undo = {v: k for k, v in reps.items()}
did = self.xreplace(reps).doit(**hints)
if isinstance(did, tuple): # when separate=True
did = tuple([i.xreplace(undo) for i in did])
else:
did = did.xreplace(undo)
return did
from sympy.simplify.powsimp import powsimp
f = self.function
for index, limit in enumerate(self.limits):
i, a, b = limit
dif = b - a
if dif.is_integer and dif.is_negative:
a, b = b + 1, a - 1
f = 1 / f
g = self._eval_product(f, (i, a, b))
if g in (None, S.NaN):
return self.func(powsimp(f), *self.limits[index:])
else:
f = g
if hints.get('deep', True):
return f.doit(**hints)
else:
return powsimp(f)
def _eval_adjoint(self):
if self.is_commutative:
return self.func(self.function.adjoint(), *self.limits)
return None
def _eval_conjugate(self):
return self.func(self.function.conjugate(), *self.limits)
def _eval_product(self, term, limits):
(k, a, n) = limits
if k not in term.free_symbols:
if (term - 1).is_zero:
return S.One
return term**(n - a + 1)
if a == n:
return term.subs(k, a)
from .delta import deltaproduct, _has_simple_delta
if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]):
return deltaproduct(term, limits)
dif = n - a
definite = dif.is_Integer
if definite and (dif < 100):
return self._eval_product_direct(term, limits)
elif term.is_polynomial(k):
poly = term.as_poly(k)
A = B = Q = S.One
all_roots = roots(poly)
M = 0
for r, m in all_roots.items():
M += m
A *= RisingFactorial(a - r, n - a + 1)**m
Q *= (n - r)**m
if M < poly.degree():
arg = quo(poly, Q.as_poly(k))
B = self.func(arg, (k, a, n)).doit()
return poly.LC()**(n - a + 1) * A * B
elif term.is_Add:
factored = factor_terms(term, fraction=True)
if factored.is_Mul:
return self._eval_product(factored, (k, a, n))
elif term.is_Mul:
# Factor in part without the summation variable and part with
without_k, with_k = term.as_coeff_mul(k)
if len(with_k) >= 2:
# More than one term including k, so still a multiplication
exclude, include = [], []
for t in with_k:
p = self._eval_product(t, (k, a, n))
if p is not None:
exclude.append(p)
else:
include.append(t)
if not exclude:
return None
else:
arg = term._new_rawargs(*include)
A = Mul(*exclude)
B = self.func(arg, (k, a, n)).doit()
return without_k**(n - a + 1)*A * B
else:
# Just a single term
p = self._eval_product(with_k[0], (k, a, n))
if p is None:
p = self.func(with_k[0], (k, a, n)).doit()
return without_k**(n - a + 1)*p
elif term.is_Pow:
if not term.base.has(k):
s = summation(term.exp, (k, a, n))
return term.base**s
elif not term.exp.has(k):
p = self._eval_product(term.base, (k, a, n))
if p is not None:
return p**term.exp
elif isinstance(term, Product):
evaluated = term.doit()
f = self._eval_product(evaluated, limits)
if f is None:
return self.func(evaluated, limits)
else:
return f
if definite:
return self._eval_product_direct(term, limits)
def _eval_simplify(self, **kwargs):
from sympy.simplify.simplify import product_simplify
rv = product_simplify(self, **kwargs)
return rv.doit() if kwargs['doit'] else rv
def _eval_transpose(self):
if self.is_commutative:
return self.func(self.function.transpose(), *self.limits)
return None
def _eval_product_direct(self, term, limits):
(k, a, n) = limits
return Mul(*[term.subs(k, a + i) for i in range(n - a + 1)])
def _eval_derivative(self, x):
if isinstance(x, Symbol) and x not in self.free_symbols:
return S.Zero
f, limits = self.function, list(self.limits)
limit = limits.pop(-1)
if limits:
f = self.func(f, *limits)
i, a, b = limit
if x in a.free_symbols or x in b.free_symbols:
return None
h = Dummy()
rv = Sum( Product(f, (i, a, h - 1)) * Product(f, (i, h + 1, b)) * Derivative(f, x, evaluate=True).subs(i, h), (h, a, b))
return rv
def is_convergent(self):
r"""
See docs of :obj:`.Sum.is_convergent()` for explanation of convergence
in SymPy.
Explanation
===========
The infinite product:
.. math::
\prod_{1 \leq i < \infty} f(i)
is defined by the sequence of partial products:
.. math::
\prod_{i=1}^{n} f(i) = f(1) f(2) \cdots f(n)
as n increases without bound. The product converges to a non-zero
value if and only if the sum:
.. math::
\sum_{1 \leq i < \infty} \log{f(n)}
converges.
Examples
========
>>> from sympy import Product, Symbol, cos, pi, exp, oo
>>> n = Symbol('n', integer=True)
>>> Product(n/(n + 1), (n, 1, oo)).is_convergent()
False
>>> Product(1/n**2, (n, 1, oo)).is_convergent()
False
>>> Product(cos(pi/n), (n, 1, oo)).is_convergent()
True
>>> Product(exp(-n**2), (n, 1, oo)).is_convergent()
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Infinite_product
"""
sequence_term = self.function
log_sum = log(sequence_term)
lim = self.limits
try:
is_conv = Sum(log_sum, *lim).is_convergent()
except NotImplementedError:
if Sum(sequence_term - 1, *lim).is_absolutely_convergent() is S.true:
return S.true
raise NotImplementedError("The algorithm to find the product convergence of %s "
"is not yet implemented" % (sequence_term))
return is_conv
def reverse_order(expr, *indices):
"""
Reverse the order of a limit in a Product.
Explanation
===========
``reverse_order(expr, *indices)`` reverses some limits in the expression
``expr`` which can be either a ``Sum`` or a ``Product``. The selectors in
the argument ``indices`` specify some indices whose limits get reversed.
These selectors are either variable names or numerical indices counted
starting from the inner-most limit tuple.
Examples
========
>>> from sympy import gamma, Product, simplify, Sum
>>> from sympy.abc import x, y, a, b, c, d
>>> P = Product(x, (x, a, b))
>>> Pr = P.reverse_order(x)
>>> Pr
Product(1/x, (x, b + 1, a - 1))
>>> Pr = Pr.doit()
>>> Pr
1/RisingFactorial(b + 1, a - b - 1)
>>> simplify(Pr.rewrite(gamma))
Piecewise((gamma(b + 1)/gamma(a), b > -1), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
>>> P = P.doit()
>>> P
RisingFactorial(a, -a + b + 1)
>>> simplify(P.rewrite(gamma))
Piecewise((gamma(b + 1)/gamma(a), a > 0), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
While one should prefer variable names when specifying which limits
to reverse, the index counting notation comes in handy in case there
are several symbols with the same name.
>>> S = Sum(x*y, (x, a, b), (y, c, d))
>>> S
Sum(x*y, (x, a, b), (y, c, d))
>>> S0 = S.reverse_order(0)
>>> S0
Sum(-x*y, (x, b + 1, a - 1), (y, c, d))
>>> S1 = S0.reverse_order(1)
>>> S1
Sum(x*y, (x, b + 1, a - 1), (y, d + 1, c - 1))
Of course we can mix both notations:
>>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1)
Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
>>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x)
Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
See Also
========
sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
reorder_limit,
sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder
References
==========
.. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
Volume 28 Issue 2, April 1981, Pages 305-350
https://dl.acm.org/doi/10.1145/322248.322255
"""
l_indices = list(indices)
for i, indx in enumerate(l_indices):
if not isinstance(indx, int):
l_indices[i] = expr.index(indx)
e = 1
limits = []
for i, limit in enumerate(expr.limits):
l = limit
if i in l_indices:
e = -e
l = (limit[0], limit[2] + 1, limit[1] - 1)
limits.append(l)
return Product(expr.function ** e, *limits)
def product(*args, **kwargs):
r"""
Compute the product.
Explanation
===========
The notation for symbols is similar to the notation used in Sum or
Integral. product(f, (i, a, b)) computes the product of f with
respect to i from a to b, i.e.,
::
b
_____
product(f(n), (i, a, b)) = | | f(n)
| |
i = a
If it cannot compute the product, it returns an unevaluated Product object.
Repeated products can be computed by introducing additional symbols tuples::
Examples
========
>>> from sympy import product, symbols
>>> i, n, m, k = symbols('i n m k', integer=True)
>>> product(i, (i, 1, k))
factorial(k)
>>> product(m, (i, 1, k))
m**k
>>> product(i, (i, 1, k), (k, 1, n))
Product(factorial(k), (k, 1, n))
"""
prod = Product(*args, **kwargs)
if isinstance(prod, Product):
return prod.doit(deep=False)
else:
return prod

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,499 @@
from sympy.concrete import Sum
from sympy.concrete.delta import deltaproduct as dp, deltasummation as ds, _extract_delta
from sympy.core import Eq, S, symbols, oo
from sympy.functions import KroneckerDelta as KD, Piecewise, piecewise_fold
from sympy.logic import And
from sympy.testing.pytest import raises
i, j, k, l, m = symbols("i j k l m", integer=True, finite=True)
x, y = symbols("x y", commutative=False)
def test_deltaproduct_trivial():
assert dp(x, (j, 1, 0)) == 1
assert dp(x, (j, 1, 3)) == x**3
assert dp(x + y, (j, 1, 3)) == (x + y)**3
assert dp(x*y, (j, 1, 3)) == (x*y)**3
assert dp(KD(i, j), (k, 1, 3)) == KD(i, j)
assert dp(x*KD(i, j), (k, 1, 3)) == x**3*KD(i, j)
assert dp(x*y*KD(i, j), (k, 1, 3)) == (x*y)**3*KD(i, j)
def test_deltaproduct_basic():
assert dp(KD(i, j), (j, 1, 3)) == 0
assert dp(KD(i, j), (j, 1, 1)) == KD(i, 1)
assert dp(KD(i, j), (j, 2, 2)) == KD(i, 2)
assert dp(KD(i, j), (j, 3, 3)) == KD(i, 3)
assert dp(KD(i, j), (j, 1, k)) == KD(i, 1)*KD(k, 1) + KD(k, 0)
assert dp(KD(i, j), (j, k, 3)) == KD(i, 3)*KD(k, 3) + KD(k, 4)
assert dp(KD(i, j), (j, k, l)) == KD(i, l)*KD(k, l) + KD(k, l + 1)
def test_deltaproduct_mul_x_kd():
assert dp(x*KD(i, j), (j, 1, 3)) == 0
assert dp(x*KD(i, j), (j, 1, 1)) == x*KD(i, 1)
assert dp(x*KD(i, j), (j, 2, 2)) == x*KD(i, 2)
assert dp(x*KD(i, j), (j, 3, 3)) == x*KD(i, 3)
assert dp(x*KD(i, j), (j, 1, k)) == x*KD(i, 1)*KD(k, 1) + KD(k, 0)
assert dp(x*KD(i, j), (j, k, 3)) == x*KD(i, 3)*KD(k, 3) + KD(k, 4)
assert dp(x*KD(i, j), (j, k, l)) == x*KD(i, l)*KD(k, l) + KD(k, l + 1)
def test_deltaproduct_mul_add_x_y_kd():
assert dp((x + y)*KD(i, j), (j, 1, 3)) == 0
assert dp((x + y)*KD(i, j), (j, 1, 1)) == (x + y)*KD(i, 1)
assert dp((x + y)*KD(i, j), (j, 2, 2)) == (x + y)*KD(i, 2)
assert dp((x + y)*KD(i, j), (j, 3, 3)) == (x + y)*KD(i, 3)
assert dp((x + y)*KD(i, j), (j, 1, k)) == \
(x + y)*KD(i, 1)*KD(k, 1) + KD(k, 0)
assert dp((x + y)*KD(i, j), (j, k, 3)) == \
(x + y)*KD(i, 3)*KD(k, 3) + KD(k, 4)
assert dp((x + y)*KD(i, j), (j, k, l)) == \
(x + y)*KD(i, l)*KD(k, l) + KD(k, l + 1)
def test_deltaproduct_add_kd_kd():
assert dp(KD(i, k) + KD(j, k), (k, 1, 3)) == 0
assert dp(KD(i, k) + KD(j, k), (k, 1, 1)) == KD(i, 1) + KD(j, 1)
assert dp(KD(i, k) + KD(j, k), (k, 2, 2)) == KD(i, 2) + KD(j, 2)
assert dp(KD(i, k) + KD(j, k), (k, 3, 3)) == KD(i, 3) + KD(j, 3)
assert dp(KD(i, k) + KD(j, k), (k, 1, l)) == KD(l, 0) + \
KD(i, 1)*KD(l, 1) + KD(j, 1)*KD(l, 1) + \
KD(i, 1)*KD(j, 2)*KD(l, 2) + KD(j, 1)*KD(i, 2)*KD(l, 2)
assert dp(KD(i, k) + KD(j, k), (k, l, 3)) == KD(l, 4) + \
KD(i, 3)*KD(l, 3) + KD(j, 3)*KD(l, 3) + \
KD(i, 2)*KD(j, 3)*KD(l, 2) + KD(i, 3)*KD(j, 2)*KD(l, 2)
assert dp(KD(i, k) + KD(j, k), (k, l, m)) == KD(l, m + 1) + \
KD(i, m)*KD(l, m) + KD(j, m)*KD(l, m) + \
KD(i, m)*KD(j, m - 1)*KD(l, m - 1) + KD(i, m - 1)*KD(j, m)*KD(l, m - 1)
def test_deltaproduct_mul_x_add_kd_kd():
assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, 3)) == 0
assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, 1)) == x*(KD(i, 1) + KD(j, 1))
assert dp(x*(KD(i, k) + KD(j, k)), (k, 2, 2)) == x*(KD(i, 2) + KD(j, 2))
assert dp(x*(KD(i, k) + KD(j, k)), (k, 3, 3)) == x*(KD(i, 3) + KD(j, 3))
assert dp(x*(KD(i, k) + KD(j, k)), (k, 1, l)) == KD(l, 0) + \
x*KD(i, 1)*KD(l, 1) + x*KD(j, 1)*KD(l, 1) + \
x**2*KD(i, 1)*KD(j, 2)*KD(l, 2) + x**2*KD(j, 1)*KD(i, 2)*KD(l, 2)
assert dp(x*(KD(i, k) + KD(j, k)), (k, l, 3)) == KD(l, 4) + \
x*KD(i, 3)*KD(l, 3) + x*KD(j, 3)*KD(l, 3) + \
x**2*KD(i, 2)*KD(j, 3)*KD(l, 2) + x**2*KD(i, 3)*KD(j, 2)*KD(l, 2)
assert dp(x*(KD(i, k) + KD(j, k)), (k, l, m)) == KD(l, m + 1) + \
x*KD(i, m)*KD(l, m) + x*KD(j, m)*KD(l, m) + \
x**2*KD(i, m - 1)*KD(j, m)*KD(l, m - 1) + \
x**2*KD(i, m)*KD(j, m - 1)*KD(l, m - 1)
def test_deltaproduct_mul_add_x_y_add_kd_kd():
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 3)) == 0
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 1)) == \
(x + y)*(KD(i, 1) + KD(j, 1))
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 2, 2)) == \
(x + y)*(KD(i, 2) + KD(j, 2))
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 3, 3)) == \
(x + y)*(KD(i, 3) + KD(j, 3))
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, 1, l)) == KD(l, 0) + \
(x + y)*KD(i, 1)*KD(l, 1) + (x + y)*KD(j, 1)*KD(l, 1) + \
(x + y)**2*KD(i, 1)*KD(j, 2)*KD(l, 2) + \
(x + y)**2*KD(j, 1)*KD(i, 2)*KD(l, 2)
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, l, 3)) == KD(l, 4) + \
(x + y)*KD(i, 3)*KD(l, 3) + (x + y)*KD(j, 3)*KD(l, 3) + \
(x + y)**2*KD(i, 2)*KD(j, 3)*KD(l, 2) + \
(x + y)**2*KD(i, 3)*KD(j, 2)*KD(l, 2)
assert dp((x + y)*(KD(i, k) + KD(j, k)), (k, l, m)) == KD(l, m + 1) + \
(x + y)*KD(i, m)*KD(l, m) + (x + y)*KD(j, m)*KD(l, m) + \
(x + y)**2*KD(i, m - 1)*KD(j, m)*KD(l, m - 1) + \
(x + y)**2*KD(i, m)*KD(j, m - 1)*KD(l, m - 1)
def test_deltaproduct_add_mul_x_y_mul_x_kd():
assert dp(x*y + x*KD(i, j), (j, 1, 3)) == (x*y)**3 + \
x*(x*y)**2*KD(i, 1) + (x*y)*x*(x*y)*KD(i, 2) + (x*y)**2*x*KD(i, 3)
assert dp(x*y + x*KD(i, j), (j, 1, 1)) == x*y + x*KD(i, 1)
assert dp(x*y + x*KD(i, j), (j, 2, 2)) == x*y + x*KD(i, 2)
assert dp(x*y + x*KD(i, j), (j, 3, 3)) == x*y + x*KD(i, 3)
assert dp(x*y + x*KD(i, j), (j, 1, k)) == \
(x*y)**k + Piecewise(
((x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)),
(0, True)
)
assert dp(x*y + x*KD(i, j), (j, k, 3)) == \
(x*y)**(-k + 4) + Piecewise(
((x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)),
(0, True)
)
assert dp(x*y + x*KD(i, j), (j, k, l)) == \
(x*y)**(-k + l + 1) + Piecewise(
((x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)),
(0, True)
)
def test_deltaproduct_mul_x_add_y_kd():
assert dp(x*(y + KD(i, j)), (j, 1, 3)) == (x*y)**3 + \
x*(x*y)**2*KD(i, 1) + (x*y)*x*(x*y)*KD(i, 2) + (x*y)**2*x*KD(i, 3)
assert dp(x*(y + KD(i, j)), (j, 1, 1)) == x*(y + KD(i, 1))
assert dp(x*(y + KD(i, j)), (j, 2, 2)) == x*(y + KD(i, 2))
assert dp(x*(y + KD(i, j)), (j, 3, 3)) == x*(y + KD(i, 3))
assert dp(x*(y + KD(i, j)), (j, 1, k)) == \
(x*y)**k + Piecewise(
((x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)),
(0, True)
).expand()
assert dp(x*(y + KD(i, j)), (j, k, 3)) == \
((x*y)**(-k + 4) + Piecewise(
((x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)),
(0, True)
)).expand()
assert dp(x*(y + KD(i, j)), (j, k, l)) == \
((x*y)**(-k + l + 1) + Piecewise(
((x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)),
(0, True)
)).expand()
def test_deltaproduct_mul_x_add_y_twokd():
assert dp(x*(y + 2*KD(i, j)), (j, 1, 3)) == (x*y)**3 + \
2*x*(x*y)**2*KD(i, 1) + 2*x*y*x*x*y*KD(i, 2) + 2*(x*y)**2*x*KD(i, 3)
assert dp(x*(y + 2*KD(i, j)), (j, 1, 1)) == x*(y + 2*KD(i, 1))
assert dp(x*(y + 2*KD(i, j)), (j, 2, 2)) == x*(y + 2*KD(i, 2))
assert dp(x*(y + 2*KD(i, j)), (j, 3, 3)) == x*(y + 2*KD(i, 3))
assert dp(x*(y + 2*KD(i, j)), (j, 1, k)) == \
(x*y)**k + Piecewise(
(2*(x*y)**(i - 1)*x*(x*y)**(k - i), And(1 <= i, i <= k)),
(0, True)
).expand()
assert dp(x*(y + 2*KD(i, j)), (j, k, 3)) == \
((x*y)**(-k + 4) + Piecewise(
(2*(x*y)**(i - k)*x*(x*y)**(3 - i), And(k <= i, i <= 3)),
(0, True)
)).expand()
assert dp(x*(y + 2*KD(i, j)), (j, k, l)) == \
((x*y)**(-k + l + 1) + Piecewise(
(2*(x*y)**(i - k)*x*(x*y)**(l - i), And(k <= i, i <= l)),
(0, True)
)).expand()
def test_deltaproduct_mul_add_x_y_add_y_kd():
assert dp((x + y)*(y + KD(i, j)), (j, 1, 3)) == ((x + y)*y)**3 + \
(x + y)*((x + y)*y)**2*KD(i, 1) + \
(x + y)*y*(x + y)**2*y*KD(i, 2) + \
((x + y)*y)**2*(x + y)*KD(i, 3)
assert dp((x + y)*(y + KD(i, j)), (j, 1, 1)) == (x + y)*(y + KD(i, 1))
assert dp((x + y)*(y + KD(i, j)), (j, 2, 2)) == (x + y)*(y + KD(i, 2))
assert dp((x + y)*(y + KD(i, j)), (j, 3, 3)) == (x + y)*(y + KD(i, 3))
assert dp((x + y)*(y + KD(i, j)), (j, 1, k)) == \
((x + y)*y)**k + Piecewise(
(((x + y)*y)**(-1)*((x + y)*y)**i*(x + y)*((x + y)*y
)**k*((x + y)*y)**(-i), (i >= 1) & (i <= k)), (0, True))
assert dp((x + y)*(y + KD(i, j)), (j, k, 3)) == (
(x + y)*y)**4*((x + y)*y)**(-k) + Piecewise((((x + y)*y)**i*(
(x + y)*y)**(-k)*(x + y)*((x + y)*y)**3*((x + y)*y)**(-i),
(i >= k) & (i <= 3)), (0, True))
assert dp((x + y)*(y + KD(i, j)), (j, k, l)) == \
(x + y)*y*((x + y)*y)**l*((x + y)*y)**(-k) + Piecewise(
(((x + y)*y)**i*((x + y)*y)**(-k)*(x + y)*((x + y)*y
)**l*((x + y)*y)**(-i), (i >= k) & (i <= l)), (0, True))
def test_deltaproduct_mul_add_x_kd_add_y_kd():
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, 3)) == \
KD(i, 1)*(KD(i, k) + x)*((KD(i, k) + x)*y)**2 + \
KD(i, 2)*(KD(i, k) + x)*y*(KD(i, k) + x)**2*y + \
KD(i, 3)*((KD(i, k) + x)*y)**2*(KD(i, k) + x) + \
((KD(i, k) + x)*y)**3
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, 1)) == \
(x + KD(i, k))*(y + KD(i, 1))
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 2, 2)) == \
(x + KD(i, k))*(y + KD(i, 2))
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 3, 3)) == \
(x + KD(i, k))*(y + KD(i, 3))
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, 1, k)) == \
((KD(i, k) + x)*y)**k + Piecewise(
(((KD(i, k) + x)*y)**(-1)*((KD(i, k) + x)*y)**i*(KD(i, k) + x
)*((KD(i, k) + x)*y)**k*((KD(i, k) + x)*y)**(-i), (i >= 1
) & (i <= k)), (0, True))
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, k, 3)) == (
(KD(i, k) + x)*y)**4*((KD(i, k) + x)*y)**(-k) + Piecewise(
(((KD(i, k) + x)*y)**i*((KD(i, k) + x)*y)**(-k)*(KD(i, k)
+ x)*((KD(i, k) + x)*y)**3*((KD(i, k) + x)*y)**(-i),
(i >= k) & (i <= 3)), (0, True))
assert dp((x + KD(i, k))*(y + KD(i, j)), (j, k, l)) == (
KD(i, k) + x)*y*((KD(i, k) + x)*y)**l*((KD(i, k) + x)*y
)**(-k) + Piecewise((((KD(i, k) + x)*y)**i*((KD(i, k) + x
)*y)**(-k)*(KD(i, k) + x)*((KD(i, k) + x)*y)**l*((KD(i, k) + x
)*y)**(-i), (i >= k) & (i <= l)), (0, True))
def test_deltasummation_trivial():
assert ds(x, (j, 1, 0)) == 0
assert ds(x, (j, 1, 3)) == 3*x
assert ds(x + y, (j, 1, 3)) == 3*(x + y)
assert ds(x*y, (j, 1, 3)) == 3*x*y
assert ds(KD(i, j), (k, 1, 3)) == 3*KD(i, j)
assert ds(x*KD(i, j), (k, 1, 3)) == 3*x*KD(i, j)
assert ds(x*y*KD(i, j), (k, 1, 3)) == 3*x*y*KD(i, j)
def test_deltasummation_basic_numerical():
n = symbols('n', integer=True, nonzero=True)
assert ds(KD(n, 0), (n, 1, 3)) == 0
# return unevaluated, until it gets implemented
assert ds(KD(i**2, j**2), (j, -oo, oo)) == \
Sum(KD(i**2, j**2), (j, -oo, oo))
assert Piecewise((KD(i, k), And(1 <= i, i <= 3)), (0, True)) == \
ds(KD(i, j)*KD(j, k), (j, 1, 3)) == \
ds(KD(j, k)*KD(i, j), (j, 1, 3))
assert ds(KD(i, k), (k, -oo, oo)) == 1
assert ds(KD(i, k), (k, 0, oo)) == Piecewise((1, S.Zero <= i), (0, True))
assert ds(KD(i, k), (k, 1, 3)) == \
Piecewise((1, And(1 <= i, i <= 3)), (0, True))
assert ds(k*KD(i, j)*KD(j, k), (k, -oo, oo)) == j*KD(i, j)
assert ds(j*KD(i, j), (j, -oo, oo)) == i
assert ds(i*KD(i, j), (i, -oo, oo)) == j
assert ds(x, (i, 1, 3)) == 3*x
assert ds((i + j)*KD(i, j), (j, -oo, oo)) == 2*i
def test_deltasummation_basic_symbolic():
assert ds(KD(i, j), (j, 1, 3)) == \
Piecewise((1, And(1 <= i, i <= 3)), (0, True))
assert ds(KD(i, j), (j, 1, 1)) == Piecewise((1, Eq(i, 1)), (0, True))
assert ds(KD(i, j), (j, 2, 2)) == Piecewise((1, Eq(i, 2)), (0, True))
assert ds(KD(i, j), (j, 3, 3)) == Piecewise((1, Eq(i, 3)), (0, True))
assert ds(KD(i, j), (j, 1, k)) == \
Piecewise((1, And(1 <= i, i <= k)), (0, True))
assert ds(KD(i, j), (j, k, 3)) == \
Piecewise((1, And(k <= i, i <= 3)), (0, True))
assert ds(KD(i, j), (j, k, l)) == \
Piecewise((1, And(k <= i, i <= l)), (0, True))
def test_deltasummation_mul_x_kd():
assert ds(x*KD(i, j), (j, 1, 3)) == \
Piecewise((x, And(1 <= i, i <= 3)), (0, True))
assert ds(x*KD(i, j), (j, 1, 1)) == Piecewise((x, Eq(i, 1)), (0, True))
assert ds(x*KD(i, j), (j, 2, 2)) == Piecewise((x, Eq(i, 2)), (0, True))
assert ds(x*KD(i, j), (j, 3, 3)) == Piecewise((x, Eq(i, 3)), (0, True))
assert ds(x*KD(i, j), (j, 1, k)) == \
Piecewise((x, And(1 <= i, i <= k)), (0, True))
assert ds(x*KD(i, j), (j, k, 3)) == \
Piecewise((x, And(k <= i, i <= 3)), (0, True))
assert ds(x*KD(i, j), (j, k, l)) == \
Piecewise((x, And(k <= i, i <= l)), (0, True))
def test_deltasummation_mul_add_x_y_kd():
assert ds((x + y)*KD(i, j), (j, 1, 3)) == \
Piecewise((x + y, And(1 <= i, i <= 3)), (0, True))
assert ds((x + y)*KD(i, j), (j, 1, 1)) == \
Piecewise((x + y, Eq(i, 1)), (0, True))
assert ds((x + y)*KD(i, j), (j, 2, 2)) == \
Piecewise((x + y, Eq(i, 2)), (0, True))
assert ds((x + y)*KD(i, j), (j, 3, 3)) == \
Piecewise((x + y, Eq(i, 3)), (0, True))
assert ds((x + y)*KD(i, j), (j, 1, k)) == \
Piecewise((x + y, And(1 <= i, i <= k)), (0, True))
assert ds((x + y)*KD(i, j), (j, k, 3)) == \
Piecewise((x + y, And(k <= i, i <= 3)), (0, True))
assert ds((x + y)*KD(i, j), (j, k, l)) == \
Piecewise((x + y, And(k <= i, i <= l)), (0, True))
def test_deltasummation_add_kd_kd():
assert ds(KD(i, k) + KD(j, k), (k, 1, 3)) == piecewise_fold(
Piecewise((1, And(1 <= i, i <= 3)), (0, True)) +
Piecewise((1, And(1 <= j, j <= 3)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, 1, 1)) == piecewise_fold(
Piecewise((1, Eq(i, 1)), (0, True)) +
Piecewise((1, Eq(j, 1)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, 2, 2)) == piecewise_fold(
Piecewise((1, Eq(i, 2)), (0, True)) +
Piecewise((1, Eq(j, 2)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, 3, 3)) == piecewise_fold(
Piecewise((1, Eq(i, 3)), (0, True)) +
Piecewise((1, Eq(j, 3)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, 1, l)) == piecewise_fold(
Piecewise((1, And(1 <= i, i <= l)), (0, True)) +
Piecewise((1, And(1 <= j, j <= l)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, l, 3)) == piecewise_fold(
Piecewise((1, And(l <= i, i <= 3)), (0, True)) +
Piecewise((1, And(l <= j, j <= 3)), (0, True)))
assert ds(KD(i, k) + KD(j, k), (k, l, m)) == piecewise_fold(
Piecewise((1, And(l <= i, i <= m)), (0, True)) +
Piecewise((1, And(l <= j, j <= m)), (0, True)))
def test_deltasummation_add_mul_x_kd_kd():
assert ds(x*KD(i, k) + KD(j, k), (k, 1, 3)) == piecewise_fold(
Piecewise((x, And(1 <= i, i <= 3)), (0, True)) +
Piecewise((1, And(1 <= j, j <= 3)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, 1, 1)) == piecewise_fold(
Piecewise((x, Eq(i, 1)), (0, True)) +
Piecewise((1, Eq(j, 1)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, 2, 2)) == piecewise_fold(
Piecewise((x, Eq(i, 2)), (0, True)) +
Piecewise((1, Eq(j, 2)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, 3, 3)) == piecewise_fold(
Piecewise((x, Eq(i, 3)), (0, True)) +
Piecewise((1, Eq(j, 3)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, 1, l)) == piecewise_fold(
Piecewise((x, And(1 <= i, i <= l)), (0, True)) +
Piecewise((1, And(1 <= j, j <= l)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, l, 3)) == piecewise_fold(
Piecewise((x, And(l <= i, i <= 3)), (0, True)) +
Piecewise((1, And(l <= j, j <= 3)), (0, True)))
assert ds(x*KD(i, k) + KD(j, k), (k, l, m)) == piecewise_fold(
Piecewise((x, And(l <= i, i <= m)), (0, True)) +
Piecewise((1, And(l <= j, j <= m)), (0, True)))
def test_deltasummation_mul_x_add_kd_kd():
assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, 3)) == piecewise_fold(
Piecewise((x, And(1 <= i, i <= 3)), (0, True)) +
Piecewise((x, And(1 <= j, j <= 3)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, 1)) == piecewise_fold(
Piecewise((x, Eq(i, 1)), (0, True)) +
Piecewise((x, Eq(j, 1)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, 2, 2)) == piecewise_fold(
Piecewise((x, Eq(i, 2)), (0, True)) +
Piecewise((x, Eq(j, 2)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, 3, 3)) == piecewise_fold(
Piecewise((x, Eq(i, 3)), (0, True)) +
Piecewise((x, Eq(j, 3)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, 1, l)) == piecewise_fold(
Piecewise((x, And(1 <= i, i <= l)), (0, True)) +
Piecewise((x, And(1 <= j, j <= l)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, l, 3)) == piecewise_fold(
Piecewise((x, And(l <= i, i <= 3)), (0, True)) +
Piecewise((x, And(l <= j, j <= 3)), (0, True)))
assert ds(x*(KD(i, k) + KD(j, k)), (k, l, m)) == piecewise_fold(
Piecewise((x, And(l <= i, i <= m)), (0, True)) +
Piecewise((x, And(l <= j, j <= m)), (0, True)))
def test_deltasummation_mul_add_x_y_add_kd_kd():
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 3)) == piecewise_fold(
Piecewise((x + y, And(1 <= i, i <= 3)), (0, True)) +
Piecewise((x + y, And(1 <= j, j <= 3)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, 1)) == piecewise_fold(
Piecewise((x + y, Eq(i, 1)), (0, True)) +
Piecewise((x + y, Eq(j, 1)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 2, 2)) == piecewise_fold(
Piecewise((x + y, Eq(i, 2)), (0, True)) +
Piecewise((x + y, Eq(j, 2)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 3, 3)) == piecewise_fold(
Piecewise((x + y, Eq(i, 3)), (0, True)) +
Piecewise((x + y, Eq(j, 3)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, 1, l)) == piecewise_fold(
Piecewise((x + y, And(1 <= i, i <= l)), (0, True)) +
Piecewise((x + y, And(1 <= j, j <= l)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, l, 3)) == piecewise_fold(
Piecewise((x + y, And(l <= i, i <= 3)), (0, True)) +
Piecewise((x + y, And(l <= j, j <= 3)), (0, True)))
assert ds((x + y)*(KD(i, k) + KD(j, k)), (k, l, m)) == piecewise_fold(
Piecewise((x + y, And(l <= i, i <= m)), (0, True)) +
Piecewise((x + y, And(l <= j, j <= m)), (0, True)))
def test_deltasummation_add_mul_x_y_mul_x_kd():
assert ds(x*y + x*KD(i, j), (j, 1, 3)) == \
Piecewise((3*x*y + x, And(1 <= i, i <= 3)), (3*x*y, True))
assert ds(x*y + x*KD(i, j), (j, 1, 1)) == \
Piecewise((x*y + x, Eq(i, 1)), (x*y, True))
assert ds(x*y + x*KD(i, j), (j, 2, 2)) == \
Piecewise((x*y + x, Eq(i, 2)), (x*y, True))
assert ds(x*y + x*KD(i, j), (j, 3, 3)) == \
Piecewise((x*y + x, Eq(i, 3)), (x*y, True))
assert ds(x*y + x*KD(i, j), (j, 1, k)) == \
Piecewise((k*x*y + x, And(1 <= i, i <= k)), (k*x*y, True))
assert ds(x*y + x*KD(i, j), (j, k, 3)) == \
Piecewise(((4 - k)*x*y + x, And(k <= i, i <= 3)), ((4 - k)*x*y, True))
assert ds(x*y + x*KD(i, j), (j, k, l)) == Piecewise(
((l - k + 1)*x*y + x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True))
def test_deltasummation_mul_x_add_y_kd():
assert ds(x*(y + KD(i, j)), (j, 1, 3)) == \
Piecewise((3*x*y + x, And(1 <= i, i <= 3)), (3*x*y, True))
assert ds(x*(y + KD(i, j)), (j, 1, 1)) == \
Piecewise((x*y + x, Eq(i, 1)), (x*y, True))
assert ds(x*(y + KD(i, j)), (j, 2, 2)) == \
Piecewise((x*y + x, Eq(i, 2)), (x*y, True))
assert ds(x*(y + KD(i, j)), (j, 3, 3)) == \
Piecewise((x*y + x, Eq(i, 3)), (x*y, True))
assert ds(x*(y + KD(i, j)), (j, 1, k)) == \
Piecewise((k*x*y + x, And(1 <= i, i <= k)), (k*x*y, True))
assert ds(x*(y + KD(i, j)), (j, k, 3)) == \
Piecewise(((4 - k)*x*y + x, And(k <= i, i <= 3)), ((4 - k)*x*y, True))
assert ds(x*(y + KD(i, j)), (j, k, l)) == Piecewise(
((l - k + 1)*x*y + x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True))
def test_deltasummation_mul_x_add_y_twokd():
assert ds(x*(y + 2*KD(i, j)), (j, 1, 3)) == \
Piecewise((3*x*y + 2*x, And(1 <= i, i <= 3)), (3*x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, 1, 1)) == \
Piecewise((x*y + 2*x, Eq(i, 1)), (x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, 2, 2)) == \
Piecewise((x*y + 2*x, Eq(i, 2)), (x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, 3, 3)) == \
Piecewise((x*y + 2*x, Eq(i, 3)), (x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, 1, k)) == \
Piecewise((k*x*y + 2*x, And(1 <= i, i <= k)), (k*x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, k, 3)) == Piecewise(
((4 - k)*x*y + 2*x, And(k <= i, i <= 3)), ((4 - k)*x*y, True))
assert ds(x*(y + 2*KD(i, j)), (j, k, l)) == Piecewise(
((l - k + 1)*x*y + 2*x, And(k <= i, i <= l)), ((l - k + 1)*x*y, True))
def test_deltasummation_mul_add_x_y_add_y_kd():
assert ds((x + y)*(y + KD(i, j)), (j, 1, 3)) == Piecewise(
(3*(x + y)*y + x + y, And(1 <= i, i <= 3)), (3*(x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, 1, 1)) == \
Piecewise(((x + y)*y + x + y, Eq(i, 1)), ((x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, 2, 2)) == \
Piecewise(((x + y)*y + x + y, Eq(i, 2)), ((x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, 3, 3)) == \
Piecewise(((x + y)*y + x + y, Eq(i, 3)), ((x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, 1, k)) == Piecewise(
(k*(x + y)*y + x + y, And(1 <= i, i <= k)), (k*(x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, k, 3)) == Piecewise(
((4 - k)*(x + y)*y + x + y, And(k <= i, i <= 3)),
((4 - k)*(x + y)*y, True))
assert ds((x + y)*(y + KD(i, j)), (j, k, l)) == Piecewise(
((l - k + 1)*(x + y)*y + x + y, And(k <= i, i <= l)),
((l - k + 1)*(x + y)*y, True))
def test_deltasummation_mul_add_x_kd_add_y_kd():
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, 3)) == piecewise_fold(
Piecewise((KD(i, k) + x, And(1 <= i, i <= 3)), (0, True)) +
3*(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, 1)) == piecewise_fold(
Piecewise((KD(i, k) + x, Eq(i, 1)), (0, True)) +
(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 2, 2)) == piecewise_fold(
Piecewise((KD(i, k) + x, Eq(i, 2)), (0, True)) +
(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 3, 3)) == piecewise_fold(
Piecewise((KD(i, k) + x, Eq(i, 3)), (0, True)) +
(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, 1, k)) == piecewise_fold(
Piecewise((KD(i, k) + x, And(1 <= i, i <= k)), (0, True)) +
k*(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, k, 3)) == piecewise_fold(
Piecewise((KD(i, k) + x, And(k <= i, i <= 3)), (0, True)) +
(4 - k)*(KD(i, k) + x)*y)
assert ds((x + KD(i, k))*(y + KD(i, j)), (j, k, l)) == piecewise_fold(
Piecewise((KD(i, k) + x, And(k <= i, i <= l)), (0, True)) +
(l - k + 1)*(KD(i, k) + x)*y)
def test_extract_delta():
raises(ValueError, lambda: _extract_delta(KD(i, j) + KD(k, l), i))

View File

@ -0,0 +1,204 @@
"""Tests for Gosper's algorithm for hypergeometric summation. """
from sympy.core.numbers import (Rational, pi)
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.functions.combinatorial.factorials import (binomial, factorial)
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.special.gamma_functions import gamma
from sympy.polys.polytools import Poly
from sympy.simplify.simplify import simplify
from sympy.concrete.gosper import gosper_normal, gosper_sum, gosper_term
from sympy.abc import a, b, j, k, m, n, r, x
def test_gosper_normal():
eq = 4*n + 5, 2*(4*n + 1)*(2*n + 3), n
assert gosper_normal(*eq) == \
(Poly(Rational(1, 4), n), Poly(n + Rational(3, 2)), Poly(n + Rational(1, 4)))
assert gosper_normal(*eq, polys=False) == \
(Rational(1, 4), n + Rational(3, 2), n + Rational(1, 4))
def test_gosper_term():
assert gosper_term((4*k + 1)*factorial(
k)/factorial(2*k + 1), k) == (-k - S.Half)/(k + Rational(1, 4))
def test_gosper_sum():
assert gosper_sum(1, (k, 0, n)) == 1 + n
assert gosper_sum(k, (k, 0, n)) == n*(1 + n)/2
assert gosper_sum(k**2, (k, 0, n)) == n*(1 + n)*(1 + 2*n)/6
assert gosper_sum(k**3, (k, 0, n)) == n**2*(1 + n)**2/4
assert gosper_sum(2**k, (k, 0, n)) == 2*2**n - 1
assert gosper_sum(factorial(k), (k, 0, n)) is None
assert gosper_sum(binomial(n, k), (k, 0, n)) is None
assert gosper_sum(factorial(k)/k**2, (k, 0, n)) is None
assert gosper_sum((k - 3)*factorial(k), (k, 0, n)) is None
assert gosper_sum(k*factorial(k), k) == factorial(k)
assert gosper_sum(
k*factorial(k), (k, 0, n)) == n*factorial(n) + factorial(n) - 1
assert gosper_sum((-1)**k*binomial(n, k), (k, 0, n)) == 0
assert gosper_sum((
-1)**k*binomial(n, k), (k, 0, m)) == -(-1)**m*(m - n)*binomial(n, m)/n
assert gosper_sum((4*k + 1)*factorial(k)/factorial(2*k + 1), (k, 0, n)) == \
(2*factorial(2*n + 1) - factorial(n))/factorial(2*n + 1)
# issue 6033:
assert gosper_sum(
n*(n + a + b)*a**n*b**n/(factorial(n + a)*factorial(n + b)), \
(n, 0, m)).simplify() == -exp(m*log(a) + m*log(b))*gamma(a + 1) \
*gamma(b + 1)/(gamma(a)*gamma(b)*gamma(a + m + 1)*gamma(b + m + 1)) \
+ 1/(gamma(a)*gamma(b))
def test_gosper_sum_indefinite():
assert gosper_sum(k, k) == k*(k - 1)/2
assert gosper_sum(k**2, k) == k*(k - 1)*(2*k - 1)/6
assert gosper_sum(1/(k*(k + 1)), k) == -1/k
assert gosper_sum(-(27*k**4 + 158*k**3 + 430*k**2 + 678*k + 445)*gamma(2*k
+ 4)/(3*(3*k + 7)*gamma(3*k + 6)), k) == \
(3*k + 5)*(k**2 + 2*k + 5)*gamma(2*k + 4)/gamma(3*k + 6)
def test_gosper_sum_parametric():
assert gosper_sum(binomial(S.Half, m - j + 1)*binomial(S.Half, m + j), (j, 1, n)) == \
n*(1 + m - n)*(-1 + 2*m + 2*n)*binomial(S.Half, 1 + m - n)* \
binomial(S.Half, m + n)/(m*(1 + 2*m))
def test_gosper_sum_algebraic():
assert gosper_sum(
n**2 + sqrt(2), (n, 0, m)) == (m + 1)*(2*m**2 + m + 6*sqrt(2))/6
def test_gosper_sum_iterated():
f1 = binomial(2*k, k)/4**k
f2 = (1 + 2*n)*binomial(2*n, n)/4**n
f3 = (1 + 2*n)*(3 + 2*n)*binomial(2*n, n)/(3*4**n)
f4 = (1 + 2*n)*(3 + 2*n)*(5 + 2*n)*binomial(2*n, n)/(15*4**n)
f5 = (1 + 2*n)*(3 + 2*n)*(5 + 2*n)*(7 + 2*n)*binomial(2*n, n)/(105*4**n)
assert gosper_sum(f1, (k, 0, n)) == f2
assert gosper_sum(f2, (n, 0, n)) == f3
assert gosper_sum(f3, (n, 0, n)) == f4
assert gosper_sum(f4, (n, 0, n)) == f5
# the AeqB tests test expressions given in
# www.math.upenn.edu/~wilf/AeqB.pdf
def test_gosper_sum_AeqB_part1():
f1a = n**4
f1b = n**3*2**n
f1c = 1/(n**2 + sqrt(5)*n - 1)
f1d = n**4*4**n/binomial(2*n, n)
f1e = factorial(3*n)/(factorial(n)*factorial(n + 1)*factorial(n + 2)*27**n)
f1f = binomial(2*n, n)**2/((n + 1)*4**(2*n))
f1g = (4*n - 1)*binomial(2*n, n)**2/((2*n - 1)**2*4**(2*n))
f1h = n*factorial(n - S.Half)**2/factorial(n + 1)**2
g1a = m*(m + 1)*(2*m + 1)*(3*m**2 + 3*m - 1)/30
g1b = 26 + 2**(m + 1)*(m**3 - 3*m**2 + 9*m - 13)
g1c = (m + 1)*(m*(m**2 - 7*m + 3)*sqrt(5) - (
3*m**3 - 7*m**2 + 19*m - 6))/(2*m**3*sqrt(5) + m**4 + 5*m**2 - 1)/6
g1d = Rational(-2, 231) + 2*4**m*(m + 1)*(63*m**4 + 112*m**3 + 18*m**2 -
22*m + 3)/(693*binomial(2*m, m))
g1e = Rational(-9, 2) + (81*m**2 + 261*m + 200)*factorial(
3*m + 2)/(40*27**m*factorial(m)*factorial(m + 1)*factorial(m + 2))
g1f = (2*m + 1)**2*binomial(2*m, m)**2/(4**(2*m)*(m + 1))
g1g = -binomial(2*m, m)**2/4**(2*m)
g1h = 4*pi -(2*m + 1)**2*(3*m + 4)*factorial(m - S.Half)**2/factorial(m + 1)**2
g = gosper_sum(f1a, (n, 0, m))
assert g is not None and simplify(g - g1a) == 0
g = gosper_sum(f1b, (n, 0, m))
assert g is not None and simplify(g - g1b) == 0
g = gosper_sum(f1c, (n, 0, m))
assert g is not None and simplify(g - g1c) == 0
g = gosper_sum(f1d, (n, 0, m))
assert g is not None and simplify(g - g1d) == 0
g = gosper_sum(f1e, (n, 0, m))
assert g is not None and simplify(g - g1e) == 0
g = gosper_sum(f1f, (n, 0, m))
assert g is not None and simplify(g - g1f) == 0
g = gosper_sum(f1g, (n, 0, m))
assert g is not None and simplify(g - g1g) == 0
g = gosper_sum(f1h, (n, 0, m))
# need to call rewrite(gamma) here because we have terms involving
# factorial(1/2)
assert g is not None and simplify(g - g1h).rewrite(gamma) == 0
def test_gosper_sum_AeqB_part2():
f2a = n**2*a**n
f2b = (n - r/2)*binomial(r, n)
f2c = factorial(n - 1)**2/(factorial(n - x)*factorial(n + x))
g2a = -a*(a + 1)/(a - 1)**3 + a**(
m + 1)*(a**2*m**2 - 2*a*m**2 + m**2 - 2*a*m + 2*m + a + 1)/(a - 1)**3
g2b = (m - r)*binomial(r, m)/2
ff = factorial(1 - x)*factorial(1 + x)
g2c = 1/ff*(
1 - 1/x**2) + factorial(m)**2/(x**2*factorial(m - x)*factorial(m + x))
g = gosper_sum(f2a, (n, 0, m))
assert g is not None and simplify(g - g2a) == 0
g = gosper_sum(f2b, (n, 0, m))
assert g is not None and simplify(g - g2b) == 0
g = gosper_sum(f2c, (n, 1, m))
assert g is not None and simplify(g - g2c) == 0
def test_gosper_nan():
a = Symbol('a', positive=True)
b = Symbol('b', positive=True)
n = Symbol('n', integer=True)
m = Symbol('m', integer=True)
f2d = n*(n + a + b)*a**n*b**n/(factorial(n + a)*factorial(n + b))
g2d = 1/(factorial(a - 1)*factorial(
b - 1)) - a**(m + 1)*b**(m + 1)/(factorial(a + m)*factorial(b + m))
g = gosper_sum(f2d, (n, 0, m))
assert simplify(g - g2d) == 0
def test_gosper_sum_AeqB_part3():
f3a = 1/n**4
f3b = (6*n + 3)/(4*n**4 + 8*n**3 + 8*n**2 + 4*n + 3)
f3c = 2**n*(n**2 - 2*n - 1)/(n**2*(n + 1)**2)
f3d = n**2*4**n/((n + 1)*(n + 2))
f3e = 2**n/(n + 1)
f3f = 4*(n - 1)*(n**2 - 2*n - 1)/(n**2*(n + 1)**2*(n - 2)**2*(n - 3)**2)
f3g = (n**4 - 14*n**2 - 24*n - 9)*2**n/(n**2*(n + 1)**2*(n + 2)**2*
(n + 3)**2)
# g3a -> no closed form
g3b = m*(m + 2)/(2*m**2 + 4*m + 3)
g3c = 2**m/m**2 - 2
g3d = Rational(2, 3) + 4**(m + 1)*(m - 1)/(m + 2)/3
# g3e -> no closed form
g3f = -(Rational(-1, 16) + 1/((m - 2)**2*(m + 1)**2)) # the AeqB key is wrong
g3g = Rational(-2, 9) + 2**(m + 1)/((m + 1)**2*(m + 3)**2)
g = gosper_sum(f3a, (n, 1, m))
assert g is None
g = gosper_sum(f3b, (n, 1, m))
assert g is not None and simplify(g - g3b) == 0
g = gosper_sum(f3c, (n, 1, m - 1))
assert g is not None and simplify(g - g3c) == 0
g = gosper_sum(f3d, (n, 1, m))
assert g is not None and simplify(g - g3d) == 0
g = gosper_sum(f3e, (n, 0, m - 1))
assert g is None
g = gosper_sum(f3f, (n, 4, m))
assert g is not None and simplify(g - g3f) == 0
g = gosper_sum(f3g, (n, 1, m))
assert g is not None and simplify(g - g3g) == 0

View File

@ -0,0 +1,82 @@
from sympy.concrete.guess import (
find_simple_recurrence_vector,
find_simple_recurrence,
rationalize,
guess_generating_function_rational,
guess_generating_function,
guess
)
from sympy.concrete.products import Product
from sympy.core.function import Function
from sympy.core.numbers import Rational
from sympy.core.singleton import S
from sympy.core.symbol import (Symbol, symbols)
from sympy.core.sympify import sympify
from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial)
from sympy.functions.combinatorial.numbers import fibonacci
from sympy.functions.elementary.exponential import exp
def test_find_simple_recurrence_vector():
assert find_simple_recurrence_vector(
[fibonacci(k) for k in range(12)]) == [1, -1, -1]
def test_find_simple_recurrence():
a = Function('a')
n = Symbol('n')
assert find_simple_recurrence([fibonacci(k) for k in range(12)]) == (
-a(n) - a(n + 1) + a(n + 2))
f = Function('a')
i = Symbol('n')
a = [1, 1, 1]
for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3])
assert find_simple_recurrence(a, A=f, N=i) == (
-8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3))
assert find_simple_recurrence([0, 2, 15, 74, 12, 3, 0,
1, 2, 85, 4, 5, 63]) == 0
def test_rationalize():
from mpmath import cos, pi, mpf
assert rationalize(cos(pi/3)) == S.Half
assert rationalize(mpf("0.333333333333333")) == Rational(1, 3)
assert rationalize(mpf("-0.333333333333333")) == Rational(-1, 3)
assert rationalize(pi, maxcoeff = 250) == Rational(355, 113)
def test_guess_generating_function_rational():
x = Symbol('x')
assert guess_generating_function_rational([fibonacci(k)
for k in range(5, 15)]) == ((3*x + 5)/(-x**2 - x + 1))
def test_guess_generating_function():
x = Symbol('x')
assert guess_generating_function([fibonacci(k)
for k in range(5, 15)])['ogf'] == ((3*x + 5)/(-x**2 - x + 1))
assert guess_generating_function(
[1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf'] == (
(1/(x**4 + 2*x**2 - 4*x + 1))**S.Half)
assert guess_generating_function(sympify(
"[3/2, 11/2, 0, -121/2, -363/2, 121, 4719/2, 11495/2, -8712, -178717/2]")
)['ogf'] == (x + Rational(3, 2))/(11*x**2 - 3*x + 1)
assert guess_generating_function([factorial(k) for k in range(12)],
types=['egf'])['egf'] == 1/(-x + 1)
assert guess_generating_function([k+1 for k in range(12)],
types=['egf']) == {'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)}
def test_guess():
i0, i1 = symbols('i0 i1')
assert guess([1, 2, 6, 24, 120], evaluate=False) == [Product(i1 + 1, (i1, 1, i0 - 1))]
assert guess([1, 2, 6, 24, 120]) == [RisingFactorial(2, i0 - 1)]
assert guess([1, 2, 7, 42, 429, 7436, 218348, 10850216], niter=4) == [
2**(i0 - 1)*(Rational(27, 16))**(i0**2/2 - 3*i0/2 +
1)*Product(RisingFactorial(Rational(5, 3), i1 - 1)*RisingFactorial(Rational(7, 3), i1
- 1)/(RisingFactorial(Rational(3, 2), i1 - 1)*RisingFactorial(Rational(5, 2), i1 -
1)), (i1, 1, i0 - 1))]
assert guess([1, 0, 2]) == []
x, y = symbols('x y')
assert guess([1, 2, 6, 24, 120], variables=[x, y]) == [RisingFactorial(2, x - 1)]

View File

@ -0,0 +1,410 @@
from sympy.concrete.products import (Product, product)
from sympy.concrete.summations import Sum
from sympy.core.function import (Derivative, Function, diff)
from sympy.core.numbers import (Rational, oo, pi)
from sympy.core.singleton import S
from sympy.core.symbol import (Dummy, Symbol, symbols)
from sympy.functions.combinatorial.factorials import (rf, factorial)
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (cos, sin)
from sympy.functions.special.tensor_functions import KroneckerDelta
from sympy.simplify.combsimp import combsimp
from sympy.simplify.simplify import simplify
from sympy.testing.pytest import raises
a, k, n, m, x = symbols('a,k,n,m,x', integer=True)
f = Function('f')
def test_karr_convention():
# Test the Karr product convention that we want to hold.
# See his paper "Summation in Finite Terms" for a detailed
# reasoning why we really want exactly this definition.
# The convention is described for sums on page 309 and
# essentially in section 1.4, definition 3. For products
# we can find in analogy:
#
# \prod_{m <= i < n} f(i) 'has the obvious meaning' for m < n
# \prod_{m <= i < n} f(i) = 0 for m = n
# \prod_{m <= i < n} f(i) = 1 / \prod_{n <= i < m} f(i) for m > n
#
# It is important to note that he defines all products with
# the upper limit being *exclusive*.
# In contrast, SymPy and the usual mathematical notation has:
#
# prod_{i = a}^b f(i) = f(a) * f(a+1) * ... * f(b-1) * f(b)
#
# with the upper limit *inclusive*. So translating between
# the two we find that:
#
# \prod_{m <= i < n} f(i) = \prod_{i = m}^{n-1} f(i)
#
# where we intentionally used two different ways to typeset the
# products and its limits.
i = Symbol("i", integer=True)
k = Symbol("k", integer=True)
j = Symbol("j", integer=True, positive=True)
# A simple example with a concrete factors and symbolic limits.
# The normal product: m = k and n = k + j and therefore m < n:
m = k
n = k + j
a = m
b = n - 1
S1 = Product(i**2, (i, a, b)).doit()
# The reversed product: m = k + j and n = k and therefore m > n:
m = k + j
n = k
a = m
b = n - 1
S2 = Product(i**2, (i, a, b)).doit()
assert S1 * S2 == 1
# Test the empty product: m = k and n = k and therefore m = n:
m = k
n = k
a = m
b = n - 1
Sz = Product(i**2, (i, a, b)).doit()
assert Sz == 1
# Another example this time with an unspecified factor and
# numeric limits. (We can not do both tests in the same example.)
f = Function("f")
# The normal product with m < n:
m = 2
n = 11
a = m
b = n - 1
S1 = Product(f(i), (i, a, b)).doit()
# The reversed product with m > n:
m = 11
n = 2
a = m
b = n - 1
S2 = Product(f(i), (i, a, b)).doit()
assert simplify(S1 * S2) == 1
# Test the empty product with m = n:
m = 5
n = 5
a = m
b = n - 1
Sz = Product(f(i), (i, a, b)).doit()
assert Sz == 1
def test_karr_proposition_2a():
# Test Karr, page 309, proposition 2, part a
i, u, v = symbols('i u v', integer=True)
def test_the_product(m, n):
# g
g = i**3 + 2*i**2 - 3*i
# f = Delta g
f = simplify(g.subs(i, i+1) / g)
# The product
a = m
b = n - 1
P = Product(f, (i, a, b)).doit()
# Test if Product_{m <= i < n} f(i) = g(n) / g(m)
assert combsimp(P / (g.subs(i, n) / g.subs(i, m))) == 1
# m < n
test_the_product(u, u + v)
# m = n
test_the_product(u, u)
# m > n
test_the_product(u + v, u)
def test_karr_proposition_2b():
# Test Karr, page 309, proposition 2, part b
i, u, v, w = symbols('i u v w', integer=True)
def test_the_product(l, n, m):
# Productmand
s = i**3
# First product
a = l
b = n - 1
S1 = Product(s, (i, a, b)).doit()
# Second product
a = l
b = m - 1
S2 = Product(s, (i, a, b)).doit()
# Third product
a = m
b = n - 1
S3 = Product(s, (i, a, b)).doit()
# Test if S1 = S2 * S3 as required
assert combsimp(S1 / (S2 * S3)) == 1
# l < m < n
test_the_product(u, u + v, u + v + w)
# l < m = n
test_the_product(u, u + v, u + v)
# l < m > n
test_the_product(u, u + v + w, v)
# l = m < n
test_the_product(u, u, u + v)
# l = m = n
test_the_product(u, u, u)
# l = m > n
test_the_product(u + v, u + v, u)
# l > m < n
test_the_product(u + v, u, u + w)
# l > m = n
test_the_product(u + v, u, u)
# l > m > n
test_the_product(u + v + w, u + v, u)
def test_simple_products():
assert product(2, (k, a, n)) == 2**(n - a + 1)
assert product(k, (k, 1, n)) == factorial(n)
assert product(k**3, (k, 1, n)) == factorial(n)**3
assert product(k + 1, (k, 0, n - 1)) == factorial(n)
assert product(k + 1, (k, a, n - 1)) == rf(1 + a, n - a)
assert product(cos(k), (k, 0, 5)) == cos(1)*cos(2)*cos(3)*cos(4)*cos(5)
assert product(cos(k), (k, 3, 5)) == cos(3)*cos(4)*cos(5)
assert product(cos(k), (k, 1, Rational(5, 2))) != cos(1)*cos(2)
assert isinstance(product(k**k, (k, 1, n)), Product)
assert Product(x**k, (k, 1, n)).variables == [k]
raises(ValueError, lambda: Product(n))
raises(ValueError, lambda: Product(n, k))
raises(ValueError, lambda: Product(n, k, 1))
raises(ValueError, lambda: Product(n, k, 1, 10))
raises(ValueError, lambda: Product(n, (k, 1)))
assert product(1, (n, 1, oo)) == 1 # issue 8301
assert product(2, (n, 1, oo)) is oo
assert product(-1, (n, 1, oo)).func is Product
def test_multiple_products():
assert product(x, (n, 1, k), (k, 1, m)) == x**(m**2/2 + m/2)
assert product(f(n), (
n, 1, m), (m, 1, k)) == Product(f(n), (n, 1, m), (m, 1, k)).doit()
assert Product(f(n), (m, 1, k), (n, 1, k)).doit() == \
Product(Product(f(n), (m, 1, k)), (n, 1, k)).doit() == \
product(f(n), (m, 1, k), (n, 1, k)) == \
product(product(f(n), (m, 1, k)), (n, 1, k)) == \
Product(f(n)**k, (n, 1, k))
assert Product(
x, (x, 1, k), (k, 1, n)).doit() == Product(factorial(k), (k, 1, n))
assert Product(x**k, (n, 1, k), (k, 1, m)).variables == [n, k]
def test_rational_products():
assert product(1 + 1/k, (k, 1, n)) == rf(2, n)/factorial(n)
def test_special_products():
# Wallis product
assert product((4*k)**2 / (4*k**2 - 1), (k, 1, n)) == \
4**n*factorial(n)**2/rf(S.Half, n)/rf(Rational(3, 2), n)
# Euler's product formula for sin
assert product(1 + a/k**2, (k, 1, n)) == \
rf(1 - sqrt(-a), n)*rf(1 + sqrt(-a), n)/factorial(n)**2
def test__eval_product():
from sympy.abc import i, n
# issue 4809
a = Function('a')
assert product(2*a(i), (i, 1, n)) == 2**n * Product(a(i), (i, 1, n))
# issue 4810
assert product(2**i, (i, 1, n)) == 2**(n*(n + 1)/2)
k, m = symbols('k m', integer=True)
assert product(2**i, (i, k, m)) == 2**(-k**2/2 + k/2 + m**2/2 + m/2)
n = Symbol('n', negative=True, integer=True)
p = Symbol('p', positive=True, integer=True)
assert product(2**i, (i, n, p)) == 2**(-n**2/2 + n/2 + p**2/2 + p/2)
assert product(2**i, (i, p, n)) == 2**(n**2/2 + n/2 - p**2/2 + p/2)
def test_product_pow():
# issue 4817
assert product(2**f(k), (k, 1, n)) == 2**Sum(f(k), (k, 1, n))
assert product(2**(2*f(k)), (k, 1, n)) == 2**Sum(2*f(k), (k, 1, n))
def test_infinite_product():
# issue 5737
assert isinstance(Product(2**(1/factorial(n)), (n, 0, oo)), Product)
def test_conjugate_transpose():
p = Product(x**k, (k, 1, 3))
assert p.adjoint().doit() == p.doit().adjoint()
assert p.conjugate().doit() == p.doit().conjugate()
assert p.transpose().doit() == p.doit().transpose()
A, B = symbols("A B", commutative=False)
p = Product(A*B**k, (k, 1, 3))
assert p.adjoint().doit() == p.doit().adjoint()
assert p.conjugate().doit() == p.doit().conjugate()
assert p.transpose().doit() == p.doit().transpose()
p = Product(B**k*A, (k, 1, 3))
assert p.adjoint().doit() == p.doit().adjoint()
assert p.conjugate().doit() == p.doit().conjugate()
assert p.transpose().doit() == p.doit().transpose()
def test_simplify_prod():
y, t, b, c, v, d = symbols('y, t, b, c, v, d', integer = True)
_simplify = lambda e: simplify(e, doit=False)
assert _simplify(Product(x*y, (x, n, m), (y, a, k)) * \
Product(y, (x, n, m), (y, a, k))) == \
Product(x*y**2, (x, n, m), (y, a, k))
assert _simplify(3 * y* Product(x, (x, n, m)) * Product(x, (x, m + 1, a))) \
== 3 * y * Product(x, (x, n, a))
assert _simplify(Product(x, (x, k + 1, a)) * Product(x, (x, n, k))) == \
Product(x, (x, n, a))
assert _simplify(Product(x, (x, k + 1, a)) * Product(x + 1, (x, n, k))) == \
Product(x, (x, k + 1, a)) * Product(x + 1, (x, n, k))
assert _simplify(Product(x, (t, a, b)) * Product(y, (t, a, b)) * \
Product(x, (t, b+1, c))) == Product(x*y, (t, a, b)) * \
Product(x, (t, b+1, c))
assert _simplify(Product(x, (t, a, b)) * Product(x, (t, b+1, c)) * \
Product(y, (t, a, b))) == Product(x*y, (t, a, b)) * \
Product(x, (t, b+1, c))
assert _simplify(Product(sin(t)**2 + cos(t)**2 + 1, (t, a, b))) == \
Product(2, (t, a, b))
assert _simplify(Product(sin(t)**2 + cos(t)**2 - 1, (t, a, b))) == \
Product(0, (t, a, b))
assert _simplify(Product(v*Product(sin(t)**2 + cos(t)**2, (t, a, b)),
(v, c, d))) == Product(v*Product(1, (t, a, b)), (v, c, d))
def test_change_index():
b, y, c, d, z = symbols('b, y, c, d, z', integer = True)
assert Product(x, (x, a, b)).change_index(x, x + 1, y) == \
Product(y - 1, (y, a + 1, b + 1))
assert Product(x**2, (x, a, b)).change_index(x, x - 1) == \
Product((x + 1)**2, (x, a - 1, b - 1))
assert Product(x**2, (x, a, b)).change_index(x, -x, y) == \
Product((-y)**2, (y, -b, -a))
assert Product(x, (x, a, b)).change_index(x, -x - 1) == \
Product(-x - 1, (x, - b - 1, -a - 1))
assert Product(x*y, (x, a, b), (y, c, d)).change_index(x, x - 1, z) == \
Product((z + 1)*y, (z, a - 1, b - 1), (y, c, d))
def test_reorder():
b, y, c, d, z = symbols('b, y, c, d, z', integer = True)
assert Product(x*y, (x, a, b), (y, c, d)).reorder((0, 1)) == \
Product(x*y, (y, c, d), (x, a, b))
assert Product(x, (x, a, b), (x, c, d)).reorder((0, 1)) == \
Product(x, (x, c, d), (x, a, b))
assert Product(x*y + z, (x, a, b), (z, m, n), (y, c, d)).reorder(\
(2, 0), (0, 1)) == Product(x*y + z, (z, m, n), (y, c, d), (x, a, b))
assert Product(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\
(0, 1), (1, 2), (0, 2)) == \
Product(x*y*z, (x, a, b), (z, m, n), (y, c, d))
assert Product(x*y*z, (x, a, b), (y, c, d), (z, m, n)).reorder(\
(x, y), (y, z), (x, z)) == \
Product(x*y*z, (x, a, b), (z, m, n), (y, c, d))
assert Product(x*y, (x, a, b), (y, c, d)).reorder((x, 1)) == \
Product(x*y, (y, c, d), (x, a, b))
assert Product(x*y, (x, a, b), (y, c, d)).reorder((y, x)) == \
Product(x*y, (y, c, d), (x, a, b))
def test_Product_is_convergent():
assert Product(1/n**2, (n, 1, oo)).is_convergent() is S.false
assert Product(exp(1/n**2), (n, 1, oo)).is_convergent() is S.true
assert Product(1/n, (n, 1, oo)).is_convergent() is S.false
assert Product(1 + 1/n, (n, 1, oo)).is_convergent() is S.false
assert Product(1 + 1/n**2, (n, 1, oo)).is_convergent() is S.true
def test_reverse_order():
x, y, a, b, c, d= symbols('x, y, a, b, c, d', integer = True)
assert Product(x, (x, 0, 3)).reverse_order(0) == Product(1/x, (x, 4, -1))
assert Product(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(0, 1) == \
Product(x*y, (x, 6, 0), (y, 7, -1))
assert Product(x, (x, 1, 2)).reverse_order(0) == Product(1/x, (x, 3, 0))
assert Product(x, (x, 1, 3)).reverse_order(0) == Product(1/x, (x, 4, 0))
assert Product(x, (x, 1, a)).reverse_order(0) == Product(1/x, (x, a + 1, 0))
assert Product(x, (x, a, 5)).reverse_order(0) == Product(1/x, (x, 6, a - 1))
assert Product(x, (x, a + 1, a + 5)).reverse_order(0) == \
Product(1/x, (x, a + 6, a))
assert Product(x, (x, a + 1, a + 2)).reverse_order(0) == \
Product(1/x, (x, a + 3, a))
assert Product(x, (x, a + 1, a + 1)).reverse_order(0) == \
Product(1/x, (x, a + 2, a))
assert Product(x, (x, a, b)).reverse_order(0) == Product(1/x, (x, b + 1, a - 1))
assert Product(x, (x, a, b)).reverse_order(x) == Product(1/x, (x, b + 1, a - 1))
assert Product(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1) == \
Product(x*y, (x, b + 1, a - 1), (y, 6, 1))
assert Product(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x) == \
Product(x*y, (x, b + 1, a - 1), (y, 6, 1))
def test_issue_9983():
n = Symbol('n', integer=True, positive=True)
p = Product(1 + 1/n**Rational(2, 3), (n, 1, oo))
assert p.is_convergent() is S.false
assert product(1 + 1/n**Rational(2, 3), (n, 1, oo)) == p.doit()
def test_issue_13546():
n = Symbol('n')
k = Symbol('k')
p = Product(n + 1 / 2**k, (k, 0, n-1)).doit()
assert p.subs(n, 2).doit() == Rational(15, 2)
def test_issue_14036():
a, n = symbols('a n')
assert product(1 - a**2 / (n*pi)**2, [n, 1, oo]) != 0
def test_rewrite_Sum():
assert Product(1 - S.Half**2/k**2, (k, 1, oo)).rewrite(Sum) == \
exp(Sum(log(1 - 1/(4*k**2)), (k, 1, oo)))
def test_KroneckerDelta_Product():
y = Symbol('y')
assert Product(x*KroneckerDelta(x, y), (x, 0, 1)).doit() == 0
def test_issue_20848():
_i = Dummy('i')
t, y, z = symbols('t y z')
assert diff(Product(x, (y, 1, z)), x).as_dummy() == Sum(Product(x, (y, 1, _i - 1))*Product(x, (y, _i + 1, z)), (_i, 1, z)).as_dummy()
assert diff(Product(x, (y, 1, z)), x).doit() == x**(z - 1)*z
assert diff(Product(x, (y, x, z)), x) == Derivative(Product(x, (y, x, z)), x)
assert diff(Product(t, (x, 1, z)), x) == S(0)
assert Product(sin(n*x), (n, -1, 1)).diff(x).doit() == S(0)

File diff suppressed because it is too large Load Diff