Introduzione a ”MAPLE” Andrea Ellero, Alberto Zorzi - AA.2002/2003 Questi appunti hanno lo scopo di introdurre all’uso del programma Maple (Waterloo Maple Inc.) e sono di supporto alle lezioni di Informatica (idoneità), Facoltà di Economia dell’Università Ca’Foscari di Venezia. La conoscenza del programma puù essere approfondita utilizzando un qualsiasi manuale di Maple. INDICE DEI PARAGRAFI 1. Calcoli, espressioni e funzioni. 2. Risoluzione di equazioni e sistemi. 3. Limiti, derivate, integrali. 4. Funzioni di più variabili e grafici. 5. Algebra delle matrici. 6. Introduzione alla programmazione. 1 1.1 1. Calcoli, espressioni e funzioni Calcoli numerici e letterali Maple è in grado di soddisfare esigenze di calcolo sia di tipo simbolico che numerico. 1.1.1 Numeri razionali e numeri floating point Provate a calcolare una somma algebrica di due numeri interi. Seguite l’esempio e battete il tasto di ritorno a capo. > 3-5; −2 NB: non è necessario scrivere ”=” mentre è obbligatorio concludere l’espressione con ”;” > 7/2; 7 2 NB: non viene eseguita la divisione in modo numerico per non perdere precisione negli eventuali calcoli successivi. Per Maple, 7/2 e 3.5 sono oggetti diversi: il primo è un numero razionale, il secondo è un numero in virgola mobile (floating point). Per chiarire la differenza consideriamo uno stesso calcolo eseguito con Maple utilizzando la notazione razionale e con virgola: > (1/3.0)*3; .9999999999 > (1/3)*3; 1 NB: il simbolo ’*’ indica la moltiplicazione e non puù essere sottinteso. Si noti che il primo calcolo viene effettuato in virgola mobile anche se un solo numero (3.0) è di tipo floating point. Il secondo calcolo è esatto perchè 1/3 è riconosciuto e trattato come numero razionale. Vediamo che pasticci possono accadere: consideriamo un calcolo in cui siano utilizzate le due notazioni appena viste > (1/3.0)*3-(1/3)*3; −.1 10−9 cioè -0.1 per un miliardesimo ovvero -0.0000000001 mentre, ovviamente, dovrebbe risultare 0. Possiamo chiedere un calcolo numerico più accurato chiedendo a Maple di utilizzare un maggior numero di cifre (digits) decimali: > Digits:= 10; Digits := 10 > (1/3.0)*3-(1/3)*3; −.1 10−24 ora gli zeri che precedono la cifra 1, dopo la virgola, sono 24: si tratta di un numero molto vicino allo zero ma non è esattemente zero. 1.1.2 Arrotondamenti, troncamenti ed errori La precisione nei calcoli effettuati da Maple è assai elevata, ma alla precisione dei calcoli è necessario accompagnare attenzione nel fornire i dati alla macchina e nell’interpretare i risultati che la macchina restituisce. Ad esempio se forniamo dati poco precisi è inutile, se non dannoso, utilizzare molte cifre decimali nei calcoli effettuati da Maple. Vediamo allora come comportarci in alcuni casi frequenti. Anzitutto, quando è opportuno trascurare alcune cifre decimali di un numero possiamo semplicemente tenere solo le cifre che ci interessano: effettuiamo in tal caso un troncamento del numero. Possiamo diminuire l’errore nel considerare il dato effettuando un arrotondamento. Effettuare un arrotondamento significa aumentare di una unità l’ultima cifra che consideriamo qualora la prima cifra che stiamo per eliminare sia maggiore o uguale a 5, troncare il numero altrimenti. Ad esempio se > x:=123.4492; x := 123.4492 volendo considerare due cifre decimali con un troncamento otteniamo il numero 123.44, arrotondando troviamo invece 123.45. Se desideriamo una sola cifra decimale allora sia troncando che arrotondando otteniamo 123.4. E’ chiaro che in generale l’arrotondamento, a parità di cifre considerate, consente di ridurre l’errore commesso ed è pertanto preferibile. Vediamo ora cosa accade se effettuiamo gli arrotondamenti troppo presto. Supponiamo di dover calcolare quanto vino sia necessario per riempire un contenitore cilindrico di alluminio (qui semplifichiamo e supponiamo si tratti di un cilindro perfetto). Prendiamo le misure interne del cilindro. Con un calibro (precisione 1/10=0.1 mm) riusciamo a misurare il diametro: > Diam:= 9.31 ; # cm Diam := 9.31 per l’altezza usiamo un metro con precisione di un millimetro: > Altezza:= 19.4 ; # cm Altezza := 19.4 Calcoliamo il volume: > Volume:=(Diam/2)^2*Pi*Altezza; # il risultato è in cm^3 Volume := 420.3790850 π > evalf(%); 1320.659845 AnzichÈ considerare l’intera cifra decidiamo di considerare solo le quattro cifre a sinistra della virgola. Quindi, arrotondando all’unità, otteniamo 1321 centimetri cubi (in litri: 1,321). Supponiamo che il vino costi 0,51 euro al litro e di dover riempire 19800 lattine. Il costo che dovremo sostenere sarà allora: > Costo_totale:=1.321*0.51*19800; Costo totale := 13339.45800 ovvero 13339,46 euro. E’ una bella cifra che ci spinge ad effettuare una valutazione più precisa. Tornando al calcolo del volume prendiamo subito qualche cifra in più, anzi teniamo tutte quelle che vediamo scritte, non ci costa più fatica: > Costo_totale:=1.320659845*0.51*19800; Costo totale := 13336.02312 La differenza, in euro, è data da > 13339.46-13336.02; 3.44 L’esempio ci suggerisce di effettuare i calcoli ritardando il più possibile il momento nel quale effettuare gli arrotondamenti. Errori nei dati iniziali e trasmissione dell’errore Ma il nostro conto è anche affetto da un altro errore, un errore inevitabile anche se meno evidente, dovuto alla precisione con la quale sono noti i dati iniziali. Per misurare il diametro del cilindro avevamo infatti a disposizione un calibro con precisione di un decimo di millimetro. In realtà dunque siamo certi che il diametro (Diam=9,31 cm) ha una misura compresa tra 9,30 e 9,32 cm; di più non possiamo chiedere al calibro. Diciamo che l’errore assoluto in questa misura è di 0,01 cm e possiamo scrivere Diam = 9,310,01. L’errore percentuale, o errore relativo, è dato dal rapporto tra l’errore assoluto e la misura effettuata > er_Diam:=0.01/9.32; er Diam := .001072961373 vale a dire circa uno su mille. Analogamente per l’altezza, l’errore assoluto è di 0,1 cm, ovvero Altezza=19,4 0,1 cm. L’errore relativo è in tal caso: > er_Altezza:=0.1/19.4; er Altezza := .005154639175 quindi dell’ordine del 5 per mille. Ma quale errore commettiamo nel calcolo del volume? Si dimostra che moltiplicando tra loro due misure l’errore percentuale trasmesso nel calcolo è dato dalla somma degli errori percentuali. Nel nostro caso allora: > er_Volume:=er_Diam+er_Diam+er_Altezza; er Volume := .007300561921 L’errore relativo nel calcolo del volume è dunque del 7 per mille (perchÈ sommiamo due volte l’errore relativo del diametro?). Lo stesso errore relativo si ripercuote sul calcolo del costo totale. Si noti che gli altri fattori che compaiono nel calcolo del costo totale sono il numero 1/2, che è esatto cioè con errore assoluto nullo, e π, che Maple tiene in memoria con un numero di cifre significative sufficientemente elevato da poter essere considerato con errore relativo trascurabile. In termini assoluti il calcolo del volume è affetto allora dall’errore: > ea_Volume:=er_Volume*Volume; ea Volume := 3.069003540 π > evalf(%); 9.641558976 In realtà quindi il volume di una singola lattina è 1320.659845 9.641558976 ovvero di millilitri 132110. L’errore su di una singola lattina è dell’ordine di mezzo cucchiaio da cucina! Per quanto riguarda il costo complessivo abbiamo invece: > ea_Costo_totale:=er_Volume*Costo_totale; ea Costo totale := 97.36046257 quindi un errore dell’ordine di un centinaio di euro, un errore che rende in realtà trascurabile, in questo caso, l’errore complessivo dovuto al precedente arrotondamento al centimetro cubo del volume della lattina! Problemi simili a quelli incontrati nel caso di una moltiplicazione si hanno anche nella trsmissione degli errori effettuando altre operazioni tra i dati: per un loro adeguato trattamento rimandiamo ai testi specializzati in analisi numerica. 1.1.3 Calcoli simbolici Maple è in grado di fare calcoli di tipo letterale utilizzando moltissimi comandi per la manipolazione algebrica. Vediamo qualche esempio. > 3*a-5*a+4*a-b*a; 2a−ba Manipoliamo l’espressione raccogliendone i fattori: > factor(%); −a (−2 + b) NB: per indicare la riga di calcolo (execution group) appena eseguita si utilizza il simbolo di percentuale (%), nelle versioni di Maple meno recenti lo stesso effetto si ha utilizzando il doppio apice (”). > 3*x*(x^2-y^2)^2/((x+y)*9*x); 1 (x2 − y2 )2 3 x+y NB: l’elevamento a potenza viene indicato dal simbolo ’ˆ’. Si puù notare che Maple effettua immediatamente le semplificazioni ovvie. Ma puù fare di meglio: > simplify(%); 1 (x − y) (x2 − y2 ) 3 Vediamo qualche altra semplificazione: > root(x^2,2); √ x2 > simplify(%); csgn(x) x NB: csgn(x) indica il segno di x. Poteva far di meglio scrivendo il valore assoluto di x, che pure è definito (si provi a scrivere >abs(x);) e si legge meglio: questa è una avvisaglia del fatto che Maple puù perdersi in un bicchier d’acqua. Ancora un esempio: > (x+2)*(x^2-1)/(x+1); (x + 2) (x2 − 1) x+1 cerchiamo di riscrivere l’espressione in modo più semplice: > factor(%); (x − 1) (x + 2) Proviamo ad eseguire le operazioni > expand(%); x2 + x − 2 ancora non ci soddisfa, torniamo a fattorizzare > factor(%); (x − 1) (x + 2) Rifacciamo il conto in ordine diverso > (x+2)*(x^2-1)/(x+1); (x + 2) (x2 − 1) x+1 proviamo prima ad espandere > expand(%); x3 x 2 x2 2 − + − x+1 x+1 x+1 x+1 un disastro! Proviamo a vedere se si semplifica qualcosa > simplify(%); x2 + x − 2 meglio. Ora proviamo a fattorizzare > factor(%); (x − 1) (x + 2) L’ordine delle operazioni è ovviamente importante: l’abbiamo provato tante volte facendo i conti a mano, qui almeno possiamo riprovare velocemente. Provate a rifare il conto cambiando ancora l’ordine delle manipolazioni e con altre espressioni. Dagli esempi fin qui visti simplify puù apparire come il comando più utile; in realtà il comando più flessibile è expand che consente, ad esempio, di stabilire quale fattore raccogliere: > expand((x+2)*(x+3)*(y+z),x); > x2 y + x 2 z + 5 x y + 5 x z + 6 y + 6 z expand((x+2)*(x+3)*(y+z),x+2); > (x + 2) x y + (x + 2) x z + 3 (x + 2) y + 3 (x + 2) z expand((x+2)*(x+3)*(y+z),y+z); (y + z) x2 + 5 (y + z) x + 6 y + 6 z > factor(%); (x + 2) (x + 3) (y + z) 1.1.4 Altre informazioni Maple consente operazioni sui numeri interi quali la divisione con resto, il fattoriale, la fattorizzazione..., sui numeri complessi, su insiemi, vettori, matrici,... Sono inoltre definite alcune costanti quali > exp(1); e > Pi; π 1.2 Assegnazioni e funzioni Possiamo associare un nome ad un oggetto matematico quale ad esempio un’espressione, un’equazione, una funzione. La sintassi è nome := oggetto; . Ad esempio > fattore1:=x*y; fattore1 := x y ora fattore1 è una variabile che vale xy. Consideriamo una seconda variabile > fattore2:=x*z; fattore2 := x z e moltiplichiamo > prodotto:=fattore1*fattore2; prodotto := x2 y z Maple ha già effettuato qualche conto riconoscendo il valore delle variabili utilizzate. Possiamo definire in questo modo anche le funzioni: > f:=x->x^2-1; f := x → x2 − 1 La variabile f è in questo caso un oggetto di tipo funzione che possiamo utilizzare come siamo abituati, per esempio per valutarla in un punto > f(2); 3 ma anche in un punto simbolico > f(antonio); antonio 2 − 1 o componendola > f(y-1); (y − 1)2 − 1 Combiniamo una variabile di tipo numero con la funzione f > alfredo:=3; alfredo := 3 > f(alfredo); 8 E’ poi semplicissimo disegnare grafici mediante il comando plot: > plot(f,-2..4,-2..5); 5 4 3 2 1 –2 –1 0 1 2 3 4 –1 –2 Nell’esempio la porzione di grafico che interessa è quella racchiusa nel rettangolo [-2,4]x[-2,5]. Il secondo argomento del comando plot è un intervallo contenuto nel dominio di f relativamente al quale interessa rappresentare il grafico di f. Il terzo argomento è opzionale e, se presente, individua l’intervallo di interesse relativamente al codominio. 2 2. Risoluzione di equazioni e sistemi 2.1 Equazioni La risoluzione di equazioni è particolarmente semplice, il comando da utilizzare è solve. > solve({x^2=4},{x}); {x = 2}, {x = −2} Maple commette errori nel caso di calcoli letterali, anche semplici, se ci sono parametri > solve({a*x+b=0},{x}); b {x = − } a (cosa succede se a=0?) e non risponde in alcuni casi numerici ”proibiti” > solve({0*x+3=0},{x}); E’ capace di lavorare con i numeri complessi > solve({x^2+1=0},{x}); {x = I}, {x = −I} (I è l’unità immaginaria vale a dire il numero complesso tale che Iˆ2=-1). Vediamo qualche altro esempio > solve({x^3+x^2-5*x=0},{x}); 1 1√ 1 1√ {x = 0}, {x = − + 21}, {x = − − 21} 2 2 2 2 > solve({x^3-13*x+12=0},{x}); > {x = 1}, {x = 3}, {x = −4} solve({x^2+y=0},{x,y}); > {y = −x2, x = x} solve({x^2+y^2=0},{x,y}); > {x = I y, y = y}, {x = −I y, y = y} solve({x^7+x^2=0},{x}); √ 1 1 √ p 1√ 5 + − I 2 5 + 5}, {x = 0}, {x = 0}, {x = −1}, {x = − 4 4 4 √ √ 1 1√ 1 1√ 1 √ p 1 √ p {x = + 5 − I 2 5 − 5}, {x = + 5 + I 2 5 − 5}, 4 4 4 4 4 4 √ 1√ 1 1 √ p {x = − 5 + + I 2 5 + 5} 4 4 4 > solve({exp(x)*x^2-3=0},{x}); 1√ 1√ {x = 2 LambertW( 3)}, {x = 2 LambertW(− 3)} 2 2 Nell’ultimo esempio LambertW è una funzione che soddisfa l’equazione LambertW(x) * exp(LambertW(x)) = x . Maple è riuscito a fornirci una soluzione esatta anche in questo caso. In genere siamo perù più interessati alla risoluzione numerica di una equazione come la precedente. Il comando da usare è in tal caso fsolve, con la stessa sintassi di solve > fsolve({exp(x)*x^2-3 = 0},{x}); {x = 1.033230904} Ecco un altro caso interessante > f:=sin(x); > f := sin(x) sol1:=solve({f=0.3},{x}); > sol1 := {x = .3046926540} sol2:=fsolve({f=0.3},{x}); sol2 := {x = .3046926540} Quale soluzione è corretta? In realtà queste sono solo due delle infinite soluzioni dell’equazione considerata. Maple non è in grado, in questo caso, di avvisarci della presenza di altre soluzioni. Possiamo comunque accertare la bontà delle soluzioni trovate sostituendole come argomento della funzione f utilizzando il comando subs > subs(sol1,f); sin(.3046926540) chiedendo il calcolo del valore della funzione nel punto mediante il comando evalf > evalf(%); .3000000000 > subs(sol2,f); sin(.3046926540) > evalf(%); .3000000000 2.2 Sistemi di equazioni Per risolvere sistemi di equazioni è sufficiente scrivere come argomenti del comando solve l’elenco delle equazioni e delle variabili rispetto alle quali risolverle > eq1:=3*x+2*y=2; eq1 := 3 x + 2 y = 2 > eq2:=x-y=4; eq2 := x − y = 4 > solve({eq1,eq2},{x,y}); {x = 2, y = −2} Vediamo un caso in cui il sistema presenta infinite soluzioni > eq3:=x+2*y=1; eq3 := x + 2 y = 1 > eq4:=2*x+4*y=2; eq4 := 2 x + 4 y = 2 > solve({eq3,eq4},{x,y}); {y = y, x = −2 y + 1} Mettiamo ora alla prova Maple su di un sistema di due equazioni non lineari. Sia > primo1:=3*x^3-exp(y); primo1 := 3 x3 − ey il primo membro della prima equazione e > primo2:=y^2+x; primo2 := y2 + x il primo membro della seconda equazione e risolviamo il sistema primo1=0, primo2=3 dando anche un nome all’insieme delle soluzioni > soll:=fsolve({primo1=0,primo2=3},{x,y}); soll := {x = .4052948807, y = −1.610808840} Controlliamo l’esattezza del risultato sostituendo le soluzioni trovate in primo1 e primo2 > subs(soll,primo1); > evalf(%); .1997260023 − e(−1.610808840) 0. > subs(soll,primo2); 3.000000000 Si noti che la soluzione trovata non è unica (anche se Maple non lo dice): > subs({x=1.098015136,y=1.379124673},primo1); 3.971423811 − e1.379124673 > evalf(%); > 0. subs({x=1.098015136,y=1.379124673},primo2); 3.000000000 3 3. Limiti, derivate, integrali Maple fornisce tutti gli strumenti tradizionali dell’analisi. Iniziamo a vedere alcune delle possibilità offerte nel caso di funzioni reali di variabile reale. Arriveremo a poter svolgere il tradizionale ”studio di funzione” in modo completo. 3.1 3.1 Limiti Definiamo una funzione della quale andremo poi a cercare alcuni limiti > g:= x-> (x+1)*(x+2)*(x-3)/(x-1); (x + 1) (x + 2) (x − 3) x−1 Proviamo a calcolare il limite per x - > 1. Prima definiamo il limite che ci interessa g := x → > Limit(g(x),x=1); lim x→1 e adesso cerchiamone il valore > value(%); (x + 1) (x + 2) (x − 3) x−1 undefined La risposta è corretta infatti calcolando i limiti da sinistra e da destra troviamo > Limit(g(x),x=1,left); lim x→1− > value(%); (x + 1) (x + 2) (x − 3) x−1 ∞ Per il limite destro utilizziamo la lettera minuscola nel comando limit , questo consente di ottenere subito il valore numerico del limite > limit(g(x),x=1,right); −∞ Se gli ultimi due limiti calcolati da Maple sono giusti (e lo sono) allora il primo limite cercato non esiste. Vediamo come si calcolano i limiti a + e - infinito. Ad esempio > Limit(g(x),x=-infinity); lim x→(−∞) > value(%); (x + 1) (x + 2) (x − 3) x−1 ∞ 3.2 3.2 Derivate Consideriamo una funzione di x e contenente il parametro k: > f:=x->x*cos(k*x); f := x → x cos(k x) > diff(f(x),x); cos(k x) − x sin(k x) k In realtà il fatto che f sia definita come funzione di x a Maple interessa poco. Possiamo infatti derivare anche rispetto a k: > diff(f(x),k); −x2 sin(k x) Cerchiamo ora i punti stazionari di una funzione. Definiamo la funzione > h:=x->x^3-2*x^2-4*x; h := x → x3 − 2 x2 − 4 x e calcoliamone i punti stazionari ai quali diamo anche un nome (staz ): > staz:=solve(diff(h(x),x)=0,x); −2 staz := 2, 3 Più in generale possiamo studiare la monotonia della funzione determinando gli intervalli nei quali la derivata si mantiene non negativa: > solve(diff(h(x),x)>=0,x); −2 ), RealRange(2, ∞) RealRange(−∞, 3 Otteniamo cosı̀ che -2/3 è un punto di massimo locale mentre è di minimo locale il punto 2. Vediamo come classificare i punti stazionari utilizzando la derivata seconda. Anzitutto calcoliamola nel generico punto x: > diff(h(x),x,x); 6x−4 Speghiamo ora a Maple di considerare l’espressione appena trovata come immagine di x tramite la funzione derivata seconda di h, che chiameremo DDh; per far questo usiamo il comando unapply > DDh:=unapply(%,x); DDh := x → 6 x − 4 Adesso possiamo valutare la derivata seconda nei due punti stazionari. Nel primo punto si ha > DDh(staz[1]); 8 perciù si tratta di un massimo. Nel secondo punto stazionario si ha > DDh(staz[2]); −8 che ci conferma che il punto 2 è di minimo per h. Vediamo il grafico (numerico) di h fornito da Maple: > plot(h(x),x=-3..5,-10..10); 10 8 6 4 2 –3 –2 –1 0 –2 –4 –6 –8 –10 1 2 x 3 4 5 3.3 3.3 Uno studio di funzione Esaminiamo le caratteristiche del grafico di una funzione in modo dettagliato. Consideriamo la funzione > f:=x->x^3*(x+1)/(x^2+2); f := x → x3 (x + 1) x2 + 2 Proviamo a farci disegnare il grafico da Maple: > plot(f(x),x=-10..10); 100 80 60 40 20 –10 –8 –6 –4 –2 0 2 4 x 6 8 10 Pare che in 0 ci sia un minimo. Ma vediamo di effettuare uno studio delle qualità delle proprietà della funzione in modo completo. Partiamo con il calcolo dei limiti: > limit(f(x),x=-infinity); ∞ > limit(f(x),x=+infinity); ∞ Studiamo il segno di f: > solve(f(x)>=0,x); RealRange(−∞, −1), RealRange(0, ∞) pertanto f è negativa tra -1 e 0, positiva altrove. Per studiare la monotonia calcoliamo la derivata di f > diff(f(x),x); 3 x2 (x + 1) x3 2 x4 (x + 1) + 2 − 2 x +2 x +2 (x2 + 2)2 che si puù semplificare un po’: > simplify(%); x2 (2 x3 + 8 x + x2 + 6) (x2 + 2)2 Cerchiamo i punti stazionari > staz:=solve(diff(f(x),x)=0,x); 47 1 1 (1/3) 6 + − , staz := 0, 0, − %1 (1/3) 6 6 %1 47 1 1 √ 1 1 1 (1/3) (1/3) %1 − − − + I 3 (− %1 12 12 %1(1/3) 6 2 6 1 1 1 47 1 1 √ (1/3) (1/3) %1 − − − I 3 (− %1 − 12 12 %1(1/3) 6 2 6 √ %1 := 253 + 18 518 47 1 ), 6 %1(1/3) 1 47 ) 6 %1(1/3) Si leggono proprio male! Prima di tentare di scrivere meglio i punti stazionari vediamo cosa si puù dire riguardo alla monotonia fornendo a Maple solo il fattore che determina il segno della derivata > solve(2*x^3+8*x+x^2+6>=0,x); 47 √ 1 1 6√ − , ∞ RealRange − (253 + 18 518)(1/3) + 6 (253 + 18 518)(1/3) 6 ancora una volta qualcosa di poco leggibile. Proviamo a riscrivere i punti stazionari: > s1:=evalf(staz[1]); s1 := 0. > s2:=evalf(staz[2]); s2 := 0. > > > s3:=evalf(staz[3]*1.); s3 := −.7212304534 s4:=evalf(staz[4]); s4 := .1106152263 − 2.036497585 I s5:=evalf(staz[5]); s5 := .1106152263 + 2.036497585 I Valutiamo f nel terzo punto stazionario: > f(s3); −.04149894724 Proviamo ora ad utilizzare la derivata seconda per classificare i punti stazionari > DDf:=unapply(diff(f(x),x,x),x); DDf := x → 6 x (x + 1) 6 x2 14 x3 (x + 1) 4 x4 8 x5 (x + 1) + 2 − − 2 + 2 2 2 2 x +2 x +2 (x + 2) (x + 2) (x2 + 2)3 Nei punti stazionari si ottiene: > evalf(DDf(staz[1])); 0. > evalf(DDf(staz[3])); .7926818053 Il punto terzo punto stazionario è pertanto di minimo locale. Per classificare il primo punto stazionario abbiamo bisogno di calcolare la derivata terza: > DDDf:=unapply(diff(f(x),x,x,x),x); DDDf := x → 6 − x+1 18 x 54 x2 (x + 1) 42 x3 96 x4 (x + 1) 24 x5 + − − + + x2 + 2 x2 + 2 (x2 + 2)2 (x2 + 2)2 (x2 + 2)3 (x2 + 2)3 48 x6 (x + 1) (x2 + 2)4 > evalf(DDDf(staz[1])); 3. L’origine è dunque un punto di flesso. Avendo capito che i punti stazionari sono certamente compresi nell’intervallo [-1, 1], proviamo a tracciare un secondo grafico numerico di f, questa volta restringendo il range da considerare > plot(f(x),x=-1.2..1); 0.6 0.5 0.4 0.3 0.2 0.1 –1 0 –0.5 0.5 x 1 Lo studio di funzione consente di guidare il modo di tracciare anche il grafico numerico molto meglio rispetto al fuorviante grafico numerico tracciato all’inizio. 3.4 3.4 Integrali Possiamo calcolare sia integrali indefiniti che definiti. Ad esempio, calcoliamo una primitiva della funzione > f:=x->x*exp(x+1); f := x → x e(x+1) usando il comando int: > int(f(x),x); e(x+1) (x + 1) − 2 e(x+1) Verifichiamo i calcoli derivando > diff(%,x); e(x+1) (x + 1) − e(x+1) e semplificando > simplify(%); x e(x+1) ottenendo la conferma della correttezza del calcolo dell’integrale. Nota: anche se non sappiamo se il programma ha fornito una primitiva corretta ci possiamo senz’altro fidare delle derivate fornite da Maple. Si tratta di un conto puramente meccanico che un programmatore puù insegnare ad un elaboratore con poche righe di istruzioni in un qualsiasi linguaggio di programmazione. Per calcolare un integrale definito è sufficiente indicare, oltre alla variabile, l’intervallo di integrazione > Int(f(x),x=0..2); Z 2 x e(x+1) dx 0 > value(%); e3 + e Consideriamo ora una classico esempio di funzione le cui primitive non possono essere scritte a partire da funzioni elementari utilizzando operazioni algebriche o composizione di funzioni: > g:=x->exp(-x^2); g := x → e(−x 2 ) calcoliamone l’integrale generalizzato nell’intervallo tra 0 e più infinito > int(g(x),x=0..+infinity); 1√ π 2 che è corretto. Se vogliamo calcolare lo stesso integrale tra 0 e 1, ad esempio, Maple ovviamente non puù far molto: > int(g(x),x=0..5); √ 1 erf(5) π 2 dove erf ( x ) = 2/sqrt(Pi) * int ((exp(-xˆ2), t = 0..x ). Quindi Maple si mangia la coda, e non poteva che essere cosı̀. Possiamo perù chiedere una valutazione numerica dell’integrale: > evalf(value(%)); .8862269255 4 4. Funzioni di più variabili e grafici 4.1 4.1 Limiti e derivate Vediamo molto velocemente come quanto visto per le funzioni di una sola variabile reale si estenda al caso di due variabili; l’estensione a più variabili risulterà poi del tutto ovvia. Cominciamo definendo una funzione nelle variabili x e y: > f:=(x,y)->x^2*y-3*y/x; 3y f := (x, y) → x2 y − x Proviamo a calcolare due limiti (facili) > limit(f(x,y),{x=2,y=3}); 15 2 > limit(f(x,y),{x=0,y=3}); undefined Se tentiamo con altre funzioni facciamo presto a mettere in difficoltà Maple. La funzione > g:=(x,y)->x^2*y^2/(x^2*y^2+(x-y)^2); g := (x, y) → x2 y 2 x2 y 2 + (x − y)2 non ha limite in (0,0), Maple non risponde: > limit(g(x,y),{x=0,y=0}); limit( x2 y 2 , {x = 0, y = 0}) x2 y2 + (x − y)2 Ma anche nel caso di una funzione con limite 0 in (0,0) Maple si arena > limit(x^3/(x^2+y^2),{x=0,y=0}); limit( x3 , {x = 0, y = 0}) x2 + y 2 Non ci sono ovviamente problemi nel calcolo delle derivate parziali: > diff(g(x,y),x); x2 y2 (2 x y2 + 2 x − 2 y) x y2 − 2 + (x − y) (x2 y2 + (x − y)2 )2 simplify(diff(g(x,y),y)); 2 > x2 y 2 2 x3 y (x − y) (x2 y2 + x2 − 2 x y + y2 )2 Ed ecco una derivata seconda mista > simplify(diff(g(x,y),x,y)); 2 x2 y2 (6 x y − 3 x2 + x2 y2 − 3 y2 ) (x2 y2 + x2 − 2 x y + y2 )3 Per valutare in un punto la derivata parziale seconda appena calcolata definiamo la funzione > DDXY:=unapply(%,(x,y)); DDXY := (x, y) → 2 x2 y2 (6 x y − 3 x2 + x2 y2 − 3 y2 ) (x2 y2 + x2 − 2 x y + y2 )3 che ora possiamo valutare dove ci interessa, ad esempio in (1,-1): > DDXY(1,-1); −22 125 4.2 4.2 Grafici Le possibilità grafiche sono uno dei punti forti dei programmi tipo Maple. Analizziamo prima alcune possibilità non ancora esplorate riguardo alle funzioni di una sola variabile. Passeremo poi alle funzioni di due variabili. 4.2.1 > 4.2.1 Alcune potenzialità grafiche per funzioni di una sola variabile plot(sin(x),x=0..2*Pi,title="funzione seno"); funzione seno 1 0.5 0 1 2 3 x 4 5 6 –0.5 –1 Possiamo anche tentare una compressione dell’asse delle x per avere un’idea del comportamento della funzione per valori molto grandi dell’argomento > plot(sin(x),x=0..infinity); 0 x infinity Ma attenzione all’interpretazione delle proprietà geometriche del grafico. Guardate cosa accade del grafico della funzione logaritmo in base e (che è una funzione concava!): > plot(ln(x),x=0..infinity); infinity 0 x infinity -infinity Per utilizzare potenzialità grafiche disponibili solo a domanda, chiediamo a Maple di caricare il modulo plots: > with(plots); Warning, the name changecoords has been redefined [animate, animate3d , animatecurve, arrow, changecoords, complexplot, complexplot3d , conformal, conformal3d, contourplot, contourplot3d , coordplot, coordplot3d , cylinderplot, densityplot, display, display3d , fieldplot, fieldplot3d, gradplot, gradplot3d , implicitplot, implicitplot3d, inequal , listcontplot, listcontplot3d, listdensityplot, listplot, listplot3d, loglogplot, logplot, matrixplot, odeplot , pareto, pointplot, pointplot3d, polarplot, polygonplot, polygonplot3d , polyhedra supported, polyhedraplot, replot, rootlocus, semilogplot, setoptions, setoptions3d , spacecurve, sparsematrixplot, sphereplot, surfdata, textplot, textplot3d , tubeplot] Possiamo ora utilizzare tutti i comandi elencati sopra. Per esempio possiamo utilizzare le coordinate polari > polarplot(1,theta=0..2*Pi); 1 0.5 –1 –0.5 1 0.5 –0.5 –1 Per un confronto tra coordinate cartesiane e polari prendiamo la curva di equazioni parametriche x=t, y=t in coordinate cartesiane > plot([t,t,t=-2*Pi..2*Pi]); 6 4 2 –6 –4 –2 0 2 4 6 –2 –4 –6 e in coordinate polari, vale a dire interpretando la prima coordinata come la distanza dall’origine e la seconda coordinata come angolo rispetto al semiasse positivo delle ascisse > polarplot([t,t,t=-2*Pi..2*Pi]); 1 –6 –4 –2 2 4 6 0 –1 –2 –3 –4 Veniamo ora ad un grafico in scala semilogaritmica > logplot(10^x,x=0..10); 1e+10 1e+09 1e+08 1e+07 1e+06 1e+05 .1e5 .1e4 .1e3 .1e2 1. 2 4 x 6 8 e ad una curva definita mediante una equazione ben nota > implicitplot(x^2+y^2=1,x=-1..1,y=-1..1); 10 1 y –1 0.5 0 –0.5 1 0.5 x –0.5 –1 Quest’ultimo comando è particolarmente utile quando si hanno curve ”meno note” che Maple traccia senza protestare > implicitplot(exp(x^2*y)-exp(y)=sin(4*x),x=-2..2,y=-5..5); 4 y 2 –1.5 –1 –0.5 0 –2 –4 0.5 1 x 1.5 2 4.2.2 4.2.2 Grafici per funzioni di due variabili Vediamo alcuni esempi di grafici in tre dimensioni ottenuti con il comando plot3d e come tracciare le curve di livello. > plot3d(x^2+y^2,x=-4..4,y=-4..4,style=patch,color=x,grid=[50,50],orien > tation=[160,100],axes=boxed,title=‘paraboloide‘); paraboloide 30 25 20 15 10 5 0 > 4 2 0 –2 x –4 2 0 y –2 –4 contourplot(x^2+y^2,x=-5..5,y=-5..5,contours=25); 4 y 2 –4 –2 0 –2 –4 2 x 4 > > plot3d(y^2-x^2,x=-4..4,y=-4..4,style=contour,contours=50,grid=[50,50] ,orientation=[200,60],color=black,axes=boxed,title=‘sella‘); sella 15 10 5 0 –5 –10 –15 4 4 > –2 2 0 y –2 –4 –4 densityplot(x^2+y^2,x=-5..5,y=-5..5); 4 y 2 –4 –2 0 –2 –4 2 x 4 2 0 x 5 5. Algebra delle matrici Esaminiamo ora alcune delle possibilità che Maple offre nel calcolo matriciale. Anzitutto vi sono due pacchetti che possiamo richiamare per operare sulle matrici e, più in generale, su probelmi di algebra lineare: linalg eLinearAlgebra . Qui esamineremo solo alcune delle possibilità offerte dal primo che andiamo a caricare: > with(linalg); Warning, the protected names norm and trace have been redefined and unprotected [BlockDiagonal , GramSchmidt, JordanBlock , LUdecomp, QRdecomp, Wronskian, addcol, addrow, adj , adjoint, angle, augment , backsub, band , basis, bezout , blockmatrix, charmat, charpoly, cholesky, col, coldim, colspace, colspan, companion, concat, cond , copyinto, crossprod, curl, definite, delcols, delrows, det, diag, diverge, dotprod, eigenvals, eigenvalues, eigenvectors, eigenvects, entermatrix, equal, exponential , extend , ffgausselim, fibonacci , forwardsub, frobenius, gausselim, gaussjord, geneqns, genmatrix , grad, hadamard , hermite, hessian, hilbert, htranspose, ihermite, indexfunc, innerprod, intbasis, inverse, ismith, issimilar, iszero, jacobian, jordan, kernel, laplacian, leastsqrs, linsolve, matadd , matrix , minor, minpoly, mulcol, mulrow, multiply, norm, normalize, nullspace, orthog, permanent, pivot, potential, randmatrix , randvector , rank , ratform, row, rowdim, rowspace, rowspan, rref , scalarmul, singularvals, smith, stackmatrix, submatrix , subvector, sumbasis, swapcol, swaprow, sylvester, toeplitz, trace, transpose, vandermonde, vecpotent, vectdim, vector, wronskian] Definiamo anzitutto due vettori di 3 componenti: > v:= vector(3,[1,2,3]);w:=vector(3,[5,6,7]); v := [1, 2, 3] w := [5, 6, 7] Possiamo ora effettuare tutte le operazioni tra vettori. Partiamo con la somma: > v+w; v+w Per vedere esplicitamente il risultato dobbiamo valutare l’espressione con l’istruzione evalm: > evalm(%); [6, 8, 10] Ecco invece la moltiplicazione per uno scalare > evalm(3*w); [15, 18, 21] Il prodotto scalare, o prodotto interno, si scrive invece > innerprod(v,w); 38 Passiamo alle matrici. Definiamo una matrice 2 per 3 e chiamiamola A: > A := matrix( 2, 3, [[1,3,2], [-5,14,9]]); 1 3 2 A := −5 14 9 una matrice 3 per 3 di nome B: > B := matrix( 3, 3, [[1,-1,1], [0,20,-8], [2,0,2]]); 1 −1 1 B := 0 20 −8 2 0 2 e una seconda matrice 2 per 3 di nome C: > C := matrix( 2, 3, [[0,5,2], [1,2,-1]]); 0 5 2 C := 1 2 −1 e facciamo un po’ di operazioni tra matrici: > > evalm(A+C); evalm(2*A); 1 8 4 −4 16 8 2 6 4 −10 28 18 Per l’operazione di moltiplicazione tra matrici (righe per colonne) dobbiamo usare il simbolo &* : > evalm(A&*B); 5 59 −19 13 285 −99 La trasposta e l’inversa richiedono solo l’utilizzo dei corrispondenti termini inglesi: > transpose(A); 1 −5 3 14 2 9 > BINV:=inverse(B); 5 2 BINV := −1 −5 2 1 8 0 −1 8 −3 4 1 2 5 4 Ha fatto bene i conti? Controlliamo: > evalm(B&*BINV); 1 0 0 0 1 0 0 0 1 OK! Ancora, possiamo calcolare determinante e rango: > det(B); 16 > rank(A); 2 Naturalmente ci vengono segnalati gli errori nell’impostare i calcoli. Ad esempio non è possibile moltiplicare B per A: > evalm(B&*A); Error, (in linalg[multiply]) non matching dimensions for vector/matrix product Data la matrice A ed il vettore dei termini noti > b:= vector(2,[1,2]); b := [1, 2] il sistema Ax=b si risolve con l’istruzione linsolve: > x:=linsolve(A,b); 5 1 29 7 x := + − t 1 , t 1, t1 19 19 19 19 (in questo caso i vettori soluzione sono infiniti e si ottengono al variare di t1 nell’insieme dei numeri reali). Possiamo ottenere il valore della prima componente del vettore soluzione > x[1]; 1 5 + t1 19 19 che in corrispondenza di un fissato valore del parametro > _t[1]:=1; t 1 := 1 diviene > x[1]; 6 19 Vediamo ora come ottenere la matrice inversa della matrice B in base alla definizione: l’inversa, se esiste, risolve l’equazione tra matrici BX=I dove I è la matrice identità. Definiamo la matrice identità come una matrice diagonale : > ID:=diag(1,1,1); 1 0 0 ID := 0 1 0 0 0 1 Quindi risolviamo il sistema > BINVbis:=linsolve(B,ID); 5 2 BINVbis := −1 −5 2 1 8 0 −1 8 −3 4 1 2 5 4 che è l’inversa trovata prima da Maple, possiamo farlo controllare a Maple: > evalm(BINV-BINVbis); 0 0 0 0 0 0 0 0 0 la loro differenza è zero quindi coincidono. 6 6. Introduzione alla programmazione 6.1 6.1.1 6.1 Procedure 6.1.1 Introduzione Spesso nella risoluzione dei problemi accade di dovere eseguire più volte una o più istruzioni in un ordine stabilito. Si supponga ad esempio di dover calcolare il montante di una operazione finanziaria oppure un tasso di interesse: per risolvere diversi problemi del genere cambiano i dati, ma le formule rimangono sempre le stesse. Sarebbe quindi opportuno evitare di dovere riscrivere le formule ogni volta, ma memorizzarle una volta per tutte e riutilizzarle quando servono (risparmiando tempo e fatica). Questa opportunità è offerta dalle procedure, vediamo come realizzarla. 6.1.2 6.1.2 Un esempio di procedura Supponiamo di dover calcolare il montante M di un capitale C in regime di interesse semplice, dopo t anni e con tasso d’interesse annuo i : M = C*(1 + i*t) I dati da considerare in ingresso ed in uscita sono, rispettivamente: • dati in ingresso: C (capitale iniziale), t (durata in anni) e i (tasso di interesse annuo) • dato in uscita: M (montante) Dichiarazione di un procedura Per poter riutilizzare più volte la formula senza avere la necessità di riscriverla ogni volta, possiamo dichiarare una procedura che esegua la formula del calcolo del montante comunque vengano specificati i dati in ingresso. A tale scopo occorre specificare: • un nome da assegnare alla sequenza di calcoli da effettuare (il nome della procedura) • un ”avviso” per MAPLE che si tratta di una procedura • quali sono i dati in ingresso • come calcolare il risultato La dichiarazione di una procedura che consenta il calcolo del montante è: > > > montante_IntSempl := proc(C, t, i) C*(1+i*t) end proc; montante IntSempl := proc(C, t, i) C ∗ (1 + i ∗ t) end proc dove, nell’ordine, sono specificati: • ”montante IntSempl”: è il nome che abbiamo scelto per la procedura ed è seguito dall’operatore di assegnazione (:=); è buona regola utilizzare dei nomi significativi che ci aiutino a ricordare ”cosa fa” la procedura; • ”proc” è una parola chiave: serve ad avvisare MAPLE che si tratta di una procedura; • di seguito sono specificati (tra parentesi tonde e separati da virgole) i dati in ingresso che sono detti parametri (o argomenti) formali della procedura; nel nostro esempio gli argomenti sono 3: il capitale C, il tempo t ed il tasso di interesse i. Anche in questo caso la scelta dei nomi e dell’ordine degli argomenti è libera; • la formula che calcola il montante. Per indicare la fine della dichiarazione della procedura si scrive end oppure end proc. Se nella dichiarazione non ci sono errori di sintassi, MAPLE riscrive la procedura (in blu) dopo l’end finale. Esecuzione di una procedura Una volta dichiarata una procedura, per eseguirla basta scriverne il nome seguito dai valori che intendiamo assegnare agli argomenti: un’istruzione di questo genere è detta chiamata della procedura ed i valori passati sono detti parametri (o argomenti) attuali della chiamata. Ad esempio, per calcolare il montante corrispondente a C = 1000 euro, t = 10 anni e i = 4% annuo, è sufficiente scrivere la chiamata avente 1000, 10 e 0.04 come argomenti attuali: > montante_IntSempl(1000, 10, 0.04); 1400.00 Il valore stampato 1400.00 è il montante richiesto e viene restituito dalla procedura perchÈ è il risultato dell’ultima istruzione eseguita (quella prima dell’end). Per effettuare un’altra esecuzione, ad esempio con C = 3500 euro , t = 9 anni e i = 5% annuo, basta scrivere un’altra chiamata: > montante_IntSempl(3500, 9, 0.05); 5075.00 Alcune raccomandazioni: è vero che il numero e l’ordine degli argomenti formali lo scegliamo noi arbitrariamente, ma una volta fatte, queste scelte vanno rispettate quando si effettuano le chiamate. Ad esempio, la chiamata: > montante_IntSempl(9, 3500, 0.05); 1584.00 dà un risultato diverso rispetto alla chiamata precedente: giustamente, perchÈ pur trattandosi degli stessi valori, gli argomenti attuali sono scritti in ordine diverso. Infatti, nella dichiarazione della procedura si era deciso che il primo argomento fosse il capitale, e perciù nell’ultima chiamata viene calcolato il montante di un capitale di 9 euro dopo 3500 anni al tasso del 5%. Dunque: l’ordine degli argomenti attuali deve corrispondere all’ordine degli argomenti formali. Vediamo ora cosa accade se togliamo un argomento attuale dalla chiamata: > montante_IntSempl(1000, 10); Error, (in montante_IntSempl) montante_IntSempl uses a 3rd argument, i, which is missing è data una segnalazione d’errore perchÈ nella dichiarazione della procedura gli argomenti formali erano 3, mentre in questa chiamata gli argomenti attuali sono solo 2 (in questo caso MAPLE non sa quanto vale il tasso i); dunque: il numero degli argomenti attuali deve corrispondere al numero degli argomenti formali. Una chiamata di procedura restituisce un risultato (lo stesso che viene stampato) che puù essere assegnato ad una variabile: ad esempio la chiamata > montante_IntSempl(1200,5,0.03); 1380.00 restituisce il valore 1380 che puù essere assegnato ad una variabile: > montante := montante_IntSempl(1200,5,0.03); montante := 1380.00 se quindi scriviamo il nome della variabile è visualizzato il dato in essa memorizzato: > montante; 1380.00 Una procedura puù anche essere priva di argomenti, ad esempio: > prova := proc() > print("ciao a tutti") > end; prova := proc() print(“ciao a tutti”) end proc la procedura ha come unica istruzione una stampa (print) che si limita a stampare la stringa (= sequenza di simboli alfanumerici) racchiusa tra doppi apici. Anche se non ci sono argomenti le parentesi nella chiamata vanno messe: > prova(); “ciao a tutti” 6.1.3 6.1.3 Un altro esempio di procedura Operiamo questa volta di operare in regime di interesse composto, in tal caso, noti il capitale iniziale C, la durata t dell’impiego ed il tasso d’intersse annuo i, il montante M è dato da: M = C*(1 + i)ˆt Supponiamo in particolare di volere determinare, dati il montante, la durata t ed il tasso di interesse i, il capitale iniziale e l’interesse corrispondenti, ovvero: C = M / (1 + i)ˆt e I=M-C. In questo caso i dati del problema sono: • dati in ingresso: M (montante), t (durata in anni) e i (tasso di interesse annuo) • dati in uscita: C (capitale iniziale) e I (interesse) La differenza sostanziale rispetto al caso precedente è che in questo caso i dati in uscita sono 2 e non uno solo. Proviamo a dichiarare la procedura come segue: > capInter_IntComp := proc(M, t, i) > M/(1+i)^t; > M-C > end; capInter IntComp := proc(M, t, i) M (i + 1)t ; M − C end proc nessuna segnalazione d’errore, ma ciù non significa che la procedura (quando eseguita) fornisca i risultati corretti; infatti, se proviamo ad effettuare una chiamata della procedura (con M = 2000 euro, t = 3 anni e i = 11 %): > capInter_IntComp(2000,3,0.11); 2000 − C C’è qualcosa che non va: la procedura dovrebbe stampare 2 numeri e invece stampa un’espressione con una C di troppo: come mai ? L’espressione rappresenta il montante (che è 2000, giusto) e corrisponde all’ultima istruzione (prima dell’end), ma al posto della variabile C dovrebbe esserci il valore del capitale calcolato nell’istruzione che precede. Già, ma noi non abbiamo specificato che il risultato di tale istruzione sia memorizzato in C; per farlo aggiungiamo una assegnazione nella seconda riga e torniamo a dichiarare la procedura: > capInter_IntComp := proc(M, t, i) > C := M/(1+i)^t; > M-C; > end; Warning, ‘C‘ is implicitly declared local to procedure ‘capInter_IntComp‘ capInter IntComp := proc(M, t, i) local C; C := M (i + 1)t ; M − C end proc La nuova dichiarazione dà una segnalazione di ”Warning” (è un tipo di errore non grave che consente comunque l’esecuzione della procedura), tale segnalazione ci avverte che la variabile C è definita implicitamente, ovvero è utilizzata nella procedura senza essere variabile di input e senza che Maple sia stato avvisato in altro modo del suo utilizzo. Evidentemente Maple preferisce una dichiarazione esplicita delle variabili utilizzate. La riscrittura della procedura fatta da Maple ci suggerisce infatti quali sono le sue preferenze; d’accordo, copiamo e riproviamo: > capInter_IntComp := proc(M, t, i) local C; > C:=M/(1+i)^t; > M-C > end; capInter IntComp := proc(M, t, i) local C; C := M (i + 1)t ; M − C end proc La segnalazione di Warning è scomparsa; rifacciamo la chiamata: > capInter_IntComp(2000,3,0.11); 537.617237 Benino: questo è il valore corretto dell’interesse, ma dov’è il valore del capitale (la procedura nelle nostre intenzioni dovrebbe avere 2 valori in uscita ) ? La risposta è: l’output fornito automaticamente è un solo numero; infatti in Maple una procedura, salvo indicazioni diverse, restituisce un solo numero ovvero il risultato dell’ultima istruzione. Per visualizzare il valore del capitale è sufficiente aggiungere un’istruzione di stampa: > capInter_IntComp := proc(M, t, i) local C; > C:=M/(1+i)^t; > print(C); > M-C > end; capInter IntComp := proc(M, t, i) local C; C := M (i + 1)t ; print(C) ; M − C end proc nessuna segnalazione d’errore; proviamo ad eseguire: > capInter_IntComp(2000,3,0.11); 1462.382763 537.617237 ora ci siamo, i due valori sono rispettivamente il capitale (stampato con l’istruzione print) e l’interesse (fornito come output automaticamente essendo stato calcolato con l’ultima istruzione della procedura). Un modo, di più chiara leggibilità, per ottenere entrambi i valori è quello di stabilire esplicitamente qual’è l’output della procedura utilizzando l’istruzione return, la cui sintassi è return(espressione). Cambiamo allora nuovamente la dichiarazione della procedura: > capInter_IntComp := proc(M, t, i) local C; > C:=M/(1+i)^t; > return(C,M-C); > end; capInter IntComp := proc(M, t, i) local C; C := M (i + 1)t ; return C, M − C end proc Cosı̀ la chiamata della procedura fornisce come output un vettore di due numeri: > capInter_IntComp(2000,3,0.11); 1462.382763, 537.617237 A tale output possiamo anche assegnare un nome, ad esempio > risultato:=capInter_IntComp(2000,3,0.11); risultato := 1462.382763, 537.617237 In questo modo i numeri ottenuti all’interno della procedura possono essere utilizzati come fossero due variabili qualsiasi all’esterno della procedura stessa; ad esempio con > risultato[1]+risultato[2]; 2000.000000 ricalcoliamo il montante M. Ancora un’osservazione circa i ”;” (”punti e virgola”): nell’ultima versione la procedura ha 3 istruzioni (calcolo di C, stampa di C e calcolo dell’interesse) ed il ”;” c’è dopo le prime due istruzioni ma non dopo la terza. La ragione sta nel fatto che il ”;” serve a separare 2 istruzioni successive; quindi è facoltativo metterlo dopo l’ultima istruzione (quella prima dell’end) perchÈ l’end non è considerata un’istruzione, mentre è obbligatorio negli altri casi e se manca è segnalato un errore. Se ad esempio togliamo il ”;” dopo la prima istruzione: > capInter_IntComp := proc(M, t, i) local C; > C:=M/(1+i)^t; > print(C); > M-C > end; capInter IntComp := proc(M, t, i) local C; C := M (i + 1)t ; print(C) ; M − C end proc è restituito un errore di sintassi e la dichiarazione della procedura non è stata accettata da MAPLE. 6.2 6.2.1 6.2 Istruzioni condizionali 6.2.1 L’istruzione if-then-else Supponiamo di voler calcolare il periodo di tempo per cui deve essere investito un capitale C in modo da ottenere il montante M se gli interesse sono calcolati al tasso annuo i con capitalizzazione composta. Una procedura che calcola la durata dell’investimento è ad esempio: > tempo_IntComp := proc(M, C, i) > log[1+i](M/C); > end proc; tempo IntComp := proc(M, C, i) logi+1 (M C) end proc ([i+1] indica la base del logaritmo). Quindi, se M = 8000 Ä, C = 5000 Ä e i = 4 %, la durata dell’impiego, in anni, deve essere: > tempo_IntComp(8000,5000,0.03); 15.90063174 Proviamo un’altra chiamata della procedura: > tempo_IntComp(8000,0,0.03); Error, (in tempo_IntComp) numeric exception: division by zero Errore: cos’è successo? Rispetto alla chiamata precedente il capitale (che è il secondo argomento attuale) è uguale a zero, ma nella procedura si divide per il capitale C e quindi occorrerebbe controllare che C non sia nullo. Meglio ancora sarebbe controllare che il capitale utilizzato nei calcoli sia positivo: per C minore o uguale a zero sarebbe simpatico stampare un messaggio di errore. Per effettuare questo controllo si puù utilizzare un’istruzione condizionale che lavori nel seguente modo: - se C <= 0 allora: - stampa un messaggio di errore - altrimenti: - calcola il tempo di impiego del capitale Per realizzare questo controllo utilizziamo l’istruzione if-then-else di Maple. Si tratta in realtà di un’istruzione presente con analoga sintassi in tutti i > tempo_IntComp := proc(M, C, i) > if C <= 0 then > print("il capitale dev’essere positivo") > else > log[1+i](M/C) > end if > end proc; tempo IntComp := proc(M, C, i) if C ≤ 0 then print(“il capitale dev’essere positivo”) else logi+1 (M C) end if end proc Se ora rifacciamo la chiamata con capitale nullo otteniamo: > tempo_IntComp(8000, 0, 0.03); “il capitale dev’essere positivo” altrimenti: > tempo_IntComp(8000, 1500, 0.03); 56.63207933 In generale la sintassi dell’istruzione condizionale if-then-else è la seguente: if espressione boolean then sequenza 1 di istruzioni MAPLE else sequenza 2 di istruzioni MAPLE end if e lavora nel seguente modo: - valuta l’espressione boolean (un’espressione boolean è un’espressione il cui risultato puù essere soltanto VERO oppure FALSO; nell’esempio l’espressione boolean era: C <= 0) - se il risultato dell’espressione boolean è VERO allora: - esegui la sequenza 1 di istruzioni MAPLE - altrimenti: - esegui la sequenza 2 di istruzioni MAPLE Si sarebbe anche potuto scrivere la procedura nel seguente modo: se C è maggiore di zero allora calcola la durata, altrimenti stampa un messaggio; in tal caso la procedura diventa: > tempo2_IntComp := proc(M, C, i) > if C > 0 then > log[1+i](M/C) > else > print("il capitale dev’essere positivo") > end > end ; tempo2 IntComp := proc(M, C, i) if 0 < C then logi+1 (M C) else print(“il capitale dev’essere positivo”) end if end proc e lavora sostanzialmente nello stesso modo della procedura precedente tempo IntComp; proviamo come esempio le chiamate con gli stessi argomenti: > tempo2_IntComp(8000, 1500, 0.03); 56.63207933 > tempo2_IntComp(8000, 0, 0.03); “il capitale dev’essere positivo” Si osservi che nell’ultima procedura la fine dell’istruzione if-then-else è stata segnalata da end (il primo) anzichÈ da end if e, analogamente, end proc è stata sostituita da end (quello dell’ultima riga). In realtà infatti per Maple le parole proc ed if (e altre che utilizzeremo tra poco) indicano a Maple l’inizio di un particolare gruppo di istruzioni e sono come delle ”parentesi aperte”: ciascun end è come una ”parentesi chiusa” corrispondente all’ultima ”parentesi aperta”. In altri linguaggi di programmazione la ”parentesi aperta” è indicata esplicitamente dalla parola begin, in altri ancora anzichÈ le parole begin - end si utilizzano proprio una parentesi graffa aperta e una parentesi graffa chiusa. 6.2.2 6.2.2 L’istruzione if Riprendiamo l’esempio della sezione precedente: se si desidera fare il controllo che C sia non nullo, ma non interessa la stampa del messaggio, allora si puù utilizzare l’istruzione if: > tempo3_IntComp := proc(M, C, i) > if C > 0 then > log[1+i](M/C) > end if > end; tempo3 IntComp := proc(M, C, i) if 0 < C then logi+1 (M C) end if end proc in questo caso se C <= 0 non viene stampato alcun messaggio: > tempo3_IntComp(8000,0,3.5); In generale la sintassi dell’istruzione condizionale if è la seguente: if espressione boolean then sequenza di istruzioni MAPLE end if e lavora nel seguente modo: - valuta l’espressione boolean (nell’esempio è C <> 0) - se il risultato dell’espressione boolean è VERO allora: - esegui la sequenza di istruzioni MAPLE 6.2.3 6.2.3 Espressioni boolean Nell’esempio della sezione 2.2 (calcolo del tempo di impiego di un capitale C al tasso di interesse composto i per ottenere un montante M) si è inserito un controllo che C sia maggiore di zero: > tempo3_IntComp := proc(M, C, i) > if C > 0 then > log[1+i](M/C) > end if > end; tempo3 IntComp := proc(M, C, i) if 0 < C then logi+1 (M C) end if end proc ma se esaminiamo il campo di esistenza dell’espressione log[i+1](M/C) (supponendo il tasso di interesse i costante) ci rendiamo conto che il controllo C > 0 non è sufficiente, occorre anche che M/C (l’argomento del logaritmo) sia > 0 ovvero, in altri termini, il montante M dev’essere positivo, cioÈ si deve avere: M>0eC>0 evidentemente questa è un’espressione boolean (puù solo essere o VERA o FALSA) quindi, per realizzare il controllo, basta inserire l’espressione in un’istruzione if salvo sostituire la congiunzione e con la corrispondente and dell’inglese (l’oppure si sostituisce invece con or): > tempo4_IntComp := proc(M, C, i) > if M > 0 and C > 0 then > log[1+i](M/C) > end if > end; tempo4 IntComp := proc(M, C, i) if 0 < M and 0 < C then logi+1(M C) end if end proc Gli operatori or (somma logica) e and (prodotto logico) sono detti operatori boolean e lavorano in modo simile agli usuali operatori aritmetici + e *, salvo che se scriviamo x + y, le variabili x ed y devono rappresentare dei numeri, mentre se scriviamo x or y, le variabili x ed y devono assumere solo i valori true (VERO) oppure false (FALSO). La descrizione completa degli operatori or e and è data dalle loro tabelle di verità in cui sono forniti i risultati per tutti i possibili valori degli operandi. L’opertore or dà come risultato FALSO solo se entrambi gli operandi sono falsi, quindi la sua tabella di verità è: x y x or y false false false false true true true false true true true true invece l’opertore and dà come risultato VERO solo se entrambi gli operandi sono veri e la sua tabella di verità è: x y x and y false false false false true false true false false true true true un altro operatore boolean è il not (negazione logica), la cui tabella di verità è: x not x false true true false Gli operatori boolean or, and e not insieme con i valori false e true costituiscono un’algebra (analoga a quella usuale sui numeri reali) detta algebra di Boole. Nell’algebra di Boole si possiamo valutare espressioni in cui si utilizzano solo gli operatori boolean e le variabili possono assumere solo i valori true e false; espressioni di questo tipo sono dette espressioni boolean. Un esempio di espressione boolean è: x or y and (x or y and not x) Naturalmente per valutare un’espressione boolean dobbiamo sapere in quale ordine si devono valutare gli operatori; ad esempio per valutare l’espressione x or y and not x qual’operatore da valutare per primo ? E’ l’or, l’and o il not ? L’ordine di valutazione (o più precisamente la priorità degli operatori bobolean) è: not, and, or (cosı̀ come nell’algebra sui numeri reali la priorità è: elevamento a potenza, prodotto, somma). 6.3 6.3.1 6.3 Istruzioni iterative 6.3.1 L’istruzione while Nella risoluzione di un problema puù accadere che un gruppo di istruzioni debba essere ripetuto finchÈ continua a verificarsi una determinata condizione. Consideriamo ad esempio il seguente problema: si investe un capitale C in un’operazione finanziaria al tasso di interesse annuo i (capitalizzazione composta) e si desiderano calcolare tutti i montanti dopo 1 anno, dopo 2 anni, eccetera, finchÈ il montante non abbia raggiunto un certo valore Mfin. Desideriamo cioè calcolare e stampare gli importi dei montanti: C*(1+i)ˆ1, C*(1+i)ˆ2, C*(1+i)ˆ3, . . .C*(1+i)ˆn continuando finchÈ continua a valere la condizione C*(1+i)ˆn < Mfin . I dati del problema sono in questo caso: • dati in ingresso: C (capitale iniziale), i (tasso di interesse annuo) e Mfin (importo finale del montante) • dati in uscita: i montanti dopo 1 anno, 2 anni, 3 anni, ... finchÈ C*(1+i)ˆn < Mfin Per effettuare la stampa dobbiamo perciù riutilizzare sempre la stessa formula ma per valori diversi del numero di anni, che indicheremo con n; i passi da eseguire sono perciù: - inizializza n (numero di anni) ad 1 - finchÈ C*(1+i)ˆn < Mfin esegui: - stampa C*(1+i)ˆn - aumenta n di 1 Si tratta ora di tradurre questi passi in istruzioni MAPLE tenendo conto che: • inizializzare n ad 1 significa assegnare ad n il valore 1; • il comando ”finchÈ ... esegui” si traduce con l’istruzione ”while ... do” di Maple; anche con questa istruzione sarà necessario indicare la fine delle istruzioni da ripetere con end do (oppure anche semplicemente con end); • per aumentare n di 1 si utilizza l’operazione di assegnazione ”n := n + 1” il cui effetto è quello di assegnare alla variabile n un nuovo valore uguale a quello che aveva prima aumentato di 1. Ecco dunque la procedura che esegue quanto richiesto: > > > > > > > montanti_IntComp := proc(C, i, Mfin) local n; n:=1; while C*(1+i)^n < Mfin do print(C*(1+i)^n); n:= n + 1; end do end; montanti IntComp := proc(C, i, Mfin) local n; n := 1 ; while C ∗ (i + 1)n < Mfin do print(C ∗ (i + 1)n ) ; n := n + 1 end do end proc Si osservi che la variabile n è dichiarata locale per evitare la solita fastidiosa segnalazione di Warning descritta in sez. 6.1.3. Vediamo ad esempio quali sono i montanti anno dopo anno che consentono ad un capitale C = 1000 Ä di raddoppiare al tasso annuo del 4 %: > montanti_IntComp(1000, 0.04, 2000); 1040.00 1081.6000 1124.864000 1169.858560 1216.652902 1265.319018 1315.931779 1368.569050 1423.311812 1480.244285 1539.454056 1601.032219 1665.073507 1731.676448 1800.943506 1872.981246 1947.900496 18 L’ultimo numero stampato è il valore di n al termine della procedura (infatti l’istruzione che aumenta n è l’ultima della procedura) e rappresenta il numero di anni necessari per raggiungere Mfin = 2000 Ä. Si osservi che le istruzioni all’interno del while sono state ripetute 17 volte, ciù si esprime dicendo che ci sono state 17 iterazioni del ciclo while. In generale l’istruzione while ha la seguente sintassi: while espressione boolean do sequenza di istruzioni MAPLE end do e impone a Maple le seguenti operazioni: - valuta l’espressione boolean (nell’esempio è ”C*(1+i)ˆn < Mfin”) - se il risultato dell’espressione boolean è VERO allora: - esegui la sequenza di istruzioni MAPLE (detta ”corpo” dell’istruzione while) - ripeti dall’inizio (valuta ..eccetera) - altrimenti termina Attenzione! Puù accadere, per errore, di scrivere come condizione per l’esecuzione del corpo dell’istruzione while una espressione boolean sempre vera: questo avviene ad esempio, se nella condizione dell’istruzione while della procedura montanti IntComp si sostituisce C*(1+i)ˆn < Mfin con - C*(1+i)ˆn < Mfin In tal caso ripetendo la chiamata montanti IntComp(1000, 0.04, 2000); l’espressione boolean dell’istruzione while è sempre vera e il corpo dell’istruzione viene continuamente ripetuto (per arrestare l’elaborazione cliccare sul pulsante con l’indicazione di STOP). In questi casi si dice che si ha un ciclo infinito o loop infinito. 6.3.2 6.3.2 L’istruzione for Spesso quando alcune istruzioni devono essere ripetute più volte si conosce preventivamente il numero esatto di ripetizioni richieste. In questi casi conviene utilizzare l’istruzione for anzichè la while. Partiamo anche in questo caso da un problema: supponiamo di voler calcolare il fattoriale del numero n (ci dimentichiamo per un attimo che in Maple il fattoriale di un numero n si calcola semplicemente scrivendo n!). Costruiamo anche qui una procedura che contenga la nuova istruzione. Abbiamo in questo caso: • dato in ingresso: n • dato in uscita: il fattoriale di n Una procedura che calcola il fattoriale di un numero è: > > > > > > > > > fattoriale := proc(n) local m, fatt; fatt:=1; if n>0 then for m from 1 to n do fatt := fatt*m; end do; end if; return(fatt) end; fattoriale := proc(n) local m, fatt; fatt := 1 ; if 0 < n then for m to n do fatt := m ∗ fatt end do end if ; return fatt end proc Che possiamo richiamare per calcolare il fattoriale di 6: > fattoriale(6); 720 La sintassi dell’istruzione for (nella sua forma più semplice) è: for variabile indice from valore iniziale to valore finale do sequenza di istruzioni MAPLE end do e lavora nel seguente modo: - variabile indice := valore iniziale - finchÈ variabile indice <= valore finale esegui: - esegui la sequenza di istruzioni MAPLE - variabile indice := variabile indice + 1 Un esempio più elaborato di utilizzo dell’istruzione for Supponiamo di voler calcolare l’integrale definito: noti gli estremi di integrazione a e b. Non esiste in questo caso alcun modo di scrivere una primitiva della funzione integranda utilizzando composizioni di funzioni elementari, possiamo perù calcolare un valore approssimato dell’integrale applicando la definizione: approssimiamo cioè l’area rappresentata dall’integrale con l’area di un plurirettangolo. I dati del problema sono perciù: • dati in ingresso: a e b (estremi dell’intervallo di integrazione) e m (numero delle parti uguali in cui si intende suddividere l’intervallo di integrazione) • dato in uscita: valore dell’integrale Di seguito sono descritti i passi da eseguire per ottenere una soluzione del problema: - inizializzare a zero una variabile (area) in cui memorizzare la somma delle aree dei rettangoli che costituiscono il plurirettangolo - inizializzare una variabile (delta) con il valore (b - a)/m (è la lunghezza di ciascuna delle m parti in cui viene suddiviso l’intervallo [a,b]) - inizializzare con a (estremo sin. dell’intervallo di integrazione) una variabile (x) che rappresenti il generico punto x = a + n * delta al variare di n = 1, 2, , ..., m - per n = 1, 2, , ..., m esegui: - calcola il nuovo valore di x aggiungendo delta al valore attuale di x - calcola il nuovo valore di area aggiungendo l’area del rettangolo di base delta ed altezza exp(xˆ2), al valore attuale di area - restituisci il valore di area (area del plurirettangolo) calcolato che tradotto in istruzioni MAPLE diventa: > integrale := proc(a,b,m) local n, area, x, delta; > area := 0; > delta := (b - a) / m; > x := a; > for n from 1 to m do > x := x + delta; > area := area + delta*exp(x^2); > od; > return(evalf(area)) > end; integrale := proc(a, b, m) local n, area, x, δ; area := 0 ; δ := (b − a) m ; x := a ; for n to m do x := δ + x ; area := area + δ ∗ exp(x2 ) end do ; return evalf(area) end proc Osservazioni: • il comando evalf (espressione) restituisce il valore di un’espressione (reale) • al solito le variabili che non sono argomenti formali della procedura (n, area, x e delta) sono dichiarate come variabili locali per evitare la segnalazione di Warning • l’istruzione for incrementa automaticamente la variabile n Proviamo a fare qualche chiamata: integrando nell’intervallo [1,3] con m = 1000: > integrale(1,3,1000); 1451.199053 ... con m = 2000: > integrale(1,3,2000); 1447.136700 ... e con m = 3000: > integrale(1,3,3000); 1445.784397 si osservi che il valore approssimato dell’integrale diminuisce, infatti l’approsimazione è fatta per eccesso dato che il valore x è l’estremo destro dei sotto-intervalli e che la funzione integranda è strettamente crescente. Per ottenere un’approsimazione per difetto basta far variare n da 0 ad m - 1. Naturalmente a Maple di calcolare l’integrale utilizzando il comando int di Maple: > evalf(int(exp(x^2),x=1..3)); 1443.082472