integrals.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. from .utils import find_variables, replace_variable, find_variable
  2. from ..node import ExpressionLeaf as L, OP_INT, OP_INT_INDEF, OP_MUL, OP_DIV, \
  3. OP_LOG, OP_SIN, OP_COS, Scope, sin, cos, ln, integral, indef, absolute
  4. from ..possibilities import Possibility as P, MESSAGES
  5. from ..translate import _
  6. def choose_constant(integral):
  7. """
  8. Choose a constant to be added to the antiderivative.
  9. """
  10. # TODO: comments
  11. occupied = find_variables(integral)
  12. c = 'c'
  13. i = 96
  14. while c in occupied:
  15. i += 2 if i == 98 else 1
  16. c = chr(i)
  17. return L(c)
  18. def solve_integral(integral, F):
  19. """
  20. Solve an integral given its anti-derivative F:
  21. - First, finish the anti-derivative by adding a constant.
  22. - If no bounds are specified, return the anti-derivative.
  23. - Given a lower bound a and upper bound b, the solution is the indefinite
  24. integral [F(x)]_a^b. If F(x) contains multiple variables so that the 'x'
  25. is not identified by 'find_variable(F)' (which is used by the indefinite
  26. integral), skip the reduction of the indefinite integral and return the
  27. solution F(b) - F(a).
  28. """
  29. F += choose_constant(integral)
  30. if len(integral) < 3:
  31. return F
  32. x, lbnd, ubnd = integral[1:4]
  33. if x != find_variable(F):
  34. return replace_variable(F, x, ubnd) - replace_variable(F, x, lbnd)
  35. return indef(F, lbnd, ubnd)
  36. def match_solve_indef(node):
  37. """
  38. [F(x)]_a^b -> F(b) - F(a)
  39. """
  40. assert node.is_op(OP_INT_INDEF)
  41. return [P(node, solve_indef)]
  42. def solve_indef(root, args):
  43. """
  44. [F(x)]_a^b -> F(b) - F(a)
  45. """
  46. Fx, a, b = root
  47. x = find_variable(Fx)
  48. return replace_variable(Fx, x, b) - replace_variable(Fx, x, a)
  49. def match_integrate_variable_power(node):
  50. """
  51. int x ^ n dx -> x ^ (n + 1) / (n + 1)
  52. int g ^ x dx -> g ^ x / ln(g)
  53. """
  54. assert node.is_op(OP_INT)
  55. f, x = node[:2]
  56. if f.is_power() and not f.negated:
  57. root, exponent = f
  58. if root == x and not exponent.contains(x):
  59. return [P(node, integrate_variable_root)]
  60. if exponent == x and not root.contains(x):
  61. return [P(node, integrate_variable_exponent)]
  62. return []
  63. def integrate_variable_root(root, args):
  64. """
  65. int x ^ n dx -> x ^ (n + 1) / (n + 1)
  66. """
  67. x, n = root[0]
  68. return solve_integral(root, x ** (n + 1) / (n + 1))
  69. MESSAGES[integrate_variable_root] = \
  70. _('Apply standard integral int(x ^ n) = x ^ (n + 1) / (n + 1) + c.')
  71. def integrate_variable_exponent(root, args):
  72. """
  73. int g ^ x dx -> g ^ x / ln(g)
  74. """
  75. g, x = root[0]
  76. return solve_integral(root, g ** x / ln(g))
  77. MESSAGES[integrate_variable_exponent] = \
  78. _('Apply standard integral int(g ^ x) = g ^ x / ln(g) + c.')
  79. def match_constant_integral(node):
  80. """
  81. int x dx -> int x ^ 1 dx # -> x ^ 2 / 2 + c
  82. int c dx -> cx
  83. """
  84. assert node.is_op(OP_INT)
  85. fx, x = node[:2]
  86. if fx == x:
  87. return [P(node, single_variable_integral)]
  88. if not fx.contains(x):
  89. return [P(node, constant_integral)]
  90. return []
  91. def single_variable_integral(root, args):
  92. """
  93. int x dx -> int x ^ 1 dx # -> x ^ 2 / 2 + c
  94. """
  95. return integral(root[0] ** 1, *root[1:])
  96. MESSAGES[single_variable_integral] = _('Rewrite {0[0]} to {0[0]} ^ 1 and ' \
  97. 'apply the standard integral for {0[0]} ^ n.')
  98. def constant_integral(root, args):
  99. """
  100. int c dx -> cx
  101. """
  102. c, x = root[:2]
  103. return solve_integral(root, c * x)
  104. MESSAGES[constant_integral] = _('{0[0]} does not contain {0[1]}, so its ' \
  105. 'integral over {0[1]} is its multiplication with {0[1]}.')
  106. def match_factor_out_constant(node):
  107. """
  108. int cf(x) dx -> c int f(x) dx
  109. int -f(x) dx -> -1 int f(x) dx
  110. """
  111. assert node.is_op(OP_INT)
  112. fx, x = node[:2]
  113. if fx.negated:
  114. return [P(node, split_negation_to_constant)]
  115. if not fx.is_op(OP_MUL):
  116. return []
  117. p = []
  118. scope = Scope(fx)
  119. for n in scope:
  120. if not n.contains(x):
  121. p.append(P(node, factor_out_constant, (scope, n)))
  122. return p
  123. def split_negation_to_constant(root, args):
  124. """
  125. int -f(x) dx -> int -1 * f(x) dx # =>* -int f(x) dx
  126. """
  127. return integral(-L(1) * root[0].reduce_negation(), *root[1:])
  128. MESSAGES[split_negation_to_constant] = _('Write the negation of {0[0]} as an' \
  129. ' explicit -1 and bring it outside of the integral.')
  130. def factor_out_constant(root, args):
  131. """
  132. int cf(x) dx -> c int f(x) dx
  133. """
  134. scope, c = args
  135. scope.remove(c)
  136. return c * integral(scope.as_nary_node(), *root[1:])
  137. MESSAGES[factor_out_constant] = _('Factor out {2} from integral {0}.')
  138. def match_division_integral(node):
  139. """
  140. int 1 / x dx -> ln|x|
  141. int a / x dx -> int a(1 / x) dx # -> a int 1 / x dx -> aln|x|
  142. """
  143. assert node.is_op(OP_INT)
  144. fx, x = node[:2]
  145. if fx.is_op(OP_DIV) and fx[1] == x:
  146. if fx[0] == 1:
  147. return [P(node, division_integral)]
  148. return [P(node, extend_division_integral)]
  149. return []
  150. def division_integral(root, args):
  151. """
  152. int 1 / x dx -> ln|x|
  153. """
  154. return solve_integral(root, ln(absolute(root[0][1])))
  155. MESSAGES[division_integral] = \
  156. _('1 / {0[1]} has the standard anti-derivative ln|{0[1]}| + c.')
  157. def extend_division_integral(root, args):
  158. """
  159. int a / x dx -> int a(1 / x) dx # -> a int 1 / x dx -> aln|x|
  160. """
  161. a, x = root[0]
  162. return integral(a * (L(1) / x), *root[1:])
  163. MESSAGES[extend_division_integral] = _('Bring nominator {0[0][0]} out of the' \
  164. ' fraction to obtain a standard 1 / {0[0][1]} integral.')
  165. def match_function_integral(node):
  166. """
  167. int log_g(x) dx -> (xln(x) - x) / log_g(x)
  168. int sin(x) dx -> -cos(x)
  169. int cos(x) dx -> sin(x)
  170. """
  171. assert node.is_op(OP_INT)
  172. fx, x = node[:2]
  173. if fx.is_leaf or fx[0] != x:
  174. return []
  175. if fx.op == OP_LOG:
  176. return [P(node, logarithm_integral)]
  177. if fx.op == OP_SIN:
  178. return [P(node, sinus_integral)]
  179. if fx.op == OP_COS:
  180. return [P(node, cosinus_integral)]
  181. return []
  182. def logarithm_integral(root, args):
  183. """
  184. int log_g(x) dx -> (xln(x) - x) / log_g(x)
  185. """
  186. x, g = root[0]
  187. return solve_integral(root, (x * ln(x) - x) / ln(g))
  188. MESSAGES[logarithm_integral] = _('log_g(x) has the standard anti-derivative ' \
  189. '(xln(x) - x) / log_g(x) + c.')
  190. def sinus_integral(root, args):
  191. """
  192. int sin(x) dx -> -cos(x)
  193. """
  194. return solve_integral(root, -cos(root[0][0]))
  195. MESSAGES[sinus_integral] = \
  196. _('{0[0]} has the standard anti-derivative -cos({0[0][0]}) + c.')
  197. def cosinus_integral(root, args):
  198. """
  199. int cos(x) dx -> sin(x)
  200. """
  201. return solve_integral(root, sin(root[0][0]))
  202. MESSAGES[cosinus_integral] = \
  203. _('{0[0]} has the standard anti-derivative sin({0[0][0]}) + c.')