/** 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< 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 #include 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); }