delta.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. """
  2. This module implements sums and products containing the Kronecker Delta function.
  3. References
  4. ==========
  5. .. [1] http://mathworld.wolfram.com/KroneckerDelta.html
  6. """
  7. from .products import product
  8. from .summations import Sum, summation
  9. from sympy.core import Add, Mul, S, Dummy
  10. from sympy.core.cache import cacheit
  11. from sympy.core.sorting import default_sort_key
  12. from sympy.functions import KroneckerDelta, Piecewise, piecewise_fold
  13. from sympy.polys.polytools import factor
  14. from sympy.sets.sets import Interval
  15. from sympy.solvers.solvers import solve
  16. @cacheit
  17. def _expand_delta(expr, index):
  18. """
  19. Expand the first Add containing a simple KroneckerDelta.
  20. """
  21. if not expr.is_Mul:
  22. return expr
  23. delta = None
  24. func = Add
  25. terms = [S.One]
  26. for h in expr.args:
  27. if delta is None and h.is_Add and _has_simple_delta(h, index):
  28. delta = True
  29. func = h.func
  30. terms = [terms[0]*t for t in h.args]
  31. else:
  32. terms = [t*h for t in terms]
  33. return func(*terms)
  34. @cacheit
  35. def _extract_delta(expr, index):
  36. """
  37. Extract a simple KroneckerDelta from the expression.
  38. Explanation
  39. ===========
  40. Returns the tuple ``(delta, newexpr)`` where:
  41. - ``delta`` is a simple KroneckerDelta expression if one was found,
  42. or ``None`` if no simple KroneckerDelta expression was found.
  43. - ``newexpr`` is a Mul containing the remaining terms; ``expr`` is
  44. returned unchanged if no simple KroneckerDelta expression was found.
  45. Examples
  46. ========
  47. >>> from sympy import KroneckerDelta
  48. >>> from sympy.concrete.delta import _extract_delta
  49. >>> from sympy.abc import x, y, i, j, k
  50. >>> _extract_delta(4*x*y*KroneckerDelta(i, j), i)
  51. (KroneckerDelta(i, j), 4*x*y)
  52. >>> _extract_delta(4*x*y*KroneckerDelta(i, j), k)
  53. (None, 4*x*y*KroneckerDelta(i, j))
  54. See Also
  55. ========
  56. sympy.functions.special.tensor_functions.KroneckerDelta
  57. deltaproduct
  58. deltasummation
  59. """
  60. if not _has_simple_delta(expr, index):
  61. return (None, expr)
  62. if isinstance(expr, KroneckerDelta):
  63. return (expr, S.One)
  64. if not expr.is_Mul:
  65. raise ValueError("Incorrect expr")
  66. delta = None
  67. terms = []
  68. for arg in expr.args:
  69. if delta is None and _is_simple_delta(arg, index):
  70. delta = arg
  71. else:
  72. terms.append(arg)
  73. return (delta, expr.func(*terms))
  74. @cacheit
  75. def _has_simple_delta(expr, index):
  76. """
  77. Returns True if ``expr`` is an expression that contains a KroneckerDelta
  78. that is simple in the index ``index``, meaning that this KroneckerDelta
  79. is nonzero for a single value of the index ``index``.
  80. """
  81. if expr.has(KroneckerDelta):
  82. if _is_simple_delta(expr, index):
  83. return True
  84. if expr.is_Add or expr.is_Mul:
  85. for arg in expr.args:
  86. if _has_simple_delta(arg, index):
  87. return True
  88. return False
  89. @cacheit
  90. def _is_simple_delta(delta, index):
  91. """
  92. Returns True if ``delta`` is a KroneckerDelta and is nonzero for a single
  93. value of the index ``index``.
  94. """
  95. if isinstance(delta, KroneckerDelta) and delta.has(index):
  96. p = (delta.args[0] - delta.args[1]).as_poly(index)
  97. if p:
  98. return p.degree() == 1
  99. return False
  100. @cacheit
  101. def _remove_multiple_delta(expr):
  102. """
  103. Evaluate products of KroneckerDelta's.
  104. """
  105. if expr.is_Add:
  106. return expr.func(*list(map(_remove_multiple_delta, expr.args)))
  107. if not expr.is_Mul:
  108. return expr
  109. eqs = []
  110. newargs = []
  111. for arg in expr.args:
  112. if isinstance(arg, KroneckerDelta):
  113. eqs.append(arg.args[0] - arg.args[1])
  114. else:
  115. newargs.append(arg)
  116. if not eqs:
  117. return expr
  118. solns = solve(eqs, dict=True)
  119. if len(solns) == 0:
  120. return S.Zero
  121. elif len(solns) == 1:
  122. for key in solns[0].keys():
  123. newargs.append(KroneckerDelta(key, solns[0][key]))
  124. expr2 = expr.func(*newargs)
  125. if expr != expr2:
  126. return _remove_multiple_delta(expr2)
  127. return expr
  128. @cacheit
  129. def _simplify_delta(expr):
  130. """
  131. Rewrite a KroneckerDelta's indices in its simplest form.
  132. """
  133. if isinstance(expr, KroneckerDelta):
  134. try:
  135. slns = solve(expr.args[0] - expr.args[1], dict=True)
  136. if slns and len(slns) == 1:
  137. return Mul(*[KroneckerDelta(*(key, value))
  138. for key, value in slns[0].items()])
  139. except NotImplementedError:
  140. pass
  141. return expr
  142. @cacheit
  143. def deltaproduct(f, limit):
  144. """
  145. Handle products containing a KroneckerDelta.
  146. See Also
  147. ========
  148. deltasummation
  149. sympy.functions.special.tensor_functions.KroneckerDelta
  150. sympy.concrete.products.product
  151. """
  152. if ((limit[2] - limit[1]) < 0) == True:
  153. return S.One
  154. if not f.has(KroneckerDelta):
  155. return product(f, limit)
  156. if f.is_Add:
  157. # Identify the term in the Add that has a simple KroneckerDelta
  158. delta = None
  159. terms = []
  160. for arg in sorted(f.args, key=default_sort_key):
  161. if delta is None and _has_simple_delta(arg, limit[0]):
  162. delta = arg
  163. else:
  164. terms.append(arg)
  165. newexpr = f.func(*terms)
  166. k = Dummy("kprime", integer=True)
  167. if isinstance(limit[1], int) and isinstance(limit[2], int):
  168. result = deltaproduct(newexpr, limit) + sum([
  169. deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) *
  170. delta.subs(limit[0], ik) *
  171. deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1))]
  172. )
  173. else:
  174. result = deltaproduct(newexpr, limit) + deltasummation(
  175. deltaproduct(newexpr, (limit[0], limit[1], k - 1)) *
  176. delta.subs(limit[0], k) *
  177. deltaproduct(newexpr, (limit[0], k + 1, limit[2])),
  178. (k, limit[1], limit[2]),
  179. no_piecewise=_has_simple_delta(newexpr, limit[0])
  180. )
  181. return _remove_multiple_delta(result)
  182. delta, _ = _extract_delta(f, limit[0])
  183. if not delta:
  184. g = _expand_delta(f, limit[0])
  185. if f != g:
  186. try:
  187. return factor(deltaproduct(g, limit))
  188. except AssertionError:
  189. return deltaproduct(g, limit)
  190. return product(f, limit)
  191. return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \
  192. S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1))
  193. @cacheit
  194. def deltasummation(f, limit, no_piecewise=False):
  195. """
  196. Handle summations containing a KroneckerDelta.
  197. Explanation
  198. ===========
  199. The idea for summation is the following:
  200. - If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j),
  201. we try to simplify it.
  202. If we could simplify it, then we sum the resulting expression.
  203. We already know we can sum a simplified expression, because only
  204. simple KroneckerDelta expressions are involved.
  205. If we couldn't simplify it, there are two cases:
  206. 1) The expression is a simple expression: we return the summation,
  207. taking care if we are dealing with a Derivative or with a proper
  208. KroneckerDelta.
  209. 2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do
  210. nothing at all.
  211. - If the expr is a multiplication expr having a KroneckerDelta term:
  212. First we expand it.
  213. If the expansion did work, then we try to sum the expansion.
  214. If not, we try to extract a simple KroneckerDelta term, then we have two
  215. cases:
  216. 1) We have a simple KroneckerDelta term, so we return the summation.
  217. 2) We didn't have a simple term, but we do have an expression with
  218. simplified KroneckerDelta terms, so we sum this expression.
  219. Examples
  220. ========
  221. >>> from sympy import oo, symbols
  222. >>> from sympy.abc import k
  223. >>> i, j = symbols('i, j', integer=True, finite=True)
  224. >>> from sympy.concrete.delta import deltasummation
  225. >>> from sympy import KroneckerDelta
  226. >>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo))
  227. 1
  228. >>> deltasummation(KroneckerDelta(i, k), (k, 0, oo))
  229. Piecewise((1, i >= 0), (0, True))
  230. >>> deltasummation(KroneckerDelta(i, k), (k, 1, 3))
  231. Piecewise((1, (i >= 1) & (i <= 3)), (0, True))
  232. >>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo))
  233. j*KroneckerDelta(i, j)
  234. >>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo))
  235. i
  236. >>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo))
  237. j
  238. See Also
  239. ========
  240. deltaproduct
  241. sympy.functions.special.tensor_functions.KroneckerDelta
  242. sympy.concrete.sums.summation
  243. """
  244. if ((limit[2] - limit[1]) < 0) == True:
  245. return S.Zero
  246. if not f.has(KroneckerDelta):
  247. return summation(f, limit)
  248. x = limit[0]
  249. g = _expand_delta(f, x)
  250. if g.is_Add:
  251. return piecewise_fold(
  252. g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args]))
  253. # try to extract a simple KroneckerDelta term
  254. delta, expr = _extract_delta(g, x)
  255. if (delta is not None) and (delta.delta_range is not None):
  256. dinf, dsup = delta.delta_range
  257. if (limit[1] - dinf <= 0) == True and (limit[2] - dsup >= 0) == True:
  258. no_piecewise = True
  259. if not delta:
  260. return summation(f, limit)
  261. solns = solve(delta.args[0] - delta.args[1], x)
  262. if len(solns) == 0:
  263. return S.Zero
  264. elif len(solns) != 1:
  265. return Sum(f, limit)
  266. value = solns[0]
  267. if no_piecewise:
  268. return expr.subs(x, value)
  269. return Piecewise(
  270. (expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)),
  271. (S.Zero, True)
  272. )