Curvature of an interface

The curvature field is defined only in interfacial cells. In all the other cells it takes the value nodata.

On trees, we need to redefine the restriction function to take this into account i.e. the curvature of the parent cell is the average of the curvatures in the interfacial child cells.

``````#if TREE
static void curvature_restriction (Point point, scalar kappa)
{
double k = 0., s = 0.;
foreach_child()
if (κ[] != nodata)
k += κ[], s++;
κ[] = s ? k/s : nodata;
}``````

The prolongation function performs a similar averaging, but using the same stencil as that used for bilinear interpolation, so that the symmetries of the volume fraction field and curvature field are preserved.

``````static void curvature_prolongation (Point point, scalar kappa)
{
foreach_child() {
double sk = 0., s = 0.;
for (int i = 0; i <= 1; i++)
#if dimension > 1
for (int j = 0; j <= 1; j++)
#endif
#if dimension > 2
for (int k = 0; k <= 1; k++)
#endif
if (coarse(κ,child.x*i,child.y*j,child.z*k) != nodata)
sk += coarse(κ,child.x*i,child.y*j,child.z*k), s++;
κ[] = s ? sk/s : nodata;
}
}
#endif // TREE``````

Height-function curvature

To compute the curvature, we estimate the derivatives of the height functions in a given direction (x, y or z). We first check that all the heights are defined and that their orientations are the same. We then compute the curvature as $\kappa =\frac{{h}_{xx}}{\left(1+{h}_{x}^{2}{\right)}^{3/2}}$ in two dimensions, or $\kappa =\frac{{h}_{xx}\left(1+{h}_{y}^{2}\right)+{h}_{yy}\left(1+{h}_{x}^{2}\right)-2{h}_{xy}{h}_{x}{h}_{y}}{\left(1+{h}_{x}^{2}+{h}_{y}^{2}{\right)}^{3/2}}$ in three dimensions.

``````#include "heights.h"

#if dimension == 2
foreach_dimension()
static double kappa_y (Point point, vector h) {
int ori = orientation(h.y[]);
for (int i = -1; i <= 1; i++)
if (h.y[i] == nodata || orientation(h.y[i]) != ori)
return nodata;
double hx = (h.y[1] - h.y[-1])/2.;
double hxx = (h.y[1] + h.y[-1] - 2.*h.y[])/Δ;
return hxx/pow(1. + sq(hx), 3/2.);
}
#else // dimension == 3
foreach_dimension()
static double kappa_z (Point point, vector h) {
int ori = orientation(h.z[]);
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (h.z[i,j] == nodata || orientation(h.z[i,j]) != ori)
return nodata;
double hx = (h.z[1] - h.z[-1])/2.;
double hy = (h.z[0,1] - h.z[0,-1])/2.;
double hxx = (h.z[1] + h.z[-1] - 2.*h.z[])/Δ;
double hyy = (h.z[0,1] + h.z[0,-1] - 2.*h.z[])/Δ;
double hxy = (h.z[1,1] + h.z[-1,-1] - h.z[1,-1] - h.z[-1,1])/(4.*Δ);
return (hxx*(1. + sq(hy)) + hyy*(1. + sq(hx)) - 2.*hxy*hx*hy)/
pow(1. + sq(hx) + sq(hy), 3/2.);
}
#endif``````

We now need to choose one of the $x$, $y$ or $z$ height functions to compute the curvature. This is done by the function below which returns the HF curvature given a volume fraction field c and a height function field h.

