FreeFem++ mini tutorial F. Nobile, S. Perotto FreeFem++ è un codice per la risoluzione di equazioni alle derivate parziali con elementi finiti, sviluppato da O. Píronneau, F. Hecht, e A. Le Hyaric. Il software, alla versione 2.24-3, e il manuale di utilizzazione sono scaricabili liberamente da internet al sito http://www.freefem.org. È disponibile anche una semplice interfaccia grafica FFedit, scritta nel linguaggio tcl, anch essa scaricabile dalle pagine sopra citate. 1 Sintassi di base FreeFem++ è un linguaggio di programmazione con una sintassi di base simile al C++. È pertanto possibile dichiarare variabili e funzioni all interno di uno script FreeFem++, eseguire operazioni aritmetiche, utilizzare cicli o istruzioni condizionali. Per la sintassi corretta riferirsi al Capitolo 4 del manuale di utilizzazione di FreeFem++. Esempio 1.1 Questo è un esempio di script in linguaggio FreeFem++. // dichiarazione e assegnazione di interi e reali int i=20; real pi=4*atan(1.); cout << "i=" << i << "\n"; cout << "pi=" << pi << "\n"; // dichiarazione di un array real[int] a(i); // esempio di ciclo for int n; for (n=0; n<i; n++) a[n] = cos(n*pi/i); cout << "a=" << a[n] << "\n"; } //esempio di ciclo condizionale 1
int r=10; while ( r>4 ) cout << "r= "<< r <<"\n"; r--; } // esempio di funzione func real inverso(real x) real ans; ans=1./x; return ans; } cout << "l inverso di 5 e " << inverso(5.) << "\n\n"; La sequenza di comandi presentata nell Esempio precedente deve essere scritta in un file di testo con estensione.edp, ed eseguita con il comando FreeFem++ nomefile.edp. Se si usano i tools di interfaccia grafica (FFedit), ricordarsi di salvare le istruzioni in un file, prima di eseguirle. Il risultato apparirà sul terminale da cui è stato lanciato il programma FFedit. Attenzione I nomi delle variabili non possono contenere il carattere. Inoltre, alcuni nomi sono riservati a variabili globali, ad esempio x, y, z, P, N, area, ecc. È consigliabile non sovrascrivere queste variabili. 2 Generazione di griglia Per una descrizione completa si veda il Capitolo 5 del manuale. Il primo passo per generare un dominio è definirne il bordo. Ciò può essere fatto tramite il comando border che permette di introdurre la parametrizzazione di una curva in 2D: border gamma(t=a,b) x=f(t); y=g(t); label=1;} In questo esempio la curvagamma è definita in funzione del parametrotche varia tra gli estremi a e b. Label permette di associare un identificatore alla curva (utile nell assegnazione delle condizioni al bordo). Una volta definita l entità gamma, con l istruzione gamma(m) si genera poi una struttura dati associata a m sotto-intervalli di egual ampiezza sulla curva gamma. La curva può essere visualizzata utilizzando il comando plot (in FreeFem++ il comando plot permette di visualizzare vari oggetti come curve, mesh, soluzioni elementi finiti, ecc.). 2
Esempio 2.1 // linea retta real[int] v(2); v[0]=.5; v[1]=sqrt(3.)/2; border a(t=0,5) x=t*v[0]; y=t*v[1]; label=1;} plot(a(20), wait=1); // cerchio border b(s=0,2*pi) x=cos(s); y=sin(s); label=2;} plot(b(100), wait=1); Una volta descritto il bordo del dominio (curva chiusa orientata), l istruzione buildmesh permette di generare una triangolazione del dominio contenuto alla sinistra del bordo. La griglia risultante è un oggeto di tipo mesh. Esempio 2.2 // rettangolo [0 2]x[-1 0] border a(t=0,2) x=t; y=-1; label=1;} border b(t=-1,0) x=2; y=t; label=2;} border c(t=0,2) x=2-t; y=0; label=3;} border d(t=0,1) x=0; y=-t; label=4;} plot(a(10)+b(5)+c(10)+d(5),wait=1); mesh Th1 = buildmesh(a(10)+b(5)+c(10)+d(5)); plot(th1,fill=1,wait=1,ps="rettangolo.eps"); // ellisse border gamma(t=0,2*pi) x=1+.5*cos(t); y=sin(t); label=5;} mesh Th2 = buildmesh(gamma(20)); plot(th2,fill=1,wait=1); Attenzione Se il bordo è costituito da varie curve, si faccia attenzione a o- rientare ciascuna curva correttamente, definendo l opportuna parametrizzazione. Una mesh può essere salvata su un file con l istruzione savemesh e riletta da file con l istruzione readmesh mesh th=buildmesh(...); savemesh(th,"nomefile.msh"); mesh th1=readmesh("nomefile.msh"); 3
Esempio 2.3 // scrittura su file mesh di output savemesh(th1,"rettangolo.msh"); savemesh(th2,"ellisse.msh"); // lettura da file mesh di input mesh th=readmesh("ellisse.msh"); plot(th); Esercizio 2.1 Generare la mesh rettangolare dell Esempio 2.2 utilizzando una diversa parametrizzazione del bordo in modo da addensare i punti della griglia vicino al vertice (2, 0). (Suggerimento: si provi usando una parametrizzazione del tipo 1 eαt 1, oppure e α 1 1 log(1 + α t(eα 1)), con α > 0 ). Esercizio 2.2 Si generi una triangolazione del dominio compreso tra il cerchio di raggio r 1 =.5 centrato in (0,.5) e il cerchio di raggio r 2 = 2 centrato nell origine. Esercizio 2.3 Si generi la triangolazione di un cerchio di raggio unitario a cui manca uno spicchio di 30 gradi. Esercizio 2.4 Si generi il dominio a forma di L Ω = [0, 2] [0, 1] [0, 1] [1, 2] e una triangolazione raffinata attorno al vertice rientrante. 3 Spazi a elementi finiti - equazione di Laplace Per risolvere equazioni alle derivate parziali in FreeFem++, come prima cosa bisogna definire lo spazio o gli spazi ad elementi finiti con cui si vuole lavorare su una data triangolazione. Questo può essere fatto mediante la keyword fespace. Ad esempio, se vogliamo definire lo spazio degli elementi finiti P 1 continui sulla mesh Th (precedentemente definita), procederemo nel modo seguente: fespace Xh(Th,P1); dove Xh è il nome della variabile di tipo fespace. In FreeFem++ sono disponibili vari elementi finiti. Ricordiamo in particolare gli elementi costanti a tratti P0; i lineari e quadratici continui P1 e P2 e le rispettive versioni discontinue P1dc e P2dc, gli elementi P1-bolla P1b, ecc. Riferirsi al manuale, Capitolo 6, per una lista completa. Una volta introdotto lo spazio ad elementi finiti, ad esempio Xh, è possibile definire degli elementi dello spazio (funzioni ad elementi finiti): 4
Xh uh,vh; È particolarmente facile interpolare, su uno spazio elementi finiti, funzioni definite analiticamente, nonché interpolare una funzione ad elementi finiti su uno spazio ad elementi finiti differente: Esempio 3.1 mesh Th1=square(4,4); mesh Th2=square(3,2); plot(th1,wait=1); plot(th2,wait=1); fespace Xh(Th1,P2); fespace Yh(Th1,P1); fespace Vh(Th2,P1); Xh u1=x^2 + y^2; // la funzione x^2 + y^2 e automaticamente // interpolata sullo spazio Xh plot(th1,u1,nbiso=30,value=1,wait=1); // interpolazione di u1 su Yh: Yh u2; u2 = u1; plot(th1,u2,nbiso=30,value=1,wait=1); // interpolazione di u1 su Vh: Vh u3; u3 = u1; plot(th2,u3,nbiso=30,value=1,wait=1); L istruzione problem permette di definire un problema variazionale su spazi agli elementi finiti. Esempio 3.2 Si vuole risolvere l equazione u = 1 in Ω = (0, 1) 2, u = 0 su Ω, (1) utilizzando elementi finiti P1 continui. border a(t=0,1)x=t;y=0;label=1;} border b(t=0,1)x=1;y=t;label=2;} border c(t=0,1)x=1-t;y=1;label=3;} border d(t=0,1)x=0;y=1-t;label=4;} 5
mesh Th=buildmesh(a(20)+b(20)+c(20)+d(20)); plot(th,wait=1); fespace Xh(Th,P1); Xh uh,vh; func f = 1; // definisco analiticamente il termine forzante // definizione del problema variazionale problem laplace(uh,vh) = int2d(th)(dx(uh)*dx(vh) + dy(uh)*dy(vh)) //forma bilineare - int2d(th)(f*vh) //termine forzante + on(1,2,3,4,uh=0); //cond. di Dirichlet laplace; plot(th,uh,nbiso=40,fill=1); // soluzione dell equazione La keyword func permette di definire una funzione di x e y, analiticamente. L istruzione int2d(mesh) esegue l integrazione su tutti gli elementi della griglia. Analogamente, int1d(mesh,n) esegue l integrazione sul lato di bordo etichettato con il valore n. È possibile specificare l ordine della formula di quadratura da utilizzare mediante int2d(th,qforder=...). L istruzione dx (resp. dy) calcola la derivata di una funzione elementi finiti nella direzione x. Attenzione L istruzione dx non funziona se applicata a una funzione definita analiticamente. Le condizioni al bordo di Dirichlet sono imposte mediante il comando on, soltanto sui tratti di bordo specificati nell istruzione. È possibile associare un risolutore lineare al problema mediante l opzione solver, nonchè un precondizionatore precon= nome di una funzione problem laplace(uh,vh,solver=cg,precon=p)=...; func real[int] P(real[int] & xx);...} Le scelte possibili per il risolutore sono: solver = LU, CG, Crout, Cholesky, GMRES, UMFPACK. Invece di definire direttamente il problema variazionale, è possibile introdurre innanzi tutto una forma variazionale su una data triangolazione utilizzando la keyword varf e in seguito il problema a partire dalla forma variazionale introdotta. Il vantaggio è che da un elemento di tipo varf è possibile estrarre le matrici e i termini noti associati al problema discreto. 6
Esempio 3.3 L Esempio 3.2 può essere risolto alternativamente nel modo seguente: border a(t=0,1)x=t;y=0;label=1;} border b(t=0,1)x=1;y=t;label=2;} border c(t=0,1)x=1-t;y=1;label=3;} border d(t=0,1)x=0;y=1-t;label=4;} mesh Th=buildmesh(a(20)+b(20)+c(20)+d(20)); // definizione del termine forzante func f=1; // definizione della forma variazionale varf B(u,v)=int2d(Th)(dx(u)*dx(v)+dy(u)*dy(v)) // forma bilineare -int2d(th)(f*v) // termine forzante +on(1,2,3,4,u=0); // condizioni al bordo fespace Xh(Th,P1); Xh uh,vh; // matrice di stiffness matrix K=B(Xh,Xh); // termine noto: gli devo cambiare di segno perche ufficialmente lui // sta a sinistra Xh g; g[]=b(0,xh); g=-g; // risoluzione del sistema lineare uh[]=k^-1*g[]; plot(th,uh,nbiso=40,value=1,fill=1); 7