• Non ci sono risultati.

001. #include <stdio.h>

002. #include <math.h>

003. #include <stdlib.h>

Vengono passate al pre-compilatore le librerie (di input/output, matema-tica e standard) che contengono i prototipi delle funzioni usate nel corpo del programma.

004. #define P 8 005. #define K 500 006. #define N 500 007. #define NGP 50

Si definiscono le variabili a livello globale. La prima indica la dimensione della stringa mesh[ ]. Le due seguenti costituiscono il numero di punti da prendere per il campionamento della funzione nell’intervallo rispettivamente sull’asse x e y, mentre con NGP definiamo il numero di punti gaussiani.

008. double U0(double x); double ** allocate(int N_, int K_);

009. doubleIntegra1(double *T, double *XG, double *WG, double **AA, int n, int k, double h);

010. void gauleg(double x1, double x2, double x[ ],double w[ ], int n);

011. void map(double *Y, double *WY, double *YY,double *WWY, int NG, double RMIN, double RMAX, double RMED);

012. double phit(double *T, int k, double **AA, double yy, double h);

013. double phix(double *X, int n, double **AA, double xp, double h);

Prima del main si riportano infine i prototipi delle funzioni che vengono richiamate nel resto del codice. Ricordiamo che la prima parola di ogni riga di testo fa riferimento al tipo di variabile restituita dalla funzione, la seconda al nome stesso della funzione, mentre tra le parentesi tonde sono riportati i parametri da cui essa dipende.

