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