Resolución numérica de ecuaciones

A continuación se resuelven detalladamente once problemas de resolución numérica de ecuaciones, provenientes del libro Numerical Methods in Engineering with Python de Jaan Kiusalaas (Problem Set 4.1).

Esta libreta interactiva hecha en Jupyter fue creada por Iván Sánchez y Samuel Ochoa.

Ejercicio 1

Usar el método de Newton-Raphson y una calculadora de cuatro funciones (solo operaciones de suma, resta, multiplicación y división) para calcular $\sqrt[3]{75}$ con precisión de cuatro cifras significativas.

Partimos sabiendo que el valor que buscamos es $x = \sqrt[3]{75}$, y realizamos un proceso de "despeje inverso", de manera que quede una expresión $f(x) = 0$ que se pueda expresar usando las cuatro operaciones básicas solamente:

$x = \sqrt[3]{75} ~~ \Rightarrow ~~ x^3 - 75 = 0 ~~ \Rightarrow ~~ x \cdot x \cdot x - 75 = 0$

Luego, necesitamos la expresión de la derivada de $f(x)$:

$f'(x) = \frac{d}{dx}(x^3 - 75) = 3x^2 ~~ \Rightarrow ~~ f'(x) = 3 \cdot x \cdot x$

Para aplicar el método de Newton-Raphson necesitamos una estimación inicial de x. Buscamos un número que elevado al cubo sea cercano a 75. Como 4³ = 64 y 5³ = 125, se toma $x_0 = 4$ por ser el entero cuyo cubo está más cercano a 75. Alternativamente se puede tomar un valor no entero como 4.2, por ejemplo.

A partir de esta estimación inicial, el método de Newton-Raphson consiste en generar nuevos valores de x hasta que se llegue a un límite de repeticiones o hasta que la variación de una estimación a otra esté por debajo de una tolerancia determinada. Cada iteración es generada con la fórmula:

$x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$

Como se quieren cuatro cifras significativas, se establece una tolerancia de 0.0001 y se aplica redondeo en el algoritmo. El número máximo de repeticiones se establece en un número arbitrario suficientemente grande, en este caso 1000, el valor predeterminado. A continuación se presenta la implementación de este procedimiento en Python:

In [23]:
def f(x):
    return x*x*x - 75

def fp(x):
    return 3*x*x

def newton_raphson(x, tol=1.0e-9, n=1000):
    print(f'{"n": <4}{"xa": <8}{"x": <8}')
    i = 0
    while i < n:
        i += 1
        xa = round(x, 4)
        x = round(xa - f(xa) / fp(xa), 4)
        print(f'{i: <4}{xa: <8}{x: <8}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')
        
newton_raphson(4.0, 0.0001)
n   xa      x       
1   4.0     4.2292  
2   4.2292  4.2172  
3   4.2172  4.2172  
Out[23]:
4.2172

Si se observa la gráfica de la función $f(x)$ se evidencia que la raíz de la función se encuentra cerca del valor aproximado que obtuvimos:

In [59]:
from numpy import linspace
import matplotlib.pyplot as plt

x = linspace(0, 5, 100)
plt.plot(x, x**3-75)
plt.plot(75**(1/3), 0, 'ro', label='x ≈ 4.2172')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.grid()
plt.legend()
plt.show()

Ejercicio 2

Encontrar la raíz real positiva más pequeña de $x^3-3.23x^2-5.54x+9.84=0$ mediante el método de bisección.

Para hacer esto se requiere un intervalo inicial donde esté la raíz, una tolerancia que estableceremos en 0.0001 y un número máximo de repeticiones, en este caso como el ejercicio anterior se dejará en 1000 repeticiones.

Para el intervalo tomaremos como límite izquierdo el 0, porque el enunciado dice que se buscan números reales positivos; para el límite derecho analizaremos los puntos críticos de la función.

Para hallar los puntos críticos necesitamos la expresión de la derivada de $f(x)$:

$f'(x)= \frac{d}{dx}\left(x^3-3.23x^2-5.54x+9.84\right) = 3x^2-6.46x-5.54$

Los puntos críticos son aquellos en los que la derivada de la función es igual a cero:

$f'(x) = 0 \Rightarrow 3x^2-6.46x-5.54 = 0 \\ x = \frac{6.46 \pm \sqrt{(-6.46)^2 - 4(3)(-5.54)}}{2\cdot 300} \Rightarrow x_1 \approx -0.65708 \lor x_2 \approx 2.81041$

Como $x_1$ es negativo, se toma un número cercano a $x_2$ por debajo como límite superior. Entonces, nuestro intervalo de búsqueda será (0, 2.8)

Ya con todos los datos iniciales, a continuación se presenta la implementación de este procedimiento en Python y el resultado de su ejecución:

In [28]:
def f(x):
    return x**3 - 3.23*x**2 - 5.54*x + 9.84

def biseccion(a, b, tol=1.0e-9, n=1000):
    fa = f(a)
    if fa == 0.0: return a
    fb = f(b)
    if fb == 0.0: return b
    if fa*fb > 0.0: print('La raíz no está encerrada.')
    print(f'n\t{"a": <20}{"c": <20}{"b": <20}')
    i = 0
    while i < n:
        i += 1
        c = (a + b)/2
        fc = f(c)
        print(f'{i}\t{a: <20}{c: <20}{b: <20}')
        if fc == 0 or (b-a)/2 <= tol:
            return c
        elif fa*fc >0:
            a = c
            fa = fc
        else:
            b = c
            fb = fc
    return None
    print('ERROR: Raíz no encontrada.')

