""" Change index / Reorder / Reverse order of limits of Sums and Products"""
from sympy.concrete import Product, Sum
from sympy import S
class ReorderError(NotImplementedError):
"""
Exception raised when trying to reorder dependent limits.
"""
def __init__(self, expr, msg):
super(ReorderError, self).__init__(
"%s could not be reordered: %s." % (expr, msg))
[docs]def index(expr, x):
"""
Return the index of a limit variable.
Usage
=====
``index(expr, x)`` returns the index of the limit variable ``x`` in the
limits of ``expr``. Note that we start counting with 0 at the inner-most
limits tuple.
Examples
========
>>> from sympy.concrete.simplification import index
>>> from sympy.abc import x, y, a, b, c, d
>>> from sympy import Sum, Product
>>> index(Sum(x*y, (x, a, b), (y, c, d)), x)
0
>>> index(Sum(x*y, (x, a, b), (y, c, d)), y)
1
>>> index(Product(x*y, (x, a, b), (y, c, d)), x)
0
>>> index(Product(x*y, (x, a, b), (y, c, d)), y)
1
See Also
========
sympy.concrete.simplification.change_index,
sympy.concrete.simplification.reorder_limit,
sympy.concrete.simplification.reorder,
sympy.concrete.simplification.reverse_order
"""
if isinstance(expr, Sum) or isinstance(expr, Product):
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)
[docs]def change_index(expr, var, trafo, newvar=None):
"""
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.
Usage
=====
``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.concrete.simplification import change_index
>>> 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 = change_index(S, 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 = change_index(S, x, -x, y)
>>> Sn
Sum(-y, (y, -b, -a))
>>> Sn.doit()
-a**2/2 + a/2 + b**2/2 + b/2
>>> Sn = change_index(S, 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 = change_index(S, 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 = change_index(P, i, i+3, k)
>>> P2
Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d))
>>> P3 = change_index(P2, 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 = change_index(S, 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.simplification.index,
sympy.concrete.simplification.reorder_limit,
sympy.concrete.simplification.reorder,
sympy.concrete.simplification.reverse_order
"""
if newvar is None:
newvar = var
limits = []
for limit in expr.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 = expr.function.subs(var, (var - beta)/alpha)
function = function.subs(var, newvar)
if isinstance(expr, Sum):
return Sum(function, *tuple(limits))
elif isinstance(expr, Product):
return Product(function, *tuple(limits))
else:
raise NotImplementedError(expr, "change_index only implemented for Sum and Product")
[docs]def reorder(expr, *arg):
"""
Reorder limits in a expression containing a Sum or a Product.
Usage
=====
``reorder(expr, *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.concrete.simplification import reorder
>>> from sympy import Sum, Product
>>> from sympy.abc import x, y, z, a, b, c, d, e, f
>>> reorder(Sum(x*y, (x, a, b), (y, c, d)), (x, y))
Sum(x*y, (y, c, d), (x, a, b))
>>> reorder(Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)), (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))
>>> reorder(P, (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:
>>> reorder(Sum(x**2, (x, a, b), (x, c, d)), (0, 1))
Sum(x**2, (x, c, d), (x, a, b))
And of course we can mix both schemes:
>>> reorder(Sum(x*y, (x, a, b), (y, c, d)), (y, x))
Sum(x*y, (y, c, d), (x, a, b))
>>> reorder(Sum(x*y, (x, a, b), (y, c, d)), (y, 0))
Sum(x*y, (y, c, d), (x, a, b))
See Also
========
sympy.concrete.simplification.index,
sympy.concrete.simplification.change_index,
sympy.concrete.simplification.reorder_limit,
sympy.concrete.simplification.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 = index(expr, r[0])
if not isinstance(r[1], int):
index2 = index(expr, r[1])
new_expr = reorder_limit(new_expr, index1, index2)
return new_expr
[docs]def reorder_limit(expr, x, y):
"""
Interchange two limit tuples of a Sum or Product expression.
Usage
=====
``reorder_limit(expr, 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.concrete.simplification import reorder_limit
>>> from sympy.abc import x, y, z, a, b, c, d, e, f
>>> from sympy import Sum, Product
>>> reorder_limit(Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)), 0, 2)
Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
>>> reorder_limit(Sum(x**2, (x, a, b), (x, c, d)), 1, 0)
Sum(x**2, (x, c, d), (x, a, b))
>>> reorder_limit(Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)), 0, 2)
Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
See Also
========
sympy.concrete.simplification.index,
sympy.concrete.simplification.change_index,
sympy.concrete.simplification.reorder,
sympy.concrete.simplification.reverse_order
"""
var = set([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)
if isinstance(expr, Sum):
return Sum(expr.function, *limits)
elif isinstance(expr, Product):
return Product(expr.function, *limits)
else:
raise NotImplementedError(expr, "reorder only implemented for Sum and Product")
else:
raise ReorderError(expr, "could not interchange the two limits specified")
[docs]def reverse_order(expr, *indices):
"""
Reverse the order of a limit in a Sum or Product.
Usage
=====
``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.concrete.simplification import reverse_order
>>> from sympy import Sum
>>> from sympy.abc import x, y, a, b, c, d
>>> reverse_order(Sum(x, (x, 0, 3)), x)
Sum(-x, (x, 4, -1))
>>> reverse_order(Sum(x*y, (x, 1, 5), (y, 0, 6)), x, y)
Sum(x*y, (x, 6, 0), (y, 7, -1))
>>> reverse_order(Sum(x, (x, a, b)), x)
Sum(-x, (x, b + 1, a - 1))
>>> reverse_order(Sum(x, (x, a, b)), 0)
Sum(-x, (x, b + 1, a - 1))
>>> from sympy import Product, simplify, RisingFactorial, gamma
>>> P = Product(x, (x, a, b))
>>> Pr = reverse_order(P, x)
>>> Pr
Product(1/x, (x, b + 1, a - 1))
>>> Pr = Pr.doit()
>>> Pr
1/RisingFactorial(b + 1, a - b - 1)
>>> simplify(Pr)
gamma(b + 1)/gamma(a)
>>> P = P.doit()
>>> P
RisingFactorial(a, -a + b + 1)
>>> simplify(P)
gamma(b + 1)/gamma(a)
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**2, (x, a, b), (x, c, d))
>>> S
Sum(x**2, (x, a, b), (x, c, d))
>>> S0 = reverse_order(S, 0)
>>> S0
Sum(-x**2, (x, b + 1, a - 1), (x, c, d))
>>> S1 = reverse_order(S0, 1)
>>> S1
Sum(x**2, (x, b + 1, a - 1), (x, d + 1, c - 1))
Of course we can mix both notations:
>>> reverse_order(Sum(x*y, (x, a, b), (y, 2, 5)), x, 1)
Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
>>> reverse_order(Sum(x*y, (x, a, b), (y, 2, 5)), y, x)
Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
See Also
========
sympy.concrete.simplification.index,
sympy.concrete.simplification.change_index,
sympy.concrete.simplification.reorder_limit,
sympy.concrete.simplification.reorder
References
==========
.. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
Volume 28 Issue 2, April 1981, Pages 305-350
http://dl.acm.org/citation.cfm?doid=322248.322255
"""
l_indices = list(indices)
for i, indx in enumerate(l_indices):
if not isinstance(indx, int):
l_indices[i] = index(expr, indx)
if isinstance(expr, Sum) or isinstance(expr, Product):
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)
if isinstance(expr, Sum):
return Sum(e * expr.function, *limits)
elif isinstance(expr, Product):
return Product(expr.function ** e, *limits)
else:
return expr