expr_with_intlimits.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. from sympy.concrete.expr_with_limits import ExprWithLimits
  2. from sympy.core.singleton import S
  3. from sympy.core.relational import Eq
  4. class ReorderError(NotImplementedError):
  5. """
  6. Exception raised when trying to reorder dependent limits.
  7. """
  8. def __init__(self, expr, msg):
  9. super().__init__(
  10. "%s could not be reordered: %s." % (expr, msg))
  11. class ExprWithIntLimits(ExprWithLimits):
  12. """
  13. Superclass for Product and Sum.
  14. See Also
  15. ========
  16. sympy.concrete.expr_with_limits.ExprWithLimits
  17. sympy.concrete.products.Product
  18. sympy.concrete.summations.Sum
  19. """
  20. __slots__ = ()
  21. def change_index(self, var, trafo, newvar=None):
  22. r"""
  23. Change index of a Sum or Product.
  24. Perform a linear transformation `x \mapsto a x + b` on the index variable
  25. `x`. For `a` the only values allowed are `\pm 1`. A new variable to be used
  26. after the change of index can also be specified.
  27. Explanation
  28. ===========
  29. ``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the
  30. index variable `x` to transform. The transformation ``trafo`` must be linear
  31. and given in terms of ``var``. If the optional argument ``newvar`` is
  32. provided then ``var`` gets replaced by ``newvar`` in the final expression.
  33. Examples
  34. ========
  35. >>> from sympy import Sum, Product, simplify
  36. >>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l
  37. >>> S = Sum(x, (x, a, b))
  38. >>> S.doit()
  39. -a**2/2 + a/2 + b**2/2 + b/2
  40. >>> Sn = S.change_index(x, x + 1, y)
  41. >>> Sn
  42. Sum(y - 1, (y, a + 1, b + 1))
  43. >>> Sn.doit()
  44. -a**2/2 + a/2 + b**2/2 + b/2
  45. >>> Sn = S.change_index(x, -x, y)
  46. >>> Sn
  47. Sum(-y, (y, -b, -a))
  48. >>> Sn.doit()
  49. -a**2/2 + a/2 + b**2/2 + b/2
  50. >>> Sn = S.change_index(x, x+u)
  51. >>> Sn
  52. Sum(-u + x, (x, a + u, b + u))
  53. >>> Sn.doit()
  54. -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
  55. >>> simplify(Sn.doit())
  56. -a**2/2 + a/2 + b**2/2 + b/2
  57. >>> Sn = S.change_index(x, -x - u, y)
  58. >>> Sn
  59. Sum(-u - y, (y, -b - u, -a - u))
  60. >>> Sn.doit()
  61. -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
  62. >>> simplify(Sn.doit())
  63. -a**2/2 + a/2 + b**2/2 + b/2
  64. >>> P = Product(i*j**2, (i, a, b), (j, c, d))
  65. >>> P
  66. Product(i*j**2, (i, a, b), (j, c, d))
  67. >>> P2 = P.change_index(i, i+3, k)
  68. >>> P2
  69. Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d))
  70. >>> P3 = P2.change_index(j, -j, l)
  71. >>> P3
  72. Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c))
  73. When dealing with symbols only, we can make a
  74. general linear transformation:
  75. >>> Sn = S.change_index(x, u*x+v, y)
  76. >>> Sn
  77. Sum((-v + y)/u, (y, b*u + v, a*u + v))
  78. >>> Sn.doit()
  79. -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
  80. >>> simplify(Sn.doit())
  81. a**2*u/2 + a/2 - b**2*u/2 + b/2
  82. However, the last result can be inconsistent with usual
  83. summation where the index increment is always 1. This is
  84. obvious as we get back the original value only for ``u``
  85. equal +1 or -1.
  86. See Also
  87. ========
  88. sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
  89. reorder_limit,
  90. sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder,
  91. sympy.concrete.summations.Sum.reverse_order,
  92. sympy.concrete.products.Product.reverse_order
  93. """
  94. if newvar is None:
  95. newvar = var
  96. limits = []
  97. for limit in self.limits:
  98. if limit[0] == var:
  99. p = trafo.as_poly(var)
  100. if p.degree() != 1:
  101. raise ValueError("Index transformation is not linear")
  102. alpha = p.coeff_monomial(var)
  103. beta = p.coeff_monomial(S.One)
  104. if alpha.is_number:
  105. if alpha == S.One:
  106. limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta))
  107. elif alpha == S.NegativeOne:
  108. limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
  109. else:
  110. raise ValueError("Linear transformation results in non-linear summation stepsize")
  111. else:
  112. # Note that the case of alpha being symbolic can give issues if alpha < 0.
  113. limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
  114. else:
  115. limits.append(limit)
  116. function = self.function.subs(var, (var - beta)/alpha)
  117. function = function.subs(var, newvar)
  118. return self.func(function, *limits)
  119. def index(expr, x):
  120. """
  121. Return the index of a dummy variable in the list of limits.
  122. Explanation
  123. ===========
  124. ``index(expr, x)`` returns the index of the dummy variable ``x`` in the
  125. limits of ``expr``. Note that we start counting with 0 at the inner-most
  126. limits tuple.
  127. Examples
  128. ========
  129. >>> from sympy.abc import x, y, a, b, c, d
  130. >>> from sympy import Sum, Product
  131. >>> Sum(x*y, (x, a, b), (y, c, d)).index(x)
  132. 0
  133. >>> Sum(x*y, (x, a, b), (y, c, d)).index(y)
  134. 1
  135. >>> Product(x*y, (x, a, b), (y, c, d)).index(x)
  136. 0
  137. >>> Product(x*y, (x, a, b), (y, c, d)).index(y)
  138. 1
  139. See Also
  140. ========
  141. reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order,
  142. sympy.concrete.products.Product.reverse_order
  143. """
  144. variables = [limit[0] for limit in expr.limits]
  145. if variables.count(x) != 1:
  146. raise ValueError(expr, "Number of instances of variable not equal to one")
  147. else:
  148. return variables.index(x)
  149. def reorder(expr, *arg):
  150. """
  151. Reorder limits in a expression containing a Sum or a Product.
  152. Explanation
  153. ===========
  154. ``expr.reorder(*arg)`` reorders the limits in the expression ``expr``
  155. according to the list of tuples given by ``arg``. These tuples can
  156. contain numerical indices or index variable names or involve both.
  157. Examples
  158. ========
  159. >>> from sympy import Sum, Product
  160. >>> from sympy.abc import x, y, z, a, b, c, d, e, f
  161. >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y))
  162. Sum(x*y, (y, c, d), (x, a, b))
  163. >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z))
  164. Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
  165. >>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f))
  166. >>> P.reorder((x, y), (x, z), (y, z))
  167. Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
  168. We can also select the index variables by counting them, starting
  169. with the inner-most one:
  170. >>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1))
  171. Sum(x**2, (x, c, d), (x, a, b))
  172. And of course we can mix both schemes:
  173. >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x))
  174. Sum(x*y, (y, c, d), (x, a, b))
  175. >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0))
  176. Sum(x*y, (y, c, d), (x, a, b))
  177. See Also
  178. ========
  179. reorder_limit, index, sympy.concrete.summations.Sum.reverse_order,
  180. sympy.concrete.products.Product.reverse_order
  181. """
  182. new_expr = expr
  183. for r in arg:
  184. if len(r) != 2:
  185. raise ValueError(r, "Invalid number of arguments")
  186. index1 = r[0]
  187. index2 = r[1]
  188. if not isinstance(r[0], int):
  189. index1 = expr.index(r[0])
  190. if not isinstance(r[1], int):
  191. index2 = expr.index(r[1])
  192. new_expr = new_expr.reorder_limit(index1, index2)
  193. return new_expr
  194. def reorder_limit(expr, x, y):
  195. """
  196. Interchange two limit tuples of a Sum or Product expression.
  197. Explanation
  198. ===========
  199. ``expr.reorder_limit(x, y)`` interchanges two limit tuples. The
  200. arguments ``x`` and ``y`` are integers corresponding to the index
  201. variables of the two limits which are to be interchanged. The
  202. expression ``expr`` has to be either a Sum or a Product.
  203. Examples
  204. ========
  205. >>> from sympy.abc import x, y, z, a, b, c, d, e, f
  206. >>> from sympy import Sum, Product
  207. >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
  208. Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
  209. >>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0)
  210. Sum(x**2, (x, c, d), (x, a, b))
  211. >>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
  212. Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
  213. See Also
  214. ========
  215. index, reorder, sympy.concrete.summations.Sum.reverse_order,
  216. sympy.concrete.products.Product.reverse_order
  217. """
  218. var = {limit[0] for limit in expr.limits}
  219. limit_x = expr.limits[x]
  220. limit_y = expr.limits[y]
  221. if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and
  222. len(set(limit_x[2].free_symbols).intersection(var)) == 0 and
  223. len(set(limit_y[1].free_symbols).intersection(var)) == 0 and
  224. len(set(limit_y[2].free_symbols).intersection(var)) == 0):
  225. limits = []
  226. for i, limit in enumerate(expr.limits):
  227. if i == x:
  228. limits.append(limit_y)
  229. elif i == y:
  230. limits.append(limit_x)
  231. else:
  232. limits.append(limit)
  233. return type(expr)(expr.function, *limits)
  234. else:
  235. raise ReorderError(expr, "could not interchange the two limits specified")
  236. @property
  237. def has_empty_sequence(self):
  238. """
  239. Returns True if the Sum or Product is computed for an empty sequence.
  240. Examples
  241. ========
  242. >>> from sympy import Sum, Product, Symbol
  243. >>> m = Symbol('m')
  244. >>> Sum(m, (m, 1, 0)).has_empty_sequence
  245. True
  246. >>> Sum(m, (m, 1, 1)).has_empty_sequence
  247. False
  248. >>> M = Symbol('M', integer=True, positive=True)
  249. >>> Product(m, (m, 1, M)).has_empty_sequence
  250. False
  251. >>> Product(m, (m, 2, M)).has_empty_sequence
  252. >>> Product(m, (m, M + 1, M)).has_empty_sequence
  253. True
  254. >>> N = Symbol('N', integer=True, positive=True)
  255. >>> Sum(m, (m, N, M)).has_empty_sequence
  256. >>> N = Symbol('N', integer=True, negative=True)
  257. >>> Sum(m, (m, N, M)).has_empty_sequence
  258. False
  259. See Also
  260. ========
  261. has_reversed_limits
  262. has_finite_limits
  263. """
  264. ret_None = False
  265. for lim in self.limits:
  266. dif = lim[1] - lim[2]
  267. eq = Eq(dif, 1)
  268. if eq == True:
  269. return True
  270. elif eq == False:
  271. continue
  272. else:
  273. ret_None = True
  274. if ret_None:
  275. return None
  276. return False