biseccion(0.0, 2.8, 0.0001)
n	a                   c                   b                   
1	0.0                 1.4                 2.8                 
2	0.0                 0.7                 1.4                 
3	0.7                 1.0499999999999998  1.4                 
4	1.0499999999999998  1.2249999999999999  1.4                 
5	1.2249999999999999  1.3125              1.4                 
6	1.2249999999999999  1.2687499999999998  1.3125              
7	1.2249999999999999  1.2468749999999997  1.2687499999999998  
8	1.2249999999999999  1.2359375           1.2468749999999997  
9	1.2249999999999999  1.23046875          1.2359375           
10	1.2249999999999999  1.2277343749999998  1.23046875          
11	1.2277343749999998  1.2291015625        1.23046875          
12	1.2291015625        1.22978515625       1.23046875          
13	1.22978515625       1.230126953125      1.23046875          
14	1.22978515625       1.2299560546875     1.230126953125      
15	1.2299560546875     1.23004150390625    1.230126953125      
Out[28]:
1.23004150390625

Si se observa la gráfica de la función $f(x)$ se evidencia que la raíz de la función se encuentra cerca del valor aproximado que obtuvimos:

In [61]:
from numpy import linspace
import matplotlib.pyplot as plt

x = linspace(0, 3, 100)
plt.plot(x, x**3 - 3.23*x**2 - 5.54*x + 9.84)
plt.plot(1.23004150390625, 0, 'ro', label='x ≈ 1.23')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=2.8,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 3

La raíz no nula, positiva, más pequeña de $\cosh(x)\cos(x) - 1 = 0$ se encuentra en el intervalo (4, 5). Calcular esta raíz mediante el método de Ridder.

Para empezar, se explica en qué consiste el método de Ridder. El método de Ridder es un algoritmo de búsqueda de raíces basado en el método de falsa posición y en el uso de una función exponencial para aproximar sucesivamente la raíz de una función contínua $f(x)$.

En resumen, se requiere que la raíz de $f(x)$ esté dentro de un intervalo inicial $(x_1,x_2)$ tal que $f(x_1)\cdot f(x_2)<0$. El método comienza evaluando la función en su punto medio $x_3 = \frac{x_1 + x_2}{2}$. La fórmula iterativa para generar la siguiente aproximación de la raíz es la siguiente (en el libro se explica cómo se obtiene):

$x_4 = x_3 \pm (x_3 - x_1)\cdot\frac{f(x_3)}{\sqrt{(f(x_3))^2 - f(x_1)\cdot f(x_2)}}$

Se usa el signo más (+) si $f(x_1) - f(x_2) > 0$, y el signo menos (-) si $f(x_1) - f(x_2) < 0$. Después de calcular $x_4$, se determina un nuevo intervalo para la raíz y se aplica el proceso de nuevo hasta que la diferencia entre dos valores sucesivos de $x_4$ sea insignificante (menor a una tolerancia preestablecida).

El método de Ridder tiene la propiedad de que si $x_1$ y $x_2$ encierran la raíz, entonces $x_4$ siempre estará dentro del intervalo $(x_1, x_2)$, haciendo al método muy confiable. Además, se puede demostrar que este método converge cuadráticamente, haciéndolo mucho más rápido que los métodos de la secante y de falsa posición, y no necesita calcular la derivada de la función.

El libro provee el código de una implementación del método de Ridder, el cual es mostrado a continuación:

In [30]:
''' root = ridder(f,a,b,tol=1.0e-9).
    Finds a root of f(x) = 0 with Ridder’s method.
    The root must be bracketed in (a,b).
'''
from math import sqrt

def ridder(f,a,b,tol=1.0e-9):
    fa = f(a)
    if fa == 0.0: return a
    fb = f(b)
    if fb == 0.0: return b
    if fa*fb > 0.0: print('Root is not bracketed')
    for i in range(30):
        # Compute the improved root x from Ridder’s formula
        c = 0.5*(a + b); fc = f(c)
        s = sqrt(fc**2 - fa*fb)
        if s == 0.0: return None
        dx = (c - a)*fc/s
        if (fa - fb) < 0.0: dx = -dx
        x = c + dx; fx = f(x)
        # Test for convergence
        if i > 0:
            if abs(x - xOld) < tol*max(abs(x),1.0): return x
        xOld = x
        # Re-bracket the root as tightly as possible
        if fc*fx > 0.0:
            if fa*fx < 0.0: b = x; fb = fx
            else: a = x; fa = fx
        else:
            a = c; b = x; fa = fc; fb = fx
    return None
    print('Too many iterations')

El ejercicio pide encontrar la raíz positiva más pequeña de $\cosh(x)\cdot\cos(x) - 1 = 0$ en el intervalo (4,5). A continuación se traduce y modifica el código proveído por el libro para mostrar el proceso de aproximación aplicado a este ejercicio y permitir un mayor número de iteraciones:

In [31]:
from math import sqrt
from numpy import cos, cosh, sin, sinh

def f(x):
    return cosh(x)*cos(x) - 1

