Numerical method

Interface capturing

To begin with, I consider an indicator function \(H\), which works as a marker taking \(0\) or \(1\) when the infinitesimal control volume is occupied with the primary or the secondary liquids, respectively. By this definition, as an equation governing the evolution of \(H\), I have

\[\pder{H}{t} + \frac{u_j}{h_{\gcs^j}} \pder{H}{\gcs^j} = 0.\]

Since I assume the liquids are incompressible, this advection equation is equal to

\[\pder{H}{t} + \frac{1}{J} \pder{}{\gcs^j} \left( \frac{J}{h_{\gcs^j}} u_j H \right) = 0,\]

whose volume-integrated form leads to

\[J \pder{\phi}{t} + \int_{\partial V} \frac{J}{h_{\gcs^j}} u_j H n_j dS = 0,\]

where \(\phi\) is the rate of the primary phase volume inside the control volume, or known as volume-of-fluid. Note that I assume the coordinate systems do not change in time. To integrate this equation, I employ the THINC/QQ method (Xie and Xiao, J. Comput. Phys. (349), 2017), whose numerical treatment is extensively discussed in the other project.

Consistency

Note that the following idea is widely known in the phase-field community (e.g. Mirjalili and Mani, J. Comput. Phys. (426), 2021), which is slightly customised and applied here.

As discussed above, how the volume fraction is evolved is totally determined by the volume-of-fluid method. Since \(H\) and \(\rho\) are related by

\[\rho = 1 + \left( \hat{\rho} - 1 \right) H,\]

the evolution of the density (i.e. mass per unit volume) is also governed by the VOF method. Since the momentum is tightly linked to the flux of the mass, it is natural to apply this information (mass flux) to update the momentum field. To this end, I focus on the evolution of \(\phi\) again:

\[J \pder{\phi}{t} + \pder{}{\gcs^j} \left( \frac{J}{h_{\gcs^j}} H u_j \right) = 0.\]

As the flux of the indicator function \(H u_j\) is already known, the density flux \(\rho u_j\) is obtained by

\[\rho u_j = u_j + \left( \hat{\rho} - 1 \right) H u_j.\]
src/interface/mass_flux.c
31for(int j = 0; j <= jsize + 1; j++){
32  for(int i = 1; i <= isize + 1; i++){
33    FLUXX(i, j) = UX(i, j) + (denr - 1.) * FLUXX(i, j);
34  }
35}
src/interface/mass_flux.c
38for(int k = 0; k <= ksize + 1; k++){
39  for(int j = 0; j <= jsize + 1; j++){
40    for(int i = 1; i <= isize + 1; i++){
41      FLUXX(i, j, k) = UX(i, j, k) + (denr - 1.) * FLUXX(i, j, k);
42    }
43  }
44}
src/interface/mass_flux.c
64for(int j = 0; j <= jsize + 1; j++){
65  for(int i = 0; i <= isize + 1; i++){
66    FLUXY(i, j) = UY(i, j) + (denr - 1.) * FLUXY(i, j);
67  }
68}
src/interface/mass_flux.c
71for(int k = 0; k <= ksize + 1; k++){
72  for(int j = 0; j <= jsize + 1; j++){
73    for(int i = 0; i <= isize + 1; i++){
74      FLUXY(i, j, k) = UY(i, j, k) + (denr - 1.) * FLUXY(i, j, k);
75    }
76  }
77}
src/interface/mass_flux.c
 95for(int k = 0; k <= ksize + 1; k++){
 96  for(int j = 0; j <= jsize + 1; j++){
 97    for(int i = 0; i <= isize + 1; i++){
 98      FLUXZ(i, j, k) = UZ(i, j, k) + (denr - 1.) * FLUXZ(i, j, k);
 99    }
100  }
101}

Surface tension force

For simplicity, the continuum surface force model (Brackbill et al., J. Comput. Phys. (100), 1992) is adopted to model the interfacial tension:

