singularities.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. """
  2. Singularities
  3. =============
  4. This module implements algorithms for finding singularities for a function
  5. and identifying types of functions.
  6. The differential calculus methods in this module include methods to identify
  7. the following function types in the given ``Interval``:
  8. - Increasing
  9. - Strictly Increasing
  10. - Decreasing
  11. - Strictly Decreasing
  12. - Monotonic
  13. """
  14. from sympy.core.power import Pow
  15. from sympy.core.singleton import S
  16. from sympy.core.symbol import Symbol
  17. from sympy.core.sympify import sympify
  18. from sympy.functions.elementary.exponential import log
  19. from sympy.functions.elementary.trigonometric import sec, csc, cot, tan, cos
  20. from sympy.functions.elementary.hyperbolic import (
  21. sech, csch, coth, tanh, cosh, asech, acsch, atanh, acoth)
  22. from sympy.utilities.misc import filldedent
  23. def singularities(expression, symbol, domain=None):
  24. """
  25. Find singularities of a given function.
  26. Parameters
  27. ==========
  28. expression : Expr
  29. The target function in which singularities need to be found.
  30. symbol : Symbol
  31. The symbol over the values of which the singularity in
  32. expression in being searched for.
  33. Returns
  34. =======
  35. Set
  36. A set of values for ``symbol`` for which ``expression`` has a
  37. singularity. An ``EmptySet`` is returned if ``expression`` has no
  38. singularities for any given value of ``Symbol``.
  39. Raises
  40. ======
  41. NotImplementedError
  42. Methods for determining the singularities of this function have
  43. not been developed.
  44. Notes
  45. =====
  46. This function does not find non-isolated singularities
  47. nor does it find branch points of the expression.
  48. Currently supported functions are:
  49. - univariate continuous (real or complex) functions
  50. References
  51. ==========
  52. .. [1] https://en.wikipedia.org/wiki/Mathematical_singularity
  53. Examples
  54. ========
  55. >>> from sympy import singularities, Symbol, log
  56. >>> x = Symbol('x', real=True)
  57. >>> y = Symbol('y', real=False)
  58. >>> singularities(x**2 + x + 1, x)
  59. EmptySet
  60. >>> singularities(1/(x + 1), x)
  61. {-1}
  62. >>> singularities(1/(y**2 + 1), y)
  63. {-I, I}
  64. >>> singularities(1/(y**3 + 1), y)
  65. {-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2}
  66. >>> singularities(log(x), x)
  67. {0}
  68. """
  69. from sympy.solvers.solveset import solveset
  70. if domain is None:
  71. domain = S.Reals if symbol.is_real else S.Complexes
  72. try:
  73. sings = S.EmptySet
  74. e = expression.rewrite([sec, csc, cot, tan], cos)
  75. e = e.rewrite([sech, csch, coth, tanh], cosh)
  76. for i in e.atoms(Pow):
  77. if i.exp.is_infinite:
  78. raise NotImplementedError
  79. if i.exp.is_negative:
  80. # XXX: exponent of varying sign not handled
  81. sings += solveset(i.base, symbol, domain)
  82. for i in expression.atoms(log, asech, acsch):
  83. sings += solveset(i.args[0], symbol, domain)
  84. for i in expression.atoms(atanh, acoth):
  85. sings += solveset(i.args[0] - 1, symbol, domain)
  86. sings += solveset(i.args[0] + 1, symbol, domain)
  87. return sings
  88. except NotImplementedError:
  89. raise NotImplementedError(filldedent('''
  90. Methods for determining the singularities
  91. of this function have not been developed.'''))
  92. ###########################################################################
  93. # DIFFERENTIAL CALCULUS METHODS #
  94. ###########################################################################
  95. def monotonicity_helper(expression, predicate, interval=S.Reals, symbol=None):
  96. """
  97. Helper function for functions checking function monotonicity.
  98. Parameters
  99. ==========
  100. expression : Expr
  101. The target function which is being checked
  102. predicate : function
  103. The property being tested for. The function takes in an integer
  104. and returns a boolean. The integer input is the derivative and
  105. the boolean result should be true if the property is being held,
  106. and false otherwise.
  107. interval : Set, optional
  108. The range of values in which we are testing, defaults to all reals.
  109. symbol : Symbol, optional
  110. The symbol present in expression which gets varied over the given range.
  111. It returns a boolean indicating whether the interval in which
  112. the function's derivative satisfies given predicate is a superset
  113. of the given interval.
  114. Returns
  115. =======
  116. Boolean
  117. True if ``predicate`` is true for all the derivatives when ``symbol``
  118. is varied in ``range``, False otherwise.
  119. """
  120. from sympy.solvers.solveset import solveset
  121. expression = sympify(expression)
  122. free = expression.free_symbols
  123. if symbol is None:
  124. if len(free) > 1:
  125. raise NotImplementedError(
  126. 'The function has not yet been implemented'
  127. ' for all multivariate expressions.'
  128. )
  129. variable = symbol or (free.pop() if free else Symbol('x'))
  130. derivative = expression.diff(variable)
  131. predicate_interval = solveset(predicate(derivative), variable, S.Reals)
  132. return interval.is_subset(predicate_interval)
  133. def is_increasing(expression, interval=S.Reals, symbol=None):
  134. """
  135. Return whether the function is increasing in the given interval.
  136. Parameters
  137. ==========
  138. expression : Expr
  139. The target function which is being checked.
  140. interval : Set, optional
  141. The range of values in which we are testing (defaults to set of
  142. all real numbers).
  143. symbol : Symbol, optional
  144. The symbol present in expression which gets varied over the given range.
  145. Returns
  146. =======
  147. Boolean
  148. True if ``expression`` is increasing (either strictly increasing or
  149. constant) in the given ``interval``, False otherwise.
  150. Examples
  151. ========
  152. >>> from sympy import is_increasing
  153. >>> from sympy.abc import x, y
  154. >>> from sympy import S, Interval, oo
  155. >>> is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
  156. True
  157. >>> is_increasing(-x**2, Interval(-oo, 0))
  158. True
  159. >>> is_increasing(-x**2, Interval(0, oo))
  160. False
  161. >>> is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
  162. False
  163. >>> is_increasing(x**2 + y, Interval(1, 2), x)
  164. True
  165. """
  166. return monotonicity_helper(expression, lambda x: x >= 0, interval, symbol)
  167. def is_strictly_increasing(expression, interval=S.Reals, symbol=None):
  168. """
  169. Return whether the function is strictly increasing in the given interval.
  170. Parameters
  171. ==========
  172. expression : Expr
  173. The target function which is being checked.
  174. interval : Set, optional
  175. The range of values in which we are testing (defaults to set of
  176. all real numbers).
  177. symbol : Symbol, optional
  178. The symbol present in expression which gets varied over the given range.
  179. Returns
  180. =======
  181. Boolean
  182. True if ``expression`` is strictly increasing in the given ``interval``,
  183. False otherwise.
  184. Examples
  185. ========
  186. >>> from sympy import is_strictly_increasing
  187. >>> from sympy.abc import x, y
  188. >>> from sympy import Interval, oo
  189. >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
  190. True
  191. >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
  192. True
  193. >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
  194. False
  195. >>> is_strictly_increasing(-x**2, Interval(0, oo))
  196. False
  197. >>> is_strictly_increasing(-x**2 + y, Interval(-oo, 0), x)
  198. False
  199. """
  200. return monotonicity_helper(expression, lambda x: x > 0, interval, symbol)
  201. def is_decreasing(expression, interval=S.Reals, symbol=None):
  202. """
  203. Return whether the function is decreasing in the given interval.
  204. Parameters
  205. ==========
  206. expression : Expr
  207. The target function which is being checked.
  208. interval : Set, optional
  209. The range of values in which we are testing (defaults to set of
  210. all real numbers).
  211. symbol : Symbol, optional
  212. The symbol present in expression which gets varied over the given range.
  213. Returns
  214. =======
  215. Boolean
  216. True if ``expression`` is decreasing (either strictly decreasing or
  217. constant) in the given ``interval``, False otherwise.
  218. Examples
  219. ========
  220. >>> from sympy import is_decreasing
  221. >>> from sympy.abc import x, y
  222. >>> from sympy import S, Interval, oo
  223. >>> is_decreasing(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
  224. True
  225. >>> is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
  226. True
  227. >>> is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
  228. True
  229. >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
  230. False
  231. >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
  232. False
  233. >>> is_decreasing(-x**2, Interval(-oo, 0))
  234. False
  235. >>> is_decreasing(-x**2 + y, Interval(-oo, 0), x)
  236. False
  237. """
  238. return monotonicity_helper(expression, lambda x: x <= 0, interval, symbol)
  239. def is_strictly_decreasing(expression, interval=S.Reals, symbol=None):
  240. """
  241. Return whether the function is strictly decreasing in the given interval.
  242. Parameters
  243. ==========
  244. expression : Expr
  245. The target function which is being checked.
  246. interval : Set, optional
  247. The range of values in which we are testing (defaults to set of
  248. all real numbers).
  249. symbol : Symbol, optional
  250. The symbol present in expression which gets varied over the given range.
  251. Returns
  252. =======
  253. Boolean
  254. True if ``expression`` is strictly decreasing in the given ``interval``,
  255. False otherwise.
  256. Examples
  257. ========
  258. >>> from sympy import is_strictly_decreasing
  259. >>> from sympy.abc import x, y
  260. >>> from sympy import S, Interval, oo
  261. >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
  262. True
  263. >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
  264. False
  265. >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
  266. False
  267. >>> is_strictly_decreasing(-x**2, Interval(-oo, 0))
  268. False
  269. >>> is_strictly_decreasing(-x**2 + y, Interval(-oo, 0), x)
  270. False
  271. """
  272. return monotonicity_helper(expression, lambda x: x < 0, interval, symbol)
  273. def is_monotonic(expression, interval=S.Reals, symbol=None):
  274. """
  275. Return whether the function is monotonic in the given interval.
  276. Parameters
  277. ==========
  278. expression : Expr
  279. The target function which is being checked.
  280. interval : Set, optional
  281. The range of values in which we are testing (defaults to set of
  282. all real numbers).
  283. symbol : Symbol, optional
  284. The symbol present in expression which gets varied over the given range.
  285. Returns
  286. =======
  287. Boolean
  288. True if ``expression`` is monotonic in the given ``interval``,
  289. False otherwise.
  290. Raises
  291. ======
  292. NotImplementedError
  293. Monotonicity check has not been implemented for the queried function.
  294. Examples
  295. ========
  296. >>> from sympy import is_monotonic
  297. >>> from sympy.abc import x, y
  298. >>> from sympy import S, Interval, oo
  299. >>> is_monotonic(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
  300. True
  301. >>> is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
  302. True
  303. >>> is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
  304. True
  305. >>> is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
  306. True
  307. >>> is_monotonic(-x**2, S.Reals)
  308. False
  309. >>> is_monotonic(x**2 + y + 1, Interval(1, 2), x)
  310. True
  311. """
  312. from sympy.solvers.solveset import solveset
  313. expression = sympify(expression)
  314. free = expression.free_symbols
  315. if symbol is None and len(free) > 1:
  316. raise NotImplementedError(
  317. 'is_monotonic has not yet been implemented'
  318. ' for all multivariate expressions.'
  319. )
  320. variable = symbol or (free.pop() if free else Symbol('x'))
  321. turning_points = solveset(expression.diff(variable), variable, interval)
  322. return interval.intersection(turning_points) is S.EmptySet