def ridder(a, b, tol=1.0e-9, n=1000):
    fa = f(a)
    if fa == 0.0: return a
    fb = f(b)
    if fb == 0.0: return b
    if fa*fb > 0.0: print('La raíz no está encerrada.')
    print(f'{"n": <4}{"a": <20}{"c": <20}{"b": <20}{"x": <20}')
    i = 0
    while i < n:
        i += 1
        c = 0.5*(a + b); fc = f(c)
        s = sqrt(fc**2 - fa*fb)
        if s == 0.0: return None
        dx = (c - a)*fc/s
        if (fa - fb) < 0.0: dx = -dx
        xa = c
        x = c + dx; fx = f(x)
        print(f'{i: <4}{a: <20}{c: <20}{b: <20}{x: <20}')
        if abs(x - xa) <= tol: return x
        if fc*fx > 0.0:
            if fa*fx < 0.0: b = x; fb = fx
            else: a = x; fa = fx
        else:
            a = c; b = x; fa = fc; fb = fx
    return None
    print('ERROR: Raíz no encontrada.')
    
ridder(4, 5)
n   a                   c                   b                   x                   
1   4                   4.5                 5                   4.737411140182351   
2   4.5                 4.618705570091175   4.737411140182351   4.73007095219325    
3   4.618705570091175   4.674388261142212   4.73007095219325    4.730040774888355   
4   4.674388261142212   4.702214518015284   4.730040774888355   4.730040744870174   
5   4.702214518015284   4.716127631442729   4.730040744870174   4.7300407448627055  
6   4.716127631442729   4.7230841881527175  4.7300407448627055  4.730040744862704   
7   4.730040744862704   4.730040744862705   4.7300407448627055  4.730040744862704   
Out[31]:
4.730040744862704

Si se observa la gráfica de la función $f(x)$ se evidencia que la raíz de la función se encuentra cerca del valor aproximado que obtuvimos:

In [62]:
from numpy import linspace, cosh, cos
import matplotlib.pyplot as plt

x = linspace(0, 5, 100)
plt.plot(x, cosh(x)*cos(x) - 1)
plt.plot(4.730040744862704, 0, 'ro', label='x ≈ 4.73')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=4,linestyle=':',color='green')
plt.axvline(x=5,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 4

Resolver el ejercicio 3 por el método de Newton-Raphson.

Se nos indica encontrar la raíz más pequeña de la función $\cosh(x)\cos(x) - 1$ en el intervalo (4, 5), esta vez usando el método de Newton-Raphson.

Idealmente se haría un estudio de los puntos críticos de la función usando su derivada, pero como se puede observar esta consiste de funciones trigonométricas e hiperbólicas que imposibilitan la resolución de la ecuación por métodos comunes:

$f'(x) = 0 \Rightarrow \sinh(x)\cos(x) - \cosh(x)\sin(x) = 0 \\ \Rightarrow \frac{\sinh(x)}{\cosh(x)}=\frac{\sin(x)}{\cos(x)} \Rightarrow \tanh(x)=\tan(x)$

Como no tenemos manera de verificar los puntos críticos matemáticamente, aprovechamos la gráfica de la función hecha en el ejercicio anterior. Como se puede ver, la función tiene un mínimo local muy cercano a $x = 4$, lo cual puede traer problemas (y si trae problemas, como se explica en el ejercicio 8). Por estos motivos usaremos un valor inicial de 5.

A continuación se presenta el código en Python para realizar lo que se pide. Se usan los valores predeterminados para la tolerancia ($1.0 \times 10^{-9}$) y el número de repeticiones (1000).

In [33]:
from numpy import cos, cosh, sin, sinh

def f(x):
    return cosh(x)*cos(x) - 1

def fp(x):
    return sinh(x)*cos(x) - cosh(x)*sin(x)

def newton_raphson(x, tol=1.0e-9, n=1000):
    print(f'{"n": <4}{"xa": <20}{"x": <20}')
    i = 0
    while i < n:
        i += 1
        xa = x
        x = xa - f(xa) / fp(xa)
        print(f'{i: <4}{xa: <20}{x: <20}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')

newton_raphson(5.0)
n   xa                  x                   
1   5.0                 4.782556375558778   
2   4.782556375558778   4.73257518692485    
3   4.73257518692485    4.730047034975389   
4   4.730047034975389   4.730040744901577   
5   4.730040744901577   4.730040744862704   
Out[33]:
4.730040744862704

Este valor es igual al obtenido mediante el método de Ridder en el ejercicio anterior. De igual manera, si se observa la gráfica de la función $f(x)$ se evidencia que la raíz de la función se encuentra cerca del valor aproximado que obtuvimos:

In [63]:
from numpy import linspace, cosh, cos
import matplotlib.pyplot as plt

x = linspace(0, 5, 100)
plt.plot(x, cosh(x)*cos(x) - 1)
plt.plot(4.730040744862704, 0, 'ro', label='x ≈ 4.73')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=4,linestyle=':',color='green')
plt.axvline(x=5,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 6

Determinar las dos raíces de $\sin(x) + 3\cos(x) - 2 = 0$ que se encuentran en el intervalo (-2, 2). Usar el método de Newton-Raphson.

Para buscar las dos raíces de la función $f(x)=\sin(x) + 3\cos(x) - 2$ usando el método de Newton-Raphson, se necesitan dos valores iniciales. Primero analizamos los puntos críticos de la función:

$f'(x) = 0 \Rightarrow \frac{d}{dx}\left(\sin(x) + 3\cos(x) - 2\right) = 0 \Rightarrow \cos(x)-3\sin(x) = 0 \\ \Rightarrow \cos(x)=3\sin(x) \Rightarrow \frac{1}{3} = \frac{\sin(x)}{\cos(x)} \Rightarrow \tan(x) = \frac{1}{3} \\ \Rightarrow x = \arctan\left(\frac{1}{3}\right) \approx 0.32175 $

Entonces, necesitamos un valor inicial a la izquierda del punto crítico y el otro a la derecha. Los extremos del intervalo servirán para esto.

A continuación se muestra la implementación en Python del método de Newton-Raphson aplicado con valores iniciales de -2 y 2 respectivamente, usando los valores predeterminados de la tolerancia y el número máximo de repeticiones:

In [34]:
from numpy import cos, sin

def f(x):
    return sin(x) + 3*cos(x) - 2

def fp(x):
    return cos(x) - 3*sin(x)

def newton_raphson(x, tol=1.0e-9, n=1000):
    print(f'{"n": <4}{"xa": <24}{"x": <24}')
    i = 0
    while i < n:
        i += 1
        xa = x
        x = xa - f(xa) / fp(xa)
        print(f'{i: <4}{xa: <24}{x: <24}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')   

for x0 in [-2, 2]:
    print(f'Con x0 = {x0}:')
    print(f'La raíz se encuentra en x = {newton_raphson(x0)}\n')
Con x0 = -2:
n   xa                      x                       
1   -2                      -0.20147242102959595    
2   -0.20147242102959595    -0.6692923817637498     
3   -0.6692923817637498     -0.5681232246987821     
4   -0.5681232246987821     -0.5643324177771821     
5   -0.5643324177771821     -0.564326569409935      
6   -0.564326569409935      -0.5643265693959715     
La raíz se encuentra en x = -0.5643265693959715

Con x0 = 2:
n   xa                      x                       
1   2                       1.2560070038092501      
2   1.2560070038092501      1.2087040585735176      
3   1.2087040585735176      1.2078279912929746      
4   1.2078279912929746      1.207827678189296       
5   1.207827678189296       1.207827678189256       
La raíz se encuentra en x = 1.207827678189256

Si se observa la gráfica de la función $f(x)$ se evidencia que las raíces de la función en el intervalo dado se encuentran cerca de los valores aproximados que obtuvimos:

In [64]:
from numpy import linspace, cos, sin
import matplotlib.pyplot as plt

x = linspace(-2.5, 2.5, 100)
plt.plot(x, sin(x) + 3*cos(x) - 2)
plt.plot(-0.5643265693959715, 0, 'ro', label='x ≈ -0.5643')
plt.plot(1.207827678189256, 0 , 'mo', label='x ≈ 1.2078')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=-2,linestyle=':',color='green')
plt.axvline(x=2,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 8

Dibujar una gráfica de $f(x) = \cosh(x) \cos(x) - 1$ en el rango $0 \leq x \leq 10$.

In [44]:
from numpy import linspace, cosh, cos
import matplotlib.pyplot as plt

x = linspace(0, 10, 100)
plt.plot(x, cosh(x)*cos(x) - 1)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=4,linestyle=':',color='green')
plt.axvline(x=5,linestyle=':',color='green')
plt.grid()
plt.show()
  1. Verificar a partir de la gráfica que la raíz no nula, positiva, más pequeña de f(x) = 0 se encuentra en el intervalo (4, 5).