``````static double height_curvature (Point point, scalar c, vector h)
{``````

We first define pairs of normal coordinates n (computed by simple differencing of c) and corresponding HF curvature function kappa (defined above).

``````  typedef struct {
double n;
double (* κ) (Point, vector);
} NormKappa;
struct { NormKappa x, y, z; } n;
foreach_dimension()
n.x.n = c[1] - c[-1], n.x.κ = kappa_x;
double (* kappaf) (Point, vector) = NULL; NOT_UNUSED (kappaf);``````

We sort these pairs in decreasing order of $\mid n\mid$.

``````  if (fabs(n.x.n) < fabs(n.y.n))
swap (NormKappa, n.x, n.y);
#if dimension == 3
if (fabs(n.x.n) < fabs(n.z.n))
swap (NormKappa, n.x, n.z);
if (fabs(n.y.n) < fabs(n.z.n))
swap (NormKappa, n.y, n.z);
#endif``````

We try each curvature function in turn.

``````  double κ = nodata;
foreach_dimension()
if (κ == nodata) {
κ = n.x.κ (point, h);
if (κ != nodata) {
kappaf = n.x.κ;
if (n.x.n < 0.)
κ = - κ;
}
}

if (κ != nodata) {``````

We limit the maximum curvature to $1/\Delta$.

``````    if (fabs(κ) > 1./Δ)
κ = sign(κ)/Δ;``````

We add the axisymmetric curvature if necessary.

``````#if AXI
double nr, r = y, hx;
if (kappaf == kappa_x) {
hx = (height(h.x[0,1]) - height(h.x[0,-1]))/2.;
nr = hx*(orientation(h.x[]) ? 1 : -1);
}
else {
r += height(h.y[])*Δ;
hx = (height(h.y[1,0]) - height(h.y[-1,0]))/2.;
nr = orientation(h.y[]) ? -1 : 1;
}
/* limit the minimum radius to half the grid size */
κ += nr/max (sqrt(1. + sq(hx))*r, Δ/2.);
#endif
}

return κ;
}``````

Parabolic fit of “mixed” height-functions

When the standard height function curvature calculation is not possible (for example because not enough heights are available in any given direction), one can try to combine all the available heights (thus using “mixed” directions) to obtain points on the interface. These point locations can then be fitted with a parabola (using least-mean-square optimisation) and the resulting curvature can be computed. The fitting functions are defined in the file included below.

``````#include "parabola.h"
``````

Given n (interface) point coordinates, this function returns the number of “independent” points i.e. points which are more than half-a-cell distant from all the other points.

``````static int independents (coord * p, int n)
{
if (n < 2)
return n;
int ni = 1;
for (int j = 1; j < n; j++) {
bool depends = false;
for (int i = 0; i < j && !depends; i++) {
double d2 = 0.;
foreach_dimension()
d2 += sq(p[i].x - p[j].x);
depends = (d2 < sq(0.5));
}
ni += !depends;
}
return ni;
}``````

Given a volume fraction field c and a height function field h, this function returns the “mixed heights” parabola-fitted curvature (or nodata if the curvature cannot be computed).

``````static double height_curvature_fit (Point point, scalar c, vector h)
{``````

The coordinates of the interface points and the number of interface points.

``````  coord ip[dimension == 2 ? 6 : 27];
int n = 0;``````

We collect the points along all directions.

``  foreach_dimension() {``

We don’t want to mix heights with different orientations. We first find the “dominant” orientation ori.

``````    int n1 = 0, n2 = 0;
#if dimension == 2
for (int i = -1; i <= 1; i++)
if (h.y[i] != nodata) {
if (orientation(h.y[i])) n1++; else n2++;
}
#else // dimension == 3
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (h.z[i,j] != nodata) {
if (orientation(h.z[i,j])) n1++; else n2++;
}
#endif
int ori = (n1 > n2);``````

We look for height-functions with the dominant orientation and store the corresponding interface coordinates (relative to the center of the cell and normalised by the cell size).

``````#if dimension == 2
for (int i = -1; i <= 1; i++)
if (h.y[i] != nodata && orientation(h.y[i]) == ori)
ip[n].x = i, ip[n++].y = height(h.y[i]);
#else // dimension == 3
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (h.z[i,j] != nodata && orientation(h.z[i,j]) == ori)
ip[n].x = i, ip[n].y = j, ip[n++].z = height(h.z[i,j]);
#endif
}``````

If we don’t have enough independent points, we cannot do the parabolic fit.

``````  if (independents (ip, n) < (dimension == 2 ? 3 : 9))
return nodata;``````

We recover the interface normal and the centroid of the interface fragment and initialize the parabolic fit.

``````  coord m = mycs (point, c), fc;
double α = plane_alpha (c[], m);
double area = plane_area_center (m, α, &fc);
ParabolaFit fit;
parabola_fit_init (&fit, fc, m);
#if dimension == 2
NOT_UNUSED(area);
#else // dimension == 3
#endif``````

We add the collected interface positions and compute the curvature.

``````  for (int i = 0; i < n; i++)
parabola_fit_solve (&fit);
double κ = parabola_fit_curvature (&fit, 2., NULL)/Δ;
#if AXI
parabola_fit_axi_curvature (&fit, y + fc.y*Δ, Δ, &κ, NULL);
#endif
return κ;
}``````

