Esercizi sui metodi per la determinazione di radici di equazioni non

Esercizi sui metodi per la determinazione di radici di equazioni non lineari
1. Metodo dicotomico o di bisezione
Il metodo di bisezione si basa sulla dimostrazione costruttiva del teorema degli zeri di una funzione
continua su un intervallo chiuso e limitato con segno opposto agli estremi.
Siano a e b gli estremi dell'intervallo di ricerca e tol la massima tolleranza che si vuole
nell'approssimazione cercata allo zero della funzione.
Il seguente codice realizza il metodo di bisezione, riportando in x l'approssimazione desiderata e in it il
numero di iterazioni eseguite.
La complessità del metodo è pari a it+2 valutazioni di funzione.
Per ottenere una precisione pari a tol, occorre fare almeno log10 (b-a)/tol /log10 2 passi. Perciò si pone
maxit= round(log((b-a)/tol)/log(2)+1).
Il metodo di bisezione, pur essendo convergente sotto ipotesi abbastanza deboli, ha tuttavia una
convergenza lenta. Per ottenere 1 cifra di precisione decimale, supposto b-a=1, occorrono circa tre
passi (log10(1/10-1)/ log10 2  3).
Per fare in modo che il codice sia generale e possa essere usato su qualunque funzione, si passa come
parametro di ingresso il nome della function associata alla funzione di cui si vuole trovare lo zero e si
esegue la valutazione di funzione con l'istruzione di Matlab feval.
[y1,…yn]=feval(stringa,x1,…,xm);
determina l'esecuzione di una function il cui nome è stringa
(ovviamente stringa può essere una variabile che contiene il nome di una function), in corrispondenza
dei parametri x1,…,xm; il numero dei parametri x1,…,xm dipende dal numero dei parametri d'ingresso
della function stringa che viene eseguita. y1,..,yn sono i parametri di uscita della function stringa;
anche il loro numero dipende dal numero dei parametri di uscita della function stringa.
Il punto medio dell'intervallo [a,b] viene determinato mediante la formula a+(b-a)/2 che è
numericamente più stabile di (a+b)/2; la seconda formula, infatti, in aritmetica finita può produrre
valori esterni all'intervallo [a,b], mentre la prima fornisce un valore interno all'intervallo o uno degli
estremi.
Si consideri il seguente esempio, ove si assume di lavorare con 3 cifre di precisione decimale e
troncamento:
a=0.983 b=0.986
fl(0.983+0.986)= fl(1.969)=1.97
fl(1.97/2)= 9.80 esterno ad [a,b] !!
fl(0.986-0.983)=0.003 fl(0.003/2)=0.0015 fl(0.983+0.0015)=0.984 interno ad [a,b]!!
Per evitare eventuali overflow nel fare il controllo del segno della funzione agli estremi dell'intervallo
corrente mediante il prodotto f(a)*f(b), si può usare la funzione Matlab sign(y) che ritorna 1 se y>0, 0
se y=0, -1 se y<0.
Infine per controllare la fine dell'iterazione quando la tolleranza tol fornita come parametro d'ingresso è
troppo piccola o comunque inferiore alla precisione di macchina, si esegue il test d'arresto nel seguente
modo:
abs(b-a)>=tol+eps*max([abs(a) abs(b)])
ove eps è la precisione di macchina. Ciò previene la eventualità che la successione degli iterati stalli in
corrispondenza di uno degli estremi delle successione degli intervalli senza verificare il test di arresto.
Un esempio è dato da:
tol=0.004
a=98.5 b=98.6 con aritmetica finita con tre cifre di precisione decimale con troncamento
Si osserva che
fl( a+(b-a)/3)=98.5 ma fl(b-a)>0.04.
function [x,it]=bisez(fname,a,b,tol);
maxit=round(log((b-a)/tol)/log(2)+1);
fprintf('n. di passi necessari=%g \n',maxit);
fa=feval(fname,a);
fb=feval(fname,b);
if sign(fa)*sign(fb) >=0
error('intervallo non corretto');
else
it=0;
while abs(b-a)>=tol+eps*max([abs(a) abs(b)]) & it<=maxit
it=it+1;
pm=a+(b-a)*0.5;
fprintf('it=%g x=%g\n',it,pm);
fpm=feval(fname,pm);
if fpm ==0
break;
end;
if sign(fpm)*sign(fa) >0
a=pm;
fa=fpm;
else
b=pm;
fb=fpm;
end;
end;
if it>maxit
error('Raggiunto max limite di iterazioni');
else
x=pm;
end;
end;
Il codice bisez viene usato per determinare lo zero di f(x)=x2-78.8, mediante il seguente M-script file,
che accetta in ingresso a,b, e tol, esegue il grafico della funzione e poi determina lo zero cercato,
segnandolo sul grafico:
a=input('primo estremo intervallo=');
b=input('secondo estremo intervallo=');
tol=input('tolleranza=');
xtab=linspace(a,b,20);
ytab=feval('fun',xtab);
plot(xtab,ytab);
hold on;
[x,it]=bisez('fun',a,b,tol);
fprintf('zero= %g \n',x);
fprintf('n. di valutazioni di funzione=%g \n',it+2);
plot(x,0,'*');grid on;
hold off;
function y=fun(x);
y=x.^2-78.8;
2. Metodo delle approssimazioni successive
Il seguente codice realizza il metodo delle approssimazioni successive per il calcolo del punto fisso di
una funzione implementata come M-function di nome gname; x0 è l'approssimazione iniziale del punto
fisso, tol è una tolleranza prefissata e maxit è il massimo numero di iterazioni consentite.
Il codice riporta in x l'approssimazione desiderata e in it il numero di iterazioni eseguite.
function [x,it]=iterazione(gname,x0,tol,maxit);
x=x0;
for it=1:maxit
x0=x;
x=feval(gname,x);
% fprintf('it=%g x=%g x0=%g \n',it,x,x0);
if abs(x-x0)<=tol*abs(x0)
break
end;
end;
if it>=maxit
fprintf('Raggiunto max limite di iterazioni \n');
end;
La tolleranza non deve essere inferiore a 2 volte il valore entro cui si riesce ad approssimare il punto
fisso in precisione finita.
Si osservi i risultati del seguente codice relativo alla funzione implementata nella function pol.m, il cui
punto fisso è 1.
function y=pol(x);
p=[0.5 -1.5 0.5001 1.4999];
y=polyval(p,x);
» [x,it]=iterazione('pol',0.9,5e-2,1500)
x = 0.97437
it = 1228
» [x,it]=iterazione('pol',0.9,1e-2,1500)
Raggiunto max limite di iterazioni
x = 0.97698
it = 1500
Troviamo ora una approssimazione dello zero di x3-4x-10 usando
differenti funzioni g(x), a partire da x0=1.5.
function y=f1(x);
y=x-(x.^3+4*x.^2-10);
» [x,it]=iterazione('f1',1.5,1e-4,50)
Raggiunto max limite di iterazioni
x = NaN
it = 50
diverge!!
function y=f2(x);
y=(10./x-4*x).^0.5;
» [x,it]=iterazione('f2',1.5,1e-4,50)
Raggiunto max limite di iterazioni
x = 2.2748 3.6088i
it =50
non ben definito!!
function y=f3(x);
y=0.5*(10-x.^3).^0.5;
» [x,it]=iterazione('f3',1.5,1e-4,50)
x = 1.3652
it = 13
converge in 13 iterazioni!!
function y=f4(x);
y=(10./(x+4)).^0.5;
» [x,it]=iterazione('f4',1.5,1e-4,50)
x = 1.3652
it = 5
converge in 5 iterazioni!!
function y=f5(x);
y=x- (x.^3+4*x.^2-10)./(3*x.^2+8*x);
» [x,it]=iterazione('f5',1.5,1e-4,50)
x = 1.3652
it = 3
converge in 3 iterazioni!!
3. Il metodo di Newton
Nel seguente codice viene realizzato il metodo di Newton per la determinazione di una
approssimazione della radice dell'equazione f(x)=0.
Esso richiede per ogni passo la valutazione della funzione f(x) (implementata nella function di nome
fname) e della sua derivata prima (implementata nella function di nome fpname) in corrispondenza
dell'iterato corrente. Pertanto si dice che richiede due valutazioni di funzione per passo.
A partire da un iterato iniziale, fornito come parametro di ingresso x0, si determina una successione di
iterati che converge quadraticamente alla soluzione x*, se x0 è scelto sufficientemente vicino a x* e se
x* è uno zero semplice della funzione f(x).
Si forniscono come parametri di ingresso due tolleranze: tolx controlla la distanza tra due iterati
successivi e tolf il valore di f(x) in corrispondenza dell'ultimo iterato calcolato. Maxit è il numero
massimo di iterazioni consentite.
Il codice riporta in x l'approssimazione desiderata e in it il numero di iterazioni eseguite.
function [x,it]=newton(fname, fpname,x0,tolx,tolf,maxit);
% metodo di Newton
x=x0;fx=feval(fname,x);
fprintf('it
x corrente f(x)\n');
for it=1:maxit
d=fx/feval(fpname,x);
x=x-d;
fx=feval(fname,x);
fprintf('%g
%g
%g \n',it,x,fx);
if (abs(fx)<tolf & abs(d)<tolx*abs(x))|fx==0
break;
end;
end;
if it>=maxit
fprintf('raggiunto massimo numero di iterate \n');
end;
Il seguente M-script file mostra il funzionamento del codice per la funzione realizzata mediante la
function fun1.m:
tolx=input('tolleranza sulla x=');
tolf=input('tolleranza sulla funzione=');
x0=input('punto iniziale=');
maxit=input('n. massimo di iterazioni=');
[x,it]=newton('fun1','fun1p',x0,tolx,tolf,maxit);
fprintf('zero= %g \n',x);
fprintf('n. di valutazioni di funzione=%g \n',2*it);
function y=fun1(x);
y=sin(x)-(x/2).^2;
function y=fun1p(x);
y=cos(x)-x/2;
» prova_newton
tolleranza sulla x=1e-6
tolleranza sulla funzione=1e-6
punto iniziale=1.5
n. massimo di iterazioni=10
it
x corrente f(x)
1
2.14039
-0.303202
2
1.95201
-0.0243706
3
1.93393
-0.000233752
4
1.93375
-2.24233e-008
5
1.93375
-2.22045e-016
zero= 1.93375
n. di valutazioni di funzione=10
» [x,it]=bisez('fun1',1,2,1e-6)
x = 1.9338
it = 20
Si noti che il metodo di bisezione richiede molti più passi e il doppio quasi di valutazioni di
funzione!!
Per funzioni per cui f'(x*) assume valori piccoli la costante asintotica di errore può diventare molto
grande e rallentare notevolmente la convergenza anche se essa è quadratica.
Si veda cosa succede nel caso di f(x)=x2 -a quando sqrt(a) è piccolo. In tal caso, la costante asintotica
di errore diventa:
f''(x*)/(2f'(x*))= 1 /(2sqrt(a))
tolf=1e-10;
maxit=400;
global a;
for a= (1/10).^(0:4:16)
x0=2;
tolx=1e-10;
[x,it]=newton('sq','sqp',x0,tolx,tolf,maxit);
fprintf('a=%g sqrt(a)=%g it=%g x=%g\n',a,sqrt(a),it,x);
end;
» prova_sq
a=1 sqrt(a)=1 it=6 x=1
a=0.0001 sqrt(a)=0.01 it=13 x=0.01
a=1e-008 sqrt(a)=0.0001 it=19 x=0.0001
a=1e-012 sqrt(a)=1e-006 it=26 x=1e-006
a=1e-016 sqrt(a)=1e-008 it=33 x=1e-008
Il metodo di Newton si arresta per l'effetto combinato di due test. Il test solo sul valore della funzione
non basta. Può succedere che f(x) sia piccolo in valore assoluto pur essendo x ancora lontano dalla
soluzione. Viceversa, può succedere che x sia vicino alla soluzione e |f(x)| sia grande.
Per esempio, si veda il caso di f(x)=(x-1)^10 .
function y=fun2(x);
y=(x-1)^10;;
function y=fun2p(x);
y=10*(x-1).^9;
» prova_newton
tolleranza sulla x=1e-5
tolleranza sulla funzione=1e-5
punto iniziale=2
n. massimo di iterazioni=100
it
x corrente f(x)
1
1.9
0.348678
….
87
1.0001
1.55233e-040
88
1.00009
5.41263e-041
89
1.00008
1.88727e-041
zero= 1.00008
n. di valutazioni di funzione=178
Si osservi che questo è un caso di zero di molteplicità 10 e quindi la convergenza diventa lineare.
Se si modifica la formula di Newton nel seguente modo:
x=x-10*d
si ottiene
» prova_newton
tolleranza sulla x=1e-5
tolleranza sulla funzione=1e-5
punto iniziale=2
n. massimo di iterazioni=10
it
x corrente f(x)
zero= 1
n. di valutazioni di funzione=2
Il test |d|<tolx*abs(x) non garantisce la convergenza, poiché può succedere che la successione
converga in modo estremamente lento o diverga, per cui d soddisfa il test ma si è ancora lontani dalla
soluzione.
Si veda il seguente caso:
function y=fun3(x);
y=tan(x);
function y=fun3p(x);
y=1+tan(x).^2;
» prova_newton
tolleranza sulla x=1e-5
tolleranza sulla funzione=1e-5
punto iniziale=pi/2-1e-5
n. massimo di iterazioni=10
it
x corrente f(x)
1
1.57078
50000
2
1.57076
25000
3
1.57072
12500
4
1.57064
6250
5
1.57048
3125
6
1.57016
1562.5
7
1.56952
781.25
8
1.56824
390.624
9
1.56568
195.311
10
1.56056
97.654
raggiunto massimo numero di iterate
zero= 1.56056
n. di valutazioni di funzione=20
Non sempre il metodo di Newton converge. Occorre essere sufficientemente vicini alla soluzione.
function y=fun4(x);
y=atan(x);
function y=fun4p(x);
y=1./(1+x.^2);
» prova_newton
tolleranza sulla x=1e-5
tolleranza sulla funzione=1e-5
punto iniziale=1.4
n. massimo di iterazioni=100
it
x corrente f(x)
1
-1.41362
-0.955118
2
1.45013
0.967089
3
-1.55063
-0.998014
4
1.84705
1.07458
5
-2.89356
-1.23805
6
8.71033
1.45649
7
-103.25
-1.56111
8
16540.6
1.57074
9
-4.29721e+008
-1.5708
10
2.90064e+017
1.5708
11
-1.32162e+035
-1.5708
12
2.74369e+070
1.5708
13
-1.18247e+141
-1.5708
14
2.19635e+282
1.5708
Warning: Divide by zero
....
Se si vuole sempre avere convergenza, occorre rendere globale il metodo di Newton.
Per fare ciò, si parte con due valori a e b in cui la funzione f(x) ha segno opposto e con x0=(a+b)/2.
Per ogni iterazione si calcola se l'iterato ottenuto con il metodo di Newton a partire dall'iterato
precedente è contenuto nell'intervallo corrente [a,b]; se ciò accade si considera come nuovo iterato
l'iterato di Newton altrimenti si prende come nuovo iterato il punto medio dell'intervallo corrente
(passo di bisezione); poi si aggiorna l'intervallo di ricerca valutando il segno della funzione nel nuovo
iterato; combinando allora il metodo di Newton con il metodo di bisezione si ottiene convergenza
globale.
Per valutare se il valore dell'iterato di Newton è contenuto nell'intervallo corrente [a,b] si esegue la
seguente function:
function ok=isin(a,b,x,fx,fxp);
if fxp>0
ok=(-fx<=(b-x)*fxp) & ((a-x)*fxp<=-fx);
% a<= x-fx/fxp <= b
elseif fxp<0
ok=(-fx>=(b-x)*fxp) & ((a-x)*fxp>=-fx);
else
ok=0
end;
Il metodo di Newton globale, realizzato nel seguente codice, riporta l'approssimazione dello zero
cercato e il numero di valutazioni di funzione necessarie ad ottenerlo.
function [x,it]=glob_newton(fname, fpname,a,b,tolx,tolf);
% metodo di Newton globale
maxit=round(log((b-a)/tolx)/log(2)+1);
x=(a+b)/2;
fa=feval(fname,a);
fb=feval(fname,b);
if sign(fa)*sign(fb)>=0
error('intervallo errato!!');
else
fx=feval(fname,x);fxp=feval(fpname,x);
fprintf('metodo
it
x corrente f(x)
a
b\n');
it=3;
for i=1:maxit
if isin(a,b,x,fx,fxp)
d=fx/fxp;
x=x-d;
fprintf('Newton
');
else
x=a+(b-a)/2;
fprintf('Bisezione ');
end;
fx=feval(fname,x);
it=it+1;
fxp=feval(fpname,x);
it=it+1;
if sign(fa)*sign(fx)<=0
b=x;
fb=fx;
else
a=x;
fa=fx;
end;
fprintf(' %g
%g
%g
%g
%g\n',i,x,fx,a,b);
if (abs(fx)<tolf & abs(b-a)<tolx*abs(x))| fx==0
break;
end;
end;
if i>=maxit
fprintf('raggiunto massimo numero di iterate \n');
end;
end;
Si prova con la funzione precedente (fun4) ad ottenere un'approssimazione dello zero nell'intervallo
[-6,3]:
» prova_glob_newton
tolleranza sulla x=1e-5
tolleranza sulla funzione=1e-5
a=-6
b=3
metodo
it
x corrente f(x)
a
b
Newton
1
1.69408
1.03755
-6
1.69408
Newton
2
-2.32113
-1.164
-2.32113
1.69408
Bisezione 3
-0.313524
-0.303817
-0.313524
1.69408
Newton
4
0.0201579
0.0201551
-0.313524
0.0201579
Newton
5
-5.46019e-006
-5.46019e-006
-5.46019e-006
0.0201579
Newton
6
1.08526e-016
1.08526e-016
-5.46019e-006
1.08526e-016
Newton
7
0
0
-5.46019e-006
0
zero= 0
n. di val. di funzione=17
5. Matlab possiede una sua istruzione per calcolare gli zeri di una funzione non lineare:
x=fzero(fname,x0,tol,trace)
ove fname è il nome della function che calcola la funzione non lineare, x0 è una approssimazione
iniziale, tol è una tolleranza e se trace ha un valore superiore ad 1 vengono date informazioni sulle
iterazioni intermedie. I parametri tol e trace possono essere omessi.
x=fzero('fun1',1.5,1e-6,2)
Func-count
x
f(x)
Procedure
1
1.5
0.434995
initial
2
1.45757
0.462467
search
3
1.54243
0.404828
search
4
1.44
0.473058
search
5
1.56
0.391542
search
6
1.41515
0.487251
search
7
1.58485
0.371962
search
8
1.38
0.505754
search
9
1.62
0.34269
search
10
1.33029
0.528798
search
11
1.66971
0.298133
search
12
1.26
0.55519
search
13
1.74
0.228819
search
14
1.16059
0.580297
search
15
1.83941
0.118281
search
16
1.02
0.592008
search
17
1.98 -0.062662
search
Looking for a zero in the interval [1.02, 1.98]
18
1.88811 0.0588332
interpolation
19
1.93261 0.00151273
interpolation
20
1.93376 -2.0273e-006
interpolation
21
1.93375 3.08523e-006
interpolation
Zero found in the interval: [1.02, 1.98].
x = 1.9338