Solución de ecuaciones no lineales#
Considere una ecuación no lineal de la forma
donde \(f\) es una función real. Para determinar las soluciones reales se emplean métodos iterativos. Es decir, dado un valor inicial \(x_{0}\), se construye una sucesión de números reales \(\{ x_{n} \}_{n=0}^{\infty} = \{x_{0} , x_{1} , x_{2} ,\dots \}\). Si el método es adecuado, se espera que \(|x_{n} - x^{\star}|\rightarrow 0\) cuando \(n\rightarrow \infty\). En este caso se dice que el método converge a la solución \(x^{\star}\); de lo contrario se dice que el método diverge.
Método de bisección#
Suponga que se desea encontrar el valor de \(x\) tal que \(f(x)=0\), es decir, un cruce por cero. Para este método, se determinan dos puntos \(x_{0}\) y \(\tilde{x}_{0}\) en los cuales la función toma valores con signo opuesto. Suponiendo que la función es continua, por el teorema del valor intermedio se garantiza que debe existir al menos un cruce por cero de \(f\) entre \(x_{0}\) y \(\tilde{x}_{0}\).
Entonces, la función se evalúa en el punto \(x_{1} = \frac{1}{2} \left( x_{0} + \tilde{x}_{0} \right)\). Por consiguiente, el punto \(\tilde{x}_{1}\) se elige como análogo del par \(\left(x_{0}, \tilde{x}_{0}\right)\) en el cual el valor de la función tiene signo opuesto a \(f(x_{1})\). Con esto, obtenemos un intervalo \(\left[x_{1}, \tilde{x}_{1} \right]\) que continúa conteniendo un cruce por cero y con la mitad del tamaño del intervalo original. Este proceso se repite hasta que los límites inferior y superior del cruce por cero estén suficientemente cercanos.
El método se detiene cuando \(|x_{n} − \tilde{x}_{n}| < \text{tol}\) o bien, cuando \(|f \left( x_{n+1} \right)| < \text{tol}\) , donde \(\text{tol}\) es la tolerancia especificada para el método de bisección.
Truco
El método de bisección es sencillo. Sin embargo, convergen lento y cuando las iteraciones se aproximan al cruce por cero, es mejor utilizar otro método que converja más rápido.
Teorema 16
Suponga que \(f\) es continua en el intervalo \(\left[x_{0},\tilde{x}_{0} \right]\) y \(f(x_{0}), f(\tilde{x}_{0})<0\), el método de bisección genera una sucesión \(\left\{x_{n} \right\}_{n=1}^{\infty}\) que aproxima \(x^{\star}\) a un cruce por cero de \(f\), tal que
Esto implica que
donde \(\mathcal{O}\left( 1/2^{n} \right)\) es la velocidad de convergencia del método. Además, el método de bisección satisface
por lo que el método converge linealmente.
import numpy as np
import matplotlib.pyplot as plt
# Suprimir advertencias menores de integración durante los bucles de optimización
import warnings
warnings.filterwarnings("ignore")
# Configurar el estilo de graficación para una mejor visualización
plt.style.use('ggplot')
# Definición de la función continua
def funcion(x):
"""Calcula el valor de la función para un valor dado de x."""
return 1 + 2 * x - 3 * (x**2) * np.exp(-x) + 2 * (x**3) * np.sin(x) * np.exp(-x / 5)
# Función auxiliar: Método de Bisección
def biseccion(a, b, tol=1e-6, max_iter=100):
"""
Encuentra la raíz de una función en el intervalo [a, b] usando el método de bisección.
Retorna la raíz encontrada y una matriz con el historial de iteraciones.
"""
mat = []
fa = funcion(a)
fb = funcion(b)
c_old = a
for i in range(1, max_iter + 1):
c = (a + b) / 2.0
fc = funcion(c)
# Cálculo del error relativo aproximado
if i > 1:
error = abs((c - c_old) / c) if c != 0 else abs(c - c_old)
else:
error = 1.0
# Almacena la iteración: [iteración, a, b, c, f(c), error]
mat.append([i, a, b, c, fc, error])
# Criterio de parada por tolerancia o raíz exacta
if abs(fc) < 1e-12 or error < tol:
break
if fa * fc < 0:
b = c
fb = fc
else:
a = c
fa = fc
c_old = c
return c, np.array(mat)
# Programa Principal
print("Buscando cruces por cero en el intervalo [4, 20]...")
r = 0 # Inicia el contador de cruces por cero
cruce_list = [] # Lista para almacenar los valores de los cruces
M_list = [] # Lista para almacenar las matrices de iteración de cada cruce
# Ciclo para evaluar subintervalos de longitud 1 dentro del intervalo [4, 20]
# En Python, range(4, 21) recorre k desde 4 hasta 20 inclusivo.
for k in range(4, 21):
l = k + 1
# Evaluación de la función en los puntos k y l
gk = funcion(k)
gl = funcion(l)
# Condición que marca el subintervalo donde se encuentra un cruce por cero
if gk * gl < 1e-3:
# Llamada al método de bisección
cero, mat = biseccion(k, l)
r += 1
cruce_list.append(cero) # Almacena el cruce por cero
M_list.append(mat) # Almacena la matriz de iteraciones
# Conversión de los cruces almacenados a un arreglo de NumPy
Cruce = np.array(cruce_list)
# Extracción de las iteraciones de cada cruce por cero encontrado (usando índices base 0)
# Nota: Se accede de forma segura mediante condicionales o índices si existen suficientes cruces.
M1 = M_list[0] if len(M_list) > 0 else None
M2 = M_list[1] if len(M_list) > 1 else None
M3 = M_list[2] if len(M_list) > 2 else None
M4 = M_list[3] if len(M_list) > 3 else None
M5 = M_list[4] if len(M_list) > 4 else None
# Mostrar resultados en pantalla
print(f"\nTotal de cruces por cero encontrados: {r}")
print("Valores de los cruces por cero:", Cruce)
if M1 is not None:
print("\nIteraciones del primer cruce por cero (M1):\n", M1)
Buscando cruces por cero en el intervalo [4, 20]...
Total de cruces por cero encontrados: 5
Valores de los cruces por cero: [ 6.18744278 9.50275421 12.48523712 15.80555725 18.72535706]
Iteraciones del primer cruce por cero (M1):
[[ 1.00000000e+00 6.00000000e+00 7.00000000e+00 6.50000000e+00
4.60103386e+01 1.00000000e+00]
[ 2.00000000e+00 6.00000000e+00 6.50000000e+00 6.25000000e+00
8.63217105e+00 4.00000000e-02]
[ 3.00000000e+00 6.00000000e+00 6.25000000e+00 6.12500000e+00
-8.26244788e+00 2.04081633e-02]
[ 4.00000000e+00 6.12500000e+00 6.25000000e+00 6.18750000e+00
7.34941617e-03 1.01010101e-02]
[ 5.00000000e+00 6.12500000e+00 6.18750000e+00 6.15625000e+00
-4.17380019e+00 5.07614213e-03]
[ 6.00000000e+00 6.15625000e+00 6.18750000e+00 6.17187500e+00
-2.09456071e+00 2.53164557e-03]
[ 7.00000000e+00 6.17187500e+00 6.18750000e+00 6.17968750e+00
-1.04641046e+00 1.26422250e-03]
[ 8.00000000e+00 6.17968750e+00 6.18750000e+00 6.18359375e+00
-5.20228063e-01 6.31711939e-04]
[ 9.00000000e+00 6.18359375e+00 6.18750000e+00 6.18554688e+00
-2.56613248e-01 3.15756236e-04]
[ 1.00000000e+01 6.18554688e+00 6.18750000e+00 6.18652344e+00
-1.24675340e-01 1.57853197e-04]
[ 1.10000000e+01 6.18652344e+00 6.18750000e+00 6.18701172e+00
-5.86738103e-02 7.89203693e-05]
[ 1.20000000e+01 6.18701172e+00 6.18750000e+00 6.18725586e+00
-2.56649083e-02 3.94586276e-05]
[ 1.30000000e+01 6.18725586e+00 6.18750000e+00 6.18737793e+00
-9.15842378e-03 1.97289246e-05]
[ 1.40000000e+01 6.18737793e+00 6.18750000e+00 6.18743896e+00
-9.04673217e-04 9.86436498e-06]
[ 1.50000000e+01 6.18743896e+00 6.18750000e+00 6.18746948e+00
3.22232913e-03 4.93215816e-06]
[ 1.60000000e+01 6.18743896e+00 6.18746948e+00 6.18745422e+00
1.15881737e-03 2.46608516e-06]
[ 1.70000000e+01 6.18743896e+00 6.18745422e+00 6.18744659e+00
1.27069428e-04 1.23304410e-06]
[ 1.80000000e+01 6.18743896e+00 6.18744659e+00 6.18744278e+00
-3.88802557e-04 6.16522431e-07]]
Método de la falsa posición o regla falsa#
Suponga que se eligen dos aproximaciones iniciales \(x_{0}\) y \(\tilde{x}_{0}\) tales que los dos valores de la función en esos puntos tengan signo opuesto, entonces es posible generar una sucesión de valores que tengan esta propiedad. Si se construye una recta que pasa por los puntos \(\left(x_{0},f\left(x_{0} \right) \right)\) y \(\left( \tilde{x}_{0},f\left(\tilde{x}_{0} \right) \right)\). Dado que \(m_{1} = m_{2}\) se tiene
El valor del cruce por cero se define cuando se tiene un valor de \(x_{1}\) donde se cumple que \(f(x_{1}) = 0\). Así, la ecuación queda de la siguiente forma
despejando \(x_{1}\) se tiene
El valor de \(\tilde{x}_{1}\) se elige tomando un valor entre \(x_{0}\) y \(\tilde{x}_{0}\) tal que el valor de la función sea opuesto en signo a \(f(x_{1})\). Por consiguiente, los valores de \(x_{1}\) y \(\tilde{x}_{1}\) definen un menor intervalo que contiene el cruce por cero.
Aunque el método de la falsa posición suele tener una mejor convergencia que el método de bisección, no siempre es así.
# Definición de la función continua
def funcion(x):
"""Calcula el valor de la función para un valor dado de x."""
return 1 + 2 * x - 3 * (x**2) * np.exp(-x) + 2 * (x**3) * np.sin(x) * np.exp(-x / 5)
# Función auxiliar: Método de Regla Falsa
def regla_falsa(a, b, tol=1e-6, max_iter=100):
"""
Encuentra la raíz de una función en el intervalo [a, b] usando el método de regla falsa.
Retorna la raíz encontrada y una matriz con el historial de iteraciones.
"""
mat = []
fa = funcion(a)
fb = funcion(b)
c_old = a
for i in range(1, max_iter + 1):
# Fórmula de la regla falsa para calcular la aproximación c
c = b - (fb * (a - b)) / (fa - fb)
fc = funcion(c)
# Cálculo del error relativo aproximado
if i > 1:
error = abs((c - c_old) / c) if c != 0 else abs(c - c_old)
else:
error = 1.0
# Almacena la iteración: [iteración, a, b, c, f(c), error]
mat.append([i, a, b, c, fc, error])
# Criterio de parada por tolerancia o raíz exacta
if abs(fc) < 1e-12 or error < tol:
break
if fa * fc < 0:
b = c
fb = fc
else:
a = c
fa = fc
c_old = c
return c, np.array(mat)
# Programa Principal
print("Buscando cruces por cero en el intervalo [4, 20] mediante Regla Falsa...")
r = 0 # Inicia contador de cruces por cero.
cruce_list = [] # Lista para almacenar los valores de los cruces.
M_list = [] # Lista para almacenar las matrices de iteración de cada cruce.
# Ciclo para evaluar subintervalos de longitud 1 dentro del intervalo [4, 20].
# En Python, range(4, 21) recorre k desde 4 hasta 20 inclusivo.
for k in range(4, 21):
l = k + 1
# Evaluación de la función en los puntos k y l.
gk = funcion(k)
gl = funcion(l)
# Condición que marca el subintervalo donde se encuentra un cruce por cero.
if gk * gl < 1e-3:
# Llamada al método de regla falsa.
cero, mat = regla_falsa(k, l)
r += 1
cruce_list.append(cero) # Almacena el cruce por cero.
M_list.append(mat) # Almacena la matriz de iteraciones.
# Conversión de los cruces almacenados a un arreglo de NumPy.
Cruce = np.array(cruce_list)
# Extracción segura de las iteraciones de cada cruce por cero encontrado (usando índices base 0).
M1 = M_list[0] if len(M_list) > 0 else None
M2 = M_list[1] if len(M_list) > 1 else None
M3 = M_list[2] if len(M_list) > 2 else None
M4 = M_list[3] if len(M_list) > 3 else None
M5 = M_list[4] if len(M_list) > 4 else None
# Mostrar resultados en pantalla
print(f"\nTotal de cruces por cero encontrados: {r}")
print("Valores de los cruces por cero:", Cruce)
if M1 is not None:
print("\nIteraciones del primer cruce por cero (M1):\n", M1)
Buscando cruces por cero en el intervalo [4, 20] mediante Regla Falsa...
Total de cruces por cero encontrados: 5
Valores de los cruces por cero: [ 6.18744552 9.50275574 12.48524454 15.80556286 18.72534361]
Iteraciones del primer cruce por cero (M1):
[[ 1.00000000e+00 6.00000000e+00 7.00000000e+00 6.15788396e+00
-3.95744150e+00 1.00000000e+00]
[ 2.00000000e+00 6.15788396e+00 7.00000000e+00 6.18352686e+00
-5.29249577e-01 4.14696994e-03]
[ 3.00000000e+00 6.18352686e+00 7.00000000e+00 6.18694188e+00
-6.81152424e-02 5.51971697e-04]
[ 4.00000000e+00 6.18694188e+00 7.00000000e+00 6.18738116e+00
-8.72144996e-03 7.09961924e-05]
[ 5.00000000e+00 6.18738116e+00 7.00000000e+00 6.18743740e+00
-1.11594980e-03 9.08961394e-06]
[ 6.00000000e+00 6.18743740e+00 7.00000000e+00 6.18744460e+00
-1.42778789e-04 1.16304643e-06]
[ 7.00000000e+00 6.18744460e+00 7.00000000e+00 6.18744552e+00
-1.82674532e-05 1.48804317e-07]]
Método de la secante#
Este método considera los puntos en sucesión estricta en el sentido de que el valor más viejo se descarta, y sólo se usan toman en cuenta dos valores más recientes al calcular el nuevo valor. si se tiene que \(m_{1}=m_{2}\), entonces
Si se tiene que \(f\left(x_{2}\right)=0\), entonces
reagrupando términos
Este método necesita dos valores iniciales \(x_{0}\) y \(x_{1}\) de modo que los valores \(f(x_{0})\) y \(f(x_{1})\) se calculan y se obtienen dos puntos sobre la curva. Entonces, el nuevo punto de la sucesión es el punto en el cual la recta que une los dos puntos previos corta al eje \(x\).
Truco
La velocidad de convergencia de este método, es superior a la de los métodos de bisección y falsa posición cuando se está suficien temente cerca de la solución.
# Definición de la función continua
def funcion(x):
"""Calcula el valor de la función para un valor dado de x."""
return 1 + 2 * x - 3 * (x**2) * np.exp(-x) + 2 * (x**3) * np.sin(x) * np.exp(-x / 5)
# Función auxiliar: Método de la Secante
def secante(x0, x1, tol=1e-6, max_iter=100):
"""
Encuentra la raíz de una función usando el método de la secante a partir de dos puntos iniciales x0 y x1.
Retorna la raíz encontrada y una matriz con el historial de iteraciones.
"""
mat = []
for i in range(1, max_iter + 1):
f0 = funcion(x0)
f1 = funcion(x1)
# Validación para evitar división por cero
if f1 - f0 == 0:
break
# Fórmula del método de la secante
x2 = x1 - (f1 * (x1 - x0)) / (f1 - f0)
f2 = funcion(x2)
# Cálculo del error relativo aproximado
error = abs((x2 - x1) / x2) if x2 != 0 else abs(x2 - x1)
# Almacena la iteración: [iteración, x0, x1, x2, f(x2), error]
mat.append([i, x0, x1, x2, f2, error])
# Criterio de parada por tolerancia o raíz exacta
if abs(f2) < 1e-12 or error < tol:
break
# Actualización de puntos para la siguiente iteración
x0 = x1
x1 = x2
return x2, np.array(mat)
# Programa Principal
print("Buscando cruces por cero en el intervalo [4, 20] mediante el Método de la Secante...")
r = 0 # Inicia el contador de cruces por cero.
cruce_list = [] # Lista para almacenar los valores de los cruces.
M_list = [] # Lista para almacenar las matrices de iteración de cada cruce.
# Ciclo para evaluar subintervalos de longitud 1 dentro del intervalo [4, 20].
# En Python, range(4, 21) recorre k desde 4 hasta 20 inclusivo.
for k in range(4, 21):
l = k + 1
# Evaluación de la función en los puntos k y l.
gk = funcion(k)
gl = funcion(l)
# Condición que marca el subintervalo donde se encuentra un cruce por cero.
if gk * gl < 1e-3:
# Llamada al método de la secante usando k y l como puntos iniciales.
cero, mat = secante(k, l)
r += 1
cruce_list.append(cero) # Almacena el cruce por cero.
M_list.append(mat) # Almacena la matriz de iteraciones.
# Conversión de los cruces almacenados a un arreglo de NumPy.
Cruce = np.array(cruce_list)
# Extracción segura de las iteraciones de cada cruce por cero encontrado (usando índices base 0).
M1 = M_list[0] if len(M_list) > 0 else None
M2 = M_list[1] if len(M_list) > 1 else None
M3 = M_list[2] if len(M_list) > 2 else None
M4 = M_list[3] if len(M_list) > 3 else None
M5 = M_list[4] if len(M_list) > 4 else None
# Mostrar resultados en pantalla
print(f"\nTotal de cruces por cero encontrados: {r}")
print("Valores de los cruces por cero:", Cruce)
if M1 is not None:
print("\nIteraciones del primer cruce por cero (M1):\n", M1)
Buscando cruces por cero en el intervalo [4, 20] mediante el Método de la Secante...
Total de cruces por cero encontrados: 5
Valores de los cruces por cero: [ 6.18744565 9.50275574 12.48524454 15.80556286 18.72534361]
Iteraciones del primer cruce por cero (M1):
[[ 1.00000000e+00 6.00000000e+00 7.00000000e+00 6.15788396e+00
-3.95744150e+00 1.36754125e-01]
[ 2.00000000e+00 7.00000000e+00 6.15788396e+00 6.18352686e+00
-5.29249577e-01 4.14696994e-03]
[ 3.00000000e+00 6.15788396e+00 6.18352686e+00 6.18748565e+00
5.40935770e-03 6.39806026e-04]
[ 4.00000000e+00 6.18352686e+00 6.18748565e+00 6.18744560e+00
-7.15024603e-06 6.47321466e-06]
[ 5.00000000e+00 6.18748565e+00 6.18744560e+00 6.18744565e+00
-9.61470903e-11 8.54518774e-09]]
Método de Newton-Raphson#
En los métodos de cálculo estándar si la iteración ha alcanzado el punto \(x_{n}\), entonces se requiere un incremento \(\Delta\, x_{n}\) que tomará el proceso hacia el punto solución \(x^{\star}\). Si se realiza una expansión de \(f\left(x^{\star} \right)\) con series de Taylor se tiene
Si la distancia \(\Delta\,x_{n}\) entre el punto de la iteración actual y la solución verdadera es suficientemente pequeña, entonces si se toman los dos primeros términos del lado derecho de la ecuación anterior se tiene
Despejando \(\Delta\, x_{n}\)
El método de Newton-Raphson se obtiene tomando
despejando \(x_{n+1}\) y sustituyendo en el valor de \(\Delta\, x_{n}\) como sigue
Trazando la tangente a la curva en el punto \(x_{0}\)
# Definición de la función continua y su derivada
def funcion(x):
"""Calcula el valor de la función para un valor dado de x."""
return 1 + 2 * x - 3 * (x**2) * np.exp(-x) + 2 * (x**3) * np.sin(x) * np.exp(-x / 5)
def derivada(x):
"""Calcula la derivada analítica de la función con respecto a x."""
# Derivada de 1 + 2x: 2
# Derivada de -3x^2 * e^(-x): -3 * (2x*e^(-x) - x^2*e^(-x)) = -6x*e^(-x) + 3x^2*e^(-x)
# Derivada de 2x^3 * sin(x) * e^(-x/5): Regla del producto de tres términos
term1 = 2
term2 = (-6 * x + 3 * (x**2)) * np.exp(-x)
term3 = (6 * (x**2) * np.sin(x) + 2 * (x**3) * np.cos(x) - 0.4 * (x**3) * np.sin(x)) * np.exp(-x / 5)
return term1 + term2 + term3
# Función auxiliar: Método de Newton-Raphson
def newton_raphson(x0, tol=1e-6, max_iter=100):
"""
Encuentra la raíz de una función usando el método de Newton-Raphson a partir de un punto inicial x0.
Retorna la raíz encontrada y una matriz con el historial de iteraciones.
"""
mat = []
x = x0
for i in range(1, max_iter + 1):
fx = funcion(x0)
dfx = derivada(x0)
# Validación para evitar división por cero en la derivada
if dfx == 0:
break
# Fórmula del método de Newton-Raphson
x = x0 - fx / dfx
fx_new = funcion(x)
# Cálculo del error relativo aproximado
error = abs((x - x0) / x) if x != 0 else abs(x - x0)
# Almacena la iteración: [iteración, x0, x, f(x), error]
mat.append([i, x0, x, fx_new, error])
# Criterio de parada por tolerancia o raíz exacta
if abs(fx_new) < 1e-12 or error < tol:
break
x0 = x
return x, np.array(mat)
# Programa Principal
print("Buscando cruces por cero en el intervalo [4, 20] mediante el Método de Newton-Raphson...")
r = 0 # Inicia el contador de cruces por cero.
cruce_list = [] # Lista para almacenar los valores de los cruces.
M_list = [] # Lista para almacenar las matrices de iteración de cada cruce.
# Ciclo para evaluar subintervalos de longitud 1 dentro del intervalo [4, 20].
# En Python, range(4, 21) recorre k desde 4 hasta 20 inclusivo.
for k in range(4, 21):
l = k + 1
# Evaluación de la función en los puntos k y l.
gk = funcion(k)
gl = funcion(l)
# Condición que marca el subintervalo donde se encuentra un cruce por cero.
if gk * gl < 1e-3:
# Llamada al método de Newton-Raphson usando k como valor inicial.
cero, mat = newton_raphson(k)
r += 1
cruce_list.append(cero) # Almacena el cruce por cero.
M_list.append(mat) # Almacena la matriz de iteraciones.
# Conversión de los cruces almacenados a un arreglo de NumPy.
Cruce = np.array(cruce_list)
# Extracción segura de las iteraciones de cada cruce por cero encontrado (usando índices base 0).
M1 = M_list[0] if len(M_list) > 0 else None
M2 = M_list[1] if len(M_list) > 1 else None
M3 = M_list[2] if len(M_list) > 2 else None
M4 = M_list[3] if len(M_list) > 3 else None
M5 = M_list[4] if len(M_list) > 4 else None
# Mostrar resultados en pantalla
print(f"\nTotal de cruces por cero encontrados: {r}")
print("Valores de los cruces por cero:", Cruce)
if M1 is not None:
print("\nIteraciones del primer cruce por cero (M1):\n", M1)
Buscando cruces por cero en el intervalo [4, 20] mediante el Método de Newton-Raphson...
Total de cruces por cero encontrados: 5
Valores de los cruces por cero: [ 6.18744565 9.50275574 12.48524454 15.80556286 18.72534361]
Iteraciones del primer cruce por cero (M1):
[[ 1.00000000e+00 6.00000000e+00 6.20329685e+00 2.15494489e+00
3.27723872e-02]
[ 2.00000000e+00 6.20329685e+00 6.18752805e+00 1.11427905e-02
2.54848086e-03]
[ 3.00000000e+00 6.18752805e+00 6.18744566e+00 3.08689238e-07
1.33160788e-05]
[ 4.00000000e+00 6.18744566e+00 6.18744565e+00 -5.68434189e-14
3.68916454e-10]]