# sandbox/lchirco/signature.h

We want to detect thin sheets and ligaments in 2 phase flows. First, we compute the quadratic form f (x, x) = x_i x_j T_{ij}, where T_{ij} are the quadratic moments, by integrating over a shell of arbitrary thickness and radius. Finally, we compute the signature s of the quadratic form.

int find_moments_level(scalar f, double length, double target_delta){

int level = depth();
bool found = false;

while (level >= 0 && !found){
double num = 1<<level;
if (length/num > target_delta) found = true;
level--;
}

return level;
}

To compute the eigenvalues used in the signature method in 3D we use the GNU scientific library.

#include <gsl/gsl_math.h>
#include <gsl/gsl_eigen.h>

void eigen ( double data[], double tau[], double dim){

gsl_matrix_view m
= gsl_matrix_view_array (data, dim, dim);

gsl_vector_complex *eval = gsl_vector_complex_alloc (dim);
gsl_matrix_complex *evec = gsl_matrix_complex_alloc (dim, dim);

gsl_eigen_nonsymmv_workspace * w =
gsl_eigen_nonsymmv_alloc (dim);

gsl_eigen_nonsymmv (&m.matrix, eval, evec, w);

gsl_eigen_nonsymmv_free (w);

gsl_eigen_nonsymmv_sort (eval, evec,
GSL_EIGEN_SORT_ABS_DESC);

for (int i = 0; i < dim; i++)
{
gsl_complex eval_i
= gsl_vector_complex_get (eval, i);
gsl_vector_complex_view evec_i
= gsl_matrix_complex_column (evec, i);

//         printf ("eigenvalue = %g + %gi\n", GSL_REAL(eval_i), GSL_IMAG(eval_i));
tau[i]=GSL_REAL(eval_i);
}

gsl_vector_complex_free(eval);
gsl_matrix_complex_free(evec);

}

This function is used to compute the signature at a given level of refinement lev. It can be found using the function find_moments_level.

void compute_signature_neigh_level(scalar f, scalar phii, scalar s, int lev){

scalar int_xx[], int_xy[], int_yy[];
#if dimension == 3
scalar int_xz[], int_yz[], int_zz[];
#endif
vector Tij[], sign[];

foreach_level(lev){
int_xx[] = 0;
int_xy[] = 0;
int_yy[] = 0;
#if dimension == 3
int_xz[] = 0;
int_yz[] = 0;
int_zz[] = 0;
#endif
}

boundary ({phii});

We integrate over the cells of the 5x5 stencil and subtract the values of those in the 3x3 stencil. We can say the thickness of the shell is equal to the grid-size and the radius is twice the grid-size

  double mom_xx, mom_yy, mom_xy;
#if dimension == 3
double mom_xz, mom_yz, mom_zz;
#endif

foreach_level(lev){

mom_xx = mom_xy = mom_yy = 0.;
#if dimension == 3
mom_xz = mom_yz = mom_zz = 0.;
#endif

//     if (phii[] > -0.99){
double xp = x;
double yp = y;
#if dimension == 3
double zp = z;
#endif

foreach_neighbor() {
mom_xx += sq(x - xp)*phii[];
mom_yy += sq(y - yp)*phii[];
mom_xy += (x - xp)*(y - yp)*phii[];
#if dimension == 3
mom_xz += (x - xp)*(z - zp)*phii[];
mom_yz += (y - yp)*(z - zp)*phii[];
mom_zz += sq(z - zp)*phii[];
#endif
}

foreach_neighbor(1) {
mom_xx -= sq(x - xp)*phii[];
mom_yy -= sq(y - yp)*phii[];
mom_xy -= (x - xp)*(y - yp)*phii[];
#if dimension == 3
mom_xz -= (x - xp)*(z - zp)*phii[];
mom_yz -= (y - yp)*(z - zp)*phii[];
mom_zz -= sq(z - zp)*phii[];
#endif
}

int_xx[] = mom_xx;
int_yy[] = mom_yy;
int_xy[] = mom_xy;
#if dimension == 3
int_xz[] = mom_xz;
int_yz[] = mom_yz;
int_zz[] = mom_zz;
#endif
}

foreach_level(lev)
foreach_dimension()
Tij.x[] = 0;

We now compute the eigenvalues of the quadratic form. For a 2x2 symmetric matrix the eigenvalues can be found analytically. In the 3D case we call the function eigen that uses the GSL library.

#if dimension == 2
double Txx, Tyy, Txy;
double tau[2]={0.};

foreach_level(lev){
Txx = int_xx[]/sq(Delta);
Tyy = int_yy[]/sq(Delta);
Txy = int_xy[]/sq(Delta);
double  data[] = { Txx, Txy,
Txy, Tyy };

eigen(data, tau, 2);

Tij.x[] = tau[0];
Tij.y[] = tau[1];

}
#else            //dimension == 3
double Txx, Tyy, Tzz, Txy, Txz, Tyz=0.;
double tau[3]={0.};

foreach_level(lev){
Txx = int_xx[]/sq(Delta);
Tyy = int_yy[]/sq(Delta);
Txy = int_xy[]/sq(Delta);
Tzz = int_zz[]/sq(Delta);
Txz = int_xz[]/sq(Delta);
Tyz = int_yz[]/sq(Delta);
double  data[] = { Txx, Txy, Txz,
Txy, Tyy, Tyz,
Txz, Tyz, Tzz };
eigen(data, tau, 3);

Tij.x[] = tau[0];
Tij.y[] = tau[1];
Tij.z[] = tau[2];
}
#endif

The signature of the quadratic form are the signs of the eigenvalues.

  foreach_level(lev){
foreach_dimension(){
sign.x[] = fabs(Tij.x[])/(Tij.x[] + 1.e-10);
if (fabs(Tij.x[]) < 10.) sign.x[] = 0;
}
}

We identify thin ligaments by looking at the signature. In 2D we can have the signatures:

• (+,+) bulk of phase
• (+,-) thin ligament
• (+,0) or (-,0) interface
• (-,-) outside of phase
#if dimension ==2
foreach_level(lev){
if (sign.x[]*sign.y[] > 0.999 && sign.x[] > 0.999) s[] = 2;   //bulk of phase
if (sign.x[]*sign.y[] > 0.999 && sign.x[] < -0.999) s[] = -1; //outside of phase
if (fabs(sign.x[]*sign.y[]) < 0.01) s[] = 0;                 //interface
if (sign.x[]*sign.y[] < -0.999) s[] = 1;                     //ligament
}
#else //dimension == 3
foreach_level(lev){
s[] = 1; //thin
if (sign.x[] == 0. || sign.y[] == 0. || sign.z[] == 0.) s[] = 0;   //interface
if (sign.x[] > 0.999 && sign.y[] > 0.999 && sign.z[] > 0.999) s[] = 2;   //bulk of phase
if (sign.x[] < -0.999 && sign.y[] < -0.999 && sign.z[] < -0.999) s[] = -1; //outside of phase

}
#endif
}

