Numerical flux

In the previous step, I have obtained the surface function

\[\psi \left( X, Y \right) + d \equiv n_X X + n_Y Y + d,\]

and its diffused representation

\[\hat{H} \left( X, Y \right) \equiv \frac{1}{ 1 + \exp{ \left\{ -2 \beta \left( \psi + d \right) \right\} } }\]

for each cell center \(\left( \pic, \pjc \right)\).

In this section, I compute the numerical flux

\[\frac{1}{V_{ij}} \int_{\partial V} u_j \hat{H} n_j dS\]

to integrate the avection equation and to update \(\phi\).

Explicitly, on a two-dimensional Cartesian domain, this integral is consisted of four terms:

\[\int_{x, \pim} \ux \hat{H} dy - \int_{x, \pip} \ux \hat{H} dy + \int_{y, \pjm} \uy \hat{H} dx - \int_{y, \pjp} \uy \hat{H} dx\]

divided by the cell size

\[V_{ij} = \Delta x_{\pic} \Delta y.\]

Now I focus the first term, and the other terms can be evaluated in the same manner.

First of all, since \(\ux\) is constant on the local cell face, I can take it out of the integral:

\[\frac{1}{\Delta x_i \Delta y} \ux \int_{x, \pim} \hat{H} dy.\]

To stabilise the cell-face integral, I need to use the upward scheme: namely information of the upwind cell should be used, i.e.

\[\begin{split}\vat{\hat{H}}{\pimm, \pjc} \,\, & \text{if} \,\, \vat{\ux}{\pim, \pjc} \ge 0, \\ \vat{\hat{H}}{\pic, \pjc} \,\, & \text{if} \,\, \vat{\ux}{\pim, \pjc} < 0.\end{split}\]
../_images/flux0.png

\(\vat{\ux}{\pim, \pjc} \ge 0\)

../_images/flux1.png

\(\vat{\ux}{\pim, \pjc} < 0\)

Recall that \(\hat{H}\) is defined on the local coordinate system attached to each cell center. Thus, for the left cell \(\left( \pimm, \pjc \right)\), the integrand should be evaluated at \(x = \frac{1}{2}\), while \(x = -\frac{1}{2}\) for the right cell \(\left( \pic, \pjc \right)\).

Same applied to the all fluxes.

src/interface/update/flxx.c
29const double vel = UX(i, j);
30const int    ii = vel < 0. ?    i : i - 1;
31const double  x = vel < 0. ? -0.5 :  +0.5;
src/interface/update/flxx.c
52const double vel = UX(i, j, k);
53const int    ii = vel < 0. ?    i : i - 1;
54const double  x = vel < 0. ? -0.5 :  +0.5;
src/interface/update/flxy.c
29const double vel = UY(i, j);
30const int    jj = vel < 0. ?    j : j - 1;
31const double  y = vel < 0. ? -0.5 :  +0.5;
src/interface/update/flxy.c
52const double vel = UY(i, j, k);
53const int    jj = vel < 0. ?    j : j - 1;
54const double  y = vel < 0. ? -0.5 :  +0.5;
src/interface/update/flxz.c
28const double vel = UZ(i, j, k);
29const int    kk = vel < 0. ?    k : k - 1;
30const double  z = vel < 0. ? -0.5 :  +0.5;

Also notice that

\[dy = \der{y}{Y} dY \approx \Delta y dY,\]

and thus

\[\begin{split}\frac{1}{\Delta x_i \Delta y} \ux \int_{x, \pim} \hat{H} dy = \begin{cases} \ux \ge 0 & \frac{1}{\Delta x_i} \ux \int_{x = +\frac{1}{2}} \hat{H} dY_{i-1, j}, \\ \ux < 0 & \frac{1}{\Delta x_i} \ux \int_{x = -\frac{1}{2}} \hat{H} dY_{i , j}, \end{cases}\end{split}\]

whose integral is again approximated by the \(N\)-th-order Gaussian quadrature:

\[\frac{1}{\Delta x_i} \ux \sum_{j}^N w_j \hat{H} \left( x, y_j \right),\]

which is implemented here:

