refine.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. from typing import Dict as tDict, Callable
  2. from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational
  3. from sympy.core.logic import fuzzy_not
  4. from sympy.logic.boolalg import Boolean
  5. from sympy.assumptions import ask, Q # type: ignore
  6. def refine(expr, assumptions=True):
  7. """
  8. Simplify an expression using assumptions.
  9. Explanation
  10. ===========
  11. Unlike :func:`~.simplify()` which performs structural simplification
  12. without any assumption, this function transforms the expression into
  13. the form which is only valid under certain assumptions. Note that
  14. ``simplify()`` is generally not done in refining process.
  15. Refining boolean expression involves reducing it to ``S.true`` or
  16. ``S.false``. Unlike :func:`~.ask()`, the expression will not be reduced
  17. if the truth value cannot be determined.
  18. Examples
  19. ========
  20. >>> from sympy import refine, sqrt, Q
  21. >>> from sympy.abc import x
  22. >>> refine(sqrt(x**2), Q.real(x))
  23. Abs(x)
  24. >>> refine(sqrt(x**2), Q.positive(x))
  25. x
  26. >>> refine(Q.real(x), Q.positive(x))
  27. True
  28. >>> refine(Q.positive(x), Q.real(x))
  29. Q.positive(x)
  30. See Also
  31. ========
  32. sympy.simplify.simplify.simplify : Structural simplification without assumptions.
  33. sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
  34. """
  35. if not isinstance(expr, Basic):
  36. return expr
  37. if not expr.is_Atom:
  38. args = [refine(arg, assumptions) for arg in expr.args]
  39. # TODO: this will probably not work with Integral or Polynomial
  40. expr = expr.func(*args)
  41. if hasattr(expr, '_eval_refine'):
  42. ref_expr = expr._eval_refine(assumptions)
  43. if ref_expr is not None:
  44. return ref_expr
  45. name = expr.__class__.__name__
  46. handler = handlers_dict.get(name, None)
  47. if handler is None:
  48. return expr
  49. new_expr = handler(expr, assumptions)
  50. if (new_expr is None) or (expr == new_expr):
  51. return expr
  52. if not isinstance(new_expr, Expr):
  53. return new_expr
  54. return refine(new_expr, assumptions)
  55. def refine_abs(expr, assumptions):
  56. """
  57. Handler for the absolute value.
  58. Examples
  59. ========
  60. >>> from sympy import Q, Abs
  61. >>> from sympy.assumptions.refine import refine_abs
  62. >>> from sympy.abc import x
  63. >>> refine_abs(Abs(x), Q.real(x))
  64. >>> refine_abs(Abs(x), Q.positive(x))
  65. x
  66. >>> refine_abs(Abs(x), Q.negative(x))
  67. -x
  68. """
  69. from sympy.functions.elementary.complexes import Abs
  70. arg = expr.args[0]
  71. if ask(Q.real(arg), assumptions) and \
  72. fuzzy_not(ask(Q.negative(arg), assumptions)):
  73. # if it's nonnegative
  74. return arg
  75. if ask(Q.negative(arg), assumptions):
  76. return -arg
  77. # arg is Mul
  78. if isinstance(arg, Mul):
  79. r = [refine(abs(a), assumptions) for a in arg.args]
  80. non_abs = []
  81. in_abs = []
  82. for i in r:
  83. if isinstance(i, Abs):
  84. in_abs.append(i.args[0])
  85. else:
  86. non_abs.append(i)
  87. return Mul(*non_abs) * Abs(Mul(*in_abs))
  88. def refine_Pow(expr, assumptions):
  89. """
  90. Handler for instances of Pow.
  91. Examples
  92. ========
  93. >>> from sympy import Q
  94. >>> from sympy.assumptions.refine import refine_Pow
  95. >>> from sympy.abc import x,y,z
  96. >>> refine_Pow((-1)**x, Q.real(x))
  97. >>> refine_Pow((-1)**x, Q.even(x))
  98. 1
  99. >>> refine_Pow((-1)**x, Q.odd(x))
  100. -1
  101. For powers of -1, even parts of the exponent can be simplified:
  102. >>> refine_Pow((-1)**(x+y), Q.even(x))
  103. (-1)**y
  104. >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
  105. (-1)**y
  106. >>> refine_Pow((-1)**(x+y+2), Q.odd(x))
  107. (-1)**(y + 1)
  108. >>> refine_Pow((-1)**(x+3), True)
  109. (-1)**(x + 1)
  110. """
  111. from sympy.functions.elementary.complexes import Abs
  112. from sympy.functions import sign
  113. if isinstance(expr.base, Abs):
  114. if ask(Q.real(expr.base.args[0]), assumptions) and \
  115. ask(Q.even(expr.exp), assumptions):
  116. return expr.base.args[0] ** expr.exp
  117. if ask(Q.real(expr.base), assumptions):
  118. if expr.base.is_number:
  119. if ask(Q.even(expr.exp), assumptions):
  120. return abs(expr.base) ** expr.exp
  121. if ask(Q.odd(expr.exp), assumptions):
  122. return sign(expr.base) * abs(expr.base) ** expr.exp
  123. if isinstance(expr.exp, Rational):
  124. if isinstance(expr.base, Pow):
  125. return abs(expr.base.base) ** (expr.base.exp * expr.exp)
  126. if expr.base is S.NegativeOne:
  127. if expr.exp.is_Add:
  128. old = expr
  129. # For powers of (-1) we can remove
  130. # - even terms
  131. # - pairs of odd terms
  132. # - a single odd term + 1
  133. # - A numerical constant N can be replaced with mod(N,2)
  134. coeff, terms = expr.exp.as_coeff_add()
  135. terms = set(terms)
  136. even_terms = set()
  137. odd_terms = set()
  138. initial_number_of_terms = len(terms)
  139. for t in terms:
  140. if ask(Q.even(t), assumptions):
  141. even_terms.add(t)
  142. elif ask(Q.odd(t), assumptions):
  143. odd_terms.add(t)
  144. terms -= even_terms
  145. if len(odd_terms) % 2:
  146. terms -= odd_terms
  147. new_coeff = (coeff + S.One) % 2
  148. else:
  149. terms -= odd_terms
  150. new_coeff = coeff % 2
  151. if new_coeff != coeff or len(terms) < initial_number_of_terms:
  152. terms.add(new_coeff)
  153. expr = expr.base**(Add(*terms))
  154. # Handle (-1)**((-1)**n/2 + m/2)
  155. e2 = 2*expr.exp
  156. if ask(Q.even(e2), assumptions):
  157. if e2.could_extract_minus_sign():
  158. e2 *= expr.base
  159. if e2.is_Add:
  160. i, p = e2.as_two_terms()
  161. if p.is_Pow and p.base is S.NegativeOne:
  162. if ask(Q.integer(p.exp), assumptions):
  163. i = (i + 1)/2
  164. if ask(Q.even(i), assumptions):
  165. return expr.base**p.exp
  166. elif ask(Q.odd(i), assumptions):
  167. return expr.base**(p.exp + 1)
  168. else:
  169. return expr.base**(p.exp + i)
  170. if old != expr:
  171. return expr
  172. def refine_atan2(expr, assumptions):
  173. """
  174. Handler for the atan2 function.
  175. Examples
  176. ========
  177. >>> from sympy import Q, atan2
  178. >>> from sympy.assumptions.refine import refine_atan2
  179. >>> from sympy.abc import x, y
  180. >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
  181. atan(y/x)
  182. >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
  183. atan(y/x) - pi
  184. >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
  185. atan(y/x) + pi
  186. >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
  187. pi
  188. >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
  189. pi/2
  190. >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
  191. -pi/2
  192. >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
  193. nan
  194. """
  195. from sympy.functions.elementary.trigonometric import atan
  196. y, x = expr.args
  197. if ask(Q.real(y) & Q.positive(x), assumptions):
  198. return atan(y / x)
  199. elif ask(Q.negative(y) & Q.negative(x), assumptions):
  200. return atan(y / x) - S.Pi
  201. elif ask(Q.positive(y) & Q.negative(x), assumptions):
  202. return atan(y / x) + S.Pi
  203. elif ask(Q.zero(y) & Q.negative(x), assumptions):
  204. return S.Pi
  205. elif ask(Q.positive(y) & Q.zero(x), assumptions):
  206. return S.Pi/2
  207. elif ask(Q.negative(y) & Q.zero(x), assumptions):
  208. return -S.Pi/2
  209. elif ask(Q.zero(y) & Q.zero(x), assumptions):
  210. return S.NaN
  211. else:
  212. return expr
  213. def refine_re(expr, assumptions):
  214. """
  215. Handler for real part.
  216. Examples
  217. ========
  218. >>> from sympy.assumptions.refine import refine_re
  219. >>> from sympy import Q, re
  220. >>> from sympy.abc import x
  221. >>> refine_re(re(x), Q.real(x))
  222. x
  223. >>> refine_re(re(x), Q.imaginary(x))
  224. 0
  225. """
  226. arg = expr.args[0]
  227. if ask(Q.real(arg), assumptions):
  228. return arg
  229. if ask(Q.imaginary(arg), assumptions):
  230. return S.Zero
  231. return _refine_reim(expr, assumptions)
  232. def refine_im(expr, assumptions):
  233. """
  234. Handler for imaginary part.
  235. Explanation
  236. ===========
  237. >>> from sympy.assumptions.refine import refine_im
  238. >>> from sympy import Q, im
  239. >>> from sympy.abc import x
  240. >>> refine_im(im(x), Q.real(x))
  241. 0
  242. >>> refine_im(im(x), Q.imaginary(x))
  243. -I*x
  244. """
  245. arg = expr.args[0]
  246. if ask(Q.real(arg), assumptions):
  247. return S.Zero
  248. if ask(Q.imaginary(arg), assumptions):
  249. return - S.ImaginaryUnit * arg
  250. return _refine_reim(expr, assumptions)
  251. def refine_arg(expr, assumptions):
  252. """
  253. Handler for complex argument
  254. Explanation
  255. ===========
  256. >>> from sympy.assumptions.refine import refine_arg
  257. >>> from sympy import Q, arg
  258. >>> from sympy.abc import x
  259. >>> refine_arg(arg(x), Q.positive(x))
  260. 0
  261. >>> refine_arg(arg(x), Q.negative(x))
  262. pi
  263. """
  264. rg = expr.args[0]
  265. if ask(Q.positive(rg), assumptions):
  266. return S.Zero
  267. if ask(Q.negative(rg), assumptions):
  268. return S.Pi
  269. return None
  270. def _refine_reim(expr, assumptions):
  271. # Helper function for refine_re & refine_im
  272. expanded = expr.expand(complex = True)
  273. if expanded != expr:
  274. refined = refine(expanded, assumptions)
  275. if refined != expanded:
  276. return refined
  277. # Best to leave the expression as is
  278. return None
  279. def refine_sign(expr, assumptions):
  280. """
  281. Handler for sign.
  282. Examples
  283. ========
  284. >>> from sympy.assumptions.refine import refine_sign
  285. >>> from sympy import Symbol, Q, sign, im
  286. >>> x = Symbol('x', real = True)
  287. >>> expr = sign(x)
  288. >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
  289. 1
  290. >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
  291. -1
  292. >>> refine_sign(expr, Q.zero(x))
  293. 0
  294. >>> y = Symbol('y', imaginary = True)
  295. >>> expr = sign(y)
  296. >>> refine_sign(expr, Q.positive(im(y)))
  297. I
  298. >>> refine_sign(expr, Q.negative(im(y)))
  299. -I
  300. """
  301. arg = expr.args[0]
  302. if ask(Q.zero(arg), assumptions):
  303. return S.Zero
  304. if ask(Q.real(arg)):
  305. if ask(Q.positive(arg), assumptions):
  306. return S.One
  307. if ask(Q.negative(arg), assumptions):
  308. return S.NegativeOne
  309. if ask(Q.imaginary(arg)):
  310. arg_re, arg_im = arg.as_real_imag()
  311. if ask(Q.positive(arg_im), assumptions):
  312. return S.ImaginaryUnit
  313. if ask(Q.negative(arg_im), assumptions):
  314. return -S.ImaginaryUnit
  315. return expr
  316. def refine_matrixelement(expr, assumptions):
  317. """
  318. Handler for symmetric part.
  319. Examples
  320. ========
  321. >>> from sympy.assumptions.refine import refine_matrixelement
  322. >>> from sympy import MatrixSymbol, Q
  323. >>> X = MatrixSymbol('X', 3, 3)
  324. >>> refine_matrixelement(X[0, 1], Q.symmetric(X))
  325. X[0, 1]
  326. >>> refine_matrixelement(X[1, 0], Q.symmetric(X))
  327. X[0, 1]
  328. """
  329. from sympy.matrices.expressions.matexpr import MatrixElement
  330. matrix, i, j = expr.args
  331. if ask(Q.symmetric(matrix), assumptions):
  332. if (i - j).could_extract_minus_sign():
  333. return expr
  334. return MatrixElement(matrix, j, i)
  335. handlers_dict = {
  336. 'Abs': refine_abs,
  337. 'Pow': refine_Pow,
  338. 'atan2': refine_atan2,
  339. 're': refine_re,
  340. 'im': refine_im,
  341. 'arg': refine_arg,
  342. 'sign': refine_sign,
  343. 'MatrixElement': refine_matrixelement
  344. } # type: tDict[str, Callable[[Expr, Boolean], Expr]]