Spaces:
Sleeping
Sleeping
""" | |
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 | |
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) | |
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)) | |
def _has_simple_delta(expr, index): | |
""" | |
Returns True if ``expr`` is an expression that contains a KroneckerDelta | |
that is simple in the index ``index``, meaning that this KroneckerDelta | |
is nonzero for a single value of the index ``index``. | |
""" | |
if expr.has(KroneckerDelta): | |
if _is_simple_delta(expr, index): | |
return True | |
if expr.is_Add or expr.is_Mul: | |
for arg in expr.args: | |
if _has_simple_delta(arg, index): | |
return True | |
return False | |
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 | |
def _remove_multiple_delta(expr): | |
""" | |
Evaluate products of KroneckerDelta's. | |
""" | |
if expr.is_Add: | |
return expr.func(*list(map(_remove_multiple_delta, expr.args))) | |
if not expr.is_Mul: | |
return expr | |
eqs = [] | |
newargs = [] | |
for arg in expr.args: | |
if isinstance(arg, KroneckerDelta): | |
eqs.append(arg.args[0] - arg.args[1]) | |
else: | |
newargs.append(arg) | |
if not eqs: | |
return expr | |
solns = solve(eqs, dict=True) | |
if len(solns) == 0: | |
return S.Zero | |
elif len(solns) == 1: | |
for key in solns[0].keys(): | |
newargs.append(KroneckerDelta(key, solns[0][key])) | |
expr2 = expr.func(*newargs) | |
if expr != expr2: | |
return _remove_multiple_delta(expr2) | |
return expr | |
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 | |
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)) | |
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) | |
) | |