This function is used to perforate thin sheets. The scalar s must be filled using the compute_signature_neigh_level function.

void change_topology (scalar f, scalar s, scalar M, int lev, const int max_change, bool large){

double f_avg[max_change];  // average f in the neighbor
int num = 0.; int i_change = 0;
srand(time(NULL));   // Initialization of random seed.
int r;

Cache to_perf = {0};
#ifndef SQUARES
int i;
scalar hole[];
double xc[max_change], yc[max_change], zc[max_change];
#endif
foreach_level(lev){
M[] = 0.;
r = rand() % 50;

if (s[] > 0.99 && s[] < 1.01 && r==0 && i_change < max_change){
f_avg[i_change] = 0.;
num = 0;
#ifndef SQUARES
xc[i_change] = x; yc[i_change] = y; zc[i_change] = z;
#endif
#ifdef SQUARES
if (large == false){
foreach_neighbor(1){       //compute average f
f_avg[i_change] += f[];
cache_append (&to_perf, point, i_change);
num += 1;
}
}
else{
foreach_neighbor(){       //compute average f
f_avg[i_change] += f[];
cache_append (&to_perf, point, i_change);
num += 1;
}
}

f_avg[i_change]/=num;
cache_shrink (&to_perf);
#endif
i_change++;
}
}

#ifndef SQUARES
printf("Holes are spheres \n");
vertex scalar phi[];
double Ddelta = 0.001/64.;
double R = 1.2*2.5*Ddelta;
foreach_vertex() {
phi[] = HUGE;
for (i = 0; i < max_change; i++){
phi[] = intersection (phi[], (sq(x - xc[i])/sq(1)+sq(y -yc[i])/sq(1)+sq(z -zc[i])/sq(1) - sq(R)));
//       phi[] = intersection (phi[], (/*sq(x - xc[i]) +*/ (sq(y - yc[i]) + sq(z - zc[i]) - sq(R))));
}
}
boundary ({phi});
fractions (phi, hole);
foreach(){
if (f[] > 1e-4 && hole[] < 1 - 1.e-4){
f[] = hole[];
}
}
boundary({f});
#endif

#ifdef SQUARES
printf("Holes are squares \n");
foreach_cache(to_perf){
s[] = -2.;
M[] = f[] - (1 - f_avg[_flags]);
if (f_avg[_flags] < 0.3) {
s[] = -3.;
f[] = 1.;
}
else
f[] = 0.;
}
#endif

for (int ilev = lev; ilev < depth(); ilev++)
foreach_level(ilev){
s.prolongation = M.prolongation = f.prolongation = refine_injection;
if(is_refined(cell) && s[] == -2){
s.prolongation (point, s);
M.prolongation (point, M);
f.prolongation (point, f);
}
}
free(to_perf.p);
boundary(all);
}