014. int main(void) 015. {

016. double **U, *X, *T;

017. double deltax, deltat;

018. double x0, x1, t0, t1;

019. double xp;

020. double PI = 3.141592653858979;

021. int k,n,i;

022. int M[P] = {0, 2, 4, 9, 19, 29, 49, 54};

023. char mesh[50];

024. X = (double*)calloc(K, sizeof(double));

025. T = (double*)calloc(N, sizeof(double));

Si apre il main definendo la tipologia (double, int o char) delle diverse variabili. Si passa quindi ad allocare memoria in maniera continua, tramite la funzione calloc [1], creando una matrice K*N in cui salvare i dati di tipo double.

026. x0 = -3*PI/2;

027. x1 = 3*PI/2;

028. t0 = 0;

029. t1 = 10;

030. deltax=(x1 -x0)/(K-1);

031. deltat=(t1 -t0)/(N-1);

032. for(k=0; k<K; ++k) X[k]= x0 + k*deltax;

033. for(n=0; n<N; ++n) T[n]= t0 + n*deltat;

Vengono definiti a questo punto gli estremi degli intervalli su ambo gli assi. Si calcolano dunque i rispettivi passi e si riempiono i vettori X[k] e T[n]

con i valori dei punti della griglia cos`ı ottenuti.

034. U = allocate(N, K);

035. for(k=0;k<K;++k) U[0][k] = U0(X[k]);

Con queste due linee di codice si crea lo spazio necessario per salvare i valori dalla variabile U e si riempie la prima mesh con i dati relativi alla condizione iniziale.

036. double XG[NGP], WG[NGP];

037. bool exit = false;

038. double LL = x1-x0;

039. double TT = t1-t0;

Si definiscono i vettori XG e WG con dimensione NGP e le variabili LL e TT rappresentano delle costanti di normalizzazione.

La variabile exit `e di tipo booleano e viene settata su false.

040. for(n=0; n<N-1; ++n)

041. {

042. for(k=0;k<K;++k)

043. {

044. xp = Integra1(T, XG, WG, U, n, k, deltat);

045. if (xp>x1 || xp<x0)

046. {

047. printf("DEBUG:\n Siamo fuori dall’intervallo:

x’=%f\n",xp);

048. exit = true;

049. n--;

050. break;

051. }

052. U[n+1][k] = U[n][k] + deltat*phix(X, n, U, xp, deltax);

053. }

054. if (exit) break;

055. }

056. printf("\n");

I due cicli for innestati permettono di muoverci su tutta la griglia prima lungo una mesh e poi passando alla successiva assegnando ad ogni punto il valore della funzione U. Quest’ultimo `e calcolato a partire dal valore ad esso sottostante e facendo ricorso alla funzione phix.

Per calcolare il valore di xp si usa invece la funzione Integra1 a cui vengono passati i valori della variabili T, XG, WG, U, n, k e deltat. Per un’analisi dettagliata della funzione si rimanda al codice seguente il main.

Tramite il comando if si controlla che xp ricada all’interno del dominio. In caso negativo viene stampato in video un messaggio d’errore, si ritorna alla mesh precedente e si esce dal ciclo.

057. FILE *pfile;

058. pfile = fopen("dati.txt","w");

059. for(i=0;i<n+1;++i)

060. {

061. for(k=0;k<K;++k)

062. {

063. fprintf(pfile,"%f %f %f\n",X[k],T[i],U[i][k]);

064. }

065. }

066. fclose(pfile);

Nell’ultima parte del main si fa ricorso ai puntatori a file in modo tale da creare un file txt di nome 00dati00 nel quale vengono salvati i valori della griglia, ricorrendo ai vettori X[ ] e T[ ], e quelli della funzione U.

067. for(i=0;i<P;++i)

068. {

069. sprintf(mesh, "dati_%d_mesh.txt", M[i]+1);

070. pfile = fopen(mesh,"w");

071. printf("Mesh %d al tempo t = %lf\n", M[i]+1, T[M[i]]);

072. for(k=0;k<K;++k)

073. {

074. fprintf(pfile,"%f %f\n",X[k], U[M[i]][k]);

075. }

076. fclose(pfile);

077. }

078. printf("\n");

079. system("PAUSE");

080. return 0;

081. }

Usiamo dei cicli for innestati e l’istruzione sprintf per creare tanti file quanti sono gli elementi del vettore M[ ]. Ciascun file ha nel nome il numero della mesh considerata e all’interno i valori di U(x, t) al variare di x. Fac-ciamo inoltre stampare in video il valore della variabile t a cui ogni mesh considerata si riferisce.

Riportiamo ora le funzioni usate. Solitamente queste vengono scritte su-bito dopo il main, oppure inserite in un’apposita libreria che deve essere per`o richiamata all’inizio del file. Abbiamo optato per la prima possibilit`a che rende l’intero codice pi`u semplice da leggere e modificare.

082. double U0(double x) 083. {

084. return sin(x)*cos(x);

085. }

Questa funzione definisce le condizioni iniziali della funzione U sulla prima mesh, caratterizzata dall’indice i pari a 0. In questo caso stiamo supponendo che U0 sia del tipo sin(x)cos(x).

086. double phit(double *T,int k, double **AA, double yy, double h) 087. {

088. double zp,zz;

089. int np=0;

090. while((np*h)<yy)

091. ++np;

092. if (T[np]==yy) 093. {

094. return 0;

095. }

096. zz = AA[np][k]*( 1. - (yy -T[np])/h) + (yy - T[np])/h*AA[np+1][k];

097. return zz;

098. }

La funzione precedente ha lo scopo di interpolare i valori lungo l’asse delle ordinate restituendo il valore della funzione cui punta **A, cio`e U, nel punto indicato con XXG[j] in Integra1.

099. double phix(double *X,int n, double **AA, double xp, double h) 100. {

101. double zp,zz;

102. int np=0;

103. while((np*h)<xp) 104. ++np;

105. zz= AA[n][np]*( 1. - (xp -X[np])/h) + (xp - X[np])/h*AA[n][np+1];

106. return zz;

107. }

Anche questa funzione ha il compito di interpolare i valori della funzione cui punta **A. A differenza della precedente viene restituito il valore della funzione in questione, quindi U, nel punto d’ascissa xp che non coincide a priori con uno dei punti del reticolo.

108. double Integra1(double *T, double *XG, double *WG, double **AA, int n, int k, double h) 109. {

110. int j;

111. double zed,rmed;

112. double XXG[NGP], WWG[NGP];

113. rmed = T[n]/2;

114. map(XG,WG,XXG,WWG,NGP,0.,T[n],rmed);

115. gauleg(0., T[n], XXG, WWG, NGP);

116. zed=0.;

117. for(j=0;j<NGP;++j)

118. {

119. double prova = phit(T,k,AA,XXG[j], h);

120. zed += prova *WWG[j];

121. }

122. return zed;

123. }

La funzione Integra1 calcola l’integrale che costituisce poi il valore di xp.

Per far ci`o si ricorre al metodo di Gauss-Legendre 5 calcolando il valore della

5Si consulti B per maggiori dettagli sul processo di integrazione gaussiana.

U solo su un insieme discreto di punti, nel nostro caso NGP=50. Si noti il ricorso alle funzioni gauleg e map, esposte di seguito, per generare e map-pare sull’intervallo [0,t] i punti gaussiani.

124. double ** allocate(int N_, int K_) 125. {

126. double **A;

127. A = (double **)calloc(N_,sizeof(double *));

128. for(int i=0;i<N_;++i) *(A+i)=(double *)calloc(K_,sizeof(double));

129. return A;

130. }

Con questa funzione si alloca memoria in cui salvare i dati relativi ai punti della griglia. Si genera per prima cosa un vettore di dimensione N che fa riferimento ai diversi istanti di tempo e i cui elementi rimandano ad un altro vettore, di dimensione K , che interessa la parte spaziale. Si crea cos`ı una griglia N *K .

131. void gauleg(double x1,double x2,double x[], double w[], int n) 132. {

133. double PI=3.141592653858979;

134. double EPS = 3.0e-11;

135. int m,j,i;

136. double z1,z,xm,xl,pp,p3,p2,p1;

137. m = (n+1)/2;

138. xm=0.5 *(x2 + x1);

139. xl= 0.5 *(x2 - x1);

140. for(i=1; i<m+1; ++i)

141. {

142. z = cos(PI*(i-0.25)/(n+0.5));

143. do

144. {

145. p1=1.0;

146. p2=0.0;

147. for(j=1;j<=n;j++)

148. {

149. p3=p2;

150. p2=p1;

151. p1=((2.0*j - 1.0)*z*p2 -(j - 1.0)*p3)/j;

152. }

153. pp=n*(z*p1 - p2)/(z*z - 1.0);

154. z1=z;

155. z=z1 - p1/pp;

156. }

157. while( fabs(z - z1)>EPS);

158. x[i-1]=xm - xl*z;

159. x[n-i]=xm + xl*z;

160. w[i-1]=2.0*xl/((1.0 - z*z)*pp*pp);

161. w[n-i]=w[i-1];

162. }

163. }

Come detto in precedenza tale funzione serve a generare i punti ed i pesi gaussiani. I valori in ingresso sono costituiti dagli estremi dell’intervallo in questione, da due vettori in cui salvare la posizione dei singoli punti gaussiani ed il relativo peso, e infine da un intero che determina il numero complessivo dei punti gaussiani da determinare. Si noti inoltre come il parametro d’usci-ta di gauleg [4] sia imposd’usci-tato su void in quanto la funzione non restituisce alcun valore bens`ı calcola i punti gaussiani ed i rispettivi pesi salvandoli nei vettori x[ ] e w[ ].

164. void map(double *Y, double *WY, double *YY, double *WWY, int NG, double RMIN, double RMAX, double RMED) 165. {

166. double PI=3.141592653858979;

167. double DELTA = RMAX-RMIN;

168. double SN,CS,RDIST=(RMED-RMIN)*(RMAX-RMIN)/(RMAX-RMED);

169. int i;

170. for(i=0;i<NG;++i)

171. {

172. SN=sin(PI/4*(1+Y[i]));

173. CS=cos(PI/4*(1+Y[i]));

174. WWY[i]=WY[i]*RDIST*PI/4/(CS+RDIST/DELTA*SN)/

(CS+RDIST/DELTA*SN);

175. YY[i]=RMIN+RDIST*SN/(CS+RDIST/DELTA*SN);

176. }

177. }

La funzione map ridistribuisce opportunamente i punti gaussiani, trovati per un intervallo unitario, sull’intero dominio d’integrazione.

Figura 3.3: Grafico 3D della soluzione numerica per U0(x) = sin(x)cos(x).

Figura 3.4: Grafico 3D della soluzione numerica per U0(x) = sin(x)cos(x).

Figura 3.5: Sezione bidimensionale della soluzione a diversi istanti di tempo.

Da questi grafici si nota come la funzione sia caratterizzata da un an-damento cosinusoidale lungo l’asse x, dovuto alla condizione iniziale, anche sulle mesh successive alla prima. Inoltre la funzione U(x, t) sembra decadere in maniera abbastanza rapida, quasi in modo esponenziale sebbene questo andamento potrebbe essere osservato meglio per un intervallo temporale mag-giore. Tuttavia la funzione U va a pescare ben presto dei punti che si trovano al di fuori dell’intervallo considerato. Ipotizziamo allora che essa sia caratte-rizzata da un’instabilit`a che risulta essere intrinseca al problema (3.12) che abbiamo tentato di risolvere numericamente.

Supponiamo di fissare i parametri t0, t1, x0, x1 e K=300, come riportato nel codice di cui al paragrafo 3.4. Vogliamo studiare il comportamento della soluzione al variare del parametro α dato da

α = deltat deltax =

µ t1 − t0 x1 − x0

¶ µK − 1 N − 1

. (3.18)

Si nota allora che α `e funzione della sola variabile N che rappresenta il nume-ro di mesh considerate sull’intervallo, costante, t1-t0. Nella seguente tabella

riportiamo, al variare di N, il valore di α(N) e di ts, tempo relativo alla mesh in cui la soluzione considera punti esterni al dominio.

N α(N) ts

400 0.795110 1.203008 500 0.635769 1.182365 600 0.529631 1.185309 700 0.453861 1.187411 800 0.397057 1.188986 1000 0.317566 1.191191 5000 0.063462 1.186237

Grafichiamo ora il comportamento delle varie soluzioni ottenute, per diversi valori di α, sull’ultima mesh.

Figura 3.6: Grafico relativo all’andamento della soluzione numerica sull’ultima mesh al variare di N.

Naturalmente si ottengono dei risultati del tutto confrontabili con i pre-cedenti se si considera α come α(K). In questo caso si tiene dunque fisso il numero N e si fa variare K. Ovviamente ci`o che importa `e il comportamento della funzione al variare del rapporto (deltat/deltax), qualsiasi sia la varia-bile N o K che andiamo a cambiare. Ecco spiegato il motivo del perch`e i risultati, che omettiamo per ovvi motivi, risultano essere equivalenti.

Proviamo poi a cambiare la condizione iniziale. Supponiamo, ad esempio, che la U0 sia una funzione lineare con coefficiente angolare pari ad un ven-tesimo dell’unit`a. Modificando opportunamente il codice e ponendo N=500, K=300, x1=200 otteniamo l’andamento mostrato nella figura 3.7.

Figura 3.7: Grafico 3D della soluzione numerica per U0(x) = 0.05 · x.

Figura 3.8: Andamento della soluzione per U(x) = 0.05 · x a diversi istanti di tempo.

Abbiamo notato che la soluzione, in questo caso, usciva dall’intervallo a ts = 8.016032. Naturalmente aumentando il coefficiente angolare della con-dizione iniziale si otteneva una soluzione per un intervallo temporale minore, mentre facendo tendere a zero la costante che moltiplica x si notava un al-lungamento dei tempi in cui la funzione si aggiorna considerando punti nel dominio. Inoltre per la condizione limite in cui U0 sia identicamente nulla si ottiene una soluzione, sebbene banale, definita su ogni intervallo temporale considerato.

Consideriamo infine un’altra condizione iniziale, U0(x) = 1/(x + 3),che risul-ta essere ancora Lipschitziana.

Fissando i parametri N=600, K=300, e facendo variare x e t rispettivamente in [0,50] e [0,5] otteniamo

Figura 3.9: Grafico 3D della soluzione numerica per U0(x) = 1/(x + 3).

Figura 3.10: Andamento della soluzione per U0(x) = 1/(x + 3) a diversi istanti di tempo.

Si noti come in questo caso la funzione viene rappresentata, nel grafico 3D, su tutto il dominio temporale in quanto per la particolare configurazione considerata la funzione non esce mai dall’intervallo lungo le ascisse. Sebbene siamo costretti ad osservare la funzione solo su un intervallo molto ridotto della variabile t, possiamo comunque notare come la funzione assuma un comportamento quasi esponenziale. Purtroppo non `e possibile osservare il comportamento della soluzione per t maggiori, in quanto aumentando l’in-tervallo temporale la funzione va a considerare punti esterni al dominio delle x.

In questo capitolo abbiamo introdotto il concetto di processi markoviani e non markoviani, nonch`e l’equazione di Pascali. Abbiamo tentato di ottener-ne una soluzioottener-ne numerica attraverso un processo di discretizzazioottener-ne tuttavia data la sua complessit`a non si pu`o condurre uno studio sulla stabilit`a. Inoltre non si pu`o neanche tentare, come indicato in [19], di rendere non markovia-no il processo cercando di ottenere poi una soluzione approssimata. Tutto ci`o che abbiamo fatto non `e che un possibile approccio al problema, il quale sicuramente deve essere studiato pi`u a fondo non solo dal punto di vista ma-tematico, ma anche dal punto di vista fisico per capire se equazioni come la (3.12) possono essere usate per lo studio di fenomeni fisici o finanziari. Anche l’algoritmo per lo studio della (3.12) pu`o essere migliorato per fare in modo che la funzione resti nel dominio il pi`u a lungo possibile. Questo probabil-mente pu`o essere fatto normalizzando l’integrale di u(x, s), moltiplicandolo per (1/t), oppure inserendo opportuni fattori di scala.

Capitolo 4

Verso la CFD

Continuiamo il nostro studio considerando alcune equazioni differenziali pro-prie della Fluidodinamica computazionale (CFD). Ripercorriamo quin-di la strada che porta alla loro derivazione prima quin-di passare a considerare dei metodi di discretizzazione che permettono di risolvere alcune di esse in particolari casi.

4.1 Cenni preliminari

Un primo approccio `e quello di considerare il fluido in ciascuna sua parte, cio`e facendo riferimento alle caratteristiche di tutte le particelle che lo com-pongono. Dato per`o il numero molto elevato di queste possiamo far ricorso ai metodi statistici per determinare i vari parametri in termini di grandezze medie. Alternativamente si pu`o spostare l’attenzione passando a considera-re il fluido non nel suo insieme bens`ı considera-restringendo il campo d’indagine ad una regione finita o addirittura infinitesima. Nel primo caso si ottengono direttamente delle equazioni di tipo integrale, nel secondo invece delle equazioni differenziali. Sia le prime che le seconde possono poi essere conservative o non conservative a seconda del fatto che l’elemento con-siderato si pensi fisso all’interno del fluido oppure che si possa muovere con le linee di flusso.

Iniziamo con l’introduzione di alcuni concetti chiave che verranno spesso usa-ti nel proseguo.

Sia df

dt = ∂f

∂t +∂f

∂x

∂x

∂t +∂f

∂y

∂y

∂t +∂f

∂z

∂z

∂t (4.1)

la derivata totale di una generica funzione f (t, x, y, z) fatta rispetto al tempo.

Notiamo che ∂x/∂t, ∂y/∂t, ∂z/∂t possono essere interpretate come le

com-ponenti, lungo i tre assi cartesiani, di un vettore V che esprime la velocit`a.

Ricordando poi l’espressione dell’operatore divergenza, data da

∇ ≡ i

∂x + j

∂y + k

∂z, (4.2)

possiamo scrivere la (4.1) nella forma seguente df

dt ∂f

∂t + V · ∇f. (4.3)

Definiamo allora l’operatore derivata sostanziale come D

Dt

∂t + (V · ∇). (4.4)

Possiamo interpretare la derivata sostanziale come la velocit`a di variazione della funzione f (t, x, y, z), mentre ∂/∂t altro non `e che la derivata locale fatta rispetto al tempo e V · ∇ la derivata convettiva. Si pu`o pensare che la prima sia dovuta ad una variazione istantanea, mentre la seconda al cambiamento della posizione.

Possiamo considerare, in ultima analisi, la derivata sostanziale come la deri-vata totale fatta rispetto al tempo di una funzione che dipende direttamente anche da quest’ultimo parametro e interpretare fisicamente l’espressione di

∇ · V come la velocit`a della variazione del volume di un elemento del fluido, di volume unitario, che si muove con esso.

Documenti correlati