products.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. from __future__ import annotations
  2. from .expr_with_intlimits import ExprWithIntLimits
  3. from .summations import Sum, summation, _dummy_with_inherited_properties_concrete
  4. from sympy.core.expr import Expr
  5. from sympy.core.exprtools import factor_terms
  6. from sympy.core.function import Derivative
  7. from sympy.core.mul import Mul
  8. from sympy.core.singleton import S
  9. from sympy.core.symbol import Dummy, Symbol
  10. from sympy.functions.combinatorial.factorials import RisingFactorial
  11. from sympy.functions.elementary.exponential import exp, log
  12. from sympy.functions.special.tensor_functions import KroneckerDelta
  13. from sympy.polys import quo, roots
  14. class Product(ExprWithIntLimits):
  15. r"""
  16. Represents unevaluated products.
  17. Explanation
  18. ===========
  19. ``Product`` represents a finite or infinite product, with the first
  20. argument being the general form of terms in the series, and the second
  21. argument being ``(dummy_variable, start, end)``, with ``dummy_variable``
  22. taking all integer values from ``start`` through ``end``. In accordance
  23. with long-standing mathematical convention, the end term is included in
  24. the product.
  25. Finite products
  26. ===============
  27. For finite products (and products with symbolic limits assumed to be finite)
  28. we follow the analogue of the summation convention described by Karr [1],
  29. especially definition 3 of section 1.4. The product:
  30. .. math::
  31. \prod_{m \leq i < n} f(i)
  32. has *the obvious meaning* for `m < n`, namely:
  33. .. math::
  34. \prod_{m \leq i < n} f(i) = f(m) f(m+1) \cdot \ldots \cdot f(n-2) f(n-1)
  35. with the upper limit value `f(n)` excluded. The product over an empty set is
  36. one if and only if `m = n`:
  37. .. math::
  38. \prod_{m \leq i < n} f(i) = 1 \quad \mathrm{for} \quad m = n
  39. Finally, for all other products over empty sets we assume the following
  40. definition:
  41. .. math::
  42. \prod_{m \leq i < n} f(i) = \frac{1}{\prod_{n \leq i < m} f(i)} \quad \mathrm{for} \quad m > n
  43. It is important to note that above we define all products with the upper
  44. limit being exclusive. This is in contrast to the usual mathematical notation,
  45. but does not affect the product convention. Indeed we have:
  46. .. math::
  47. \prod_{m \leq i < n} f(i) = \prod_{i = m}^{n - 1} f(i)
  48. where the difference in notation is intentional to emphasize the meaning,
  49. with limits typeset on the top being inclusive.
  50. Examples
  51. ========
  52. >>> from sympy.abc import a, b, i, k, m, n, x
  53. >>> from sympy import Product, oo
  54. >>> Product(k, (k, 1, m))
  55. Product(k, (k, 1, m))
  56. >>> Product(k, (k, 1, m)).doit()
  57. factorial(m)
  58. >>> Product(k**2,(k, 1, m))
  59. Product(k**2, (k, 1, m))
  60. >>> Product(k**2,(k, 1, m)).doit()
  61. factorial(m)**2
  62. Wallis' product for pi:
  63. >>> W = Product(2*i/(2*i-1) * 2*i/(2*i+1), (i, 1, oo))
  64. >>> W
  65. Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
  66. Direct computation currently fails:
  67. >>> W.doit()
  68. Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
  69. But we can approach the infinite product by a limit of finite products:
  70. >>> from sympy import limit
  71. >>> W2 = Product(2*i/(2*i-1)*2*i/(2*i+1), (i, 1, n))
  72. >>> W2
  73. Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, n))
  74. >>> W2e = W2.doit()
  75. >>> W2e
  76. 4**n*factorial(n)**2/(2**(2*n)*RisingFactorial(1/2, n)*RisingFactorial(3/2, n))
  77. >>> limit(W2e, n, oo)
  78. pi/2
  79. By the same formula we can compute sin(pi/2):
  80. >>> from sympy import combsimp, pi, gamma, simplify
  81. >>> P = pi * x * Product(1 - x**2/k**2, (k, 1, n))
  82. >>> P = P.subs(x, pi/2)
  83. >>> P
  84. pi**2*Product(1 - pi**2/(4*k**2), (k, 1, n))/2
  85. >>> Pe = P.doit()
  86. >>> Pe
  87. pi**2*RisingFactorial(1 - pi/2, n)*RisingFactorial(1 + pi/2, n)/(2*factorial(n)**2)
  88. >>> limit(Pe, n, oo).gammasimp()
  89. sin(pi**2/2)
  90. >>> Pe.rewrite(gamma)
  91. (-1)**n*pi**2*gamma(pi/2)*gamma(n + 1 + pi/2)/(2*gamma(1 + pi/2)*gamma(-n + pi/2)*gamma(n + 1)**2)
  92. Products with the lower limit being larger than the upper one:
  93. >>> Product(1/i, (i, 6, 1)).doit()
  94. 120
  95. >>> Product(i, (i, 2, 5)).doit()
  96. 120
  97. The empty product:
  98. >>> Product(i, (i, n, n-1)).doit()
  99. 1
  100. An example showing that the symbolic result of a product is still
  101. valid for seemingly nonsensical values of the limits. Then the Karr
  102. convention allows us to give a perfectly valid interpretation to
  103. those products by interchanging the limits according to the above rules:
  104. >>> P = Product(2, (i, 10, n)).doit()
  105. >>> P
  106. 2**(n - 9)
  107. >>> P.subs(n, 5)
  108. 1/16
  109. >>> Product(2, (i, 10, 5)).doit()
  110. 1/16
  111. >>> 1/Product(2, (i, 6, 9)).doit()
  112. 1/16
  113. An explicit example of the Karr summation convention applied to products:
  114. >>> P1 = Product(x, (i, a, b)).doit()
  115. >>> P1
  116. x**(-a + b + 1)
  117. >>> P2 = Product(x, (i, b+1, a-1)).doit()
  118. >>> P2
  119. x**(a - b - 1)
  120. >>> simplify(P1 * P2)
  121. 1
  122. And another one:
  123. >>> P1 = Product(i, (i, b, a)).doit()
  124. >>> P1
  125. RisingFactorial(b, a - b + 1)
  126. >>> P2 = Product(i, (i, a+1, b-1)).doit()
  127. >>> P2
  128. RisingFactorial(a + 1, -a + b - 1)
  129. >>> P1 * P2
  130. RisingFactorial(b, a - b + 1)*RisingFactorial(a + 1, -a + b - 1)
  131. >>> combsimp(P1 * P2)
  132. 1
  133. See Also
  134. ========
  135. Sum, summation
  136. product
  137. References
  138. ==========
  139. .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
  140. Volume 28 Issue 2, April 1981, Pages 305-350
  141. https://dl.acm.org/doi/10.1145/322248.322255
  142. .. [2] https://en.wikipedia.org/wiki/Multiplication#Capital_Pi_notation
  143. .. [3] https://en.wikipedia.org/wiki/Empty_product
  144. """
  145. __slots__ = ()
  146. limits: tuple[tuple[Symbol, Expr, Expr]]
  147. def __new__(cls, function, *symbols, **assumptions):
  148. obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions)
  149. return obj
  150. def _eval_rewrite_as_Sum(self, *args, **kwargs):
  151. return exp(Sum(log(self.function), *self.limits))
  152. @property
  153. def term(self):
  154. return self._args[0]
  155. function = term
  156. def _eval_is_zero(self):
  157. if self.has_empty_sequence:
  158. return False
  159. z = self.term.is_zero
  160. if z is True:
  161. return True
  162. if self.has_finite_limits:
  163. # A Product is zero only if its term is zero assuming finite limits.
  164. return z
  165. def _eval_is_extended_real(self):
  166. if self.has_empty_sequence:
  167. return True
  168. return self.function.is_extended_real
  169. def _eval_is_positive(self):
  170. if self.has_empty_sequence:
  171. return True
  172. if self.function.is_positive and self.has_finite_limits:
  173. return True
  174. def _eval_is_nonnegative(self):
  175. if self.has_empty_sequence:
  176. return True
  177. if self.function.is_nonnegative and self.has_finite_limits:
  178. return True
  179. def _eval_is_extended_nonnegative(self):
  180. if self.has_empty_sequence:
  181. return True
  182. if self.function.is_extended_nonnegative:
  183. return True
  184. def _eval_is_extended_nonpositive(self):
  185. if self.has_empty_sequence:
  186. return True
  187. def _eval_is_finite(self):
  188. if self.has_finite_limits and self.function.is_finite:
  189. return True
  190. def doit(self, **hints):
  191. # first make sure any definite limits have product
  192. # variables with matching assumptions
  193. reps = {}
  194. for xab in self.limits:
  195. d = _dummy_with_inherited_properties_concrete(xab)
  196. if d:
  197. reps[xab[0]] = d
  198. if reps:
  199. undo = {v: k for k, v in reps.items()}
  200. did = self.xreplace(reps).doit(**hints)
  201. if isinstance(did, tuple): # when separate=True
  202. did = tuple([i.xreplace(undo) for i in did])
  203. else:
  204. did = did.xreplace(undo)
  205. return did
  206. from sympy.simplify.powsimp import powsimp
  207. f = self.function
  208. for index, limit in enumerate(self.limits):
  209. i, a, b = limit
  210. dif = b - a
  211. if dif.is_integer and dif.is_negative:
  212. a, b = b + 1, a - 1
  213. f = 1 / f
  214. g = self._eval_product(f, (i, a, b))
  215. if g in (None, S.NaN):
  216. return self.func(powsimp(f), *self.limits[index:])
  217. else:
  218. f = g
  219. if hints.get('deep', True):
  220. return f.doit(**hints)
  221. else:
  222. return powsimp(f)
  223. def _eval_conjugate(self):
  224. return self.func(self.function.conjugate(), *self.limits)
  225. def _eval_product(self, term, limits):
  226. (k, a, n) = limits
  227. if k not in term.free_symbols:
  228. if (term - 1).is_zero:
  229. return S.One
  230. return term**(n - a + 1)
  231. if a == n:
  232. return term.subs(k, a)
  233. from .delta import deltaproduct, _has_simple_delta
  234. if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]):
  235. return deltaproduct(term, limits)
  236. dif = n - a
  237. definite = dif.is_Integer
  238. if definite and (dif < 100):
  239. return self._eval_product_direct(term, limits)
  240. elif term.is_polynomial(k):
  241. poly = term.as_poly(k)
  242. A = B = Q = S.One
  243. all_roots = roots(poly)
  244. M = 0
  245. for r, m in all_roots.items():
  246. M += m
  247. A *= RisingFactorial(a - r, n - a + 1)**m
  248. Q *= (n - r)**m
  249. if M < poly.degree():
  250. arg = quo(poly, Q.as_poly(k))
  251. B = self.func(arg, (k, a, n)).doit()
  252. return poly.LC()**(n - a + 1) * A * B
  253. elif term.is_Add:
  254. factored = factor_terms(term, fraction=True)
  255. if factored.is_Mul:
  256. return self._eval_product(factored, (k, a, n))
  257. elif term.is_Mul:
  258. # Factor in part without the summation variable and part with
  259. without_k, with_k = term.as_coeff_mul(k)
  260. if len(with_k) >= 2:
  261. # More than one term including k, so still a multiplication
  262. exclude, include = [], []
  263. for t in with_k:
  264. p = self._eval_product(t, (k, a, n))
  265. if p is not None:
  266. exclude.append(p)
  267. else:
  268. include.append(t)
  269. if not exclude:
  270. return None
  271. else:
  272. arg = term._new_rawargs(*include)
  273. A = Mul(*exclude)
  274. B = self.func(arg, (k, a, n)).doit()
  275. return without_k**(n - a + 1)*A * B
  276. else:
  277. # Just a single term
  278. p = self._eval_product(with_k[0], (k, a, n))
  279. if p is None:
  280. p = self.func(with_k[0], (k, a, n)).doit()
  281. return without_k**(n - a + 1)*p
  282. elif term.is_Pow:
  283. if not term.base.has(k):
  284. s = summation(term.exp, (k, a, n))
  285. return term.base**s
  286. elif not term.exp.has(k):
  287. p = self._eval_product(term.base, (k, a, n))
  288. if p is not None:
  289. return p**term.exp
  290. elif isinstance(term, Product):
  291. evaluated = term.doit()
  292. f = self._eval_product(evaluated, limits)
  293. if f is None:
  294. return self.func(evaluated, limits)
  295. else:
  296. return f
  297. if definite:
  298. return self._eval_product_direct(term, limits)
  299. def _eval_simplify(self, **kwargs):
  300. from sympy.simplify.simplify import product_simplify
  301. rv = product_simplify(self, **kwargs)
  302. return rv.doit() if kwargs['doit'] else rv
  303. def _eval_transpose(self):
  304. if self.is_commutative:
  305. return self.func(self.function.transpose(), *self.limits)
  306. return None
  307. def _eval_product_direct(self, term, limits):
  308. (k, a, n) = limits
  309. return Mul(*[term.subs(k, a + i) for i in range(n - a + 1)])
  310. def _eval_derivative(self, x):
  311. if isinstance(x, Symbol) and x not in self.free_symbols:
  312. return S.Zero
  313. f, limits = self.function, list(self.limits)
  314. limit = limits.pop(-1)
  315. if limits:
  316. f = self.func(f, *limits)
  317. i, a, b = limit
  318. if x in a.free_symbols or x in b.free_symbols:
  319. return None
  320. h = Dummy()
  321. rv = Sum( Product(f, (i, a, h - 1)) * Product(f, (i, h + 1, b)) * Derivative(f, x, evaluate=True).subs(i, h), (h, a, b))
  322. return rv
  323. def is_convergent(self):
  324. r"""
  325. See docs of :obj:`.Sum.is_convergent()` for explanation of convergence
  326. in SymPy.
  327. Explanation
  328. ===========
  329. The infinite product:
  330. .. math::
  331. \prod_{1 \leq i < \infty} f(i)
  332. is defined by the sequence of partial products:
  333. .. math::
  334. \prod_{i=1}^{n} f(i) = f(1) f(2) \cdots f(n)
  335. as n increases without bound. The product converges to a non-zero
  336. value if and only if the sum:
  337. .. math::
  338. \sum_{1 \leq i < \infty} \log{f(n)}
  339. converges.
  340. Examples
  341. ========
  342. >>> from sympy import Product, Symbol, cos, pi, exp, oo
  343. >>> n = Symbol('n', integer=True)
  344. >>> Product(n/(n + 1), (n, 1, oo)).is_convergent()
  345. False
  346. >>> Product(1/n**2, (n, 1, oo)).is_convergent()
  347. False
  348. >>> Product(cos(pi/n), (n, 1, oo)).is_convergent()
  349. True
  350. >>> Product(exp(-n**2), (n, 1, oo)).is_convergent()
  351. False
  352. References
  353. ==========
  354. .. [1] https://en.wikipedia.org/wiki/Infinite_product
  355. """
  356. sequence_term = self.function
  357. log_sum = log(sequence_term)
  358. lim = self.limits
  359. try:
  360. is_conv = Sum(log_sum, *lim).is_convergent()
  361. except NotImplementedError:
  362. if Sum(sequence_term - 1, *lim).is_absolutely_convergent() is S.true:
  363. return S.true
  364. raise NotImplementedError("The algorithm to find the product convergence of %s "
  365. "is not yet implemented" % (sequence_term))
  366. return is_conv
  367. def reverse_order(expr, *indices):
  368. """
  369. Reverse the order of a limit in a Product.
  370. Explanation
  371. ===========
  372. ``reverse_order(expr, *indices)`` reverses some limits in the expression
  373. ``expr`` which can be either a ``Sum`` or a ``Product``. The selectors in
  374. the argument ``indices`` specify some indices whose limits get reversed.
  375. These selectors are either variable names or numerical indices counted
  376. starting from the inner-most limit tuple.
  377. Examples
  378. ========
  379. >>> from sympy import gamma, Product, simplify, Sum
  380. >>> from sympy.abc import x, y, a, b, c, d
  381. >>> P = Product(x, (x, a, b))
  382. >>> Pr = P.reverse_order(x)
  383. >>> Pr
  384. Product(1/x, (x, b + 1, a - 1))
  385. >>> Pr = Pr.doit()
  386. >>> Pr
  387. 1/RisingFactorial(b + 1, a - b - 1)
  388. >>> simplify(Pr.rewrite(gamma))
  389. Piecewise((gamma(b + 1)/gamma(a), b > -1), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
  390. >>> P = P.doit()
  391. >>> P
  392. RisingFactorial(a, -a + b + 1)
  393. >>> simplify(P.rewrite(gamma))
  394. Piecewise((gamma(b + 1)/gamma(a), a > 0), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
  395. While one should prefer variable names when specifying which limits
  396. to reverse, the index counting notation comes in handy in case there
  397. are several symbols with the same name.
  398. >>> S = Sum(x*y, (x, a, b), (y, c, d))
  399. >>> S
  400. Sum(x*y, (x, a, b), (y, c, d))
  401. >>> S0 = S.reverse_order(0)
  402. >>> S0
  403. Sum(-x*y, (x, b + 1, a - 1), (y, c, d))
  404. >>> S1 = S0.reverse_order(1)
  405. >>> S1
  406. Sum(x*y, (x, b + 1, a - 1), (y, d + 1, c - 1))
  407. Of course we can mix both notations:
  408. >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1)
  409. Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
  410. >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x)
  411. Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
  412. See Also
  413. ========
  414. sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
  415. reorder_limit,
  416. sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder
  417. References
  418. ==========
  419. .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
  420. Volume 28 Issue 2, April 1981, Pages 305-350
  421. https://dl.acm.org/doi/10.1145/322248.322255
  422. """
  423. l_indices = list(indices)
  424. for i, indx in enumerate(l_indices):
  425. if not isinstance(indx, int):
  426. l_indices[i] = expr.index(indx)
  427. e = 1
  428. limits = []
  429. for i, limit in enumerate(expr.limits):
  430. l = limit
  431. if i in l_indices:
  432. e = -e
  433. l = (limit[0], limit[2] + 1, limit[1] - 1)
  434. limits.append(l)
  435. return Product(expr.function ** e, *limits)
  436. def product(*args, **kwargs):
  437. r"""
  438. Compute the product.
  439. Explanation
  440. ===========
  441. The notation for symbols is similar to the notation used in Sum or
  442. Integral. product(f, (i, a, b)) computes the product of f with
  443. respect to i from a to b, i.e.,
  444. ::
  445. b
  446. _____
  447. product(f(n), (i, a, b)) = | | f(n)
  448. | |
  449. i = a
  450. If it cannot compute the product, it returns an unevaluated Product object.
  451. Repeated products can be computed by introducing additional symbols tuples::
  452. Examples
  453. ========
  454. >>> from sympy import product, symbols
  455. >>> i, n, m, k = symbols('i n m k', integer=True)
  456. >>> product(i, (i, 1, k))
  457. factorial(k)
  458. >>> product(m, (i, 1, k))
  459. m**k
  460. >>> product(i, (i, 1, k), (k, 1, n))
  461. Product(factorial(k), (k, 1, n))
  462. """
  463. prod = Product(*args, **kwargs)
  464. if isinstance(prod, Product):
  465. return prod.doit(deep=False)
  466. else:
  467. return prod