rubimain.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. from sympy.external import import_module
  2. from sympy.utilities.decorator import doctest_depends_on
  3. from sympy.core import Integer, Float
  4. from sympy.core.add import Add
  5. from sympy.core.function import Function
  6. from sympy.core.mul import Mul
  7. from sympy.core.numbers import E
  8. from sympy.core.power import Pow
  9. from sympy.core.singleton import S
  10. from sympy.integrals.integrals import Integral
  11. from sympy.functions import exp as sym_exp
  12. import inspect
  13. import re
  14. from sympy.simplify.powsimp import powsimp
  15. matchpy = import_module("matchpy")
  16. if matchpy:
  17. from matchpy import ManyToOneReplacer, ManyToOneMatcher
  18. from sympy.integrals.rubi.utility_function import (
  19. rubi_exp, rubi_unevaluated_expr, process_trig
  20. )
  21. from sympy.utilities.matchpy_connector import op_iter, op_len
  22. @doctest_depends_on(modules=('matchpy',))
  23. def get_rubi_object():
  24. """
  25. Returns rubi ManyToOneReplacer by adding all rules from different modules.
  26. Uncomment the lines to add integration capabilities of that module.
  27. Currently, there are parsing issues with special_function,
  28. derivative and miscellaneous_integration. Hence they are commented.
  29. """
  30. from sympy.integrals.rubi.rules.integrand_simplification import integrand_simplification
  31. from sympy.integrals.rubi.rules.linear_products import linear_products
  32. from sympy.integrals.rubi.rules.quadratic_products import quadratic_products
  33. from sympy.integrals.rubi.rules.binomial_products import binomial_products
  34. from sympy.integrals.rubi.rules.trinomial_products import trinomial_products
  35. from sympy.integrals.rubi.rules.miscellaneous_algebraic import miscellaneous_algebraic
  36. from sympy.integrals.rubi.rules.exponential import exponential
  37. from sympy.integrals.rubi.rules.logarithms import logarithms
  38. from sympy.integrals.rubi.rules.sine import sine
  39. from sympy.integrals.rubi.rules.tangent import tangent
  40. from sympy.integrals.rubi.rules.secant import secant
  41. from sympy.integrals.rubi.rules.miscellaneous_trig import miscellaneous_trig
  42. from sympy.integrals.rubi.rules.inverse_trig import inverse_trig
  43. from sympy.integrals.rubi.rules.hyperbolic import hyperbolic
  44. from sympy.integrals.rubi.rules.inverse_hyperbolic import inverse_hyperbolic
  45. from sympy.integrals.rubi.rules.special_functions import special_functions
  46. #from sympy.integrals.rubi.rules.derivative import derivative
  47. #from sympy.integrals.rubi.rules.piecewise_linear import piecewise_linear
  48. from sympy.integrals.rubi.rules.miscellaneous_integration import miscellaneous_integration
  49. rules = []
  50. rules += integrand_simplification()
  51. rules += linear_products()
  52. rules += quadratic_products()
  53. rules += binomial_products()
  54. rules += trinomial_products()
  55. rules += miscellaneous_algebraic()
  56. rules += exponential()
  57. rules += logarithms()
  58. rules += special_functions()
  59. rules += sine()
  60. rules += tangent()
  61. rules += secant()
  62. rules += miscellaneous_trig()
  63. rules += inverse_trig()
  64. rules += hyperbolic()
  65. rules += inverse_hyperbolic()
  66. #rubi = piecewise_linear(rubi)
  67. rules += miscellaneous_integration()
  68. rubi = ManyToOneReplacer(*rules)
  69. return rubi, rules
  70. _E = rubi_unevaluated_expr(E)
  71. class LoadRubiReplacer:
  72. """
  73. Class trick to load RUBI only once.
  74. """
  75. _instance = None
  76. def __new__(cls):
  77. if matchpy is None:
  78. print("MatchPy library not found")
  79. return None
  80. if LoadRubiReplacer._instance is not None:
  81. return LoadRubiReplacer._instance
  82. obj = object.__new__(cls)
  83. obj._rubi = None
  84. obj._rules = None
  85. LoadRubiReplacer._instance = obj
  86. return obj
  87. def load(self):
  88. if self._rubi is not None:
  89. return self._rubi
  90. rubi, rules = get_rubi_object()
  91. self._rubi = rubi
  92. self._rules = rules
  93. return rubi
  94. def to_pickle(self, filename):
  95. import pickle
  96. rubi = self.load()
  97. with open(filename, "wb") as fout:
  98. pickle.dump(rubi, fout)
  99. def to_dill(self, filename):
  100. import dill
  101. rubi = self.load()
  102. with open(filename, "wb") as fout:
  103. dill.dump(rubi, fout)
  104. def from_pickle(self, filename):
  105. import pickle
  106. with open(filename, "rb") as fin:
  107. self._rubi = pickle.load(fin)
  108. return self._rubi
  109. def from_dill(self, filename):
  110. import dill
  111. with open(filename, "rb") as fin:
  112. self._rubi = dill.load(fin)
  113. return self._rubi
  114. @doctest_depends_on(modules=('matchpy',))
  115. def process_final_integral(expr):
  116. """
  117. Rubi's `rubi_exp` need to be replaced back to SymPy's general `exp`.
  118. Examples
  119. ========
  120. >>> from sympy import Function, E, Integral
  121. >>> from sympy.integrals.rubi.rubimain import process_final_integral
  122. >>> from sympy.integrals.rubi.utility_function import rubi_unevaluated_expr
  123. >>> from sympy.abc import a, x
  124. >>> _E = rubi_unevaluated_expr(E)
  125. >>> process_final_integral(Integral(a, x))
  126. Integral(a, x)
  127. >>> process_final_integral(_E**5)
  128. exp(5)
  129. """
  130. if expr.has(_E):
  131. expr = expr.replace(_E, E)
  132. return expr
  133. @doctest_depends_on(modules=('matchpy',))
  134. def rubi_powsimp(expr):
  135. """
  136. This function is needed to preprocess an expression as done in matchpy
  137. `x^a*x^b` in matchpy auotmatically transforms to `x^(a+b)`
  138. Examples
  139. ========
  140. >>> from sympy.integrals.rubi.rubimain import rubi_powsimp
  141. >>> from sympy.abc import a, b, x
  142. >>> rubi_powsimp(x**a*x**b)
  143. x**(a + b)
  144. """
  145. lst_pow = []
  146. lst_non_pow = []
  147. if isinstance(expr, Mul):
  148. for i in expr.args:
  149. if isinstance(i, (Pow, rubi_exp, sym_exp)):
  150. lst_pow.append(i)
  151. else:
  152. lst_non_pow.append(i)
  153. return powsimp(Mul(*lst_pow))*Mul(*lst_non_pow)
  154. return expr
  155. @doctest_depends_on(modules=('matchpy',))
  156. def rubi_integrate(expr, var, showsteps=False):
  157. """
  158. Rule based algorithm for integration. Integrates the expression by applying
  159. transformation rules to the expression.
  160. Returns `Integrate` if an expression cannot be integrated.
  161. Parameters
  162. ==========
  163. expr : integrand expression
  164. var : variable of integration
  165. Returns Integral object if unable to integrate.
  166. """
  167. rubi = LoadRubiReplacer().load()
  168. expr = expr.replace(sym_exp, rubi_exp)
  169. expr = process_trig(expr)
  170. expr = rubi_powsimp(expr)
  171. if isinstance(expr, (int, Integer, float, Float)):
  172. return S(expr)*var
  173. if isinstance(expr, Add):
  174. results = 0
  175. for ex in expr.args:
  176. results += rubi.replace(Integral(ex, var))
  177. return process_final_integral(results)
  178. results = util_rubi_integrate(Integral(expr, var))
  179. return process_final_integral(results)
  180. @doctest_depends_on(modules=('matchpy',))
  181. def util_rubi_integrate(expr, showsteps=False, max_loop=10):
  182. rubi = LoadRubiReplacer().load()
  183. expr = process_trig(expr)
  184. expr = expr.replace(sym_exp, rubi_exp)
  185. for i in range(max_loop):
  186. results = expr.replace(
  187. lambda x: isinstance(x, Integral),
  188. lambda x: rubi.replace(x, max_count=10)
  189. )
  190. if expr == results:
  191. return results
  192. return results
  193. @doctest_depends_on(modules=('matchpy',))
  194. def get_matching_rule_definition(expr, var):
  195. """
  196. Prints the list or rules which match to `expr`.
  197. Parameters
  198. ==========
  199. expr : integrand expression
  200. var : variable of integration
  201. """
  202. rubi = LoadRubiReplacer()
  203. matcher = rubi.matcher
  204. miter = matcher.match(Integral(expr, var))
  205. for fun, e in miter:
  206. print("Rule matching: ")
  207. print(inspect.getsourcefile(fun))
  208. code, lineno = inspect.getsourcelines(fun)
  209. print("On line: ", lineno)
  210. print("\n".join(code))
  211. print("Pattern matching: ")
  212. pattno = int(re.match(r"^\s*rule(\d+)", code[0]).group(1))
  213. print(matcher.patterns[pattno-1])
  214. print(e)
  215. print()