delta_functions.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. from sympy.core import S, diff
  2. from sympy.core.function import Function, ArgumentIndexError
  3. from sympy.core.logic import fuzzy_not
  4. from sympy.core.relational import Eq, Ne
  5. from sympy.functions.elementary.complexes import im, sign
  6. from sympy.functions.elementary.piecewise import Piecewise
  7. from sympy.polys.polyerrors import PolynomialError
  8. from sympy.polys.polyroots import roots
  9. from sympy.utilities.misc import filldedent
  10. ###############################################################################
  11. ################################ DELTA FUNCTION ###############################
  12. ###############################################################################
  13. class DiracDelta(Function):
  14. r"""
  15. The DiracDelta function and its derivatives.
  16. Explanation
  17. ===========
  18. DiracDelta is not an ordinary function. It can be rigorously defined either
  19. as a distribution or as a measure.
  20. DiracDelta only makes sense in definite integrals, and in particular,
  21. integrals of the form ``Integral(f(x)*DiracDelta(x - x0), (x, a, b))``,
  22. where it equals ``f(x0)`` if ``a <= x0 <= b`` and ``0`` otherwise. Formally,
  23. DiracDelta acts in some ways like a function that is ``0`` everywhere except
  24. at ``0``, but in many ways it also does not. It can often be useful to treat
  25. DiracDelta in formal ways, building up and manipulating expressions with
  26. delta functions (which may eventually be integrated), but care must be taken
  27. to not treat it as a real function. SymPy's ``oo`` is similar. It only
  28. truly makes sense formally in certain contexts (such as integration limits),
  29. but SymPy allows its use everywhere, and it tries to be consistent with
  30. operations on it (like ``1/oo``), but it is easy to get into trouble and get
  31. wrong results if ``oo`` is treated too much like a number. Similarly, if
  32. DiracDelta is treated too much like a function, it is easy to get wrong or
  33. nonsensical results.
  34. DiracDelta function has the following properties:
  35. 1) $\frac{d}{d x} \theta(x) = \delta(x)$
  36. 2) $\int_{-\infty}^\infty \delta(x - a)f(x)\, dx = f(a)$ and $\int_{a-
  37. \epsilon}^{a+\epsilon} \delta(x - a)f(x)\, dx = f(a)$
  38. 3) $\delta(x) = 0$ for all $x \neq 0$
  39. 4) $\delta(g(x)) = \sum_i \frac{\delta(x - x_i)}{\|g'(x_i)\|}$ where $x_i$
  40. are the roots of $g$
  41. 5) $\delta(-x) = \delta(x)$
  42. Derivatives of ``k``-th order of DiracDelta have the following properties:
  43. 6) $\delta(x, k) = 0$ for all $x \neq 0$
  44. 7) $\delta(-x, k) = -\delta(x, k)$ for odd $k$
  45. 8) $\delta(-x, k) = \delta(x, k)$ for even $k$
  46. Examples
  47. ========
  48. >>> from sympy import DiracDelta, diff, pi
  49. >>> from sympy.abc import x, y
  50. >>> DiracDelta(x)
  51. DiracDelta(x)
  52. >>> DiracDelta(1)
  53. 0
  54. >>> DiracDelta(-1)
  55. 0
  56. >>> DiracDelta(pi)
  57. 0
  58. >>> DiracDelta(x - 4).subs(x, 4)
  59. DiracDelta(0)
  60. >>> diff(DiracDelta(x))
  61. DiracDelta(x, 1)
  62. >>> diff(DiracDelta(x - 1), x, 2)
  63. DiracDelta(x - 1, 2)
  64. >>> diff(DiracDelta(x**2 - 1), x, 2)
  65. 2*(2*x**2*DiracDelta(x**2 - 1, 2) + DiracDelta(x**2 - 1, 1))
  66. >>> DiracDelta(3*x).is_simple(x)
  67. True
  68. >>> DiracDelta(x**2).is_simple(x)
  69. False
  70. >>> DiracDelta((x**2 - 1)*y).expand(diracdelta=True, wrt=x)
  71. DiracDelta(x - 1)/(2*Abs(y)) + DiracDelta(x + 1)/(2*Abs(y))
  72. See Also
  73. ========
  74. Heaviside
  75. sympy.simplify.simplify.simplify, is_simple
  76. sympy.functions.special.tensor_functions.KroneckerDelta
  77. References
  78. ==========
  79. .. [1] https://mathworld.wolfram.com/DeltaFunction.html
  80. """
  81. is_real = True
  82. def fdiff(self, argindex=1):
  83. """
  84. Returns the first derivative of a DiracDelta Function.
  85. Explanation
  86. ===========
  87. The difference between ``diff()`` and ``fdiff()`` is: ``diff()`` is the
  88. user-level function and ``fdiff()`` is an object method. ``fdiff()`` is
  89. a convenience method available in the ``Function`` class. It returns
  90. the derivative of the function without considering the chain rule.
  91. ``diff(function, x)`` calls ``Function._eval_derivative`` which in turn
  92. calls ``fdiff()`` internally to compute the derivative of the function.
  93. Examples
  94. ========
  95. >>> from sympy import DiracDelta, diff
  96. >>> from sympy.abc import x
  97. >>> DiracDelta(x).fdiff()
  98. DiracDelta(x, 1)
  99. >>> DiracDelta(x, 1).fdiff()
  100. DiracDelta(x, 2)
  101. >>> DiracDelta(x**2 - 1).fdiff()
  102. DiracDelta(x**2 - 1, 1)
  103. >>> diff(DiracDelta(x, 1)).fdiff()
  104. DiracDelta(x, 3)
  105. Parameters
  106. ==========
  107. argindex : integer
  108. degree of derivative
  109. """
  110. if argindex == 1:
  111. #I didn't know if there is a better way to handle default arguments
  112. k = 0
  113. if len(self.args) > 1:
  114. k = self.args[1]
  115. return self.func(self.args[0], k + 1)
  116. else:
  117. raise ArgumentIndexError(self, argindex)
  118. @classmethod
  119. def eval(cls, arg, k=S.Zero):
  120. """
  121. Returns a simplified form or a value of DiracDelta depending on the
  122. argument passed by the DiracDelta object.
  123. Explanation
  124. ===========
  125. The ``eval()`` method is automatically called when the ``DiracDelta``
  126. class is about to be instantiated and it returns either some simplified
  127. instance or the unevaluated instance depending on the argument passed.
  128. In other words, ``eval()`` method is not needed to be called explicitly,
  129. it is being called and evaluated once the object is called.
  130. Examples
  131. ========
  132. >>> from sympy import DiracDelta, S
  133. >>> from sympy.abc import x
  134. >>> DiracDelta(x)
  135. DiracDelta(x)
  136. >>> DiracDelta(-x, 1)
  137. -DiracDelta(x, 1)
  138. >>> DiracDelta(1)
  139. 0
  140. >>> DiracDelta(5, 1)
  141. 0
  142. >>> DiracDelta(0)
  143. DiracDelta(0)
  144. >>> DiracDelta(-1)
  145. 0
  146. >>> DiracDelta(S.NaN)
  147. nan
  148. >>> DiracDelta(x - 100).subs(x, 5)
  149. 0
  150. >>> DiracDelta(x - 100).subs(x, 100)
  151. DiracDelta(0)
  152. Parameters
  153. ==========
  154. k : integer
  155. order of derivative
  156. arg : argument passed to DiracDelta
  157. """
  158. if not k.is_Integer or k.is_negative:
  159. raise ValueError("Error: the second argument of DiracDelta must be \
  160. a non-negative integer, %s given instead." % (k,))
  161. if arg is S.NaN:
  162. return S.NaN
  163. if arg.is_nonzero:
  164. return S.Zero
  165. if fuzzy_not(im(arg).is_zero):
  166. raise ValueError(filldedent('''
  167. Function defined only for Real Values.
  168. Complex part: %s found in %s .''' % (
  169. repr(im(arg)), repr(arg))))
  170. c, nc = arg.args_cnc()
  171. if c and c[0] is S.NegativeOne:
  172. # keep this fast and simple instead of using
  173. # could_extract_minus_sign
  174. if k.is_odd:
  175. return -cls(-arg, k)
  176. elif k.is_even:
  177. return cls(-arg, k) if k else cls(-arg)
  178. elif k.is_zero:
  179. return cls(arg, evaluate=False)
  180. def _eval_expand_diracdelta(self, **hints):
  181. """
  182. Compute a simplified representation of the function using
  183. property number 4. Pass ``wrt`` as a hint to expand the expression
  184. with respect to a particular variable.
  185. Explanation
  186. ===========
  187. ``wrt`` is:
  188. - a variable with respect to which a DiracDelta expression will
  189. get expanded.
  190. Examples
  191. ========
  192. >>> from sympy import DiracDelta
  193. >>> from sympy.abc import x, y
  194. >>> DiracDelta(x*y).expand(diracdelta=True, wrt=x)
  195. DiracDelta(x)/Abs(y)
  196. >>> DiracDelta(x*y).expand(diracdelta=True, wrt=y)
  197. DiracDelta(y)/Abs(x)
  198. >>> DiracDelta(x**2 + x - 2).expand(diracdelta=True, wrt=x)
  199. DiracDelta(x - 1)/3 + DiracDelta(x + 2)/3
  200. See Also
  201. ========
  202. is_simple, Diracdelta
  203. """
  204. wrt = hints.get('wrt', None)
  205. if wrt is None:
  206. free = self.free_symbols
  207. if len(free) == 1:
  208. wrt = free.pop()
  209. else:
  210. raise TypeError(filldedent('''
  211. When there is more than 1 free symbol or variable in the expression,
  212. the 'wrt' keyword is required as a hint to expand when using the
  213. DiracDelta hint.'''))
  214. if not self.args[0].has(wrt) or (len(self.args) > 1 and self.args[1] != 0 ):
  215. return self
  216. try:
  217. argroots = roots(self.args[0], wrt)
  218. result = 0
  219. valid = True
  220. darg = abs(diff(self.args[0], wrt))
  221. for r, m in argroots.items():
  222. if r.is_real is not False and m == 1:
  223. result += self.func(wrt - r)/darg.subs(wrt, r)
  224. else:
  225. # don't handle non-real and if m != 1 then
  226. # a polynomial will have a zero in the derivative (darg)
  227. # at r
  228. valid = False
  229. break
  230. if valid:
  231. return result
  232. except PolynomialError:
  233. pass
  234. return self
  235. def is_simple(self, x):
  236. """
  237. Tells whether the argument(args[0]) of DiracDelta is a linear
  238. expression in *x*.
  239. Examples
  240. ========
  241. >>> from sympy import DiracDelta, cos
  242. >>> from sympy.abc import x, y
  243. >>> DiracDelta(x*y).is_simple(x)
  244. True
  245. >>> DiracDelta(x*y).is_simple(y)
  246. True
  247. >>> DiracDelta(x**2 + x - 2).is_simple(x)
  248. False
  249. >>> DiracDelta(cos(x)).is_simple(x)
  250. False
  251. Parameters
  252. ==========
  253. x : can be a symbol
  254. See Also
  255. ========
  256. sympy.simplify.simplify.simplify, DiracDelta
  257. """
  258. p = self.args[0].as_poly(x)
  259. if p:
  260. return p.degree() == 1
  261. return False
  262. def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
  263. """
  264. Represents DiracDelta in a piecewise form.
  265. Examples
  266. ========
  267. >>> from sympy import DiracDelta, Piecewise, Symbol
  268. >>> x = Symbol('x')
  269. >>> DiracDelta(x).rewrite(Piecewise)
  270. Piecewise((DiracDelta(0), Eq(x, 0)), (0, True))
  271. >>> DiracDelta(x - 5).rewrite(Piecewise)
  272. Piecewise((DiracDelta(0), Eq(x, 5)), (0, True))
  273. >>> DiracDelta(x**2 - 5).rewrite(Piecewise)
  274. Piecewise((DiracDelta(0), Eq(x**2, 5)), (0, True))
  275. >>> DiracDelta(x - 5, 4).rewrite(Piecewise)
  276. DiracDelta(x - 5, 4)
  277. """
  278. if len(args) == 1:
  279. return Piecewise((DiracDelta(0), Eq(args[0], 0)), (0, True))
  280. def _eval_rewrite_as_SingularityFunction(self, *args, **kwargs):
  281. """
  282. Returns the DiracDelta expression written in the form of Singularity
  283. Functions.
  284. """
  285. from sympy.solvers import solve
  286. from sympy.functions.special.singularity_functions import SingularityFunction
  287. if self == DiracDelta(0):
  288. return SingularityFunction(0, 0, -1)
  289. if self == DiracDelta(0, 1):
  290. return SingularityFunction(0, 0, -2)
  291. free = self.free_symbols
  292. if len(free) == 1:
  293. x = (free.pop())
  294. if len(args) == 1:
  295. return SingularityFunction(x, solve(args[0], x)[0], -1)
  296. return SingularityFunction(x, solve(args[0], x)[0], -args[1] - 1)
  297. else:
  298. # I don't know how to handle the case for DiracDelta expressions
  299. # having arguments with more than one variable.
  300. raise TypeError(filldedent('''
  301. rewrite(SingularityFunction) does not support
  302. arguments with more that one variable.'''))
  303. ###############################################################################
  304. ############################## HEAVISIDE FUNCTION #############################
  305. ###############################################################################
  306. class Heaviside(Function):
  307. r"""
  308. Heaviside step function.
  309. Explanation
  310. ===========
  311. The Heaviside step function has the following properties:
  312. 1) $\frac{d}{d x} \theta(x) = \delta(x)$
  313. 2) $\theta(x) = \begin{cases} 0 & \text{for}\: x < 0 \\ \frac{1}{2} &
  314. \text{for}\: x = 0 \\1 & \text{for}\: x > 0 \end{cases}$
  315. 3) $\frac{d}{d x} \max(x, 0) = \theta(x)$
  316. Heaviside(x) is printed as $\theta(x)$ with the SymPy LaTeX printer.
  317. The value at 0 is set differently in different fields. SymPy uses 1/2,
  318. which is a convention from electronics and signal processing, and is
  319. consistent with solving improper integrals by Fourier transform and
  320. convolution.
  321. To specify a different value of Heaviside at ``x=0``, a second argument
  322. can be given. Using ``Heaviside(x, nan)`` gives an expression that will
  323. evaluate to nan for x=0.
  324. .. versionchanged:: 1.9 ``Heaviside(0)`` now returns 1/2 (before: undefined)
  325. Examples
  326. ========
  327. >>> from sympy import Heaviside, nan
  328. >>> from sympy.abc import x
  329. >>> Heaviside(9)
  330. 1
  331. >>> Heaviside(-9)
  332. 0
  333. >>> Heaviside(0)
  334. 1/2
  335. >>> Heaviside(0, nan)
  336. nan
  337. >>> (Heaviside(x) + 1).replace(Heaviside(x), Heaviside(x, 1))
  338. Heaviside(x, 1) + 1
  339. See Also
  340. ========
  341. DiracDelta
  342. References
  343. ==========
  344. .. [1] https://mathworld.wolfram.com/HeavisideStepFunction.html
  345. .. [2] https://dlmf.nist.gov/1.16#iv
  346. """
  347. is_real = True
  348. def fdiff(self, argindex=1):
  349. """
  350. Returns the first derivative of a Heaviside Function.
  351. Examples
  352. ========
  353. >>> from sympy import Heaviside, diff
  354. >>> from sympy.abc import x
  355. >>> Heaviside(x).fdiff()
  356. DiracDelta(x)
  357. >>> Heaviside(x**2 - 1).fdiff()
  358. DiracDelta(x**2 - 1)
  359. >>> diff(Heaviside(x)).fdiff()
  360. DiracDelta(x, 1)
  361. Parameters
  362. ==========
  363. argindex : integer
  364. order of derivative
  365. """
  366. if argindex == 1:
  367. return DiracDelta(self.args[0])
  368. else:
  369. raise ArgumentIndexError(self, argindex)
  370. def __new__(cls, arg, H0=S.Half, **options):
  371. if isinstance(H0, Heaviside) and len(H0.args) == 1:
  372. H0 = S.Half
  373. return super(cls, cls).__new__(cls, arg, H0, **options)
  374. @property
  375. def pargs(self):
  376. """Args without default S.Half"""
  377. args = self.args
  378. if args[1] is S.Half:
  379. args = args[:1]
  380. return args
  381. @classmethod
  382. def eval(cls, arg, H0=S.Half):
  383. """
  384. Returns a simplified form or a value of Heaviside depending on the
  385. argument passed by the Heaviside object.
  386. Explanation
  387. ===========
  388. The ``eval()`` method is automatically called when the ``Heaviside``
  389. class is about to be instantiated and it returns either some simplified
  390. instance or the unevaluated instance depending on the argument passed.
  391. In other words, ``eval()`` method is not needed to be called explicitly,
  392. it is being called and evaluated once the object is called.
  393. Examples
  394. ========
  395. >>> from sympy import Heaviside, S
  396. >>> from sympy.abc import x
  397. >>> Heaviside(x)
  398. Heaviside(x)
  399. >>> Heaviside(19)
  400. 1
  401. >>> Heaviside(0)
  402. 1/2
  403. >>> Heaviside(0, 1)
  404. 1
  405. >>> Heaviside(-5)
  406. 0
  407. >>> Heaviside(S.NaN)
  408. nan
  409. >>> Heaviside(x - 100).subs(x, 5)
  410. 0
  411. >>> Heaviside(x - 100).subs(x, 105)
  412. 1
  413. Parameters
  414. ==========
  415. arg : argument passed by Heaviside object
  416. H0 : value of Heaviside(0)
  417. """
  418. if arg.is_extended_negative:
  419. return S.Zero
  420. elif arg.is_extended_positive:
  421. return S.One
  422. elif arg.is_zero:
  423. return H0
  424. elif arg is S.NaN:
  425. return S.NaN
  426. elif fuzzy_not(im(arg).is_zero):
  427. raise ValueError("Function defined only for Real Values. Complex part: %s found in %s ." % (repr(im(arg)), repr(arg)) )
  428. def _eval_rewrite_as_Piecewise(self, arg, H0=None, **kwargs):
  429. """
  430. Represents Heaviside in a Piecewise form.
  431. Examples
  432. ========
  433. >>> from sympy import Heaviside, Piecewise, Symbol, nan
  434. >>> x = Symbol('x')
  435. >>> Heaviside(x).rewrite(Piecewise)
  436. Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, True))
  437. >>> Heaviside(x,nan).rewrite(Piecewise)
  438. Piecewise((0, x < 0), (nan, Eq(x, 0)), (1, True))
  439. >>> Heaviside(x - 5).rewrite(Piecewise)
  440. Piecewise((0, x < 5), (1/2, Eq(x, 5)), (1, True))
  441. >>> Heaviside(x**2 - 1).rewrite(Piecewise)
  442. Piecewise((0, x**2 < 1), (1/2, Eq(x**2, 1)), (1, True))
  443. """
  444. if H0 == 0:
  445. return Piecewise((0, arg <= 0), (1, True))
  446. if H0 == 1:
  447. return Piecewise((0, arg < 0), (1, True))
  448. return Piecewise((0, arg < 0), (H0, Eq(arg, 0)), (1, True))
  449. def _eval_rewrite_as_sign(self, arg, H0=S.Half, **kwargs):
  450. """
  451. Represents the Heaviside function in the form of sign function.
  452. Explanation
  453. ===========
  454. The value of Heaviside(0) must be 1/2 for rewriting as sign to be
  455. strictly equivalent. For easier usage, we also allow this rewriting
  456. when Heaviside(0) is undefined.
  457. Examples
  458. ========
  459. >>> from sympy import Heaviside, Symbol, sign, nan
  460. >>> x = Symbol('x', real=True)
  461. >>> y = Symbol('y')
  462. >>> Heaviside(x).rewrite(sign)
  463. sign(x)/2 + 1/2
  464. >>> Heaviside(x, 0).rewrite(sign)
  465. Piecewise((sign(x)/2 + 1/2, Ne(x, 0)), (0, True))
  466. >>> Heaviside(x, nan).rewrite(sign)
  467. Piecewise((sign(x)/2 + 1/2, Ne(x, 0)), (nan, True))
  468. >>> Heaviside(x - 2).rewrite(sign)
  469. sign(x - 2)/2 + 1/2
  470. >>> Heaviside(x**2 - 2*x + 1).rewrite(sign)
  471. sign(x**2 - 2*x + 1)/2 + 1/2
  472. >>> Heaviside(y).rewrite(sign)
  473. Heaviside(y)
  474. >>> Heaviside(y**2 - 2*y + 1).rewrite(sign)
  475. Heaviside(y**2 - 2*y + 1)
  476. See Also
  477. ========
  478. sign
  479. """
  480. if arg.is_extended_real:
  481. pw1 = Piecewise(
  482. ((sign(arg) + 1)/2, Ne(arg, 0)),
  483. (Heaviside(0, H0=H0), True))
  484. pw2 = Piecewise(
  485. ((sign(arg) + 1)/2, Eq(Heaviside(0, H0=H0), S.Half)),
  486. (pw1, True))
  487. return pw2
  488. def _eval_rewrite_as_SingularityFunction(self, args, H0=S.Half, **kwargs):
  489. """
  490. Returns the Heaviside expression written in the form of Singularity
  491. Functions.
  492. """
  493. from sympy.solvers import solve
  494. from sympy.functions.special.singularity_functions import SingularityFunction
  495. if self == Heaviside(0):
  496. return SingularityFunction(0, 0, 0)
  497. free = self.free_symbols
  498. if len(free) == 1:
  499. x = (free.pop())
  500. return SingularityFunction(x, solve(args, x)[0], 0)
  501. # TODO
  502. # ((x - 5)**3*Heaviside(x - 5)).rewrite(SingularityFunction) should output
  503. # SingularityFunction(x, 5, 0) instead of (x - 5)**3*SingularityFunction(x, 5, 0)
  504. else:
  505. # I don't know how to handle the case for Heaviside expressions
  506. # having arguments with more than one variable.
  507. raise TypeError(filldedent('''
  508. rewrite(SingularityFunction) does not
  509. support arguments with more that one variable.'''))