Vamos a practicar diversas técnicas para encontrar ceros (raíces) de funciones no lineales de una variable real.
Comenzamos por un ejemplo sencillo: $$ f(x) = x + \frac{1}{2}\sin(x) -1 $$
%format long es necesario para poder ver los números con más decimales
format long;
f = @(x)(x + sin(x)/2 - 1);
xmin = -5;
xmax = 5;
xs = linspace(xmin, xmax,200);
ys = f(xs);
plot(xs, ys);
title('Grafica de f')
xlabel('x')
ylabel('y')
Observar las funciones nos puede dar pistas sobre el número y ubicación aproximada de las raíces, pero necesitamos demostrar la existencia y unicidad con alguna de estas técnicas:
En este caso concreto, tenemos un cambio de signo en el intervalo (-1,1), luego hay una raíz de $f$:
f(-1)
f(1)
hold on;
plot(xs,ys)
xleft = -1;
xright = 1;
plot([xmin, xmax], [0,0], 'k-')
plot([xleft, xright], [0,0], 'o')
axis xy
hold off;
La derivada de $f$ es fácil de calcular: $$ f'(x) = 1 + \frac{1}{2}\cos(x) $$ y podemos acotarla fácilmente por debajo si escribimos: $$ 1 = f'(x) - \frac{1}{2}\cos(x) $$ y usando la desigualdad triangular: $$ 1 \leq |f'(x)| + \frac{1}{2}|\cos(x)| $$ luego $$ |f'(x)| \geq 1 - \frac{1}{2}|\cos(x)| \geq 1 - \frac{1}{2} = \frac{1}{2} > 0 $$ Es decir, $f'$ no se anula nunca. Como $f'(0)>0$ y $f'$ no cambia de signo, $f'$ siempre es positiva, luego $f$ es creciente en todo $\mathbb{R}$, luego $f$ tiene sólo una raíz.
Ahora bien, encontrar esa raíz de forma exacta no es posible, y nos tenemos que contentar con aproximarla.
Implementamos el método de bisección:
[xleft, xright]
donde $f$ cambia de signo.xtol
para el valor de $x$.xmed = (xleft + xright)/2
.xtol
.hold on;
plot(xs,ys)
xleft = -4;
xright = 4;
xtol = 1e-2;
plot([xleft], [0], 'x')
plot([xright], [0], 'x')
j = 1;
sleft = sign(f(xleft));
sright = sign(f(xright));
while xright - xleft > xtol
xmed = (xleft + xright)/2;
smed = sign(f(xmed));
if smed==sleft
xleft = xmed;
else
xright = xmed;
end
plot([xmed], [0], 'o');
j = j + 1;
end
legends = cell(1, j+2);
legends{1} = 'f';
legends{2} = 'left end';
legends{3} = 'right end';
for k=1:j-1
legends{k+3} = sprintf('step %d', k);
end
legend(legends)
hold off;
Empaqueta el método de bisección como una función con entradas:
f
continuaa
y b
.xtol
y salidas
f(a)*f(b)>0
, se rompe y no devuelve nada (imprime un mensaje de error, lanza una "excepción", etc)f(a)*f(b)<=0
, devuelve un intervalo que contiene la raíz de longitud inferior a xtol
Atención: ¿hay más de una raíz? ¿cuántas son positivas?
Los métodos de Newton y Secante tienen diferencias cualitativas con el método de bisección:
Como no tenemos garantía de éxito, debemos programar nuestro método de forma defensiva. Si escribimos por ejemplo:
function x=newton(f, fp, x0, ytol)
x = x0;
errory = abs(f(x));
while errory > ytol
x = x - f(x)/fp(x);
errory = np.abs(f(x));
end
end
nos encontraremos en un bucle infinito siempre que el método no converja.
Una forma de evitar este problema es imponer un máximo de iteraciones usando un contador:
function x=newton(f, fp, x0, ytol, maxiter)
x = x0;
errory = abs(f(x));
k = 0; #contador
while errory > ytol
x = x - f(x)/fp(x);
errory = abs(f(x));
k = k + 1;
end
end
En la definición anterior hemos usado el criterio de parada $|f(x_n)|<ytol$.
El otro criterio de parada habitual es $|x_{n+1}-x_n|<xtol$.
El código que corresponde a ese criterio de parada es:
function x=newton(f, fp, x0, xtol, maxiter)
x = x0;
%Comenzamos con un error infinito, simplemente para que
%la ejecución del programa no se detenga antes de empezar
errorx = inf;
k = 0; %contador
#el bucle se detiene cuando se consiga la precisión deseada,
# pero también termina prematuramente si
# se alcanza el máximo de iteraciones
while errorx > xtol && k<maxiter
x = x - f(x)/fp(x);
errorx = abs(f(x));
k = k + 1;
end
end
Finalmente, ya que no hay garantías de éxito, es interesante devolver no sólo nuestra estimación de la raíz, sino nuestra estimación de la precisión obtenida:
function [x,errorx]=newton(f, fp, x0, xtol, maxiter)
x = x0;
%Comenzamos con un error infinito, simplemente para que
%la ejecución del programa no se detenga antes de empezar
errorx = inf;
k = 0; %contador
#el bucle se detiene cuando se consiga la precisión deseada,
# pero también termina prematuramente si
# se alcanza el máximo de iteraciones
while errorx > xtol && k<maxiter
x = x - f(x)/fp(x);
errorx = abs(f(x));
k = k + 1;
end
end
function [x,errorx]=newton(f, fp, x0, xtol, maxiter)
x = x0;
%Comenzamos con un error infinito, simplemente para que
%la ejecución del programa no se detenga antes de empezar
errorx = inf;
k = 0; %contador
#el bucle se detiene cuando se consiga la precisión deseada,
# pero también termina prematuramente si
# se alcanza el máximo de iteraciones
while errorx > xtol && k<maxiter
x = x - f(x)/fp(x);
errorx = abs(f(x));
k = k + 1;
end
end
f = @ (x)(x^2-2);
fp = @ (x)(2*x);
% 2 iteraciones son insuficientes para conseguir la precisión deseada
[x,e] = newton(f,fp, 1, 1e-8, 2)
% 10 iteraciones sí son suficientes para conseguir la precisión deseada
% La estimación del error es mejor que la precisión mínima que le pedimos
[x,e] = newton(f,fp, 1, 1e-8, 10)
[x,e] = newton(f,fp, 1, 1e-8, 100)
Para el método de Newton, y para muchos otros, es necesario poder evaluar la derivada de una función. Nuestras opciones son:
Implementa el método de la secante: