学习自F. Moukalled, L. Mangani, M. Darwish所著The Finite Volume Method in Computational Fluid Dynamics - An Advanced Introduction with OpenFOAM and Matlab
Chapter 6 The Finite Volume Mesh
划分网格是有限体积法的首要工作,即,把原本连续的计算域划分成一系列不重叠的单元集合,这些单元由一系列面构成(对于每个面来说,其分属于两个单元),同时还要标记边界面来定义物理边界。随后,还要计算网格相关的几何信息(比如单元体积、面积矢量等),以及拓扑关系(单元-面,面-角点的编号关系)。这些是如何处理的呢?本章便着重阐明有限体积法网格中的几何信息和拓扑信息。
如图所示,对于(a)所示的计算域,可以用结构网格剖分(b),也可用非结构网格剖分(c),将原本控制方程在连续区域上的解转化为控制方程在离散区域上的解,最终获得的是离散点上的变量值(即,有限个离散点处的值)。
结构网格的拓扑关系是规整的,所以无需额外存储,其编程较为简单、计算效率较高,然而其适用的几何计算域较为苛刻,并不是任何计算域都能很容易地划分成结构网格的,当然,可以用分块结构网格的剖分方式来提高其几何适应性,但是工作量很大,且难度较大。
非结构网格的拓扑关系比较复杂,需要额外存储(单元-面,面-角点的关系表),且编程较为复杂(需要考虑网格的非正交修正),计算效率也比较低,然而非结构网格的优点也很明显,其对于复杂几何区域的适应性很强,其划分网格的过程非常方便快捷。
实际上,非结构网格是更一般的情形,而结构网格则是非结构网格的一种特例而已,因此非结构网格的所有计算是兼容结构网格的。
在OpenFOAM中,使用的是非结构网格,但是也能使用分块结构网格和分块嵌套结构网格。下面就先讲讲简单的结构网格,然后再讲讲复杂得非结构网格。
以梯度计算为例,看看结构网格和非结构网格都需要存储什么几何量和拓扑关系。
梯度计算所需的网格信息
如图,变量一般存储在单元形心 C C C处,若采用Green-Gauss方法来计算有限体积单元形心处的变量梯度值 ∇ ϕ C \nabla\phi_C ∇ϕC,有
∇ ϕ C = 1 V C ∑ f = 1 n b ( C ) ϕ f S ⃗ f \nabla\phi_C=\frac{1}{V_C}\sum_{f=1}^{nb(C)}\phi_f \vec S_f ∇ϕC=VC1f=1∑nb(C)ϕfSf
即,用面形心处的变量值 ϕ f \phi_f ϕf与面积矢量 S ⃗ f \vec S_f Sf复合后,加和,再除以单元体积 V C V_C VC,便可得到单元形心处的变量梯度值了。
而面形心处的变量值 ϕ f \phi_f ϕf则一般用该面两侧单元形心处变量值的加权平均来获取,即
ϕ f = g F ϕ F + g C ϕ C \phi_f=g_F\phi_F+g_C\phi_C ϕf=gFϕF+gCϕC
一种计算权重系数的方法是
g F = V C V C + V F g C = V F V C + V F = 1 − g F g_F=\frac{V_C}{V_C+V_F} \quad\quad g_C=\frac{V_F}{V_C+V_F}=1-g_F gF=VC+VFVCgC=VC+VFVF=1−gF
这里只是做简要叙述,梯度的具体计算方法(含非正交修正方法),权系数的其它插值方法将在后续章节中详解。
可见,要存储的几何信息和拓扑关系还真不少呢,有
单元形心坐标值、面形心坐标值、面积矢量值、单元标识与组成单元的面标识的对应关系、面标识与面左右两侧单元标识的对应关系,等
结构网格的几何信息和拓扑信息要简单明了得狠,以下图为例
在结构网格中, i , j , k i,j,k i,j,k方向分别划分 N i , N j , N k N_i,N_j,N_k Ni,Nj,Nk个单元,则单元总数为 N i ∗ N j ∗ N k N_i*N_j*N_k Ni∗Nj∗Nk,且直接可用标识 ( i , j , k ) (i,j,k) (i,j,k)索引到特定的单元,而特定单元的邻近单元可由标识 ( i , j , k ) (i,j,k) (i,j,k)加或减1来寻访到,至于邻近单元的数目,3维情况单元为六面体单元,每个单元有6个面、8个角点、有6个邻近单元,2维情况单元为四边形单元,每个单元有4个面、4个角点、有4个邻近单元。由于拓扑关系明晰,所以无需额外存储拓扑信息。
在计算的时候,最终还是要生成整体矩阵来做计算的,那么局部标识 ( i , j ) (i,j) (i,j)与整体标识 n n n是如何关联的呢?其实很简单,如果整体标识是按照一行一行来标记变量的话,有
n = i + ( j − 1 ) N i 1 ≤ i ≤ N i 1 ≤ j ≤ N j n=i+(j-1)N_i\quad\quad 1\le i \le N_i\quad\quad 1 \le j \le N_j n=i+(j−1)Ni1≤i≤Ni1≤j≤Nj
那么,单元 ( i , j ) (i,j) (i,j)邻近单元的局部标识和整体标识为
( i , j ) → n ( i + 1 , j ) → n + 1 ( i − 1 , j ) → n − 1 ( i , j + 1 ) → n + N j ( i , j − 1 ) → n − N j (i, j) \rightarrow n \\ (i+1, j) \rightarrow n+1 \\ (i-1, j) \rightarrow n-1 \\ (i, j+1) \rightarrow n+N_j \\ (i, j-1) \rightarrow n-N_j (i,j)→n(i+1,j)→n+1(i−1,j)→n−1(i,j+1)→n+Nj(i,j−1)→n−Nj
如果是3维问题,则
n = i + ( j − 1 ) N i + ( k − 1 ) N i N j 1 ≤ i ≤ N i 1 ≤ j ≤ N j 1 ≤ k ≤ N k n=i+(j-1)N_i+(k-1)N_iN_j \quad\quad 1\le i \le N_i \quad\quad 1 \le j \le N_j \quad\quad 1 \le k \le N_k n=i+(j−1)Ni+(k−1)NiNj1≤i≤Ni1≤j≤Nj1≤k≤Nk
单元 ( i , j , k ) (i,j,k) (i,j,k)邻近单元的局部标识和整体标识为
( i , j , k ) → n ( i + 1 , j , k ) → n + 1 ( i − 1 , j , k ) → n − 1 ( i , j + 1 , k ) → n + N j ( i , j − 1 , k ) → n − N j ( i , j , k + 1 ) → n + N i N j ( i , j , k − 1 ) → n − N i N j (i, j, k) \rightarrow n \\ (i+1, j, k) \rightarrow n+1 \\ (i-1, j, k) \rightarrow n-1 \\ (i, j+1, k) \rightarrow n+N_j \\ (i, j-1, k) \rightarrow n-N_j \\ (i, j, k+1) \rightarrow n+N_iN_j \\ (i, j, k-1) \rightarrow n-N_iN_j (i,j,k)→n(i+1,j,k)→n+1(i−1,j,k)→n−1(i,j+1,k)→n+Nj(i,j−1,k)→n−Nj(i,j,k+1)→n+NiNj(i,j,k−1)→n−NiNj
非常方便,组装整体矩阵的时候也很快捷,因为在整体矩阵中的存储位置一目了然,不烦取寻找对应关系了。
访问某特定单元的局部几何信息较为简便,对单元 ( i , j ) (i,j) (i,j)来说,其构成面为 S 1 ⃗ ( i , j ) \vec {S1}(i,j) S1(i,j), S 2 ⃗ ( i , j ) \vec {S2}(i,j) S2(i,j), S 1 ⃗ ( i + 1 , j ) \vec {S1}(i+1,j) S1(i+1,j), S 2 ⃗ ( i , j + 1 ) \vec {S2}(i,j+1) S2(i,j+1),由于构成单元的面矢量是向外为正的,所以有
S ⃗ i − 1 / 2 , j = − S 1 ⃗ ( i , j ) S ⃗ i , j − 1 / 2 = − S 2 ⃗ ( i , j ) \vec S_{i-1/2,j}=-\vec {S1}(i,j) \quad\quad \vec S_{i,j-1/2}=-\vec {S2}(i,j) Si−1/2,j=−S1(i,j)Si,j−1/2=−S2(i,j)
另外俩面的正方向与坐标轴正方向相同,故
S ⃗ i + 1 / 2 , j = S 1 ⃗ ( i + 1 , j ) S ⃗ i , j + 1 / 2 = S 2 ⃗ ( i , j + 1 ) \vec S_{i+1/2,j}=\vec {S1}(i+1,j) \quad\quad \vec S_{i,j+1/2}=\vec {S2}(i,j+1) Si+1/2,j=S1(i+1,j)Si,j+1/2=S2(i,j+1)
以计算梯度为例,面 ( i + 1 / 2 , j ) (i+1/2,j) (i+1/2,j)处的值可由两侧单元值插值获取,即
ϕ i + 1 / 2 , j = g i + 1 / 2 , j ϕ i + 1 , j + ( 1 − g i + 1 / 2 , j ) ϕ i , j \phi_{i+1/2,j}=g_{i+1/2,j}\phi_{i+1,j}+(1-g_{i+1/2,j})\phi_{i,j} ϕi+1/2,j=gi+1/2,jϕi+1,j+(1−gi+1/2,j)ϕi,j
单元形心梯度值可以算得,即
∇ ϕ i , j = 1 V i , j ( ϕ i + 1 / 2 , j S ⃗ i + 1 / 2 , j + ϕ i − 1 / 2 , j S ⃗ i − 1 / 2 , j + ϕ i , j + 1 / 2 S ⃗ i , j + 1 / 2 + ϕ i , j − 1 / 2 S ⃗ i , j − 1 / 2 ) = 1 V i , j ( ϕ i + 1 / 2 , j S 1 ⃗ i + 1 , j − ϕ i − 1 / 2 , j S 1 ⃗ i , j + ϕ i , j + 1 / 2 S 2 ⃗ i , j + 1 − ϕ i , j − 1 / 2 S 2 ⃗ i , j ) \nabla\phi_{i,j}=\frac{1}{V_{i,j}}\left( \phi_{i+1/2,j} \vec S_{i+1/2,j} + \phi_{i-1/2,j} \vec S_{i-1/2,j} + \phi_{i,j+1/2} \vec S_{i,j+1/2} + \phi_{i,j-1/2} \vec S_{i,j-1/2} \right) \\ =\frac{1}{V_{i,j}}\left( \phi_{i+1/2,j} \vec {S1}_{i+1,j} - \phi_{i-1/2,j} \vec {S1}_{i,j} + \phi_{i,j+1/2} \vec {S2}_{i,j+1} - \phi_{i,j-1/2} \vec {S2}_{i,j} \right) ∇ϕi,j=Vi,j1(ϕi+1/2,jSi+1/2,j+ϕi−1/2,jSi−1/2,j+ϕi,j+1/2Si,j+1/2+ϕi,j−1/2Si,j−1/2)=Vi,j1(ϕi+1/2,jS1i+1,j−ϕi−1/2,jS1i,j+ϕi,j+1/2S2i,j+1−ϕi,j−1/2S2i,j)
如果用上图(b)的整体标识,则
∇ ϕ n = 1 V i , j ( ϕ n + 1 / 2 S ⃗ n + 1 / 2 + ϕ n − 1 / 2 S ⃗ n − 1 / 2 + ϕ n + N i / 2 S ⃗ n + N i / 2 + ϕ n − N i / 2 S ⃗ n − N i / 2 ) \nabla\phi_n=\frac{1}{V_{i,j}}\left( \phi_{n+1/2} \vec S_{n+1/2} + \phi_{n-1/2} \vec S_{n-1/2} + \phi_{n+N_i/2} \vec S_{n+N_i/2} + \phi_{n-N_i/2} \vec S_{n-N_i/2} \right) ∇ϕn=Vi,j1(ϕn+1/2Sn+1/2+ϕn−1/2Sn−1/2+ϕn+Ni/2Sn+Ni/2+ϕn−Ni/2Sn−Ni/2)
不难发现,除了边界面之外,每个内部面都被两个单元享有,那么这个面的面积矢量对于其中一个是正的,而对于另一个是负的,那么计算的时候统一算好了每个内部面的面积矢量,然后根据标识关系去添加正负号就行了(对于单元(i,j),面标识比(i,j)大的为正,面标识比(i,j)小的为负),而不用一把每个面的面积矢量都重复算两次了。
还有一种写成上图(c)所示的东西南北的局部标识形式
∇ ϕ C = 1 V C ( ϕ e S ⃗ e + ϕ w S ⃗ w + ϕ n S ⃗ n + ϕ s S ⃗ s ) \nabla\phi_C=\frac{1}{V_{C}}\left( \phi_{e} \vec S_{e} + \phi_{w} \vec S_{w} + \phi_{n} \vec S_{n} + \phi_{s} \vec S_{s} \right) ∇ϕC=VC1(ϕeSe+ϕwSw+ϕnSn+ϕsSs)
非结构网格更加灵活,然而也更加复杂。单元、面和角点都是顺序编号的,每个单元的邻近单元数目并不相等,每个单元的面数目也并不相等,甚至每个面的角点数目也并不相等,所以并不能从单元、面、角点的标识直接得到其邻近单元、面、角点的标识(与结构网格迥然不同),也就是需要存储这些额外的拓扑关系了。比如上图中,单元9的邻近单元为1、2、6、8、10、11,这是无法从单元9的标识中直接靠加减增量来获得的。
上图(a)为局部标识,(b)为整体标识,可见,单元C的整体标识为9,其邻近6个单元的整体标识为10、11、8、6、1、2,其所含6个面的整体标识为16、22、23、25、11、10,其所含6个角点的整体标识为21、22、21、14、13、12(角点标号图上并未标明,这里显然有俩21角点是不对的,仅仅作为示意不用太纠结了)。
那么梯度的计算,用局部标识表示出来便是
∇ ϕ C = 1 V C ( ϕ f 1 S ⃗ f 1 + ϕ f 2 S ⃗ f 2 + ϕ f 3 S ⃗ f 3 + ϕ f 4 S ⃗ f 4 + ϕ f 5 S ⃗ f 5 + ϕ f 6 S ⃗ f 6 ) \nabla\phi_C=\frac{1}{V_C}\left( \phi_{f_1}\vec S_{f_1} + \phi_{f_2}\vec S_{f_2} + \phi_{f_3}\vec S_{f_3} + \phi_{f_4}\vec S_{f_4} + \phi_{f_5}\vec S_{f_5} + \phi_{f_6}\vec S_{f_6} \right) ∇ϕC=VC1(ϕf1Sf1+ϕf2Sf2+ϕf3Sf3+ϕf4Sf4+ϕf5Sf5+ϕf6Sf6)
用整体标识表示出来便是
∇ ϕ 9 = 1 V 9 ( ϕ f 16 S ⃗ f 16 + ϕ f 22 S ⃗ f 22 − ϕ f 23 S ⃗ f 23 − ϕ f 15 S ⃗ f 15 − ϕ f 11 S ⃗ f 11 − ϕ f 10 S ⃗ f 10 ) \nabla\phi_9=\frac{1}{V_9}\left( \phi_{f_{16}}\vec S_{f_{16}} + \phi_{f_{22}}\vec S_{f_{22}} - \phi_{f_{23}}\vec S_{f_{23}} - \phi_{f_{15}}\vec S_{f_{15}} - \phi_{f_{11}}\vec S_{f_{11}} - \phi_{f_{10}}\vec S_{f_{10}} \right) ∇ϕ9=V91(ϕf16Sf16+ϕf22Sf22−ϕf23Sf23−ϕf15Sf15−ϕf11Sf11−ϕf10Sf10)
由于每个面的面积矢量仅计算了一次,所以对某个特定单元来说,需要判断该面积矢量是正还是负,一般的规定是,计算面的面积矢量时,其正方向为面左侧单元指向右侧单元,左单元的整体标识要小于右单元的整体标识,也常称左单元为owner单元,右单元为neighbour单元。所以,上式中出现了正负号,对于某个标识为k的单元,其梯度算式可写成如下形式
∇ ϕ k = 1 V k ( − ∑ n ← ( f − n b ( k ) ) < k ϕ n S ⃗ n + ∑ n ← ( f − n b ( k ) ) > k ϕ n S ⃗ n ) \nabla\phi_k=\frac{1}{V_k}\left( -\sum_{n\leftarrow(f-nb(k))
其中 n ← ( f − n b ( k ) ) < k n\leftarrow(f-nb(k))
在OpenFOAM中,把内部面的面积矢量方向的正方向定义为从单元1到单元2,分别称为owner和neighbor,即所属单元和邻近单元,如下图所示
具体计算梯度的时候,如果是一个单元一个单元做循环就太麻烦了,所以做面循环比较划算,即:
非结构网格除了上述拓扑信息外,还需要用到很多几何信息,比如单元体积、面面积、单元形心、面形心、面积矢量与面owner和neighbor单元形心连线的夹角,等。
常见的3维单元有四面体、六面体、三棱柱、多面体等,构成单元的面有四边形、三角形和多边形,显然,由任意多边形构成的任意多面体是一种最通有的情形,而其他单元则是该单元的特例。
首先计算形心(centroid),如果是三角形,其形心和几何中心(geometric center)是重合的,很好计算,用其三个角点平均即可
x ⃗ C E = x ⃗ G = 1 3 ∑ i = 1 3 x ⃗ i \vec x_{CE}=\vec x_G=\frac{1}{3}\sum_{i=1}^{3}\vec x_i xCE=xG=31i=1∑3xi
然而,对于普遍意义上的多边形来说,其形心的计算就没那么简单了,需要先用一个点将该多边形剖分为许多小三角形,然后再用小三角形的形心来加权计算该多边形的形心,这个剖分点一般选取为几何中心即可,对于 k k k边形来说,用其角点坐标平均可得其几何中心,即
x ⃗ G = 1 k ∑ i = 1 k x ⃗ i \vec x_G=\frac{1}{k}\sum_{i=1}^{k}\vec x_i xG=k1i=1∑kxi
加权系数用小三角形的面积即 S t S_t St可,则多边形的形心为
x ⃗ C E = ∑ t = 1 s u b T r i a n g l e s ( C ) ( x ⃗ C E ) t S t ∑ t = 1 s u b T r i a n g l e s ( C ) S t \vec x_{CE}=\frac{\displaystyle \sum_{t=1}^{subTriangles(C)}(\vec x_{CE})_t S_t}{\displaystyle \sum_{t=1}^{subTriangles(C)} S_t} xCE=t=1∑subTriangles(C)Stt=1∑subTriangles(C)(xCE)tSt
嗯,你说啥?三角形面积值 S t S_t St该咋算呢?
用两条边矢量的叉乘除以2就好了,面积矢量为
S ⃗ = 1 2 ( r ⃗ 2 − r ⃗ 1 ) × ( r ⃗ 3 − r ⃗ 1 ) = 1 2 ∣ i j k x 2 − x 1 y 2 − y 1 z 2 − z 1 x 3 − x 1 y 3 − y 1 z 3 − z 1 ∣ = S x i + S y j + S z k \vec S=\frac{1}{2}(\vec r_2 - \vec r_1)\times(\vec r_3 - \vec r_1)=\frac{1}{2} \left| \begin{matrix} \bold i & \bold j & \bold k \\ x_2-x_1 & y_2-y_1 & z_2-z_1 \\ x_3-x_1 & y_3-y_1 & z_3-z_1 \end{matrix}\right|= S_x\bold i + S_y \bold j + S_z \bold k S=21(r2−r1)×(r3−r1)=21∣∣∣∣∣∣ix2−x1x3−x1jy2−y1y3−y1kz2−z1z3−z1∣∣∣∣∣∣=Sxi+Syj+Szk
面积幅值为
S = S x 2 + S y 2 + S z 2 S=\sqrt{S_x^2+S_y^2+S_z^2} S=Sx2+Sy2+Sz2
那么如何判断面积矢量的方向是向外还是向内的呢?计算连接单元形心CE和面形心ce的矢量,然后再与 S ⃗ \vec S S做点积,如果大于0,则面积矢量是朝外的,如果小于0,则面积矢量是朝内的。
对于2维网格,面的面积相当于三角形拉伸了单位长度的控制体的体积,即
V = 1 2 ∣ ( r ⃗ 2 − r ⃗ 1 ) × ( r ⃗ 3 − r ⃗ 1 ) ∣ = 1 2 ∣ 1 1 1 x 1 x 2 x 3 y 1 y 2 y 3 ∣ = 1 2 [ x 1 ( y 2 − y 3 ) + x 2 ( y 3 − y 1 ) + x 3 ( y 1 − y 2 ) ] V=\frac{1}{2}|(\vec r_2 - \vec r_1)\times(\vec r_3 - \vec r_1)|=\frac{1}{2} \left| \begin{matrix} 1 & 1 & 1 \\ x_1 & x_2 & x_3 \\ y_1 & y_2 & y_3 \end{matrix}\right| \\ =\frac{1}{2}[x_1(y_2-y_3)+x_2(y_3-y_1)+x_3(y_1-y_2)] V=21∣(r2−r1)×(r3−r1)∣=21∣∣∣∣∣∣1x1y11x2y21x3y3∣∣∣∣∣∣=21[x1(y2−y3)+x2(y3−y1)+x3(y1−y2)]
注意,若角点1、2、3是逆时针排序,则体积(面积)为正值,如果是顺时针排序则为负值,即右手准则,这也就是为什么2维问题的角点总是逆时针排序的。
对于多边形组成的多面体,其形心和体积的计算和前面的多边形很相似,即,先用几何中心点把该多面体分成很多小多棱锥体,然后计算每个棱锥体的体积(=底面积*高/3),而每个棱锥体的形心位于其底面形心和顶部角点连线的1/4处,将所有小多棱锥体的形心做体积加权平均,便得到了多面体的形心坐标。
用下面几何中心剖分多面体为很多小多棱锥体
x ⃗ G = 1 k ∑ i = 1 k x ⃗ i \vec x_G=\frac{1}{k}\sum_{i=1}^{k}\vec x_i xG=k1i=1∑kxi
计算多棱锥体的形心
( x ⃗ C E ) p y r a m i d = 0.75 ( x ⃗ C E ) f + 0.25 ( x ⃗ G ) p y r a m i d (\vec x_{CE})_{pyramid}=0.75(\vec x_{CE})_f+0.25(\vec x_{G})_{pyramid} (xCE)pyramid=0.75(xCE)f+0.25(xG)pyramid
计算多棱锥体的体积
V p y r a m i d = 1 3 d ⃗ G f ⋅ S ⃗ f V_{pyramid}=\frac{1}{3}\vec d_{Gf} \cdot \vec S_{f} Vpyramid=31dGf⋅Sf
加权计算多面体的形心
( x ⃗ C E ) C = x ⃗ C = ∑ s u b P y r a m i d s ( C ) ( x ⃗ C E ) p y r a m i d V p y r a m i d ∑ s u b P y r a m i d s ( C ) V p y r a m i d (\vec x_{CE})_{C}=\vec x_C=\frac{\displaystyle \sum_{}^{subPyramids(C)}(\vec x_{CE})_{pyramid} V_{pyramid}}{\displaystyle \sum_{}^{subPyramids(C)} V_{pyramid}} (xCE)C=xC=∑subPyramids(C)Vpyramid∑subPyramids(C)(xCE)pyramidVpyramid
对于1维情况,已知面 f f f两侧单元 C 、 F C、F C、F的变量值,可插值算出面上的变量值,有
ϕ f = g f ϕ F + ( 1 − g f ) ϕ C \phi_f=g_f\phi_F+(1-g_f)\phi_C ϕf=gfϕF+(1−gf)ϕC
其中
g f = d C f d C f + d f F g_f=\frac{d_{Cf}}{d_{Cf}+d_{fF}} gf=dCf+dfFdCf
对于高维系统就没这么简单了,几何加权系数的定义并不唯一,可以选择对应的体积作为加权系数,即
g f = V C V C + V F g_f=\frac{V_C}{V_C+V_F} gf=VC+VFVC
这种方式在有些情况下是不靠谱的,比如下图
显然, f C = f F fC=fF fC=fF,有 g f = 0.5 g_f=0.5 gf=0.5,然而若用体积加权,由于 V C < V F V_C
还有更加麻烦的事情,当 C , f , F C,f,F C,f,F不共线的时候,情况就复杂得多了,这时候需要用到后续章节的非正交修正方法了,我们后面再谈。
如上图,一种处理方式是依据到面的法向距离来计算加权系数,即
g f = d ⃗ C f ⋅ e ⃗ f d ⃗ C f ⋅ e ⃗ f + d ⃗ f F ⋅ e ⃗ f g_f=\frac{\vec d_{Cf} \cdot \vec e_{f}}{\vec d_{Cf} \cdot \vec e_{f} + \vec d_{fF} \cdot \vec e_{f}} gf=dCf⋅ef+dfF⋅efdCf⋅ef
其中 e ⃗ f \vec e_f ef为面的单位法矢量
e ⃗ f = S ⃗ f ∣ ∣ S ⃗ f ∣ ∣ \vec e_f=\frac{\vec S_f}{||\vec S_f||} ef=∣∣Sf∣∣Sf
uFVM是仿照OpenFOAM,用matlab编写的CFD学习代码,其重在实现理论和后续深入理解OpenFOAM,而计算效率不及OpenFOAM(matlab的运行效率一向偏低,肯定比不过C++,但matlab的语言书写简便,便于理解),主要是便于学习如何实现理论,毕竟OpenFOAM理解起来确实太难了。
uFVM中,对于几何信息和拓扑信息的处理是在由cfdProcessOpenFoamMesh程序(2018年的V1.5版本中是cfdProcessGeometry函数)中进行的,该程序在cfdReadOpenFoamMesh程序读入OpenFOAM网格之后执行,读入的网格是用OpenFOAM的格式给出的,即网格由points,faces,owners,neighbours,boundaries文件给出。
cfdProcessOpenFoamMesh程序中,先计算face面的几何信息(面形心、面积、面积矢量),如下:
function cfdProcessGeometry
%--------------------------------------------------------------------------
%
% Written by the CFD Group @ AUB, Fall 2018
% Contact us at: cfd@aub.edu.lb
%==========================================================================
% Routine Description:
% This function reads poly mesh files from "constant/polyMesh" directory
% and stores them in the database ~domain
%--------------------------------------------------------------------------
% Get info
theNumberOfElements = cfdGetNumberOfElements; % 单元数目
theNumberOfFaces = cfdGetNumberOfFaces; % 面数目
theNumberOfInteriorFaces = cfdGetNumberOfInteriorFaces; % 内部面数目
theFaceNodesIndices = cfdGetFaceNodeIndices; % 面的角点标识(所有面的)
theNodeCentroids = cfdGetNodeCentroids; % 角点形心(其实就是角点)坐标(所有角点的)
theElementFaceIndices = cfdGetElementFaceIndices; % 单元的面标识(所有单元的)
owners = cfdGetOwners; % 面的owner单元标识(所有面的)
neighbours = cfdGetNeighbours; % 面的neighour单元标识(所有面的)
% Initialize mesh member arrays
elementCentroids = cfdVectorList(theNumberOfElements); % 单元形心坐标(矢量列表,所有单元)
elementVolumes = cfdScalarList(theNumberOfElements); % 单元体积(标量列表,所有单元)
faceCentroids = cfdVectorList(theNumberOfFaces); % 面形心坐标(矢量列表,所有面)
faceSf = cfdVectorList(theNumberOfFaces); % 面面积矢量(矢量列表,所有面)
faceAreas = cfdScalarList(theNumberOfFaces); % 面面积(标量列表,所有面)
faceWeights = cfdScalarList(theNumberOfFaces); % 面权重系数(标量列表,所有面)
faceCF = cfdVectorList(theNumberOfFaces); % 面左右单元形心矢量(矢量列表,所有面)
faceCf = cfdVectorList(theNumberOfFaces); % 面左单元形心到面形心矢量(矢量列表,所有面)
faceFf = cfdVectorList(theNumberOfFaces); % 面右单元形心到面形心矢量(矢量列表,所有面)
wallDist = cfdScalarList(theNumberOfFaces); % 面owner单元形心到壁面的距离(标量列表,所有面),某些湍流模型里面用到壁面距离d
wallDistLimited = cfdScalarList(theNumberOfFaces); % 面owner单元形心到壁面距离的限制值(标量列表,所有面)
% Process basic face geometry
for iFace=1:theNumberOfFaces % 对所有面做循环(包含内部面 与 边界面)
theNodesIndices = theFaceNodesIndices{iFace}; % 该面(iFace号面)的角点标识
theNumberOfFaceNodes = length(theNodesIndices); % 该面总共有多少个角点
% Compute a rough centre of the face % 计算面的大概中心(几何中心)
local_centre = cfdVector(0,0,0); % 所有角点坐标做平均处理
for iNode=theNodesIndices
local_centre = local_centre + theNodeCentroids(iNode,:);
end
local_centre = local_centre/theNumberOfFaceNodes;
centroid = cfdVector(0,0,0); % 初始化该面形心
Sf = cfdVector(0,0,0); % 初始化该面面矢量
area = 0; % 初始化该面面积
% Using the centre compute the area and centoird of vitual triangles
% based on the centre and the face nodes
for iTriangle=1:theNumberOfFaceNodes % 面共划分的小三角形数目与角点数目相同
point1 = local_centre; % 三角形第1个点为面形心
point2 = theNodeCentroids(theNodesIndices(iTriangle),:); % 第2个点为第iTriangle个角点
% 由于面角点轮换构成小三角形,即1-2,2-3,...,theNumberOfFaceNodes-1,所以最后一个点的第3个点的局部标识是1
if(iTriangle<theNumberOfFaceNodes)
point3 = theNodeCentroids(theNodesIndices(iTriangle+1),:);
else
point3 = theNodeCentroids(theNodesIndices(1),:);
end
local_centroid = (point1+point2+point3)/3; % 小三角形的形心等于重心等于几何中心
local_Sf = 0.5*cross(point2-point1,point3-point1); % 叉乘计算小三角形的面积矢量
local_area = norm(local_Sf); % 取模得到小三角形的面积标量
centroid = centroid + local_area*local_centroid; % 小三角形形心与其面积相乘后累加
Sf = Sf + local_Sf; % 小三角形面积矢量的累加,获得整体面积矢量
area = area + local_area; % 小三角形的面积累加,获得面的面积
end
centroid = centroid/area; % 除以面的整体面积,获得了面形心
%
faceCentroids(iFace,:) = centroid; % 面形心存储到面形心列表中去
faceSf(iFace,:) = Sf; % 面面积矢量存储到列表中去
faceAreas(iFace) = area; % 面面积存储到列表中去
end
这个程序接下来是计算了单元的体积和形心,如下:
% Compute volume and centroid of each element
for iElement=1:theNumberOfElements % 对所有单元做循环
theElementFaces = theElementFaceIndices{iElement}; % 该单元(iElement单元)的面的标识列表
% Compute a rough centre of the element % 计算该单元的几何中心(粗略中心),即,面形心的平均值
local_centre = cfdVector(0,0,0);
for iFace=1:length(theElementFaces)
faceIndex = theElementFaces(iFace);
local_centre = local_centre + faceCentroids(faceIndex,:);
end
local_centre = local_centre/length(theElementFaces);
% Using the centre compute the area and centoird of vitual triangles
% based on the centre and the face nodes
%
localVolumeCentroidSum = cfdVector(0,0,0); % 小多棱锥的(形心*体积)加和
localVolumeSum = 0; % 小多棱锥的体积加和
for iFace=1:length(theElementFaces) % 单元的所有面做循环
faceIndex = theElementFaces(iFace); % 取出第iFace个面的整体标识faceIndex
Cf = faceCentroids(faceIndex,:) - local_centre; % 面形心到体中心的矢量
faceSign = -1; % 判断该面面积矢量的正负号
if iElement==owners(faceIndex) % 如果该面的owner是该单元,则其面积矢量为正值,不然为负
faceSign = 1;
end
local_Sf = faceSign*faceSf(faceIndex,:); % 计算该面的面积矢量(向外为正)
% calculate face-pyramid volume
% 计算小多棱锥的体积:底面面积矢量 与 底面形心到顶点矢量 的点积 / 3
localVolume = dot(local_Sf,Cf)/3;
% Calculate face-pyramid centre % 计算小多棱锥的形心
localCentroid = 0.75*faceCentroids(faceIndex,:) + 0.25*local_centre;
%Accumulate volume-weighted face-pyramid centre % (形心*体积) 加和
localVolumeCentroidSum = localVolumeCentroidSum + localCentroid*localVolume;
% Accumulate face-pyramid volume % 小多棱锥体积加和,得到多面体的体积
localVolumeSum = localVolumeSum + localVolume;
end
% 体积平均,得到多面体形心,并存储到单元形心列表中
elementCentroids(iElement,:) = localVolumeCentroidSum/localVolumeSum;
elementVolumes(iElement) = localVolumeSum; % 多面体体积存储到单元体积列表中去
end
这个程序接下来做的事情是计算内部面形心f的own和neighbour单元形心C和F相关量,即矢量CF,矢量Cf,矢量Ff,几何权系数gC。以及边界面的矢量CF、矢量Cf,几何权系数gC=1,等
% Process secondary Face Geometry
for iFace=1:theNumberOfInteriorFaces
n = cfdUnit(faceSf(iFace,:));
%
own = owners(iFace);
nei = neighbours(iFace);
%
faceCF(iFace,:) = elementCentroids(nei,:) - elementCentroids(own,:);
faceCf(iFace,:) = faceCentroids(iFace,:) - elementCentroids(own,:);
faceFf(iFace,:) = faceCentroids(iFace,:) - elementCentroids(nei,:);
faceWeights(iFace) = (faceCf(iFace,:)*n')/(faceCf(iFace,:)*n' - faceFf(iFace,:)*n'); % -(dot(faceFf(iFace,:),n))/(-dot(faceFf(iFace,:),n)+dot(faceCf(iFace,:),n))
end
for iBFace=theNumberOfInteriorFaces+1:theNumberOfFaces
n = cfdUnit(faceSf(iBFace,:));
%
own = owners(iBFace);
%
faceCF(iBFace,:) = faceCentroids(iBFace,:) - elementCentroids(own,:);
faceCf(iBFace,:) = faceCentroids(iBFace,:) - elementCentroids(own,:);
faceWeights(iBFace) = 1;
wallDist(iBFace) = max(dot(faceCf(iBFace,:),n), cfdSMALL); % 边界单元中心到壁面的距离
wallDistLimited(iBFace) = max(wallDist(iBFace), 0.05*cfdMag(faceCf(iBFace,:)));
end
最后是把这些算好的几何信息存储到网格信息中去
% Save and Store
mesh = cfdGetMesh;
%
mesh.elementCentroids = elementCentroids;
mesh.elementVolumes = elementVolumes;
mesh.faceCentroids = faceCentroids;
mesh.faceSf = faceSf;
mesh.faceAreas = faceAreas;
mesh.faceWeights = faceWeights;
mesh.faceCF = faceCF;
mesh.faceCf = faceCf;
mesh.faceFf = faceFf;
mesh.wallDist = wallDist;
mesh.wallDistLimited = wallDistLimited;
%
cfdSetMesh(mesh);