chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
from .products import product, Product
|
||||
from .summations import summation, Sum
|
||||
|
||||
__all__ = [
|
||||
'product', 'Product',
|
||||
|
||||
'summation', 'Sum',
|
||||
]
|
||||
@@ -0,0 +1,327 @@
|
||||
"""
|
||||
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:
|
||||
return any(_has_simple_delta(arg, index) for arg in expr.args)
|
||||
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:
|
||||
newargs += [KroneckerDelta(k, v) for k, v in solns[0].items()]
|
||||
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)
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,222 @@
|
||||
"""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 = {r for r in R.ground_roots().keys() if r.is_Integer and r >= 0}
|
||||
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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,605 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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: tuple[tuple[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_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
@@ -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))
|
||||
@@ -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
|
||||
@@ -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)]
|
||||
@@ -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
Reference in New Issue
Block a user