Parabolic fit of centroids

If all else fails, we try a parabolic fit of interface centroids.

``````static double centroids_curvature_fit (Point point, scalar c)
{``````

We recover the interface normal and the centroid of the interface fragment and initialize the parabolic fit.

``````  coord m = mycs (point, c), fc;
double α = plane_alpha (c[], m);
plane_area_center (m, α, &fc);
ParabolaFit fit;
parabola_fit_init (&fit, fc, m);``````

We add the interface centroids in a ${3}^{d}$ neighborhood and compute the curvature.

``````  coord r = {x,y,z};
foreach_neighbor(1)
if (c[] > 0. && c[] < 1.) {
coord m = mycs (point, c), fc;
double α = plane_alpha (c[], m);
double area = plane_area_center (m, α, &fc);
coord rn = {x,y,z};
foreach_dimension()
fc.x += (rn.x - r.x)/Δ;
}
parabola_fit_solve (&fit);
double κ = parabola_fit_curvature (&fit, 2., NULL)/Δ;
#if AXI
parabola_fit_axi_curvature (&fit, y + fc.y*Δ, Δ, &κ, NULL);
#endif
return κ;
}``````

General curvature computation

We first need to define “interfacial cells” i.e. cells which contain an interface. A simple test would just be that the volume fraction is neither zero nor one. As usual things are more complicated because of round-off errors. They can cause the interface to be exactly aligned with cell boundaries, so that cells on either side of this interface have fractions exactly equal to zero or one. The function below takes this into account.

``````static inline bool interfacial (Point point, scalar c)
{
if (c[] >= 1.) {
for (int i = -1; i <= 1; i += 2)
foreach_dimension()
if (c[i] <= 0.)
return true;
}
else if (c[] <= 0.) {
for (int i = -1; i <= 1; i += 2)
foreach_dimension()
if (c[i] >= 1.)
return true;
}
else // c[] > 0. && c[] < 1.
return true;
return false;
}``````

The function below computes the mean curvature kappa of the interface defined by the volume fraction c. It uses a combination of the methods above: statistics on the number of curvatures computed which each method is returned in a cstats data structure.

If sigma is different from zero the curvature is multiplied by sigma.

If add is true, the curvature (optionally multiplied by sigma) is added to field kappa.

``````typedef struct {
int h; // number of standard HF curvatures
int f; // number of parabolic fit HF curvatures
int a; // number of averaged curvatures
int c; // number of centroids fit curvatures
} cstats;

struct Curvature {
scalar c, κ;
double σ;
};

trace
cstats curvature (struct Curvature p)
{
scalar c = p.c, κ = p.κ;
double σ = p.σ ? p.σ : 1.;
cstats s = {0,0,0,0};
vector h[];
heights (c, h);``````

On trees we set the prolongation and restriction functions for the curvature.

``````#if TREE
κ.refine = κ.prolongation = curvature_prolongation;
κ.restriction = curvature_restriction;
#endif``````

We first compute a temporary curvature k: a “clone” of $\kappa$.

``````  scalar k[];
scalar_clone (k, κ);

foreach() {``````

If we are not in an interfacial cell, we set $\kappa$ to nodata.

``````    if (!interfacial (point, c))
k[] = nodata;``````

Otherwise we try the standard HF curvature calculation first, and the “mixed heights” HF curvature second.

``````    else if ((k[] = height_curvature (point, c, h)) != nodata)
s.h++;
else if ((k[] = height_curvature_fit (point, c, h)) != nodata)
s.f++;
}
boundary ({k});

foreach() {``````

We then construct the final curvature field using either the computed temporary curvature…

``````    double kf;
if (k[] < nodata)
kf = k[];
else if (interfacial (point, c)) {``````

…or the average of the curvatures in the ${3}^{d}$ neighborhood of interfacial cells.

``````      double sk = 0., a = 0.;
foreach_neighbor(1)
if (k[] < nodata)
sk += k[], a++;
if (a > 0.)
kf = σ*sk/a, s.a++;
else``````

Empty neighborhood: we try centroids as a last resort.

``````	kf = σ*centroids_curvature_fit (point, c), s.c++;
}
else
kf = nodata;``````

``````    if (kf == nodata)
κ[] = nodata;
κ[] += σ*kf;
else
κ[] = σ*kf;
}
boundary ({κ});

return s;
}``````

