sandbox/fpicella/src/driver-myembed-particles.h
/*
# Rigid particles in Stokes flow
using myembed (Arthur)
and particle (Antoon)
*/
#include "fpicella/src/periodic-shift-treatment.h"
#include "fpicella/src/compute_embed_color_force_torque_RBM.h"
/*
Required definitions
I set them like this if undefined elsewhere */
#ifndef NPARTICLES
#define NPARTICLES 1
#endif
static FILE *singleParticleFile[NPARTICLES] = {NULL}; // for the moment only one set of particles...?
/*
Buffer fields, required for definition of _global_ cs and fs fractions...
*/
scalar csLOCAL[];
face vector fsLOCAL[];
/*
Some practical shortcuts
*/
#if dimension == 2
#define circle(px,py,pr) (sq (PS(x,px)) + sq (PS(y,py)) - sq (pr))
#define PARTICLE circle(p().x,p().y,p().r)
#define COLOR (sq (PS(COORD.x,p().x)) + sq (PS(COORD.y,p().y)) - sq (p().r)) // more complex, hate foreach_dimension() :D
#else
#define sphere(px,py,pz,pr) (sq (PS(x,px)) + sq (PS(y,py)) + sq (PS(z,pz)) - sq (pr))
#define PARTICLE sphere(p().x,p().y,p().z,p().r)
#define COLOR (sq (PS(COORD.x,p().x)) + sq (PS(COORD.y,p().y)) + sq (PS(COORD.z,p().z)) - sq (p().r)) // more complex, hate foreach_dimension() :D
#endif
#define THETA atan2(PS(COORD.y,p().y),PS(COORD.x,p().x))
/*
Simple way to account for variable particle orientation...
*/
#define TS (THETA - p().theta.z)
#define sinTheta sin(p().theta.z)
#define cosTheta cos(p().theta.z)
Modify the structure of the particle pointer
#define ADD_PART_MEM coord u; coord u2; coord locn; long unsigned int tag; \
double r; \
coord theta; \
coord omega; \
coord F; \
coord T; \
coord B; \
coord M; \
coord R; \
double thrust; double alpha; double beta;
F, T, translational-rotational forces MEASURED on particle. (i.e. HYDRO). B, M, translational-rotational BODY forces apllied on particle.
#include "fpicella/src/myembed-particles.h"
int alternating_series(int n) {
if (n == 0) return 0; // Base case
int value = (n + 1) / 2;
return (n % 2 == 1) ? value : -value;
}
void locate_particle_in_zero(){
int _l_particle = 0;
while (pn[_l_particle] != terminate_int) {
for (int _j_particle = 0; _j_particle < pn[_l_particle]; _j_particle++) {
/*
First, barebones version, all particles have the same radius...
...but provided the numerical method improved, I could play around with
different shapes, size...
For the moment, I stick with cylinders and spheres
*/
p().r = RADIUS;
p().x = +3.*p().r*alternating_series(_j_particle);
p().y = +0.*p().r*alternating_series(_j_particle);
p().z = 0.;
p().theta.z=+_j_particle*M_PI*1.;
p().u.x = 0.0;
p().u.y = 0.0;
p().omega.x = 0.;
p().omega.y = 0.;
p().omega.z = 0.;
p().theta.x = 0.;
p().theta.y = 0.;
p().theta.z = 0.;
// Body force (i.e. sedimentation?)
p().B.x = 0.;
p().B.y = 0.;
p().B.z = 0.;
}
_l_particle++;
}
}
/*Set list of particles to work with.
For the moment, I've fot a single set.*/
Particles ParticleList; // // Call the particles!
/*Initialize particles */
void initialize_particles ()
{
ParticleList = init_tp_circle(NPARTICLES);
locate_particle_in_zero();
}
///*
//Default particle initialization
//*/
//event init (i = 0){
// initialize_particles();
//}
// Particle output, super-compact (and working in serial!) way :)
// FP, 20250212 11h37
event output_particle_initialize(i=0){ // first iteration, define name and open files...
foreach_particle(){
char filename[100];
sprintf(filename, "particle_%03d.dat", _j_particle);
singleParticleFile[_j_particle] = fopen(filename,"w");
}
}
#define FORMAT_SPEC_7 ("%+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e\n") // to avoid writing every time it...
#define FORMAT_SPEC_10 ("%+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e %+6.5e\n") // to avoid writing every time it...
#if dimension == 2
event output_particle(i++){
foreach_particle(){
//fprintf(singleParticleFile[_j_particle],FORMAT_SPEC_7,t,p().x,p().y,p().theta.z,p().u.x,p().u.y,p().omega.z);
fprintf(singleParticleFile[_j_particle],FORMAT_SPEC_10,t,p().x,p().y,p().theta.z,p().u.x,p().u.y,p().omega.z,p().F.x,p().F.y,p().T.z);
fflush(singleParticleFile[_j_particle]);
}
}
#else
event output_particle(i++){
foreach_particle(){
fprintf(singleParticleFile[_j_particle],FORMAT_SPEC_10,t,p().x,p().y,p().z,p().u.x,p().u.y,p().z,p().F.x,p().F.y,p().F.z);
fflush(singleParticleFile[_j_particle]);
}
}
#endif
/*
### Compute volume fractions associated to the presence of the particle
Idea here, is to compute the LOCAL fractions, and to add them to the
full problem.
*/
void compute_particle_fractions(){
Updated version: each time reset ALL cs and fs fields to 1.
This is done so to avoid, in the case container is treated using mask
to _decapitate_ particle's fractions by successive multiplication of
non 1 or 0 values on the boundaries.
foreach()
cs[] = 1.;
foreach_face()
fs.x[] = 1.;
Get back to the original implementation
foreach_particle(){
solid(csLOCAL,fsLOCAL,PARTICLE);
foreach()
cs[] *= csLOCAL[];
foreach_face()
fs.x[] *= fsLOCAL.x[];
}
}
Fluid-solid coupling
The following no-slip Dirichlet boundary condition for velocity and hommogeneous Neumann boundary condition for pressure on the discrete rigid boundary \delta \Gamma_{i,\Delta} allow us to couple the motion of the fluid and the discrete rigid body \Gamma_{i,\Delta}: \displaystyle \left\{ \begin{aligned} & \mathbf{u} = \mathbf{u}_{\Gamma,i} = \mathbf{u_{\Gamma,i}} + \omega_{\Gamma,i} \times \left(\mathbf{x} - \mathbf{x}_{\Gamma,i}\right) \\ & {\nabla}_{\Gamma,i} p = 0. \end{aligned} \right. The homogeneous Neumann boundary condition is suitable for a fixed rigid body and we have found that using it with a moving rigid body does not significantly affect the computed solution.
No-slip Dirichlet boundary condition for velocity
The function velocity_noslip_x() computes the previously defined no-slip boundary condition for the x-component of the velocity \mathbf{u}.
foreach_dimension()
static inline double velocity_noslip_x (Point point,
double xc, double yc, double zc)
// xc, yc and zc are coordinates of the cell's center
{
assert (cs[] > 0. && cs[] < 1.);
foreach_particle(){
coord COORD = {xc,yc,zc}; // required to properly compute COLOR function
// owing to a logic in foreach_dimension()
if (COLOR>-0.5 && COLOR<0.5){
We first compute the relative position \mathbf{r_{i}} = \mathbf{x} - \mathbf{x}_{\Gamma,i}.
// The coordinate x,y,z are not permuted with foreach_dimension()
coord r = {xc, yc, zc};
foreach_dimension() {
r.x -= p().x;
if (Period.x) {
if (fabs (r.x) > fabs (r.x + (L0)))
r.x += (L0);
if (fabs (r.x) > fabs (r.x - (L0)))
r.x -= (L0);
}
}
/*
We then compute the surface velocity as no-slip RIGID BODY MOTION
*/
#if dimension == 2
coord sgn = {-1, 1};
return (p().u.x) + sgn.x*p().omega.x*(r.y); // in 2D, omega.x=omega.y=omega.z
#else // dimension == 3
return (p().u.x) + p().omega.y*(r.z) - p().omega.z*(r.y);
#endif // dimension
}
}
// Other fixed embedded boundaries (u=0)
#ifndef imposedFlow
return 0.;
#else
return imposedU.x;
#endif
/*
Some other BC on _fixed_ embedded boundaries? Just change them here :)
*/
}
/*
Non-zero neumann bc for pressure.
Taken from Ghigo's [implementation](implementation)
and adapted for Stokes configuration.
*/
Neumann boundary condition for pressure
The function pressure_gradient() returns in da the contribution of all the components of the particle acceleration and viscous stresses to the pressure gradient.
/*
For the case of Steady Stokes flow, the part owing to acceleration will be
accounted to zero. Viscous stresses instead will be set on.
*/
static inline void pressure_acceleration (Point point,
double xc, double yc, double zc,
coord * da)
{
assert (cs[] > 0. && cs[] < 1.);
// On all embedded cells that do not belong to a particle
// i.e. to a container, channel...
foreach_dimension()
da->x = 0.;
// if not...
foreach_particle(){
coord COORD = {xc,yc,zc}; // required to properly compute COLOR function
// owing to a logic in foreach_dimension()
if (COLOR>-0.5 && COLOR<0.5){
We first compute the acceleration of the particle gu.
coord gu;
foreach_dimension()
gu.x = 0.;
We then compute the viscous contribution.
coord gmu;
foreach_dimension()
gmu.x = 0.;
foreach_dimension() {
scalar s = u.x;
double a = 0.;
foreach_dimension()
a += (mu.x[1]*face_gradient_x (s, 1) - mu.x[0]*face_gradient_x (s, 0))/Delta;
double b, c = embed_flux (point, u.x, mu, &b);
a += (b + c*u.x[]);
gmu.x = -a;
}
Finally, we combine both contributions.
foreach_dimension()
da->x = (gu.x + gmu.x);
}
}
}
foreach_dimension()
static inline double pressure_acceleration_x (Point point,
double xc, double yc, double zc)
{
coord da;
pressure_acceleration (point, xc, yc, zc, &da);
return da.x;
}
The following function finally computes the Neumann boundary condition for pressure, where the inward unit normal \mathbf{n}_{\Gamma} is pointing from fluid to solid. To switch to a homogenous Neumann boundary condition, the user can set the scalar attribute neumann_zero to true. The default is false.
attribute {
bool neumann_zero;
}
static inline double pressure_neumann (Point point,
// coord dup, coord dwp,
// coord wp, coord pp,
double xc, double yc, double zc)
{
coord da = {0., 0., 0.};
//pressure_acceleration (point, dup, dwp, wp, pp, xc, yc, zc, &da);
pressure_acceleration (point, xc, yc, zc, &da);
coord b, n;
embed_geometry (point, &b, &n);
double dpdn = 0.;
foreach_dimension()
dpdn += (-da.x)*n.x;
dpdn *= rho[]/(cs[] + SEPS);
return dpdn;
}
void hydro_forces_torques()
{
foreach_particle(){
fraction(csLOCAL,PARTICLE);
// allocate some auxiliary variables
coord Fp, Fmu;
coord Tp, Tmu;
coord center = {p().x,p().y,p().z};
coord Omega = {p().omega.x,p().omega.y,p().omega.z}; // for the moment, it works ONLY in 2D!
embed_color_force_RBM (p, u, mu, csLOCAL, center, Omega, p().r, &Fp, &Fmu);
embed_color_torque_RBM (p, u, mu, csLOCAL, center, Omega, p().r, &Tp, &Tmu);
foreach_dimension()
p().F.x = Fp.x + Fmu.x;
/*
The rest works only in 2D for the moment
*/
#if dimension == 2
p().T.x = Tp.x + Tmu.x;
p().T.y = p().T.x;
p().T.z = p().T.x;
#endif
}
}
void velocity_for_force_free()
{
foreach_particle(){
Translational velocity.
coord propulsionAngle = {cosTheta,sinTheta};
foreach_dimension()
p().u.x += (p().F.x+p().B.x+p().thrust*propulsionAngle.x+p().R.x)*dt;
Angular velocity.
foreach_dimension()
p().omega.x += (p().T.x+p().M.x)*dt*100.;
Here dt and the arbitrary quantity (here 100) are just parameters, in a steady case, they do not have a real physical meaning. Consider them as relaxation parameters for the timestepper.
In 2D…must get back to -z variables.
#if dimension == 2
p().omega.z = p().omega.x;
#endif // compatibility with 3D.
}
}
void particle_location_update()
{
foreach_particle()
foreach_dimension()
p().x += p().u.x*dt;
Treat periodicity…
foreach_particle(){
foreach_dimension(){
if(p().x>+L0/2.)
p().x-=L0;
if(p().x<-L0/2.)
p().x+=L0;
}
}
//
//Simple update of angular position.
//foreach_particle()
// p().theta.z += p().omega.z*dt;
}
void compute_top_wall_repulsion(){
Simple model: here I compute the repulsion wrt top wall. I employ a purely repulsive potential.
foreach_particle(){
double distance = L0/2.-p().y;
double sigma = 3*p().r;
double sr = pow(sigma/distance,12.);
p().R.y -= (sr);
}
}
void compute_right_wall_repulsion(){
Simple model, a purely repulsive potential.
foreach_particle(){
double distance = L0/2.-p().x;
double sigma = 3*p().r;
double sr = pow(sigma/distance,12.);
p().R.x -= (sr);
}
}
void compute_left_wall_repulsion(){
Simple model, a purely repulsive potential.
foreach_particle(){
double distance = L0/2.+p().x;
double sigma = 3*p().r;
double sr = pow(sigma/distance,12.);
p().R.x += (sr);
}
}
void compute_bottom_wall_repulsion(){
Simple model, a purely repulsive potential.
foreach_particle(){
double distance = L0/2.+p().y;
double sigma = 3*p().r;
double sr = pow(sigma/distance,12.);
p().R.y += (sr);
}
}
void compute_particle_particle_repulsion(){
foreach_particle(){
for (int _k_particle = 0; _k_particle < pn[_l_particle]; _k_particle++) {
if(_k_particle != _j_particle){
coord distance;
foreach_dimension()
distance.x = pl[_l_particle][_k_particle].x - pl[_l_particle][_j_particle].x;
double DISTANCE = sqrt(sq(distance.x)+sq(distance.y));
double sigma = 2*p().r;
double sr = pow(sigma/DISTANCE,12.);
double angle = atan2(distance.y,distance.x);
//fprintf(stderr,"particle-particle repulsion %d %d %+6.5e %+6.5e %+6.5e \n",_j_particle,_k_particle,angle,DISTANCE,sr);
p().R.x -= 0.0*sr*cos(angle);
p().R.y -= 0.0*sr*sin(angle);
}
}
}
}