Initialisation du repository de Beta
This commit is contained in:
commit
14985f6dbb
9469 changed files with 1903273 additions and 0 deletions
|
|
@ -0,0 +1,8 @@
|
|||
from .products import product, Product
|
||||
from .summations import summation, Sum
|
||||
|
||||
__all__ = [
|
||||
'product', 'Product',
|
||||
|
||||
'summation', 'Sum',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
327
venv/lib/python3.12/site-packages/sympy/concrete/delta.py
Normal file
327
venv/lib/python3.12/site-packages/sympy/concrete/delta.py
Normal file
|
|
@ -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
|
||||
222
venv/lib/python3.12/site-packages/sympy/concrete/gosper.py
Normal file
222
venv/lib/python3.12/site-packages/sympy/concrete/gosper.py
Normal file
|
|
@ -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)
|
||||
473
venv/lib/python3.12/site-packages/sympy/concrete/guess.py
Normal file
473
venv/lib/python3.12/site-packages/sympy/concrete/guess.py
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
"""Various algorithms for helping identifying numbers and sequences."""
|
||||
|
||||
|
||||
from sympy.concrete.products import (Product, product)
|
||||
from sympy.core import Function, S
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.numbers import Integer, Rational
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.polys.polyfuncs import rational_interpolate as rinterp
|
||||
from sympy.polys.polytools import lcm
|
||||
from sympy.simplify.radsimp import denom
|
||||
from sympy.utilities import public
|
||||
|
||||
|
||||
@public
|
||||
def find_simple_recurrence_vector(l):
|
||||
"""
|
||||
This function is used internally by other functions from the
|
||||
sympy.concrete.guess module. While most users may want to rather use the
|
||||
function find_simple_recurrence when looking for recurrence relations
|
||||
among rational numbers, the current function may still be useful when
|
||||
some post-processing has to be done.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The function returns a vector of length n when a recurrence relation of
|
||||
order n is detected in the sequence of rational numbers v.
|
||||
|
||||
If the returned vector has a length 1, then the returned value is always
|
||||
the list [0], which means that no relation has been found.
|
||||
|
||||
While the functions is intended to be used with rational numbers, it should
|
||||
work for other kinds of real numbers except for some cases involving
|
||||
quadratic numbers; for that reason it should be used with some caution when
|
||||
the argument is not a list of rational numbers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import find_simple_recurrence_vector
|
||||
>>> from sympy import fibonacci
|
||||
>>> find_simple_recurrence_vector([fibonacci(k) for k in range(12)])
|
||||
[1, -1, -1]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
See the function sympy.concrete.guess.find_simple_recurrence which is more
|
||||
user-friendly.
|
||||
|
||||
"""
|
||||
q1 = [0]
|
||||
q2 = [1]
|
||||
b, z = 0, len(l) >> 1
|
||||
while len(q2) <= z:
|
||||
while l[b]==0:
|
||||
b += 1
|
||||
if b == len(l):
|
||||
c = 1
|
||||
for x in q2:
|
||||
c = lcm(c, denom(x))
|
||||
if q2[0]*c < 0: c = -c
|
||||
for k in range(len(q2)):
|
||||
q2[k] = int(q2[k]*c)
|
||||
return q2
|
||||
a = S.One/l[b]
|
||||
m = [a]
|
||||
for k in range(b+1, len(l)):
|
||||
m.append(-sum(l[j+1]*m[b-j-1] for j in range(b, k))*a)
|
||||
l, m = m, [0] * max(len(q2), b+len(q1))
|
||||
for k, q in enumerate(q2):
|
||||
m[k] = a*q
|
||||
for k, q in enumerate(q1):
|
||||
m[k+b] += q
|
||||
while m[-1]==0: m.pop() # because trailing zeros can occur
|
||||
q1, q2, b = q2, m, 1
|
||||
return [0]
|
||||
|
||||
@public
|
||||
def find_simple_recurrence(v, A=Function('a'), N=Symbol('n')):
|
||||
"""
|
||||
Detects and returns a recurrence relation from a sequence of several integer
|
||||
(or rational) terms. The name of the function in the returned expression is
|
||||
'a' by default; the main variable is 'n' by default. The smallest index in
|
||||
the returned expression is always n (and never n-1, n-2, etc.).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import find_simple_recurrence
|
||||
>>> from sympy import fibonacci
|
||||
>>> find_simple_recurrence([fibonacci(k) for k in range(12)])
|
||||
-a(n) - a(n + 1) + a(n + 2)
|
||||
|
||||
>>> from sympy import Function, Symbol
|
||||
>>> a = [1, 1, 1]
|
||||
>>> for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3])
|
||||
>>> find_simple_recurrence(a, A=Function('f'), N=Symbol('i'))
|
||||
-8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3)
|
||||
|
||||
"""
|
||||
p = find_simple_recurrence_vector(v)
|
||||
n = len(p)
|
||||
if n <= 1: return S.Zero
|
||||
|
||||
return Add(*[A(N+n-1-k)*p[k] for k in range(n)])
|
||||
|
||||
|
||||
@public
|
||||
def rationalize(x, maxcoeff=10000):
|
||||
"""
|
||||
Helps identifying a rational number from a float (or mpmath.mpf) value by
|
||||
using a continued fraction. The algorithm stops as soon as a large partial
|
||||
quotient is detected (greater than 10000 by default).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import rationalize
|
||||
>>> from mpmath import cos, pi
|
||||
>>> rationalize(cos(pi/3))
|
||||
1/2
|
||||
|
||||
>>> from mpmath import mpf
|
||||
>>> rationalize(mpf("0.333333333333333"))
|
||||
1/3
|
||||
|
||||
While the function is rather intended to help 'identifying' rational
|
||||
values, it may be used in some cases for approximating real numbers.
|
||||
(Though other functions may be more relevant in that case.)
|
||||
|
||||
>>> rationalize(pi, maxcoeff = 250)
|
||||
355/113
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
Several other methods can approximate a real number as a rational, like:
|
||||
|
||||
* fractions.Fraction.from_decimal
|
||||
* fractions.Fraction.from_float
|
||||
* mpmath.identify
|
||||
* mpmath.pslq by using the following syntax: mpmath.pslq([x, 1])
|
||||
* mpmath.findpoly by using the following syntax: mpmath.findpoly(x, 1)
|
||||
* sympy.simplify.nsimplify (which is a more general function)
|
||||
|
||||
The main difference between the current function and all these variants is
|
||||
that control focuses on magnitude of partial quotients here rather than on
|
||||
global precision of the approximation. If the real is "known to be" a
|
||||
rational number, the current function should be able to detect it correctly
|
||||
with the default settings even when denominator is great (unless its
|
||||
expansion contains unusually big partial quotients) which may occur
|
||||
when studying sequences of increasing numbers. If the user cares more
|
||||
on getting simple fractions, other methods may be more convenient.
|
||||
|
||||
"""
|
||||
p0, p1 = 0, 1
|
||||
q0, q1 = 1, 0
|
||||
a = floor(x)
|
||||
while a < maxcoeff or q1==0:
|
||||
p = a*p1 + p0
|
||||
q = a*q1 + q0
|
||||
p0, p1 = p1, p
|
||||
q0, q1 = q1, q
|
||||
if x==a: break
|
||||
x = 1/(x-a)
|
||||
a = floor(x)
|
||||
return sympify(p) / q
|
||||
|
||||
|
||||
@public
|
||||
def guess_generating_function_rational(v, X=Symbol('x')):
|
||||
"""
|
||||
Tries to "guess" a rational generating function for a sequence of rational
|
||||
numbers v.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import guess_generating_function_rational
|
||||
>>> from sympy import fibonacci
|
||||
>>> l = [fibonacci(k) for k in range(5,15)]
|
||||
>>> guess_generating_function_rational(l)
|
||||
(3*x + 5)/(-x**2 - x + 1)
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.series.approximants
|
||||
mpmath.pade
|
||||
|
||||
"""
|
||||
# a) compute the denominator as q
|
||||
q = find_simple_recurrence_vector(v)
|
||||
n = len(q)
|
||||
if n <= 1: return None
|
||||
# b) compute the numerator as p
|
||||
p = [sum(v[i-k]*q[k] for k in range(min(i+1, n)))
|
||||
for i in range(len(v)>>1)]
|
||||
return (sum(p[k]*X**k for k in range(len(p)))
|
||||
/ sum(q[k]*X**k for k in range(n)))
|
||||
|
||||
|
||||
@public
|
||||
def guess_generating_function(v, X=Symbol('x'), types=['all'], maxsqrtn=2):
|
||||
"""
|
||||
Tries to "guess" a generating function for a sequence of rational numbers v.
|
||||
Only a few patterns are implemented yet.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The function returns a dictionary where keys are the name of a given type of
|
||||
generating function. Six types are currently implemented:
|
||||
|
||||
type | formal definition
|
||||
-------+----------------------------------------------------------------
|
||||
ogf | f(x) = Sum( a_k * x^k , k: 0..infinity )
|
||||
egf | f(x) = Sum( a_k * x^k / k! , k: 0..infinity )
|
||||
lgf | f(x) = Sum( (-1)^(k+1) a_k * x^k / k , k: 1..infinity )
|
||||
| (with initial index being hold as 1 rather than 0)
|
||||
hlgf | f(x) = Sum( a_k * x^k / k , k: 1..infinity )
|
||||
| (with initial index being hold as 1 rather than 0)
|
||||
lgdogf | f(x) = derivate( log(Sum( a_k * x^k, k: 0..infinity )), x)
|
||||
lgdegf | f(x) = derivate( log(Sum( a_k * x^k / k!, k: 0..infinity )), x)
|
||||
|
||||
In order to spare time, the user can select only some types of generating
|
||||
functions (default being ['all']). While forgetting to use a list in the
|
||||
case of a single type may seem to work most of the time as in: types='ogf'
|
||||
this (convenient) syntax may lead to unexpected extra results in some cases.
|
||||
|
||||
Discarding a type when calling the function does not mean that the type will
|
||||
not be present in the returned dictionary; it only means that no extra
|
||||
computation will be performed for that type, but the function may still add
|
||||
it in the result when it can be easily converted from another type.
|
||||
|
||||
Two generating functions (lgdogf and lgdegf) are not even computed if the
|
||||
initial term of the sequence is 0; it may be useful in that case to try
|
||||
again after having removed the leading zeros.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import guess_generating_function as ggf
|
||||
>>> ggf([k+1 for k in range(12)], types=['ogf', 'lgf', 'hlgf'])
|
||||
{'hlgf': 1/(1 - x), 'lgf': 1/(x + 1), 'ogf': 1/(x**2 - 2*x + 1)}
|
||||
|
||||
>>> from sympy import sympify
|
||||
>>> l = sympify("[3/2, 11/2, 0, -121/2, -363/2, 121]")
|
||||
>>> ggf(l)
|
||||
{'ogf': (x + 3/2)/(11*x**2 - 3*x + 1)}
|
||||
|
||||
>>> from sympy import fibonacci
|
||||
>>> ggf([fibonacci(k) for k in range(5, 15)], types=['ogf'])
|
||||
{'ogf': (3*x + 5)/(-x**2 - x + 1)}
|
||||
|
||||
>>> from sympy import factorial
|
||||
>>> ggf([factorial(k) for k in range(12)], types=['ogf', 'egf', 'lgf'])
|
||||
{'egf': 1/(1 - x)}
|
||||
|
||||
>>> ggf([k+1 for k in range(12)], types=['egf'])
|
||||
{'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)}
|
||||
|
||||
N-th root of a rational function can also be detected (below is an example
|
||||
coming from the sequence A108626 from https://oeis.org).
|
||||
The greatest n-th root to be tested is specified as maxsqrtn (default 2).
|
||||
|
||||
>>> ggf([1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf']
|
||||
sqrt(1/(x**4 + 2*x**2 - 4*x + 1))
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] "Concrete Mathematics", R.L. Graham, D.E. Knuth, O. Patashnik
|
||||
.. [2] https://oeis.org/wiki/Generating_functions
|
||||
|
||||
"""
|
||||
# List of all types of all g.f. known by the algorithm
|
||||
if 'all' in types:
|
||||
types = ('ogf', 'egf', 'lgf', 'hlgf', 'lgdogf', 'lgdegf')
|
||||
|
||||
result = {}
|
||||
|
||||
# Ordinary Generating Function (ogf)
|
||||
if 'ogf' in types:
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(v) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*v[i] for i in range(n+1)) for n in range(len(v))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['ogf'] = g**Rational(1, d+1)
|
||||
break
|
||||
|
||||
# Exponential Generating Function (egf)
|
||||
if 'egf' in types:
|
||||
# Transform sequence (division by factorial)
|
||||
w, f = [], S.One
|
||||
for i, k in enumerate(v):
|
||||
f *= i if i else 1
|
||||
w.append(k/f)
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(w) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['egf'] = g**Rational(1, d+1)
|
||||
break
|
||||
|
||||
# Logarithmic Generating Function (lgf)
|
||||
if 'lgf' in types:
|
||||
# Transform sequence (multiplication by (-1)^(n+1) / n)
|
||||
w, f = [], S.NegativeOne
|
||||
for i, k in enumerate(v):
|
||||
f = -f
|
||||
w.append(f*k/Integer(i+1))
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(w) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['lgf'] = g**Rational(1, d+1)
|
||||
break
|
||||
|
||||
# Hyperbolic logarithmic Generating Function (hlgf)
|
||||
if 'hlgf' in types:
|
||||
# Transform sequence (division by n+1)
|
||||
w = []
|
||||
for i, k in enumerate(v):
|
||||
w.append(k/Integer(i+1))
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(w) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['hlgf'] = g**Rational(1, d+1)
|
||||
break
|
||||
|
||||
# Logarithmic derivative of ordinary generating Function (lgdogf)
|
||||
if v[0] != 0 and ('lgdogf' in types
|
||||
or ('ogf' in types and 'ogf' not in result)):
|
||||
# Transform sequence by computing f'(x)/f(x)
|
||||
# because log(f(x)) = integrate( f'(x)/f(x) )
|
||||
a, w = sympify(v[0]), []
|
||||
for n in range(len(v)-1):
|
||||
w.append(
|
||||
(v[n+1]*(n+1) - sum(w[-i-1]*v[i+1] for i in range(n)))/a)
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(w) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['lgdogf'] = g**Rational(1, d+1)
|
||||
if 'ogf' not in result:
|
||||
result['ogf'] = exp(integrate(result['lgdogf'], X))
|
||||
break
|
||||
|
||||
# Logarithmic derivative of exponential generating Function (lgdegf)
|
||||
if v[0] != 0 and ('lgdegf' in types
|
||||
or ('egf' in types and 'egf' not in result)):
|
||||
# Transform sequence / step 1 (division by factorial)
|
||||
z, f = [], S.One
|
||||
for i, k in enumerate(v):
|
||||
f *= i if i else 1
|
||||
z.append(k/f)
|
||||
# Transform sequence / step 2 by computing f'(x)/f(x)
|
||||
# because log(f(x)) = integrate( f'(x)/f(x) )
|
||||
a, w = z[0], []
|
||||
for n in range(len(z)-1):
|
||||
w.append(
|
||||
(z[n+1]*(n+1) - sum(w[-i-1]*z[i+1] for i in range(n)))/a)
|
||||
# Perform some convolutions of the sequence with itself
|
||||
t = [1] + [0]*(len(w) - 1)
|
||||
for d in range(max(1, maxsqrtn)):
|
||||
t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
|
||||
g = guess_generating_function_rational(t, X=X)
|
||||
if g:
|
||||
result['lgdegf'] = g**Rational(1, d+1)
|
||||
if 'egf' not in result:
|
||||
result['egf'] = exp(integrate(result['lgdegf'], X))
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@public
|
||||
def guess(l, all=False, evaluate=True, niter=2, variables=None):
|
||||
"""
|
||||
This function is adapted from the Rate.m package for Mathematica
|
||||
written by Christian Krattenthaler.
|
||||
It tries to guess a formula from a given sequence of rational numbers.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
In order to speed up the process, the 'all' variable is set to False by
|
||||
default, stopping the computation as some results are returned during an
|
||||
iteration; the variable can be set to True if more iterations are needed
|
||||
(other formulas may be found; however they may be equivalent to the first
|
||||
ones).
|
||||
|
||||
Another option is the 'evaluate' variable (default is True); setting it
|
||||
to False will leave the involved products unevaluated.
|
||||
|
||||
By default, the number of iterations is set to 2 but a greater value (up
|
||||
to len(l)-1) can be specified with the optional 'niter' variable.
|
||||
More and more convoluted results are found when the order of the
|
||||
iteration gets higher:
|
||||
|
||||
* first iteration returns polynomial or rational functions;
|
||||
* second iteration returns products of rising factorials and their
|
||||
inverses;
|
||||
* third iteration returns products of products of rising factorials
|
||||
and their inverses;
|
||||
* etc.
|
||||
|
||||
The returned formulas contain symbols i0, i1, i2, ... where the main
|
||||
variables is i0 (and auxiliary variables are i1, i2, ...). A list of
|
||||
other symbols can be provided in the 'variables' option; the length of
|
||||
the least should be the value of 'niter' (more is acceptable but only
|
||||
the first symbols will be used); in this case, the main variable will be
|
||||
the first symbol in the list.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.concrete.guess import guess
|
||||
>>> guess([1,2,6,24,120], evaluate=False)
|
||||
[Product(i1 + 1, (i1, 1, i0 - 1))]
|
||||
|
||||
>>> from sympy import symbols
|
||||
>>> r = guess([1,2,7,42,429,7436,218348,10850216], niter=4)
|
||||
>>> i0 = symbols("i0")
|
||||
>>> [r[0].subs(i0,n).doit() for n in range(1,10)]
|
||||
[1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460]
|
||||
"""
|
||||
if any(a==0 for a in l[:-1]):
|
||||
return []
|
||||
N = len(l)
|
||||
niter = min(N-1, niter)
|
||||
myprod = product if evaluate else Product
|
||||
g = []
|
||||
res = []
|
||||
if variables is None:
|
||||
symb = symbols('i:'+str(niter))
|
||||
else:
|
||||
symb = variables
|
||||
for k, s in enumerate(symb):
|
||||
g.append(l)
|
||||
n, r = len(l), []
|
||||
for i in range(n-2-1, -1, -1):
|
||||
ri = rinterp(enumerate(g[k][:-1], start=1), i, X=s)
|
||||
if ((denom(ri).subs({s:n}) != 0)
|
||||
and (ri.subs({s:n}) - g[k][-1] == 0)
|
||||
and ri not in r):
|
||||
r.append(ri)
|
||||
if r:
|
||||
for i in range(k-1, -1, -1):
|
||||
r = [g[i][0]
|
||||
* myprod(v, (symb[i+1], 1, symb[i]-1)) for v in r]
|
||||
if not all: return r
|
||||
res += r
|
||||
l = [Rational(l[i+1], l[i]) for i in range(N-k-1)]
|
||||
return res
|
||||
605
venv/lib/python3.12/site-packages/sympy/concrete/products.py
Normal file
605
venv/lib/python3.12/site-packages/sympy/concrete/products.py
Normal file
|
|
@ -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
|
||||
1659
venv/lib/python3.12/site-packages/sympy/concrete/summations.py
Normal file
1659
venv/lib/python3.12/site-packages/sympy/concrete/summations.py
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue