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