\[f_i \approx \frac{2 \rho}{1 + \hat{\rho}} \sigma \kappa \frac{1}{h_{\gcs^i}} \pder{\phi}{\gcs^i}.\]
src/interface/force.c
39for(int j = 1; j <= jsize; j++){
40  for(int i = 2; i <= isize; i++){
41    const double den_x0 = (
42        + 0.5 * DEN(i-1, j  )
43        + 0.5 * DEN(i  , j  )
44    );
45    const double kappa = (
46        + 0.5 * CURV(i-1, j  )
47        + 0.5 * CURV(i  , j  )
48    );
49    const double delta = 1. / HXXF(i  ) * (
50        - VOF(i-1, j  )
51        + VOF(i  , j  )
52    );
53    IFRCX(i, j) = den_x0 * refdeninv * tension * kappa * delta;
54  }
55}
src/interface/force.c
58for(int k = 1; k <= ksize; k++){
59  for(int j = 1; j <= jsize; j++){
60    for(int i = 2; i <= isize; i++){
61      const double den_x0 = (
62          + 0.5 * DEN(i-1, j  , k  )
63          + 0.5 * DEN(i  , j  , k  )
64      );
65      const double kappa = (
66          + 0.5 * CURV(i-1, j  , k  )
67          + 0.5 * CURV(i  , j  , k  )
68      );
69      const double delta = 1. / HXXF(i  ) * (
70          - VOF(i-1, j  , k  )
71          + VOF(i  , j  , k  )
72      );
73      IFRCX(i, j, k) = den_x0 * refdeninv * tension * kappa * delta;
74    }
75  }
76}
src/interface/force.c
100for(int j = 1; j <= jsize; j++){
101  for(int i = 1; i <= isize; i++){
102    const double den_y0 = (
103        + 0.5 * DEN(i  , j-1)
104        + 0.5 * DEN(i  , j  )
105    );
106    const double kappa = (
107        + 0.5 * CURV(i  , j-1)
108        + 0.5 * CURV(i  , j  )
109    );
110    const double delta = 1. / hy * (
111        - VOF(i  , j-1)
112        + VOF(i  , j  )
113    );
114    IFRCY(i, j) = den_y0 * refdeninv * tension * kappa * delta;
115  }
116}
src/interface/force.c
119for(int k = 1; k <= ksize; k++){
120  for(int j = 1; j <= jsize; j++){
121    for(int i = 1; i <= isize; i++){
122      const double den_y0 = (
123          + 0.5 * DEN(i  , j-1, k  )
124          + 0.5 * DEN(i  , j  , k  )
125      );
126      const double kappa = (
127          + 0.5 * CURV(i  , j-1, k  )
128          + 0.5 * CURV(i  , j  , k  )
129      );
130      const double delta = 1. / hy * (
131          - VOF(i  , j-1, k  )
132          + VOF(i  , j  , k  )
133      );
134      IFRCY(i, j, k) = den_y0 * refdeninv * tension * kappa * delta;
135    }
136  }
137}
src/interface/force.c
159for(int k = 1; k <= ksize; k++){
160  for(int j = 1; j <= jsize; j++){
161    for(int i = 1; i <= isize; i++){
162      const double den_z0 = (
163          + 0.5 * DEN(i  , j  , k-1)
164          + 0.5 * DEN(i  , j  , k  )
165      );
166      const double kappa = (
167          + 0.5 * CURV(i  , j  , k-1)
168          + 0.5 * CURV(i  , j  , k  )
169      );
170      const double delta = 1. / hz * (
171          - VOF(i  , j  , k-1)
172          + VOF(i  , j  , k  )
173      );
174      IFRCZ(i, j, k) = den_z0 * refdeninv * tension * kappa * delta;
175    }
176  }
177}

The pre-factor is computed here:

src/interface/force.c
14static double compute_refdeninv(
15    const double denr
16){
17  return 2. / (1. + denr);
18}