sandbox/vheusinkveld/fan.c
Intro
This is a minimal example for implementing a ‘fan’ after geometry examples that can be found here. The mentioned function is updated to be applicable for general phi and theta. Also this script adds a velocity based on a stated fan power. All functions can be found at the end of this file.
Power to velocity forcing and expected exit velocity
The implementation is based on a momentum forcing. An amount of work per time is done on the flow with a certain power, P. Using the work-energy theorem, which states that the work done by all acting forces on a particle is equal the change in kinetic energy, this can be expressed as \Delta E_{kin}=P\Delta t. This directly implies that,
\vec{v}^2_{t+\Delta t} - \vec{v}^2_{t} = \frac{2\textrm{P}\Delta t}{m}\hat{n}^2
which in turn implies for every spatial dimension,
v^2_{i,t+\Delta t} - v^2_{i,t} = \frac{2\textrm{P}\Delta t}{m} n_i^2
From these equations the direction of \hat{n} is not yet defined and |\hat{n}|=1. When it is assumed that the force applied to the flow is normal to the plane in which the fan blades rotate then \hat{n} is the normal vector of this plane. Rearranging and taking the square root results in, \vec{v}_{i,t+\Delta t} = \sqrt{\vec{v}^2_{i,t} + \frac{2\textrm{P}\Delta t}{m}\hat{n}^2_i }
To estimate the velocity to which air is accelerated by this forcing we assume that it is applied over a finite time from t_{start} to t_{end}, moving a distance w in the process: w=\int_{t_{start}}^{t_{end}} v\left(t\right)\textrm{dt} Assuming a zero entrance velocity, v is given at every instance as: \textrm{E}_{\textrm{kin}}=\textrm{P}\Delta t=0.5mv^2 v\left(t\right)=\sqrt{2\textrm{P}t/m} P, w and m are set by the user thus this leaves two equations and two unknowns. Solving for the exit velocity gives: v_{end} = \left(\frac{3\textrm{P}w}{m}\right)^{1/3}
The code
First we include relevant files for 2D navier stokes solving and geometric fun.
//#include "grid/octree.h" // For 3D --> to demanding for server
#include "navier-stokes/centered.h" // Navier stokes
#include "fractions.h" // Needed to compute fan fractions
Required functions and structures
void init_rotor();
void rotor_update();
void rotor_coord();
void rotor_forcing();
struct sRotor {
double rampT; // Time to start up rotor
double P, Prho; // Power, powerdensity
double R, W, A, V; // Diameter, Thickness, Area ,Volume
double diaVol; // Diagnosed rotor volume
double x0, y0, z0; // Origin of rotor
double xt, yt, zt; // Translation of origin per rotate event
double theta, phi; // Polar and Azimuthal angle
double thetat, phit; // Addition to angles per rotate event
double Work; // Work done by rotor
double cu; // Characteristic velocity
bool rotate; // Rotation yes or no
, nr; // Normal vector fan, rotation
coord nf};
Some global vars
int minlevel, maxlevel; // Grid depths
double eps; // Adaptivity criterion
struct sRotor rot; // Rotor details structure
scalar fan[]; // Fan volume fraction
scalar lev[]; // for movie diagnostic level
scalar Ekin[]; // for movie kinetic energy
Starting with the most loved c function
int main() {
= 3;
minlevel = 8;
maxlevel
= 10.;
L0 = Y0 = Z0 = 0.;
X0
(1<<7);
init_grid
foreach_dimension(){
periodic (left);
.x.refine = refine_linear; // Momentum conserved
u}
.prolongation = fraction_refine; // Fan is a volume fraction
fan.refine = p.prolongation = refine_linear;
p
= 10E-5; // For poisson solver
DT =10E-6; // For poisson solver
TOLERANCE= 0.5; // CFL condition
CFL
run(); // Start simulation
}
Initialisation
event init(t = 0){
.rotate = true; // Turn on rotation
rotinit_rotor(); // See init function for details
.prolongation = fraction_refine; // Tell basilisk that fan is volumetric field
fanrefine (fan[] > 0. && level < maxlevel); // Refine where fan is
= 0.07*rot.cu; // Wavelet estimator based on flow velocity
eps }
Reset to defaults for poisson solver
event reset_pois(i = 10){
= 1;
DT = 10E-3;
TOLERANCE }
Adaptivity
event adapt(i++) {
((scalar *){fan,u},(double []){0.,eps,eps},maxlevel,minlevel);
adapt_wavelet}
Forcing by the rotor
event forcing(i = 1; i++) {
rotor_coord();
rotor_forcing();
}
Rotate the rotor
event rotate(t+=0.1) {
if(rot.rotate) {
// Change center
.x0 += rot.xt;
rot.y0 += rot.yt;
rot.z0 += rot.zt;
rot
// Change angles
.theta += rot.thetat;
rot.phi += rot.phit;
rot
rotor_update();
}
}
Visual output
event movies(t+=1){
fprintf(stderr, "t=%g\n", t);
foreach(){
[] = level;
lev[] = sqrt(sq(u.y[]) + sq(u.x[]));
Ekin}
boundary({lev, Ekin});
output_ppm (lev, file = "ppm2mp4 level.mp4", n = 512, linear = false, min = minlevel, max = maxlevel);
output_ppm (Ekin, file = "ppm2mp4 ekin.mp4", n = 512, linear = true);
}
End the simulation
event end(t=180){
}
Results
Kinetic energy density
Grid depth
Functions
Function returning the sRotor structure, includign default properties
void init_rotor() {
.Work = 0.;
rotif(!rot.rampT)
.rampT = 1.; // fan at full thrust after 1 second, linear ramp
rotif(!rot.R)
.R = L0/80.;
rotif(!rot.W)
.W = rot.R/5;
rotif(!rot.Prho)
.Prho = L0/10.;
rotif(!rot.x0)
.x0 = L0/2.;
rotif(!rot.y0)
.y0 = L0/2.;
rotif(!rot.z0){
#if dimension == 2
.z0 = 0.;
rot#elif dimension == 3
.z0 = L0/2.;
rot#endif
}
if(!rot.theta)
.theta = 90.*M_PI/180.; // Polar angle
rotif(!rot.phi)
.phi = 90.*M_PI/180.; // Azimuthal angle
rot
if(rot.rotate) {
// determines for every rotate event how much needs to be added to x, y, z, tehta, phi.
.xt = 0;
rot.yt = 0;
rot.zt = 0;
rot.thetat = 0.;
rot.phit = -0.1*M_PI/180.; // note that this rate is linked to the frequency of the rotate event
rot} else {
.xt = 0;
rot.yt = 0;
rot.zt = 0;
rot.thetat = 0.;
rot.phit = 0.;
rot}
rotor_update();
}
Updating relevant rotor variables
void rotor_update() {
// Normal vectors to the fan plane, note that z and y are turned around for consistency regarding polar coordinates (basilisk uses y for the vertical)
.nf.x = sin(rot.theta)*cos(rot.phi);
rot.nf.y = sin(rot.theta)*sin(rot.phi);
rot.nf.z = cos(rot.theta);
rot
.nr.x = sin(rot.theta)*cos(rot.phi);
rot.nr.y = sin(rot.theta)*sin(rot.phi);
rot.nr.z = cos(rot.theta);
rot
#if dimension == 2
.A = 2*rot.R*rot.W;
rot#elif dimension == 3
.A = sq(rot.R)*M_PI;
rot#endif
// Volume is slice of a sphere
#if dimension == 2
.V = 1.*rot.A;
rot#elif dimension == 3
.V = 4.*M_PI*pow(rot.R,3.)/3. -
rot2*M_PI*pow(rot.R-rot.W/2., 2.)/3.*(2*rot.R + rot.W/2.);
#endif
.P = rot.V*rot.Prho;
rot.cu = pow(3*rot.Prho*rot.W, 1./3.);
rot}
Function returning the volume fractions of a fan object
void rotor_coord() {
scalar sph[], plnu[], plnd[];
fraction(sph, -sq((x - rot.x0)) - sq((y - rot.y0)) - sq((z - rot.z0)) + sq(rot.R)); // sphere
fraction(plnu, rot.nr.x*(x - rot.x0) + rot.nr.y*(y - rot.y0) + rot.nr.z*(z - rot.z0) + rot.W/2.); // upper plane
fraction(plnd, -rot.nr.x*(x - rot.x0) - rot.nr.y*(y - rot.y0) - rot.nr.z*(z - rot.z0) + rot.W/2.); // lower plane
foreach () {
[] = sph[]*plnu[]*plnd[]; // estimate for the overlappnig volumes, not that this is not exact at the boundaries of the 'fan', this is corrected for later in the focring by comparing diagnosed to theoretical volume.
fan}
boundary({fan}); // update boundaries
}
Function returning new velocities based on a rotor forcing. This is a function of powerdensity, width, direction, ramp-time, and diagnosed volume
void rotor_forcing(){
double tempW = 0.;
double w, wsgn, damp, usgn, utemp, corrP;
foreach(reduction(+:tempW)) {
if(fan[] > 0.) {
foreach_dimension() {
= sign(rot.nf.x*u.x[]) + (sign(rot.nf.x*u.x[]) == 0)*sign(rot.nf.x); // Get the direction of the flow relative to forcing direction
wsgn = rot.rampT > t ? t/rot.rampT : 1.; // Linear ramp for starting up the forcing
damp = rot.diaVol > 0. ? rot.V/rot.diaVol : 1.; // Correction if diagnosed volume differs from theoretical volume
corrP = wsgn*fan[]*damp*sq(rot.nf.x)*(2./rho[])*(corrP*rot.P/rot.V)*dt; // additional kinetic energy
w += 0.5*rho[]*w*dv(); // save work done to asses total work in diagnostics
tempW
// New kinetic energy
= sq(u.x[]) + w;
utemp // New sign of the velocity
= 1.*(u.x[] >= 0)*(utemp > 0) +
usgn -1.*(u.x[] >= 0)*(utemp < 0) +
1.*(u.x[] < 0)*(utemp < 0) +
-1.*(u.x[] < 0)*(utemp > 0);
// New velocity
.x[] = usgn*sqrt(fabs(utemp));
u}
}
}
.Work += tempW; // Diagnostics
rot}