4. Derivación Numérica.#
Partiremos del polinomio interpolador de Lagrange en \(N+1\) puntos de interpolación equiespaciados \(\{x_i\} \text{ con } i= 0,...,N \) y de los valores de la función a aproximar en dichos puntos \(\{f(x_i)\} \text{ con } i= 0,...,N \). Vimos que dados \(N+1\) puntos \(\{(x_0,f(x_0)), (x_1,f(x_1)), ..., (x_N, f(x_N))\}\) podemos construir una aproximación polinómica a la funciín \(f(x)\) utilizando los polinomios de Lagrange:
La forma \(I_{N} (x)\) se conoce como interpolante de Lagrange, los polinomios \(\ell_{j} (x)\) son los polinomios de Lagrange que tienen la expresión siguiente:
y los coeficientes \(b_j\) verifican:
con lo que, el polinomio interpolante, resulta ser:
Si queremos utilizar el polinomio interpolante para aproximar la derivada basta derivar a ambos lados de la igualdad:
y lo mismo para aproximar la derivada segunda:
Estas derivadas se pueden evaluar para cualquier valor de \(x\) del dominio de definición de \(f\) aunque, en la práctica, se presentan evaluadas en los puntos de interpolación. Si \(x_k\) es un punto de interpolación, las expresiones anteriores para las derivadas primera y segunda resultan como:
y
En las expresiones anteriores se observa para obtener las fórmulas de derivación propuestas, es necesario derivar los polinomios de Lagrange y evaluarlos en los puntos de interpolación. Otro aspecto a tener en cuenta en la obtención de estas fórmulas es que se suelen elegir números pequeños de puntos de interpolación alrededor de los puntos \(x_k\).
Vamos a empezar con las fórmulas de derivada primera numérica a partir del polinomio interpolador a través de dos puntos.
4.1. Fórmulas de derivación con 2 puntos.#
Dados dos puntos \(\{(x_0,f(x_0)), (x_1,f(x_1))\}\) el polinomio interpolador va a ser, desde un punto de vista geométrico, la recta que pasa por ambos.
Definimos \( x_1 - x_0 = \Delta x\), así, la fórmula se puede reescribir como:
4.1.1. Derivada primera.#
Derivamos la expresión:
Al ser un polinomio de grado 1, una recta, la derivada toma un valor constante, de modo que podemos escribir:
Esta expresión debe resultarnos familiar, si escribimos \(h = \Delta x\) y por tanto \(x_1 = x_0 + h\), resulta:
En este caso no podemos seguir derivando porque al haber aproximado con una recta, la derivada primera es una constante y la segunda se anula.
4.2. Fórmulas de derivación con 3 puntos.#
Partimos del polinomio interpolador con tres puntos \(\{(x_0,f(x_0)), (x_1,f(x_1)), (x_2,f(x_2))\}\):
desarrollando los polinomios de Lagrange:
Como hemos escogido puntos equiespaciados: \( \{x_0, x_1, x_2 \}\) con \( x_1 - x_0 = x_2 - x_1 = \Delta x\), la fórmula se puede reescribir como:
4.2.1. Derivada primera.#
Derivamos la expresión:
Evaluamos en los puntos:
Cuando la derivada se particulariza en en el punto de interpolación \(x_k = x_0\) se obtiene la fórmula de derivada primera descentrada adelantada:
Cuando la derivada se particulariza en en el punto de interpolación \(x_k = x_1\) se obtiene la fórmula de derivada primera centrada:
Por último cuando la derivada se particulariza en en el punto de interpolación \(x_k = x_2\) se obtiene la fórmula de derivada primera descentrada atrasada:
4.2.2. Derivada segunda.#
En esta ocasión si podemos calcular una aproximación de la derivada segunda a partir del polinomio interpolador de Lagrange de segundo orden.
Si volvemos a derivar \(I'_{2} (x)\) con respecto a x obtenemos:
El valor aproxiomado es constante e independiente del punto en qué se aplique.
4.3. Implementación en Python.#
Vamos a empezar con un primer ejemplo en una función sencilla:
supongamos que tenemos 9 puntos de interpolación equiespaciados en el intervalo \([-\pi, \pi]\) y a través de ellos vamos a aproximar el valor de la derivada en dichos puntos.
# Importamos los módulos necesarios
import numpy as np
import scipy as sp
from matplotlib import pyplot as plt
n = 9
m = 100
x_i = np.linspace(-np.pi, np.pi, n)
y_i = np.sin(x_i)
z_i = np.cos(x_i)
x = np.linspace(min(x_i)-0.02, max(x_i)+0.02, m)
### Representación gráfica
ax = plt.scatter(x_i,y_i, color="red")

Lo primero que vamos a probar va a ser la aproximación de la derivada usando el conjunto de puntos de interpolación a través de la fórmula:
Necesitamos calcular \(\ell'_{j} (x)\). Teniendo en cuenta que:
podemos derivar para obtener:
que podemos implementar en Python prestando atención a los índices del productorio anidado al sumatorio
"""
# Polinomio de Lagrange L_j(x)
def L_j(j, x_i, x):
n = np.size(x_i)
Lj = 1.
for i in range(n):
if i!=j: Lj *= (x-x_i[i])/(x_i[j]-x_i[i])
return Lj
def I_N(x, x_i, y_i):
n = np.size(x_i)
S = 0.
for i in range(n):
S += y_i[i]*L_j(i, x_i, x)
return S
"""
# Derivada primera del polinomio de Lagrange L'_j(x)
def L_der_j(j, x_i, x):
n = np.size(x_i)
Lj = 0.
for i in range(n):
prod = 1.
for k in range(n):
if (k!=j) & (k!=i):
prod *= (x-x_i[k])/(x_i[j]-x_i[k])
if i!=j: Lj += prod/(x_i[j]-x_i[i])
return Lj
def I_der_N(x, x_i, y_i):
n = np.size(x_i)
S = 0.
for i in range(n):
S += y_i[i]*L_der_j(i, x_i, x)
return S
Para ver si la implementación es correcta comparamos la aproximación de la derivada a partir de los puntos de la función \(f(x) = sin(x)\) con el valor del \(cos(x)\)
### Representación gráfica
fig = plt.figure()
fig, ax = plt.subplots()
ax.scatter(x_i,z_i, color="red")
ax.plot(x,I_der_N(x, x_i, y_i), color="black",linestyle='--',label="$I'(x)$")
ax.legend()
plt.show()
<Figure size 640x480 with 0 Axes>

Esta forma de derivar no deja de ser un ejercicio interesante. Normalmente el problema numérico que vamos a resolver es el cáculo de la derivada de una función en un punto a partir del valor de la función en puntos próximos. Es decir la implementación de estas aproximaciones en funciones de Python cuando los argumentos son la función \(f(x)\) y un punto de evaluación \(x_j\).
Vamos a resumir las fórmulas de las derivadas numéricas para una función \(f:\mathbb{R} \rightarrow \mathbb{R}\) en un punto \(x_j\), obtenidas a partir de tres puntos de interpolación de una partición equiespaciada.
- Derivada primera centrada:
- Derivada primera descentrada adelantada:
- Derivada primera descentrada atrasada:
- Derivada segunda centrada:
Podemos implementar cada una de ellas en una función diferente:
def f_prima_centrada_3_puntos(x, dx, f):
derivada = (f(x+dx)-f(x-dx))/(2*dx)
return derivada
def f_prima_adelantada_3_puntos(x, dx, f):
derivada = (-3.*f(x)+4.*f(x+dx)-f(x+2*dx))/(2*dx)
return derivada
def f_prima_atrasada_3_puntos(x, dx, f):
derivada = (3.*f(x)-4.*f(x-dx)+f(x-2*dx))/(2*dx)
return derivada
def f_prima_segunda_3_puntos(x, dx, f):
derivada = (f(x+dx)-2.*f(x)+f(x-dx))/(dx**2)
return derivada
def f_seno(x):
return np.sin(x)
### Representación gráfica
fig = plt.figure()
fig, ax = plt.subplots()
ax.scatter(x_i,z_i, color="red",label='$cos(x_i)$')
ax.plot(x,f_prima_centrada_3_puntos(x, 0.5, f_seno), color="black",label='Centrada dx = 0.5')
ax.plot(x,f_prima_adelantada_3_puntos(x, 0.3, f_seno), color="blue",linestyle='dashed',label='Adelantada dx = 0.3')
ax.plot(x,f_prima_atrasada_3_puntos(x, 0.2, f_seno), color="green",linestyle='dotted',label='Retrasada dx = 0.2')
ax.legend()
plt.show()
<Figure size 640x480 with 0 Axes>

4.4. Matrices de derivación.#
Vamos a suponer ahora que queremos calcular la derivada numerica de una función \(f(x)\) en un intervalo \(\left[ a, b \right]\). El proceso que seguiremos será discretizar el intervalo en un conjunto de puntos equiespaciados \(\left[ a=x_0, x_1, x_2,...,x_N = b \right]\) con \(\Delta x = x_{j+1}-x_j \) y en cada uno de los puntos calcularemos su derivada numérica con las fórmulas ya vistas.
Si pensamos en forma vectorial-matricial tenemos por una parte el vector de puntos equiespaciados en que hemos discretizado \(\left[ a, b \right]\):
el vector \(F\) donde guardamos el valor de la funcion \(f(x)\) en los puntos de \(X\):
y queremos construir el vector \(dF\) con las aproximaciones de la derivada \(f'(x)\) en los puntos de \(X\) a partir de las fórmulas ya vistas. Supongamos, por ejemplo, que voy a emplear la fórmula de derivada centrada con tres puntos para aproximar la derivada en cada punto. Es decir que:
Esta fórmula será válida para todos los puntos ‘interiores’ del vector \(i = 1,...,N-1\) ya que para \(x_0\) no existe \(x_{i-1}\) y para \(x_N\) no existe \(x_{i+1}\). En los extremos tendremos que ‘descentrar el esquema’ y utilizar derivada adelantada en \(x_0\) y derivada atrasada en \(x_N\):
Si escribimos en forma matricial las ecuaciones anteriores tendremos:
Vamos a probar su funcionamiento derivando la función:
en el intervalo \([0, \pi]\) utilizando 21 puntos para discretizar dicho intervalo.
n = 11
x_i = np.linspace(0, np.pi, n)
dx = np.pi/(n-1)
# Defino la matriz de derivación D
D = np.zeros((n,n), dtype = float)
D[0,0:3] = np.array([-3.,4.,-1.])
for i in range(1,n-1):
D[i,i-1] = -1.
D[i,i+1] = 1.
D[n-1,n-3:n] = np.array([1.,-4.,3.])
D /= (2*dx)
f_i = np.cos(x_i)
dF_i = np.matmul(D,f_i)
### Representación gráfica
fig = plt.figure()
fig, ax = plt.subplots()
ax.plot(x_i,f_i, color="red",marker='o',label='$cos(x_i)$')
ax.plot(x_i,dF_i, color="blue",marker='o',markersize=4, label="$I'(x_i) \sim cos'(x_i)$")
ax.scatter(x_i,-np.sin(x_i), color="black",marker='s',s=40,label="$cos'(x_i)=-sin(x_i)$")
ax.legend()
plt.show()
<Figure size 640x480 with 0 Axes>
