finite_diff.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. """
  2. Finite difference weights
  3. =========================
  4. This module implements an algorithm for efficient generation of finite
  5. difference weights for ordinary differentials of functions for
  6. derivatives from 0 (interpolation) up to arbitrary order.
  7. The core algorithm is provided in the finite difference weight generating
  8. function (``finite_diff_weights``), and two convenience functions are provided
  9. for:
  10. - estimating a derivative (or interpolate) directly from a series of points
  11. is also provided (``apply_finite_diff``).
  12. - differentiating by using finite difference approximations
  13. (``differentiate_finite``).
  14. """
  15. from sympy.core.function import Derivative
  16. from sympy.core.singleton import S
  17. from sympy.core.function import Subs
  18. from sympy.core.traversal import preorder_traversal
  19. from sympy.utilities.exceptions import sympy_deprecation_warning
  20. from sympy.utilities.iterables import iterable
  21. def finite_diff_weights(order, x_list, x0=S.One):
  22. """
  23. Calculates the finite difference weights for an arbitrarily spaced
  24. one-dimensional grid (``x_list``) for derivatives at ``x0`` of order
  25. 0, 1, ..., up to ``order`` using a recursive formula. Order of accuracy
  26. is at least ``len(x_list) - order``, if ``x_list`` is defined correctly.
  27. Parameters
  28. ==========
  29. order: int
  30. Up to what derivative order weights should be calculated.
  31. 0 corresponds to interpolation.
  32. x_list: sequence
  33. Sequence of (unique) values for the independent variable.
  34. It is useful (but not necessary) to order ``x_list`` from
  35. nearest to furthest from ``x0``; see examples below.
  36. x0: Number or Symbol
  37. Root or value of the independent variable for which the finite
  38. difference weights should be generated. Default is ``S.One``.
  39. Returns
  40. =======
  41. list
  42. A list of sublists, each corresponding to coefficients for
  43. increasing derivative order, and each containing lists of
  44. coefficients for increasing subsets of x_list.
  45. Examples
  46. ========
  47. >>> from sympy import finite_diff_weights, S
  48. >>> res = finite_diff_weights(1, [-S(1)/2, S(1)/2, S(3)/2, S(5)/2], 0)
  49. >>> res
  50. [[[1, 0, 0, 0],
  51. [1/2, 1/2, 0, 0],
  52. [3/8, 3/4, -1/8, 0],
  53. [5/16, 15/16, -5/16, 1/16]],
  54. [[0, 0, 0, 0],
  55. [-1, 1, 0, 0],
  56. [-1, 1, 0, 0],
  57. [-23/24, 7/8, 1/8, -1/24]]]
  58. >>> res[0][-1] # FD weights for 0th derivative, using full x_list
  59. [5/16, 15/16, -5/16, 1/16]
  60. >>> res[1][-1] # FD weights for 1st derivative
  61. [-23/24, 7/8, 1/8, -1/24]
  62. >>> res[1][-2] # FD weights for 1st derivative, using x_list[:-1]
  63. [-1, 1, 0, 0]
  64. >>> res[1][-1][0] # FD weight for 1st deriv. for x_list[0]
  65. -23/24
  66. >>> res[1][-1][1] # FD weight for 1st deriv. for x_list[1], etc.
  67. 7/8
  68. Each sublist contains the most accurate formula at the end.
  69. Note, that in the above example ``res[1][1]`` is the same as ``res[1][2]``.
  70. Since res[1][2] has an order of accuracy of
  71. ``len(x_list[:3]) - order = 3 - 1 = 2``, the same is true for ``res[1][1]``!
  72. >>> res = finite_diff_weights(1, [S(0), S(1), -S(1), S(2), -S(2)], 0)[1]
  73. >>> res
  74. [[0, 0, 0, 0, 0],
  75. [-1, 1, 0, 0, 0],
  76. [0, 1/2, -1/2, 0, 0],
  77. [-1/2, 1, -1/3, -1/6, 0],
  78. [0, 2/3, -2/3, -1/12, 1/12]]
  79. >>> res[0] # no approximation possible, using x_list[0] only
  80. [0, 0, 0, 0, 0]
  81. >>> res[1] # classic forward step approximation
  82. [-1, 1, 0, 0, 0]
  83. >>> res[2] # classic centered approximation
  84. [0, 1/2, -1/2, 0, 0]
  85. >>> res[3:] # higher order approximations
  86. [[-1/2, 1, -1/3, -1/6, 0], [0, 2/3, -2/3, -1/12, 1/12]]
  87. Let us compare this to a differently defined ``x_list``. Pay attention to
  88. ``foo[i][k]`` corresponding to the gridpoint defined by ``x_list[k]``.
  89. >>> foo = finite_diff_weights(1, [-S(2), -S(1), S(0), S(1), S(2)], 0)[1]
  90. >>> foo
  91. [[0, 0, 0, 0, 0],
  92. [-1, 1, 0, 0, 0],
  93. [1/2, -2, 3/2, 0, 0],
  94. [1/6, -1, 1/2, 1/3, 0],
  95. [1/12, -2/3, 0, 2/3, -1/12]]
  96. >>> foo[1] # not the same and of lower accuracy as res[1]!
  97. [-1, 1, 0, 0, 0]
  98. >>> foo[2] # classic double backward step approximation
  99. [1/2, -2, 3/2, 0, 0]
  100. >>> foo[4] # the same as res[4]
  101. [1/12, -2/3, 0, 2/3, -1/12]
  102. Note that, unless you plan on using approximations based on subsets of
  103. ``x_list``, the order of gridpoints does not matter.
  104. The capability to generate weights at arbitrary points can be
  105. used e.g. to minimize Runge's phenomenon by using Chebyshev nodes:
  106. >>> from sympy import cos, symbols, pi, simplify
  107. >>> N, (h, x) = 4, symbols('h x')
  108. >>> x_list = [x+h*cos(i*pi/(N)) for i in range(N,-1,-1)] # chebyshev nodes
  109. >>> print(x_list)
  110. [-h + x, -sqrt(2)*h/2 + x, x, sqrt(2)*h/2 + x, h + x]
  111. >>> mycoeffs = finite_diff_weights(1, x_list, 0)[1][4]
  112. >>> [simplify(c) for c in mycoeffs] #doctest: +NORMALIZE_WHITESPACE
  113. [(h**3/2 + h**2*x - 3*h*x**2 - 4*x**3)/h**4,
  114. (-sqrt(2)*h**3 - 4*h**2*x + 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
  115. (6*h**2*x - 8*x**3)/h**4,
  116. (sqrt(2)*h**3 - 4*h**2*x - 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
  117. (-h**3/2 + h**2*x + 3*h*x**2 - 4*x**3)/h**4]
  118. Notes
  119. =====
  120. If weights for a finite difference approximation of 3rd order
  121. derivative is wanted, weights for 0th, 1st and 2nd order are
  122. calculated "for free", so are formulae using subsets of ``x_list``.
  123. This is something one can take advantage of to save computational cost.
  124. Be aware that one should define ``x_list`` from nearest to furthest from
  125. ``x0``. If not, subsets of ``x_list`` will yield poorer approximations,
  126. which might not grand an order of accuracy of ``len(x_list) - order``.
  127. See also
  128. ========
  129. sympy.calculus.finite_diff.apply_finite_diff
  130. References
  131. ==========
  132. .. [1] Generation of Finite Difference Formulas on Arbitrarily Spaced
  133. Grids, Bengt Fornberg; Mathematics of computation; 51; 184;
  134. (1988); 699-706; doi:10.1090/S0025-5718-1988-0935077-0
  135. """
  136. # The notation below closely corresponds to the one used in the paper.
  137. order = S(order)
  138. if not order.is_number:
  139. raise ValueError("Cannot handle symbolic order.")
  140. if order < 0:
  141. raise ValueError("Negative derivative order illegal.")
  142. if int(order) != order:
  143. raise ValueError("Non-integer order illegal")
  144. M = order
  145. N = len(x_list) - 1
  146. delta = [[[0 for nu in range(N+1)] for n in range(N+1)] for
  147. m in range(M+1)]
  148. delta[0][0][0] = S.One
  149. c1 = S.One
  150. for n in range(1, N+1):
  151. c2 = S.One
  152. for nu in range(0, n):
  153. c3 = x_list[n]-x_list[nu]
  154. c2 = c2 * c3
  155. if n <= M:
  156. delta[n][n-1][nu] = 0
  157. for m in range(0, min(n, M)+1):
  158. delta[m][n][nu] = (x_list[n]-x0)*delta[m][n-1][nu] -\
  159. m*delta[m-1][n-1][nu]
  160. delta[m][n][nu] /= c3
  161. for m in range(0, min(n, M)+1):
  162. delta[m][n][n] = c1/c2*(m*delta[m-1][n-1][n-1] -
  163. (x_list[n-1]-x0)*delta[m][n-1][n-1])
  164. c1 = c2
  165. return delta
  166. def apply_finite_diff(order, x_list, y_list, x0=S.Zero):
  167. """
  168. Calculates the finite difference approximation of
  169. the derivative of requested order at ``x0`` from points
  170. provided in ``x_list`` and ``y_list``.
  171. Parameters
  172. ==========
  173. order: int
  174. order of derivative to approximate. 0 corresponds to interpolation.
  175. x_list: sequence
  176. Sequence of (unique) values for the independent variable.
  177. y_list: sequence
  178. The function value at corresponding values for the independent
  179. variable in x_list.
  180. x0: Number or Symbol
  181. At what value of the independent variable the derivative should be
  182. evaluated. Defaults to 0.
  183. Returns
  184. =======
  185. sympy.core.add.Add or sympy.core.numbers.Number
  186. The finite difference expression approximating the requested
  187. derivative order at ``x0``.
  188. Examples
  189. ========
  190. >>> from sympy import apply_finite_diff
  191. >>> cube = lambda arg: (1.0*arg)**3
  192. >>> xlist = range(-3,3+1)
  193. >>> apply_finite_diff(2, xlist, map(cube, xlist), 2) - 12 # doctest: +SKIP
  194. -3.55271367880050e-15
  195. we see that the example above only contain rounding errors.
  196. apply_finite_diff can also be used on more abstract objects:
  197. >>> from sympy import IndexedBase, Idx
  198. >>> x, y = map(IndexedBase, 'xy')
  199. >>> i = Idx('i')
  200. >>> x_list, y_list = zip(*[(x[i+j], y[i+j]) for j in range(-1,2)])
  201. >>> apply_finite_diff(1, x_list, y_list, x[i])
  202. ((x[i + 1] - x[i])/(-x[i - 1] + x[i]) - 1)*y[i]/(x[i + 1] - x[i]) -
  203. (x[i + 1] - x[i])*y[i - 1]/((x[i + 1] - x[i - 1])*(-x[i - 1] + x[i])) +
  204. (-x[i - 1] + x[i])*y[i + 1]/((x[i + 1] - x[i - 1])*(x[i + 1] - x[i]))
  205. Notes
  206. =====
  207. Order = 0 corresponds to interpolation.
  208. Only supply so many points you think makes sense
  209. to around x0 when extracting the derivative (the function
  210. need to be well behaved within that region). Also beware
  211. of Runge's phenomenon.
  212. See also
  213. ========
  214. sympy.calculus.finite_diff.finite_diff_weights
  215. References
  216. ==========
  217. Fortran 90 implementation with Python interface for numerics: finitediff_
  218. .. _finitediff: https://github.com/bjodah/finitediff
  219. """
  220. # In the original paper the following holds for the notation:
  221. # M = order
  222. # N = len(x_list) - 1
  223. N = len(x_list) - 1
  224. if len(x_list) != len(y_list):
  225. raise ValueError("x_list and y_list not equal in length.")
  226. delta = finite_diff_weights(order, x_list, x0)
  227. derivative = 0
  228. for nu in range(0, len(x_list)):
  229. derivative += delta[order][N][nu]*y_list[nu]
  230. return derivative
  231. def _as_finite_diff(derivative, points=1, x0=None, wrt=None):
  232. """
  233. Returns an approximation of a derivative of a function in
  234. the form of a finite difference formula. The expression is a
  235. weighted sum of the function at a number of discrete values of
  236. (one of) the independent variable(s).
  237. Parameters
  238. ==========
  239. derivative: a Derivative instance
  240. points: sequence or coefficient, optional
  241. If sequence: discrete values (length >= order+1) of the
  242. independent variable used for generating the finite
  243. difference weights.
  244. If it is a coefficient, it will be used as the step-size
  245. for generating an equidistant sequence of length order+1
  246. centered around ``x0``. default: 1 (step-size 1)
  247. x0: number or Symbol, optional
  248. the value of the independent variable (``wrt``) at which the
  249. derivative is to be approximated. Default: same as ``wrt``.
  250. wrt: Symbol, optional
  251. "with respect to" the variable for which the (partial)
  252. derivative is to be approximated for. If not provided it
  253. is required that the Derivative is ordinary. Default: ``None``.
  254. Examples
  255. ========
  256. >>> from sympy import symbols, Function, exp, sqrt, Symbol
  257. >>> from sympy.calculus.finite_diff import _as_finite_diff
  258. >>> x, h = symbols('x h')
  259. >>> f = Function('f')
  260. >>> _as_finite_diff(f(x).diff(x))
  261. -f(x - 1/2) + f(x + 1/2)
  262. The default step size and number of points are 1 and ``order + 1``
  263. respectively. We can change the step size by passing a symbol
  264. as a parameter:
  265. >>> _as_finite_diff(f(x).diff(x), h)
  266. -f(-h/2 + x)/h + f(h/2 + x)/h
  267. We can also specify the discretized values to be used in a sequence:
  268. >>> _as_finite_diff(f(x).diff(x), [x, x+h, x+2*h])
  269. -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h)
  270. The algorithm is not restricted to use equidistant spacing, nor
  271. do we need to make the approximation around ``x0``, but we can get
  272. an expression estimating the derivative at an offset:
  273. >>> e, sq2 = exp(1), sqrt(2)
  274. >>> xl = [x-h, x+h, x+e*h]
  275. >>> _as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2)
  276. 2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) +
  277. (-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) +
  278. (-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h)
  279. Partial derivatives are also supported:
  280. >>> y = Symbol('y')
  281. >>> d2fdxdy=f(x,y).diff(x,y)
  282. >>> _as_finite_diff(d2fdxdy, wrt=x)
  283. -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y)
  284. See also
  285. ========
  286. sympy.calculus.finite_diff.apply_finite_diff
  287. sympy.calculus.finite_diff.finite_diff_weights
  288. """
  289. if derivative.is_Derivative:
  290. pass
  291. elif derivative.is_Atom:
  292. return derivative
  293. else:
  294. return derivative.fromiter(
  295. [_as_finite_diff(ar, points, x0, wrt) for ar
  296. in derivative.args], **derivative.assumptions0)
  297. if wrt is None:
  298. old = None
  299. for v in derivative.variables:
  300. if old is v:
  301. continue
  302. derivative = _as_finite_diff(derivative, points, x0, v)
  303. old = v
  304. return derivative
  305. order = derivative.variables.count(wrt)
  306. if x0 is None:
  307. x0 = wrt
  308. if not iterable(points):
  309. if getattr(points, 'is_Function', False) and wrt in points.args:
  310. points = points.subs(wrt, x0)
  311. # points is simply the step-size, let's make it a
  312. # equidistant sequence centered around x0
  313. if order % 2 == 0:
  314. # even order => odd number of points, grid point included
  315. points = [x0 + points*i for i
  316. in range(-order//2, order//2 + 1)]
  317. else:
  318. # odd order => even number of points, half-way wrt grid point
  319. points = [x0 + points*S(i)/2 for i
  320. in range(-order, order + 1, 2)]
  321. others = [wrt, 0]
  322. for v in set(derivative.variables):
  323. if v == wrt:
  324. continue
  325. others += [v, derivative.variables.count(v)]
  326. if len(points) < order+1:
  327. raise ValueError("Too few points for order %d" % order)
  328. return apply_finite_diff(order, points, [
  329. Derivative(derivative.expr.subs({wrt: x}), *others) for
  330. x in points], x0)
  331. def differentiate_finite(expr, *symbols,
  332. points=1, x0=None, wrt=None, evaluate=False):
  333. r""" Differentiate expr and replace Derivatives with finite differences.
  334. Parameters
  335. ==========
  336. expr : expression
  337. \*symbols : differentiate with respect to symbols
  338. points: sequence, coefficient or undefined function, optional
  339. see ``Derivative.as_finite_difference``
  340. x0: number or Symbol, optional
  341. see ``Derivative.as_finite_difference``
  342. wrt: Symbol, optional
  343. see ``Derivative.as_finite_difference``
  344. Examples
  345. ========
  346. >>> from sympy import sin, Function, differentiate_finite
  347. >>> from sympy.abc import x, y, h
  348. >>> f, g = Function('f'), Function('g')
  349. >>> differentiate_finite(f(x)*g(x), x, points=[x-h, x+h])
  350. -f(-h + x)*g(-h + x)/(2*h) + f(h + x)*g(h + x)/(2*h)
  351. ``differentiate_finite`` works on any expression, including the expressions
  352. with embedded derivatives:
  353. >>> differentiate_finite(f(x) + sin(x), x, 2)
  354. -2*f(x) + f(x - 1) + f(x + 1) - 2*sin(x) + sin(x - 1) + sin(x + 1)
  355. >>> differentiate_finite(f(x, y), x, y)
  356. f(x - 1/2, y - 1/2) - f(x - 1/2, y + 1/2) - f(x + 1/2, y - 1/2) + f(x + 1/2, y + 1/2)
  357. >>> differentiate_finite(f(x)*g(x).diff(x), x)
  358. (-g(x) + g(x + 1))*f(x + 1/2) - (g(x) - g(x - 1))*f(x - 1/2)
  359. To make finite difference with non-constant discretization step use
  360. undefined functions:
  361. >>> dx = Function('dx')
  362. >>> differentiate_finite(f(x)*g(x).diff(x), points=dx(x))
  363. -(-g(x - dx(x)/2 - dx(x - dx(x)/2)/2)/dx(x - dx(x)/2) +
  364. g(x - dx(x)/2 + dx(x - dx(x)/2)/2)/dx(x - dx(x)/2))*f(x - dx(x)/2)/dx(x) +
  365. (-g(x + dx(x)/2 - dx(x + dx(x)/2)/2)/dx(x + dx(x)/2) +
  366. g(x + dx(x)/2 + dx(x + dx(x)/2)/2)/dx(x + dx(x)/2))*f(x + dx(x)/2)/dx(x)
  367. """
  368. if any(term.is_Derivative for term in list(preorder_traversal(expr))):
  369. evaluate = False
  370. Dexpr = expr.diff(*symbols, evaluate=evaluate)
  371. if evaluate:
  372. sympy_deprecation_warning("""
  373. The evaluate flag to differentiate_finite() is deprecated.
  374. evaluate=True expands the intermediate derivatives before computing
  375. differences, but this usually not what you want, as it does not
  376. satisfy the product rule.
  377. """,
  378. deprecated_since_version="1.5",
  379. active_deprecations_target="deprecated-differentiate_finite-evaluate",
  380. )
  381. return Dexpr.replace(
  382. lambda arg: arg.is_Derivative,
  383. lambda arg: arg.as_finite_difference(points=points, x0=x0, wrt=wrt))
  384. else:
  385. DFexpr = Dexpr.as_finite_difference(points=points, x0=x0, wrt=wrt)
  386. return DFexpr.replace(
  387. lambda arg: isinstance(arg, Subs),
  388. lambda arg: arg.expr.as_finite_difference(
  389. points=points, x0=arg.point[0], wrt=arg.variables[0]))