/* \brief This is library to generate a mobility matrix based on Peskin kernel functions and staggered grid solver \date $April 15, 2015$ \author Bakytzhan Kallemov, Courant Institute, NYU This code accompanies the paper: "An immersed boundary method for rigid bodies", B. Kallemov and A. Pal Singh Bhalla and B. E. Griffith and A. Donev, submitted to Comput. Methods Appl. Mech. Engrg., 2015 where the notation and ideas are explained. These fits are made available to other users of the IB method -- let us know if they are useful or if you find problems. We expect to release the complete code as part of the IBAMR library in the near future. For steady Stokes flow (beta=infinity) fitting coefficients are available for the 3 (Roma+Peskin), 4 (Peskin) and 6 (Bao+Peskin) point kernels WARNING: For unsteady Stokes, i.e., finite viscous CFL number beta, fitting is available *only* for IB6 kernel (Bao+Peskin) Note that instead of taking beta as an argument these functions take viscosity, grid spacing and time step. This is because there are some cancelations of zeros and infinities for beta=0 or infinity that are best avoided numerically. Also note that the viscosity argument to these functions is the physical viscosity multiplied by a constant kappa, where kappa depends on the temporal discretization used (e.g., kappa=1 for Backward Euler, kappa=1/2 for Crank-Nicolson, and kappa may change from stage to stage for RK integrators) Dimension: NDIM identifier must be defined before any call of the functions The following function are available getHydroRadius - Returns a hydrodynamic radius of a single "blob" for the corresponding Peskin kernel (in units of grid spacing h) input parameters IBKernelName name of the kernel {"IB3","IB4","IB6"} getEmpiricalMobilityComponents - Generates empirical f(r) and g(r) functions values for a the NDIM X NDIM block of Mobility Matrix for two markers input parameters IBKernelName name of the kernel {"IB3","IB4","IB6"} MU fluid viscosity rho fluid density Dt time step used in the fluid solver r distance between markers DX grid spacing resetAllConstants whether all constant must be reset if beta(viscous CFL number) is changed (otherwise will use the previous beta for fitting formula) L_domain length of the domain (used only only for 2 dimensional steady stokes) F_MobilityValue a pointer to return f(r) value G_Mobilityvalue a pointer to return g(r) value getEmpiricalMobilityMatrix - Generates an empirical Mobility Matrix input parameters IBKernelName name of the kernel {"IB3","IB4","IB6"} MU fluid viscosity rho fluid density Dt time step used in the fluid solver DX grid spacing X a pointer to array of size NDIM*N that containts coordinates of markers N numbers of markers resetAllConstants whether all constant must be reset if beta(viscous CFL number) is changed (otherwise will use the previous beta for fitting formula) PERIODIC_CORRECTION periodic corrections for f(r) function, set to 0.0 for all other cases or if it's not known. L_domain length of the domain (used only only for 2 dimensional steady stokes) MM a pointer to return a moblity matrix stored in column-major format. The allocated space size for MM must be non less than sizeof(double)*(NDIM*N)^2 getRPYMobilityMatrix - Generates an Mobility Matrix based on Ronte-Prager-Yamakawa (RPY) approximation input parameters IBKernelName name of the kernel {"IB3","IB4","IB6"} MU fluid viscosity DX grid spacing X a pointer to array of size NDIM*N that containts coordinates of markers N numbers of markers PERIODIC_CORRECTION periodic corrections for f(r) function, set to 0.0 for all other cases or if it's not known. MM a pointer to return a moblity matrix stored in column-major format. The allocated space size for MM must be non less than sizeof(double)*(NDIM*N)^2 */ #ifdef __cplusplus extern "C"{ #endif #include #include #include #include #ifndef KRON #define KRON(i, j) ((i==j)?1:0) //Kronecker symbol #endif static const double MOB_FIT_FG_TOL = 1.0e-5;//min distance between blobs to apply empirical fitting static const double ZERO_TOL = 1.0e-10;//tolerance for zero value static double MOB_FIT_FACTOR; //constant for normalization static int reUse=0; //flag for reuse data typedef enum _KERNEL_TYPES{IB3, IB4, IB6, UNKNOWN_TYPE = -1}KERNEL_TYPES; //Hydro radii for each kernel IB3,IB4,IB6 const double MOB_FIT_HydroRadius[] = {0.91, 1.255, 1.4685}; static double HRad; //current hydrodynamic radius //coefficients of empirical fit for steady stokes static double F_s[7]; static double G_s[4]; //coefficients of empirical fit for finite beta static double F_b[10]; static double G_b[6]; //coefficients of empirical fit for f(0) static double Z_b[5]; #if (NDIM==2) static double Z_s[3]; #endif static KERNEL_TYPES GetKernelType( const char* IBKernelName) { if (!strcmp(IBKernelName,"IB_3")) return IB3; if (!strcmp(IBKernelName,"IB_4")) return IB4; if (!strcmp(IBKernelName,"IB_6")) return IB6; fprintf (stderr, "MobilityFunctions: Unknown interpolation kernel type. Available options are IB_3, IB_4, IB_6. \n"); exit (EXIT_FAILURE); } /*! * returns Hydrodynamic radius value */ double getHydroRadius( const char* IBKernelName) { KERNEL_TYPES CurrentKernelType=GetKernelType(IBKernelName); return MOB_FIT_HydroRadius[CurrentKernelType]; } /*! * returns squared norm of the vector */ static double get_sqnorm( const double *a_vec) { #if (NDIM==3) return a_vec[0]*a_vec[0]+a_vec[1]*a_vec[1]+a_vec[2]*a_vec[2]; #elif(NDIM==2) return a_vec[0]*a_vec[0]+a_vec[1]*a_vec[1]; #endif } /*! * returns a value based on linear interpolation of array */ static double InterpolateLinear( const double*Xin, const double*Yin, const int N, double X0) { if (fabs(X0-Xin[0])Xin[N-1]) return Yin[N-1]+(Yin[N-1]-Yin[N-2])/(Xin[N-1]-Xin[N-2])*(X0-Xin[N-1]); //find nearest neighbour index int indx; for (indx=0;indx=ZERO_TOL) for (cnt=0;cnt<8;cnt++) F_b[cnt] = InterpolateLinear(M_betas, F_beta_coeff[cnt], num_cases, beta); else for (cnt=0;cnt<8;cnt++) F_b[cnt] = F0_coeff[cnt]; for (cnt=0;cnt<6;cnt++) G_b[cnt] = InterpolateLinear(M_betas, G_beta_coeff[cnt], num_cases, beta); printf("beta is %g\n",beta); #endif return; }; /*! * initialization of all constants. */ static void InitializeAllConstants( const char* IBKernelName, const double MU, const double rho, const double Dt, const double DX) { KERNEL_TYPES CurrentKernelType=GetKernelType(IBKernelName); double beta; //finding beta if (MU <= ZERO_TOL) beta=0.0; //invisid case else beta=MU*Dt/(rho*DX*DX); #if (NDIM==3) //******3D case if ((rho < ZERO_TOL)||(beta>=1000.1)) MOB_FIT_FACTOR = 1./MU/DX; //3D steady stokes else MOB_FIT_FACTOR = Dt/(rho*DX*DX*DX); #elif(NDIM==2) //*******2D case if ((rho10.1)) //2D steady stokes MOB_FIT_FACTOR = 1./MU; else MOB_FIT_FACTOR = Dt/(rho*DX*DX); #endif InterpolateConstants(CurrentKernelType, beta); } /* ! ** _F_R_INF */ static double _F_R_INF( const double rr, const double Dx, const double L_domain) { const double r=rr/Dx; #if (NDIM==3) const double factor=1.0/(8.0*M_PI); if (r<0.8) return factor/(3.0/4.0*HRad+F_s[6]*r*r); else return factor*(exp(-F_s[0]*r)*F_s[1]/HRad +(F_s[2]*r+F_s[3]*r*r*r)/(1.0 + F_s[4]*r*r + F_s[5]*pow(r,4))); #elif(NDIM==2) if (L_domain ZERO_TOL) g_value +=factor/beta*exp(-G_b[0]*r/sqrt(beta))*r/(G_b[1]+4.0*r); return g_value; } } else { return _G_R_INF(rr,Dx); } #endif } /*! * computes Empirical Mobility components f(r) and g(r) */ void getEmpiricalMobilityComponents( const char* IBKernelName, const double MU, const double rho, const double Dt, const double r, const double DX, const int resetAllConstants, const double L_domain, double *F_MobilityValue, double *G_Mobilityvalue) { //Reuse same static constants for efficiency if (resetAllConstants) reUse=0; if (!reUse) { InitializeAllConstants(IBKernelName, MU, rho, Dt,DX); reUse=1; } double beta; //finding beta if (MU <= ZERO_TOL) beta=0.0; //invisid case else beta=MU*Dt/(rho*DX*DX); if (rho < ZERO_TOL) { *F_MobilityValue = MOB_FIT_FACTOR*_F_R_INF(r,DX, L_domain); //steady stokes term for f(r) *G_Mobilityvalue = MOB_FIT_FACTOR*_G_R_INF(r,DX);//steady stokes term for g(r) } else { *F_MobilityValue = MOB_FIT_FACTOR*_F_R_BETA(r,DX,beta, L_domain); //time-dependent f(r) *G_Mobilityvalue = MOB_FIT_FACTOR*_G_R_BETA(r,DX,beta);//time-dependent g(r) } return; } /*! * returns Self-Mobility value */ double getEmpiricalSelfMobility( const char* IBKernelName, const double MU, const double rho, const double Dt, const double DX, const int resetAllConstants, const double L_domain) { if (resetAllConstants) reUse=0; if (!reUse) { InitializeAllConstants(IBKernelName, MU, rho, Dt, DX); reUse=1; } double beta; //finding beta if (MU <= ZERO_TOL) beta=0.0; //invisid case else beta=MU*Dt/(rho*DX*DX); if (rho < ZERO_TOL) return MOB_FIT_FACTOR*_F_R_INF(0.,DX, L_domain); else return MOB_FIT_FACTOR*_F_R_BETA(0., DX,beta, L_domain); } /*! * generates and returns Empirical Mobility Matrix */ void getEmpiricalMobilityMatrix( const char* IBKernelName, const double MU, const double rho, const double Dt, const double DX, const double *X, const int N, const int resetAllConstants, const double PERIODIC_CORRECTION, const double L_domain, double *MM) { int row,col; for (row = 0; row < N; row++) for (col = 0; col <=row; col++) { double r_vec[NDIM]; const int size = N*NDIM; int cdir; for (cdir = 0; cdir < NDIM; cdir++) { r_vec[cdir] = X[row*NDIM+cdir] - X[col*NDIM+cdir]; //r(i) - r(j) } const double rsq = get_sqnorm(r_vec); const double r = sqrt(rsq); double F_R, G_R; getEmpiricalMobilityComponents(IBKernelName, MU, rho, Dt, r, DX, resetAllConstants, L_domain, &F_R, &G_R); int idir, jdir; for (idir = 0; idir < NDIM; idir++) for (jdir = 0; jdir <=idir; jdir++) { const int index = (col*NDIM+jdir)*size +row*NDIM+idir; // column-major for LAPACK MM[index] = F_R*KRON(idir,jdir); if (row != col) { MM[index] += G_R*r_vec[idir]*r_vec[jdir]/rsq; MM[(row*NDIM+idir)*size +col*NDIM+jdir]=MM[index]; if (idir!=jdir) MM[(row*NDIM+jdir)*size +col*NDIM+idir]=MM[index]; } if (idir!=jdir) MM[(col*NDIM+idir)*size +row*NDIM+jdir]=MM[index]; } } return; } /*! * generates and returns RPY Mobility Matrix */ void getRPYMobilityMatrix( const char* IBKernelName, const double MU, const double DX, const double *X, const int N, const double PERIODIC_CORRECTION, double *MM) { HRad=getHydroRadius(IBKernelName)*DX; const double mu_tt = 1./(6.0*M_PI*MU*HRad); double r_vec[NDIM]; int size = N*NDIM; int row,col; for (row = 0; row < N; row++) for (col = 0; col <=row; col++) { if (row==col) { int idir,jdir; for (idir = 0; idir < NDIM; idir++) for (jdir = 0; jdir < NDIM; jdir++) { const int index = (col*NDIM+jdir)*size +row*NDIM+idir; // column-major for LAPACK MM[index] = (mu_tt- PERIODIC_CORRECTION)*KRON(idir,jdir); } } else { int cdir; for (cdir = 0; cdir < NDIM; cdir++) { r_vec[cdir] = X[row*NDIM+cdir] - X[col*NDIM+cdir]; //r(i) - r(j) } const double rsq = get_sqnorm(r_vec); const double r = sqrt(rsq); int idir,jdir; for (idir = 0; idir < NDIM; idir++) for (jdir = 0; jdir <=idir; jdir++) { const int index = (col*NDIM+jdir)*size +row*NDIM+idir; // column-major for LAPACK if (r<=2.0*HRad) { MM[index] = (mu_tt*(1-9.0/32.0*r/HRad)- PERIODIC_CORRECTION)*KRON(idir,jdir) +mu_tt*r_vec[idir]*r_vec[jdir]/rsq*3.0*r/32./HRad; MM[(row*NDIM+idir)*size +col*NDIM+jdir] = MM[index]; if (idir!=jdir) { MM[(col*NDIM+idir)*size +row*NDIM+jdir] = MM[index]; MM[(row*NDIM+jdir)*size +col*NDIM+idir] = MM[index]; } } else { double cube=HRad*HRad*HRad/r/r/r; MM[index] = (mu_tt*(3.0/4.0*HRad/r+1.0/2.0*cube) - PERIODIC_CORRECTION)*KRON(idir,jdir) + mu_tt*r_vec[idir]*r_vec[jdir]/rsq*(3.0/4.0*HRad/r-3.0/2.0*cube); MM[(row*NDIM+idir)*size +col*NDIM+jdir] = MM[index]; if (idir!=jdir) { MM[(col*NDIM+idir)*size +row*NDIM+jdir] = MM[index]; MM[(row*NDIM+jdir)*size +col*NDIM+idir] = MM[index]; } } }//jdir } }//column loop return; } #ifdef __cplusplus } #endif //extern "C"