Position of an interface

This is similar to curvature but this time for the position of the interface, defined as $pos=\mathbf{\text{G}}\cdot \left(\mathbf{\text{x}}-\mathbf{\text{Z}}\right)$ with $\mathbf{\text{G}}$ and $\mathbf{\text{Z}}$ two vectors and $\mathbf{\text{x}}$ the coordinates of the interface.

This is defined only in interfacial cells. In all the other cells it takes the value nodata.

We first need a function to compute the position $\mathbf{\text{x}}$ of an interface. For accuracy, we first try to use height functions.

``````foreach_dimension()
static double pos_x (Point point, vector h, coord * G, coord * Z) {
if (h.x[] == nodata)
return nodata;
coord o = {x, y, z};
o.x += height(h.x[])*Δ;
double pos = 0.;
foreach_dimension()
pos += (o.x - Z->x)*G->x;
return pos;
}``````

We now need to choose one of the $x$, $y$ or $z$ height functions to compute the position. This is done by the function below which returns the HF position given a volume fraction field f, a height function field h and vectors G and Z.

``````static double height_position (Point point, scalar f, vector h,
coord * G, coord * Z)
{``````

We first define pairs of normal coordinates n (computed by simple differencing of f) and corresponding HF position function pos (defined above).

``````  typedef struct {
double n;
double (* pos) (Point, vector, coord *, coord *);
} NormPos;
struct { NormPos x, y, z; } n;
foreach_dimension()
n.x.n = f[1] - f[-1], n.x.pos = pos_x;``````

We sort these pairs in decreasing order of $\mid n\mid$.

``````  if (fabs(n.x.n) < fabs(n.y.n))
swap (NormPos, n.x, n.y);
#if dimension == 3
if (fabs(n.x.n) < fabs(n.z.n))
swap (NormPos, n.x, n.z);
if (fabs(n.y.n) < fabs(n.z.n))
swap (NormPos, n.y, n.z);
#endif``````

We try each position function in turn.

``````  double pos = nodata;
foreach_dimension()
if (pos == nodata)
pos = n.x.pos (point, h, G, Z);

return pos;
}``````

The position() function fills field pos with $\mathbf{\text{G}}\cdot \left(\mathbf{\text{x}}-\mathbf{\text{Z}}\right)$ with $\mathbf{\text{x}}$ the position of the interface defined by $f$.

``````struct Position {
scalar f, pos;
coord G, Z;
};

void position (struct Position p)
{
scalar f = p.f, pos = p.pos;
coord * G = &p.G, * Z = &p.Z;``````

On trees we set the prolongation and restriction functions for the position.

``````#if TREE
pos.refine = pos.prolongation = curvature_prolongation;
pos.restriction = curvature_restriction;
#endif

vector h[];
heights (f, h);
foreach() {
if (interfacial (point, f)) {
double hp = height_position (point, f, h, G, Z);
if (hp == nodata) {``````

If the height function is not defined, we use the centroid of the reconstructed VOF interface.

``````	coord n = mycs (point, f), o = {x,y,z}, c;
double α = plane_alpha (f[], n);
plane_area_center (n, α, &c);
hp = 0.;
foreach_dimension()
hp += (o.x + Δ*c.x - Z->x)*G->x;
}