/*
This file is part of the Snoopy code.
Snoopy code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Snoopy code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Snoopy code. If not, see .
*/
#include "snoopy.h"
#include
// Timing support
#ifndef MPI_SUPPORT
#ifndef _OPENMP
#include
#include
#endif
#endif
#include "error.h"
#include "gfft.h"
#include "debug.h"
// This are global variables used throughout the code
// Wave number pointers
double *kx; /**< x Wavevector */
double *ky; /**< y Wavevector */
double *kz; /**< z Wavevector */
double *kxt; /**< Time dependant x Wavevector. Different from kx only when SHEAR is present.*/
double *k2t; /**< k squared Wavevector, function of time when SHEAR is present.*/
double *ik2t; /**< inverse of k2t Wavevector, function of time when SHEAR is present. set to 0 wheh k2t=0 to avoid singularity*/
double kxmax, kymax, kzmax, kmax; /**< Maximum wavevectors */
// Mask for dealiasing
double *mask; /**< Deasliasing Mask*/
double *wr1, *wr2, *wr3; /** Temporary real array (alias of complex w**) */
double *wr4, *wr5, *wr6; /** Temporary real array (alias of complex w**) */
double *wr7, *wr8, *wr9; /** Temporary real array (alias of complex w**) */
double *wr10, *wr11, *wr12;
double *wr13, *wr14, *wr15;
double complex *w1, *w2, *w3; /**< Temporary complex array (alias of real wr**) */
double complex *w4, *w5, *w6; /**< Temporary complex array (alias of real wr**) */
double complex *w7, *w8, *w9; /**< Temporary complex array (alias of real wr**) */
double complex *w10, *w11, *w12;
double complex *w13, *w14, *w15;
// Parameters
struct Parameters param;
// Physics variables
double nu;
#ifdef BOUSSINESQ
double nu_th;
#endif
#ifdef MHD
double eta;
#endif
#ifdef MPI_SUPPORT
int NPROC; /**< NPROC is a variable when MPI is on. Otherwise, it is preprocessor macro in gvars.h */
#endif
int rank;
int nthreads; /**< Number of OpenMP threads */
/* Function prototypes */
void allocate_field(struct Field *fldi);
void deallocate_field(struct Field *fldi);
void init_N2_profile();
void init_real_mask();
/***************************************************************/
/**
Init all global variables, aligning them in memory
*/
/***************************************************************/
void init_common(void) {
/* This routine will initialize everything */
int i,j,k;
DEBUG_START_FUNC;
#ifdef MPI_SUPPORT
#ifdef FFTW3_MPI_SUPPORT
fftw_mpi_init();
#endif
#endif
#ifdef _OPENMP
if( !(fftw_init_threads()) ) ERROR_HANDLER( ERROR_CRITICAL, "Threads initialisation failed");
#endif
/* We start with the coordinate system */
kx = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (kx == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for kx allocation");
ky = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (ky == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for ky allocation");
kz = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (kz == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for kz allocation");
kxt = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (kxt == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for kxt allocation");
k2t = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (k2t == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for k2t allocation");
ik2t = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (ik2t == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for ik2t allocation");
for( i = 0; i < NX_COMPLEX / NPROC; i++) {
for( j = 0; j < NY_COMPLEX; j++) {
for( k = 0; k < NZ_COMPLEX; k++) {
kx[ IDX3D ] = (2.0 * M_PI) / param.lx *
(fmod( NX_COMPLEX * rank / NPROC + i + (NX_COMPLEX / 2) , NX_COMPLEX ) - NX_COMPLEX / 2 );
#ifdef WITH_2D
ky[ IDX3D ] = (2.0 * M_PI) / param.ly * j;
kz[ IDX3D ] = 0.0;
#else
ky[ IDX3D ] = (2.0 * M_PI) / param.ly *
(fmod( j + (NY_COMPLEX / 2) , NY_COMPLEX ) - NY_COMPLEX / 2 );
kz[ IDX3D ] = (2.0 * M_PI) / param.lz * k;
#endif
kxt[ IDX3D ]= kx[IDX3D];
k2t[ IDX3D ] = kxt[IDX3D] * kxt[IDX3D] +
ky[IDX3D] * ky[IDX3D] +
kz[IDX3D] * kz[IDX3D];
if ( k2t[IDX3D] == 0.0 ) ik2t[IDX3D] = 1.0;
else ik2t[IDX3D] = 1.0 / k2t[IDX3D];
}
}
}
kxmax = 2.0 * M_PI/ param.lx * ( (NX / 2) - 1);
kymax = 2.0 * M_PI/ param.ly * ( (NY / 2) - 1);
kzmax = 2.0 * M_PI/ param.lz * ( (NZ / 2) - 1);
#ifdef WITH_2D
kzmax = 0.0;
#endif
kmax=pow(kxmax*kxmax+kymax*kymax+kzmax*kzmax,0.5);
/* Initialize the dealiazing mask Or the nyquist frequency mask (in case dealiasing is not required) */
mask = (double *) fftw_malloc( sizeof(double) * NTOTAL_COMPLEX );
if (mask == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for mask allocation");
for( i = 0; i < NX_COMPLEX/NPROC; i++) {
for( j = 0; j < NY_COMPLEX; j++) {
for( k = 0; k < NZ_COMPLEX; k++) {
mask[ IDX3D ] = 1.0;
if(param.antialiasing) {
if( fabs( kx[ IDX3D ] ) > 2.0/3.0 * kxmax)
mask[ IDX3D ] = 0.0;
if( fabs( ky[ IDX3D ] ) > 2.0/3.0 * kymax)
mask[ IDX3D ] = 0.0;
#ifndef WITH_2D
if( fabs( kz[ IDX3D ] ) > 2.0/3.0 * kzmax)
mask[ IDX3D ] = 0.0;
#endif
}
else {
if ( NX_COMPLEX / NPROC * rank + i == NX_COMPLEX / 2 )
mask[ IDX3D ] = 0.0;
if ( j == NY_COMPLEX / 2 )
mask[ IDX3D ] = 0.0;
#ifndef WITH_2D
if ( k == NZ_COMPLEX )
mask[ IDX3D ] = 0.0;
#endif
}
}
}
}
if(param.antialiasing) {
kxmax = kxmax * 2.0 / 3.0;
kymax = kymax * 2.0 / 3.0;
kzmax = kzmax * 2.0 / 3.0;
kmax = kmax * 2.0 / 3.0;
}
// Allocate fields
// Complex fields
w1 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w1 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w1 allocation");
w2 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w2 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w2 allocation");
w3 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w3 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w3 allocation");
w4 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w4 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w4 allocation");
w5 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w5 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w5 allocation");
w6 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w6 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w6 allocation");
w7 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w7 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w7 allocation");
w8 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w8 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w8 allocation");
w9 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w9 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w9 allocation");
w10 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w10 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w10 allocation");
w11 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w11 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w11 allocation");
w12 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w12 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w12 allocation");
w13 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w13 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w13 allocation");
w14 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w14 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w14 allocation");
w15 = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (w15 == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for w15 allocation");
/* Will use the same memory space for real and complex fields */
wr1 = (double *) w1;
wr2 = (double *) w2;
wr3 = (double *) w3;
wr4 = (double *) w4;
wr5 = (double *) w5;
wr6 = (double *) w6;
wr7 = (double *) w7;
wr8 = (double *) w8;
wr9 = (double *) w9;
wr10 = (double *) w10;
wr11 = (double *) w11;
wr12 = (double *) w12;
wr13 = (double *) w13;
wr14 = (double *) w14;
wr15 = (double *) w15;
// Physic initialisation
// init_real_mask();
nu = 1.0 / param.reynolds;
#ifdef BOUSSINESQ
nu_th = 1.0 / param.reynolds_th;
#endif
#ifdef MHD
eta = 1.0 / param.reynolds_m;
#endif
DEBUG_END_FUNC;
return;
}
void finish_common(void) {
free(kx);
free(ky);
free(kz);
free(kxt);
free(k2t);
free(ik2t);
free(mask);
free(w1);
free(w2);
free(w3);
free(w4);
free(w5);
free(w6);
free(w7);
free(w8);
free(w9);
free(w10);
free(w11);
free(w12);
free(w13);
free(w14);
free(w15);
return;
}
/*********************************************/
/**
Allocate a field structure according to the code
current configuration
This routine allows one to add extra fields
to the code very easily.
@param *fldi: pointer to a field structure to initialize
**/
/*********************************************/
void allocate_field(struct Field *fldi) {
int current_field, i;
DEBUG_START_FUNC;
// We want to allocate a field structure
fldi->nfield = 3;
#ifdef BOUSSINESQ
fldi->nfield++;
#endif
#ifdef MHD
fldi->nfield=fldi->nfield+3;
#endif
#ifdef COMPRESSIBLE
fldi->nfield=fldi->nfield+1;
#endif
// Now we want to initialize the pointers of the field structure
// farray will point to each of the array previously allocated
fldi->farray = (double complex **) fftw_malloc( sizeof(double complex *) * fldi->nfield);
if (fldi->farray == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->farray allocation");
// fname will point to the name of each field
fldi->fname = (char **) fftw_malloc(sizeof(char *) * fldi->nfield);
if (fldi->fname == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->fname allocation");
// Initialise the pointers
for(i=0 ; i < fldi->nfield ; i++) {
fldi->fname[i] = (char *) fftw_malloc(sizeof(char) * 10); // 10 character to describe each field
if (fldi->fname[i] == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->fname[i] allocation");
}
// Allocate the arrays and put the right value in each pointer
current_field = 0;
fldi->vx = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->vx == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->vx allocation");
fldi->farray[current_field] = fldi->vx;
#ifndef COMPRESSIBLE
sprintf(fldi->fname[current_field],"vx");
#else
sprintf(fldi->fname[current_field],"px");
#endif
current_field++;
fldi->vy = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->vy == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->vy allocation");
fldi->farray[current_field] = fldi->vy;
#ifndef COMPRESSIBLE
sprintf(fldi->fname[current_field],"vy");
#else
sprintf(fldi->fname[current_field],"py");
#endif
current_field++;
fldi->vz = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->vz == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->vz allocation");
fldi->farray[current_field] = fldi->vz;
#ifndef COMPRESSIBLE
sprintf(fldi->fname[current_field],"vz");
#else
sprintf(fldi->fname[current_field],"pz");
#endif
current_field++;
#ifdef BOUSSINESQ
fldi->th = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->th == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->th allocation");
fldi->farray[current_field] = fldi->th;
sprintf(fldi->fname[current_field],"th");
current_field++;
#endif
#ifdef MHD
fldi->bx = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->bx == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->bx allocation");
fldi->farray[current_field] = fldi->bx;
sprintf(fldi->fname[current_field],"bx");
current_field++;
fldi->by = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->by == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->by allocation");
fldi->farray[current_field] = fldi->by;
sprintf(fldi->fname[current_field],"by");
current_field++;
fldi->bz = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->bz == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->bz allocation");
fldi->farray[current_field] = fldi->bz;
sprintf(fldi->fname[current_field],"bz");
current_field++;
#endif
#ifdef COMPRESSIBLE
fldi->d = (double complex *) fftw_malloc( sizeof(double complex) * NTOTAL_COMPLEX);
if (fldi->d == NULL) ERROR_HANDLER( ERROR_CRITICAL, "No memory for fldi->d allocation");
fldi->farray[current_field] = fldi->d;
sprintf(fldi->fname[current_field]," d");
current_field++;
#endif
// Add a field here if you need one... (don't forget to ajust fldi.nfield accordingly)
// *
#ifdef WITH_PARTICLES
// Init space for particle storage
fldi->part = (struct Particle *) malloc(sizeof(struct Particle) * param.particles_n);
#endif
// Ok, all done...
DEBUG_END_FUNC;
return;
}
/*********************************************/
/**
Deallocate a field structure created by
allocate_field
**/
/*********************************************/
void deallocate_field(struct Field *fldi) {
int i;
// Free a field structure
DEBUG_START_FUNC;
for(i=0 ; i < fldi->nfield ; i++) {
fftw_free(fldi->fname[i]);
fftw_free(fldi->farray[i]);
}
fftw_free(fldi->farray);
fftw_free(fldi->fname);
#ifdef WITH_PARTICLES
free(fldi->part);
#endif
// Done
DEBUG_END_FUNC;
return;
}
/*********************************************/
/**
Customized random number generator
Allow one to have consistant random numbers
generators on different architectures.
**/
/*********************************************/
double randm(void) {
const int a = 16807;
const int m = 2147483647;
static int in0 = 13763;
int q;
// When using mpi, this allows us to have different number series in each process...
if(in0 == 13763) in0 += 2543 * rank;
/* find random number */
q= (int) fmod((double) a * in0, m);
in0=q;
return((double)q/(double)m);
}
/*********************************************/
/**
* Normal distribution
* Algorithm by D.E. Knut, 1997, The Art of Computer Programmin, Addison-Wesley.
*/
/*********************************************/
double randm_normal(void) {
double v1, v2;
double rsq=1.0;
while(rsq>=1. || rsq==0.0) {
v1=2.*randm()-1.0;
v2=2.*randm()-1.0;
rsq=v1*v1+v2*v2;
}
return( v1*sqrt(-2.0 * log(rsq) / rsq));
}
/****************************************************/
/**
Remove the divergence from a 3D field using
the projector operator:
q=q-k.q/k^2
@param qx: x component of the field
@param qy: y component of the field
@param qz: z component of the field
*/
/****************************************************/
void projector( double complex qx[],
double complex qy[],
double complex qz[]) {
int i;
double complex q0;
DEBUG_START_FUNC;
for( i = 0 ; i < NTOTAL_COMPLEX ; i++) {
q0 = kxt[i] * qx[i] + ky[i] * qy[i] + kz[i] * qz[i];
qx[i] = qx[i] - kxt[i] * q0 * ik2t[i];
qy[i] = qy[i] - ky[i] * q0 * ik2t[i];
qz[i] = qz[i] - kz[i] * q0 * ik2t[i];
}
DEBUG_END_FUNC;
return;
}
/*********************************************/
/** Compute the energy of a given field.
@param q complex array containing the field for which
we want the total energy
*/
/*********************************************/
double energy(const double complex q[]) {
int i,j,k;
double energ_tot;
energ_tot=0.0;
for( i = 0; i < NX_COMPLEX/NPROC; i++) {
for( j = 0; j < NY_COMPLEX; j++) {
for( k=0; k < NZ_COMPLEX; k++) {
#ifdef WITH_2D
if( j == 0)
#else
if( k == 0)
#endif
// k=0, we have all the modes.
energ_tot = energ_tot + creal( 0.5 * q[ IDX3D ] * conj( q[ IDX3D ] ) ) / ((double) NTOTAL*NTOTAL);
else
// k>0, only half of the complex plane is represented.
energ_tot = energ_tot + creal( q[ IDX3D ] * conj( q[ IDX3D ] ) ) / ((double) NTOTAL*NTOTAL);
}
}
}
// energ_tot = 0;
return(energ_tot);
}
/********************************************/
/**
Return the localtime in seconds. Use different
implementation depending on the avaiable
libraries **/
/********************************************/
double get_c_time(void) {
#ifdef MPI_SUPPORT
// We have MPI
return(MPI_Wtime());
#else
#ifdef _OPENMP
// We don't have MPI, but we have OpenMP
return(omp_get_wtime());
#else
// We really have nothing...
clock_t now;
now = clock();
return( (double) now / ( (double) CLOCKS_PER_SEC));
#endif
#endif
}
/******************************************/
/**
Reduce a variable over all the avaiable processes
Can add a value on all the process, find a maximum
or a minimum.
NOTE: This routine makes sense only when MPI_SUPPORT
is set. If not, this routine does nothing.
@param *var: variable to be reduced
@param op: operation needed to be done. Can be set to:
1= Sum over all the processes
2= Find the maximum over all the processes
3= Find the minimum over all the processes
*/
/*******************************************/
void reduce(double *var, const int op) {
// op=1 ADD
// op=2 Max
// op=3 Min
#ifdef MPI_SUPPORT
double mpi_temp;
mpi_temp=*var;
if(op==1) MPI_Allreduce( &mpi_temp, var, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
if(op==2) MPI_Allreduce( &mpi_temp, var, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
if(op==3) MPI_Allreduce( &mpi_temp, var, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
#endif
// If no MPI, then this routine does nothing...
return;
}
/* ****************************************************************************/
/** Determines if the machine is little-endian. If so,
it will force the data to be big-endian.
@param in_number floating point number to be converted in big endian */
/* *************************************************************************** */
float big_endian(float in_number)
{
static int doneTest = 0;
static int shouldSwap = 0;
if (!doneTest)
{
int tmp1 = 1;
unsigned char *tmp2 = (unsigned char *) &tmp1;
if (*tmp2 != 0)
shouldSwap = 1;
doneTest = 1;
}
if (shouldSwap)
{
unsigned char *bytes = (unsigned char*) &in_number;
unsigned char tmp = bytes[0];
bytes[0] = bytes[3];
bytes[3] = tmp;
tmp = bytes[1];
bytes[1] = bytes[2];
bytes[2] = tmp;
}
return(in_number);
}
/******************************************************************************/
/** Test a double for Not a Number error
@param xi double to be checked */
/* ************************************************************************** */
void c_nan(double xi, const char ErrorRoutine[], const int line, const char Filename[]) {
if(isnan(xi)) {
error_h( 3, "Not a number detected", ErrorRoutine, line, Filename);
}
return;
}
#ifdef COMPRESSIBLE
/***************************************************************/
/** Check that the field is definite positive *******************/
/****************************************************************/
void check_positivity(double *wri) {
int i;
#ifdef _OPENMP
#pragma omp parallel for private(i) schedule(static)
#endif
for(i=0 ; i < 2*NTOTAL_COMPLEX ; i++) {
if(wri[i] < 1.0e-2*NTOTAL) wri[i] = 1.0e-2*NTOTAL;
}
}
#endif