src/interface/update/flxx.c
33const double lvof = VOF(ii, j);
34if(lvof < vofmin || 1. - vofmin < lvof){
35  FLXX(i, j) = vel * lvof;
36  continue;
37}
38double flux = 0.;
39for(int jj = 0; jj < NGAUSS; jj++){
40  const double w = gauss_ws[jj];
41  const double y = gauss_ps[jj];
42  flux += w * indicator(NORMAL(ii, j), (const double [NDIMS]){x, y});
43}
44FLXX(i, j) = vel * flux;
src/interface/update/flxx.c
56const double lvof = VOF(ii, j, k);
57if(lvof < vofmin || 1. - vofmin < lvof){
58  FLXX(i, j, k) = vel * lvof;
59  continue;
60}
61double flux = 0.;
62for(int kk = 0; kk < NGAUSS; kk++){
63  for(int jj = 0; jj < NGAUSS; jj++){
64    const double w = gauss_ws[jj] * gauss_ws[kk];
65    const double y = gauss_ps[jj];
66    const double z = gauss_ps[kk];
67    flux += w * indicator(NORMAL(ii, j, k), (const double [NDIMS]){x, y, z});
68  }
69}
70FLXX(i, j, k) = vel * flux;
src/interface/update/flxy.c
33const double lvof = VOF(i, jj);
34if(lvof < vofmin || 1. - vofmin < lvof){
35  FLXY(i, j) = vel * lvof;
36  continue;
37}
38double flux = 0.;
39for(int ii = 0; ii < NGAUSS; ii++){
40  const double w = gauss_ws[ii];
41  const double x = gauss_ps[ii];
42  flux += w * indicator(NORMAL(i, jj), (const double [NDIMS]){x, y});
43}
44FLXY(i, j) = vel * flux;
src/interface/update/flxy.c
56const double lvof = VOF(i, jj, k);
57if(lvof < vofmin || 1. - vofmin < lvof){
58  FLXY(i, j, k) = vel * lvof;
59  continue;
60}
61double flux = 0.;
62for(int kk = 0; kk < NGAUSS; kk++){
63  for(int ii = 0; ii < NGAUSS; ii++){
64    const double w = gauss_ws[ii] * gauss_ws[kk];
65    const double x = gauss_ps[ii];
66    const double z = gauss_ps[kk];
67    flux += w * indicator(NORMAL(i, jj, k), (const double [NDIMS]){x, y, z});
68  }
69}
70FLXY(i, j, k) = vel * flux;
src/interface/update/flxz.c
32const double lvof = VOF(i, j, kk);
33if(lvof < vofmin || 1. - vofmin < lvof){
34  FLXZ(i, j, k) = vel * lvof;
35  continue;
36}
37double flux = 0.;
38for(int jj = 0; jj < NGAUSS; jj++){
39  for(int ii = 0; ii < NGAUSS; ii++){
40    const double w = gauss_ws[ii] * gauss_ws[jj];
41    const double x = gauss_ps[ii];
42    const double y = gauss_ps[jj];
43    flux += w * indicator(NORMAL(i, j, kk), (const double [NDIMS]){x, y, z});
44  }
45}
46FLXZ(i, j, k) = vel * flux;

Note

For very small (\(\approx 0\)) or very large (\(\approx 1\)) volume fractions, i.e. single-phase regions,

\[\hat{H} \approx \phi = const.\]

gives a good approximation and thus is directly used.

All the fluxes evaluated at the cell faces are stored in FLXX and FLXY, which are used to update \(\phi\):

src/interface/update/main.c
74#if NDIMS == 2
75  for(int j = 1; j <= jsize; j++){
76    for(int i = 1; i <= isize; i++){
77      const double dx = DXF(i  );
78      SRC(i, j) += 1. / dx * (
79          + FLXX(i  , j  )
80          - FLXX(i+1, j  )
81      );
82    }
83  }
84#else
85  for(int k = 1; k <= ksize; k++){
86    for(int j = 1; j <= jsize; j++){
87      for(int i = 1; i <= isize; i++){
88        const double dx = DXF(i  );
89        SRC(i, j, k) += 1. / dx * (
90            + FLXX(i  , j  , k  )
91            - FLXX(i+1, j  , k  )
92        );
93      }
94    }
95  }
96#endif
src/interface/update/main.c
 98#if NDIMS == 2
 99  for(int j = 1; j <= jsize; j++){
100    for(int i = 1; i <= isize; i++){
101      SRC(i, j) += 1. / dy * (
102          + FLXY(i  , j  )
103          - FLXY(i  , j+1)
104      );
105    }
106  }
107#else
108  for(int k = 1; k <= ksize; k++){
109    for(int j = 1; j <= jsize; j++){
110      for(int i = 1; i <= isize; i++){
111        SRC(i, j, k) += 1. / dy * (
112            + FLXY(i  , j  , k  )
113            - FLXY(i  , j+1, k  )
114        );
115      }
116    }
117  }
118#endif
src/interface/update/main.c
121for(int k = 1; k <= ksize; k++){
122  for(int j = 1; j <= jsize; j++){
123    for(int i = 1; i <= isize; i++){
124      SRC(i, j, k) += 1. / dz * (
125          + FLXZ(i  , j  , k  )
126          - FLXZ(i  , j  , k+1)
127      );
128    }
129  }
130}

A conventional three-step Runge-Kutta scheme is adopted to integrate the equation in time.

src/interface/update/main.c
149    const double coef = rkcoefs[rkstep][rk_a];
150    const double * restrict src = interface->src[rk_a].data;
151#if NDIMS == 2
152    for(int j = 1; j <= jsize; j++){
153      for(int i = 1; i <= isize; i++){
154        VOF(i, j) += dt * coef * SRC(i, j);
155      }
156    }
157#else
158    for(int k = 1; k <= ksize; k++){
159      for(int j = 1; j <= jsize; j++){
160        for(int i = 1; i <= isize; i++){
161          VOF(i, j, k) += dt * coef * SRC(i, j, k);
162        }
163      }
164    }
165#endif
src/interface/update/main.c
168  if(0 != rkstep){
169    const double coef = rkcoefs[rkstep][rk_b];
170    const double * restrict src = interface->src[rk_b].data;
171#if NDIMS == 2
172    for(int j = 1; j <= jsize; j++){
173      for(int i = 1; i <= isize; i++){
174        VOF(i, j) += dt * coef * SRC(i, j);
175      }
176    }
177#else
178    for(int k = 1; k <= ksize; k++){
179      for(int j = 1; j <= jsize; j++){
180        for(int i = 1; i <= isize; i++){
181          VOF(i, j, k) += dt * coef * SRC(i, j, k);
182        }
183      }
184    }
185#endif
186  }