Debido a que la escala de la gráfica con el rango completo no permite apreciar bien el intervalo (4,5), se traza otra gráfica (abajo) con el intervalo $0 \leq x \leq 5$. Así se puede apreciar que en este intervalo hay dos raíces, $x = 0$ y $x \approx 4.73$.

La raíz en $x \approx 4.73$ se obtuvo en los ejercicios 3 y 4, mientras que se puede comprobar que $x = 0$ es una raíz y no una asíntota reemplazando en la expresión original: $\cosh(0)\cos(0) - 1 = 0 ~~ \Rightarrow 1 \cdot 1 - 1 = 0 ~~ \Rightarrow 0 = 0$

Como se pide la raíz no nula, positiva, más pequeña de la función, se tiene que $x \approx 4.73$ es la raíz que se busca, y pertenece al intervalo (4,5), como se aprecia en la gráfica.

In [65]:
from numpy import linspace, cosh, cos
import matplotlib.pyplot as plt

x = linspace(0, 5, 100)
plt.plot(x, cosh(x)*cos(x) - 1)
plt.plot(0, 0, 'ro', label='x = 0')
plt.plot(4.730040744862704, 0, 'mo', label='x ≈ 4.73')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=4,linestyle=':',color='green')
plt.axvline(x=5,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()
  1. Mostrar gráficamente que la fórmula de Newton-Raphson no convergería a esta raíz si se comienza con $x = 4$.

Como se observa en la gráfica de abajo, la recta tangente a $f'(x)$ en $x_0 = 4$ es bastante aplanada, debido a que está cerca de un mínimo local. La intersección de esta tangente con el eje horizontal, que sería el punto $x_1$ está muy alejada de $x_0$, en $x \approx 10.6629$.

Si se sigue iterando el proceso de calcular $x_i$ con la fórmula de Newton-Raphson, se obtiene $x \approx 10.9956$, una raíz de la función mucho mayor a la esperada $x \approx 4.73$.

Entonces, se confirma que si se inicia en $x = 4$ no se llega a la raíz no nula positiva más pequeña, debido a la cercanía de dicho valor inicial con un mínimo de la función.

In [1]:
from numpy import linspace, cosh, cos, sinh, sin
import matplotlib.pyplot as plt

def f(x):
    return cosh(x)*cos(x) - 1

def fp(x):
    return sinh(x)*cos(x) - cosh(x)*sin(x)

x = linspace(0, 8, 100)
plt.plot(x, cosh(x)*cos(x) - 1) # Funcion original
x0 = 4
plt.plot(x, fp(x0)*(x-x0)+f(x0), label='Tangente en x0 = 4') # Tangente en x0
print(f'{"i": <4}{"x_i": <24}')
for i in range(10):
    print(f'{i: <4}{x0: <24}')
    x0 = x0 - f(x0)/fp(x0)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.grid()
plt.legend()
plt.show()
i   x_i                     
0   4                       
1   10.662941907066521      
2   11.190828472638431      
3   11.025734057157239      
4   10.996480264506618      
5   10.995608598219794      
6   10.995607838002249      
7   10.995607838001671      
8   10.995607838001671      
9   10.995607838001671      

Ejercicio 9

La ecuación $x^3 - 1.2x^2 - 8.19x + 13.23 = 0$ tiene una doble raíz cerca de $x = 2$. Determinar esta raíz con el método Newton-Raphson dentro de cuatro espacios decimales.

"Doble raíz" significa que la raíz en el punto que buscamos es también un punto crítico. Los puntos críticos de la función se obtienen igualando su derivada a cero:

$f'(x) = 0 \Rightarrow \frac{d}{dx}\left(x^3 - 1.2x^2 - 8.19x + 13.23\right) = 0 \Rightarrow 3x^2-2.4x-8.19 = 0 \\ x = \frac{2.4 \pm \sqrt{(-2.4)^2-4(3)(-8.19)}}{2\cdot 3} \Rightarrow x_1 = -1.3 \lor x_2 = 2.1 $

El punto crítico $x_2$ es el más cercano a $x = 2$, por lo que tenemos que comprobar que el método de Newton-Raphson produzca un valor aproximadamente igual a él. Como valor inicial tomamos un punto intermedio a los puntos críticos, como $x = 0$.

A continuación se implementa en Python el método de Newton-Raphson modificado para trabajar con cuatro decimales de precisión:

In [48]:
def f(x):
    return  x**3 - 1.2*x**2 - 8.19*x + 13.23

def fp(x):
    return 3*x**2 - 2.4*x - 8.19

def newton_raphson(x, tol=1.0e-9, n=1000):
    print(f'{"n": <4}{"xa": <8}{"x": <8}')
    i = 0
    while i < n:
        i += 1
        xa = round(x, 4)
        x = round(xa - f(xa) / fp(xa), 4)
        print(f'{i: <4}{xa: <8}{x: <8}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')  

newton_raphson(0.0, 0.0001)
n   xa      x       
1   0.0     1.6154  
2   1.6154  1.8711  
3   1.8711  1.9883  
4   1.9883  2.0448  
5   2.0448  2.0726  
6   2.0726  2.0863  
7   2.0863  2.0932  
8   2.0932  2.0966  
9   2.0966  2.0983  
10  2.0983  2.0992  
11  2.0992  2.0996  
12  2.0996  2.0998  
13  2.0998  2.0999  
Out[48]:
2.0999

Así se comprueba que la raíz de la función se encuentra en $x = 2.1$, y es una doble raíz porque coincide con un punto crítico. A continuación se muestra la gráfica de $f(x)$, donde se aprecia que la raíz en $x = 2.1$ es a su vez un mínimo local:

In [66]:
from numpy import linspace
import matplotlib.pyplot as plt

x = linspace(0, 3, 100)
plt.plot(x, x**3 - 1.2*x**2 - 8.19*x + 13.23)
plt.plot(2.1, 0, 'ro', label='x = 2.1')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.grid()
plt.legend()
plt.show()

Ejercicio 10

Escribir un programa que calcule todas las raíces de $f(x) = 0$ en un intervalo dado con el método de Ridder. Utilizar las funciones rootsearch y ridder. Puede usar el programa en el Ejemplo 4.3 como modelo. Probar el programa encontrando las raíces de $x \cdot \sin( x ) + 3\cos( x ) - x = 0$ en (-6, 6).

El libro provee una función llamada rootsearch, la cual busca una raíz de la función f(x) en el intervalo (a,b) en incrementos de dx. Su resultado es un tuple (x1,x2) cuyos elementos son los extremos del intervalo donde se encuentra la raíz, si la búsqueda es exitosa. Si en lugar de números, los valores de x1 y x2 son None, significa que no se encontró ninguna raíz.

Después de que la primera raíz (la más cercana al extremo izquierdo) ha sido detectada, rootsearch puede ser llamada de nuevo con x2 como extremo izquierdo para encontrar la siguiente raíz. Esto puede ser repetido siempre que la función detecte una raíz.

El código de rootsearch, tal como aparece en el libro, es el siguiente:

In [67]:
''' x1,x2 = rootsearch(f,a,b,dx).
    Searches the interval (a,b) in increments dx for
    the bounds (x1,x2) of the smallest root of f(x).
    Returns x1 = x2 = None if no roots were detected.
'''
def rootsearch(f,a,b,dx):
    x1 = a; f1 = f(a)
    x2 = a + dx; f2 = f(x2)
    while f1*f2 > 0.0:
        if x1 >= b: return None,None
        x1 = x2; f1 = f2
        x2 = x1 + dx; f2 = f(x2)
    else:
        return x1,x2

Para encontrar todas las raíces de $f(x) = x \cdot \sin( x ) + 3\cos( x ) - x = 0$ en (-6,6) usando el método de Ridder podemos aprovechar esta función rootsearch para generar los intervalos que verificará el método de Ridder. A continuación se implementa este procedimiento en Python:

In [52]:
from math import sqrt
from numpy import cos, sin

def f(x):
    return x*sin(x) + 3*cos(x) - x

def rootsearch(a, b, dx):
    x1 = a; f1 = f(a)
    x2 = a + dx; f2 = f(x2)
    while f1*f2 > 0.0:
        if x1 >= b: return None,None
        x1 = x2; f1 = f2
        x2 = x1 + dx; f2 = f(x2)
    else:
        return x1,x2

def ridder(a, b, tol=1.0e-9, n=1000):
    fa = f(a)
    if fa == 0.0: return a
    fb = f(b)
    if fb == 0.0: return b
    if fa*fb > 0.0: print('La raíz no está encerrada.')
    #print(f'{"n": <4}{"a": <20}{"c": <20}{"b": <20}{"x": <20}')
    i = 0
    while i < n:
        i += 1
        c = 0.5*(a + b); fc = f(c)
        s = sqrt(fc**2 - fa*fb)
        if s == 0.0: return None
        dx = (c - a)*fc/s
        if (fa - fb) < 0.0: dx = -dx
        xa = c
        x = c + dx; fx = f(x)
        #print(f'{i: <4}{a: <20}{c: <20}{b: <20}{x: <20}')
        if abs(x - xa) <= tol: return x
        if fc*fx > 0.0:
            if fa*fx < 0.0: b = x; fb = fx
            else: a = x; fa = fx
        else:
            a = c; b = x; fa = fc; fb = fx
    return None
    print('ERROR: Raíz no encontrada.')
    
def encontrar_raices(a, b, dx):
    print(f'Raíces encontradas en el intervalo [{a}, {b}]:')
    while True:
        x1, x2 = rootsearch(a, b, dx)
        if x1 != None:
            a = x2
            raiz = ridder(x1, x2)
            if raiz != None: print(raiz)
        else: break

encontrar_raices(-6.0, 6.0, 0.01)
Raíces encontradas en el intervalo [-6.0, 6.0]:
-4.71238898038469
-3.2088387319804816
1.5707963267948966

Si se observa la gráfica de la función $f(x)$ se evidencia que la función tiene raíces aproximadamente en los puntos encontrados por el algoritmo:

In [68]:
from numpy import linspace, cos,sin
import matplotlib.pyplot as plt

x = linspace(-6.1, 6.1, 100)
plt.plot(x, x*sin(x) + 3*cos(x) - x)
plt.plot(-4.71238898038469, 0, 'ro', label='x = -4.7124')
plt.plot(-3.2088387319804816, 0, 'mo', label='x = -3.2088')
plt.plot(1.5707963267948966, 0, 'bo', label='x = 1.5708')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=-6,linestyle=':',color='green')
plt.axvline(x=6,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 11

Repetir el ejercicio 10 con el método de Newton-Raphson.

Se pide crear un programa que encuentre todas las raíces de $f(x) = x \cdot \sin( x ) + 3\cos( x ) - x = 0$ en (-6, 6), esta vez usando el método de Newton-Raphson.

Primero que nada se buscan los puntos críticos de la función:

$f'(x) = 0 \Rightarrow \frac{d}{dx}\left(x \cdot \sin( x ) + 3\cos( x ) - x\right) = 0 \\ \Rightarrow \sin(x) + x\cdot\cos(x) - 3\sin(x) - 1 = 0 \\ \Rightarrow x\cdot\cos(x) - 2\sin(x) - 1 = 0 $

No es evidente una forma de resolver esta ecuación algebraicamente, así que nos basaremos en la gráfica de $f(x)$ realizada en el ejercicio anterior. A partir de ahí vemos tres puntos críticos, un mínimo cerca de $x = -4$, un máximo cerca de $x = -1$, y otro mínimo cerca de $x = 4$; estos son valores que tenemos que evitar para que el método de Newton-Raphson pueda converger.

Estos puntos críticos (aproximados) dividen el intervalo (-6, 6) en cuatro partes: una entre -6 y -4, una entre -4 y -1, una entre -1 y 4, y una entre 4 y 6. Tomaremos un valor intermedio de cada subintervalo como valor inicial para el procedimiento, por ejemplo: $x = -5$, $x = -2$, $x = 0$, $x = 5$.

A continuación se implementa en Python el método de Newton-Raphson y se aplica a estos valores iniciales, utilizando los valores predeterminados para la tolerancia y el número máximo de repeticiones:

In [69]:
from numpy import cos, sin

def f(x):
    return x*sin(x) + 3*cos(x) - x

def fp(x):
    return x*cos(x) - 2*sin(x) - 1

def newton_raphson(x, tol=1.0e-9, n=1000):
    #print(f'{"n": <4}{"xa": <20}{"x": <20}')
    i = 0
    while i < n:
        i += 1
        xa = x
        x = xa - f(xa) / fp(xa)
        #print(f'{i: <4}{xa: <20}{x: <20}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')
    
def encontrar_raices(a, b, vals):
    print(f'Raíces encontradas:')
    for x0 in vals:
        raiz = newton_raphson(x0)
        if raiz != None:
            if a <= raiz <= b:
                print(raiz)
            else:
                print(raiz,
                      f'=> No pertenece al intervalo [{a}, {b}]')

encontrar_raices(-6.0, 6.0, [-5, -2, 0, 5])
Raíces encontradas:
-4.71238898038469
-3.2088387319804816
1.5707963267948966
7.853981633974483 => No pertenece al intervalo [-6.0, 6.0]

Las tres raíces obtenidas que pertenecen al intervalo (-6, 6) son iguales a las obtenidas en el ejercicio anterior. De igual manera, si se observa la gráfica de la función $f(x)$ se evidencia que las raíces de la función se encuentran cerca de los valores aproximados que obtuvimos:

In [70]:
from numpy import linspace, cos,sin
import matplotlib.pyplot as plt

x = linspace(-6.1, 6.1, 100)
plt.plot(x, x*sin(x) + 3*cos(x) - x)
plt.plot(-4.71238898038469, 0, 'ro', label='x = -4.7124')
plt.plot(-3.2088387319804816, 0, 'mo', label='x = -3.2088')
plt.plot(1.5707963267948966, 0, 'bo', label='x = 1.5708')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.axvline(x=-6,linestyle=':',color='green')
plt.axvline(x=6,linestyle=':',color='green')
plt.grid()
plt.legend()
plt.show()

Ejercicio 13

Calcular todas las raíces reales positivas de $x^4 + 2x^3 - 7x^2 + 3 = 0$

Para hallar las raíces de $f(x) = x^4 + 2x^3 - 7x^2 + 3 = 0$ podemos usar el método de Newton-Raphson, pues no requiere encerrar las raíces en intervalos, sino dar estimaciones iniciales.

Las estimaciones iniciales no pueden ser puntos críticos, por lo que tenemos que identificar estos puntos igualando la derivada de la función a cero:

$f'(x) = 0 \Rightarrow \frac{d}{dx}\left(x^4 + 2x^3 - 7x^2 + 3\right) = 0 \\ \Rightarrow 4x^3 + 6x^2 - 14x = 0 \Rightarrow 2x(2x^2+3x-7) = 0 \\ \Rightarrow x_1 = 0 ~~ \lor ~~ 2x^2 + 3x - 7 = 0 \\ \Rightarrow x = \frac{-3 \pm \sqrt{3^2-4(2)(-7)}}{2\cdot 2} \Rightarrow x_2 \approx -2.76556 \lor x_3 \approx 1.26556$

La existencia de tres puntos críticos significa que se puede dividir el dominio en cuatro intervalos: entre $-\infty$ y -2.76556, entre -2.76556 y 0, entre 0 y 1.26556, y entre 1.26556 y $\infty$. Vamos a tomar un punto de cada intervalo como valores iniciales, en este caso: $x = -3$, $x = -1$, $x = 1$ y $x = 2$.

A continuación se muestra la implementación del método de Newton-Raphson que venimos manejando, aplicada a cada uno de estos cuatro valores iniciales, con la tolerancia y el número máximo de repeticiones predeterminados.

In [22]:
def f(x):
    return x**4 + 2*x**3 - 7*x**2 + 3

def fp(x):
    return 4*x**3 + 6*x**2 - 14*x

def newton_raphson(x, tol=1.0e-9, n=1000):
    #print(f'{"n": <4}{"xa": <20}{"x": <20}')
    i = 0
    while i < n:
        i += 1
        xa = x
        x = xa - f(xa) / fp(xa)
        #print(f'{i: <4}{xa: <20}{x: <20}')
        if abs(x - xa) <= tol:
            return x
    return None
    print('ERROR: Raíz no encontrada.')    

for x0 in [-3, -1, 1, 2]:
    print(newton_raphson(x0))
-3.79128784747792
-0.6180339887498949
0.7912878474779201
1.6180339887498945

Si se observa la gráfica de la función $f(x)$ se evidencia que las raíces de la función se encuentran cerca de estos cuatro valores aproximados que obtuvimos:

In [71]:
from numpy import linspace, cos,sin
import matplotlib.pyplot as plt

x = linspace(-4, 2, 100)
plt.plot(x, x**4 + 2*x**3 - 7*x**2 + 3)
plt.plot(-3.79128784747792, 0, 'ro', label='x ≈ -3.7913')
plt.plot(-0.6180339887498949, 0, 'mo', label='x ≈ -0.6180')
plt.plot(0.7912878474779201, 0, 'bo', label='x ≈ 0.7913')
plt.plot(1.6180339887498945, 0, 'go', label='x ≈ 1.6180')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.grid()
plt.legend()
plt.show()

Ejercicio 18

La ecuación de Bernoulli para el flujo de fluido en un canal abierto con una pequeña protuberancia es: $\frac{Q^2}{ 2gb^2{h_0}^2 } + h_0 = \frac{Q^2}{ 2gb^2 h^2 } + h + H$ donde:

  • $Q = 1.2 m^3/s$ es la tasa de flujo volumétrico.
  • $g = 9.81 m/s^2$ es la aceleración gravitatoria.
  • $b = 1.8 m$ es la anchura del canal.
  • $h_0 = 0.6 m$ es el nivel de agua aguas arriba.
  • $H = 0.075 m$ es la altura de la protuberancia.
  • $h = ?$ es el nivel del agua sobre la protuberancia.

Determinar h.

Para hallar el valor de h se puede pasar todo a un solo lado de la ecuación, de forma que quede una expresión $f(h) = 0$ a la cual se le puede aplicar el método de Newton-Raphson:

$\frac{Q^2}{ 2gb^2{h_0}^2 } + h_0 = \frac{Q^2}{ 2gb^2 h^2 } + h + H \\ \frac{Q^2}{ 2gb^2 h^2 } + h + H - \frac{Q^2}{ 2gb^2{h_0}^2 } - h_0 = 0 \\ \Rightarrow f(h) = 0 $

Como esta función posee una expresión racional con $h^2$ en el denominador, posee una asíntota en $h = 0$. Al tratarse de la altura de una protuberancia, h no puede tomar valores negativos, así que se busca una raíz real no nula y positiva.

A continuación se analizan los puntos críticos de la función para determinar cuántos valores iniciales se tienen que tomar. Los puntos críticos se obtienen resolviendo la ecuación siguiente:

$f'(h) = 0 \Rightarrow \frac{d}{dx}\left(\frac{Q^2}{ 2gb^2}\cdot h^{-2} + h + H - \frac{Q^2}{ 2gb^2{h_0}^2 } - h_0\right) = 0 \\ \Rightarrow -2\cdot\frac{Q^2}{ 2gb^2 }\cdot h^-3 + 1 = 0 \Rightarrow 1-\frac{Q^2}{ gb^2h^3 } = 0 \\ \Rightarrow 1 = \frac{Q^2}{ gb^2h^3 } \Rightarrow h^3 = \frac{Q^2}{ gb^2 } \Rightarrow h = \sqrt[3]{\frac{Q^2}{ gb^2 }} = \sqrt[3]{\frac{(1.2 m^3/s)^2}{(9.81 m/s^2)(1.8m)^2}} \approx 0.35649 m $

Este valor de h se debe evitar, así que tomamos un valor inicial entre 0 y 0.35649, y otro entre 0.35649 y $\infty$. En este caso tomaremos $x = 0.2$ y $x = 0.6$.

A continuación se implementa en Python el método de Newton-Raphson para $f(x)$ y se aplica con los valores iniciales mencionados, y usando los valores predeterminados para la tolerancia y el número máximo de iteraciones:

In [77]:
Q = 1.2 
g = 9.81
b = 1.8
h0 = 0.6 
H = 0.075

def f(h):
    return Q**2/(2*g*b**2*h**2) + h + H - Q**2/(2*g*b**2*h0**2) - h0

def fp(h):
    return 1 - Q**2/(g*b**2*h**3)

def newton_raphson(h, tol=1.0e-9, n=1000):
    print(f'{"n": <4}{"ha": <24}{"h": <24}')
    i = 0
    while i < n:
        i += 1
        ha = h
        h = ha - f(ha) / fp(ha)
        print(f'{i: <4}{ha: <24}{h: <24}')
        if abs(h - ha) <= tol:
            return h
    return None
    print('ERROR: Raíz no encontrada.')    

for h0 in [0.2, 0.6]:
    print(f'Con h0 = {h0}:')
    print(f'La raíz se encuentra en h = {newton_raphson(h0)}\n')
Con h0 = 0.2:
n   ha                      h                       
1   0.2                     0.21608352966894176     
2   0.21608352966894176     0.21892476068732689     
3   0.21892476068732689     0.21899929533476525     
4   0.21899929533476525     0.21899934491457573     
5   0.21899934491457573     0.21899934491459766     
La raíz se encuentra en h = 0.21899934491459766

Con h0 = 0.6:
n   ha                      h                       
1   0.6                     0.5050937451893216      
2   0.5050937451893216      0.49589909974173835     
3   0.49589909974173835     0.4957551613177875      
4   0.4957551613177875      0.4957551242401354      
5   0.4957551242401354      0.4957551242401331      
La raíz se encuentra en h = 0.4957551242401331

Si se observa la gráfica de la función $f(h)$ se evidencia que las raíces de la función se encuentran cerca de estos dos valores aproximados que obtuvimos:

In [79]:
from numpy import linspace, cos,sin
import matplotlib.pyplot as plt

Q = 1.2 
g = 9.81
b = 1.8
h0 = 0.6 
H = 0.075

x = linspace(0.1, 1, 100)
plt.plot(x, Q**2/(2*g*b**2*x**2) + x + H - Q**2/(2*g*b**2*h0**2) - h0)
plt.plot(0.21899934491459766, 0, 'ro', label='x ≈ 0.2189')
plt.plot(0.4957551242401331, 0, 'mo', label='x ≈ 0.4958')
plt.xlabel('h')
plt.ylabel('f(h)')
plt.axhline(y=0,color='k')
plt.axvline(x=0,color='k')
plt.grid()
plt.legend()
plt.show()

La primera raíz encontrada parece estar alejada de la posición real de la raíz en la gráfica, lo cual indica que hay una pérdida de precisión, posiblemente causada porque las fórmulas tienen varias divisiones y restas, que se acentúa conforme los valores con los que se trabaja son más pequeños.

In [ ]: