#include "mpi.h"
#include <stdio.h>
#include <math.h>

/*
   VERY simple version of an n-body code.  This uses the simple but highly
   suboptimal n^2 algorithm, and does not take advantage of symmetry.

   The time integrator is a simple leapfrog scheme.

   
 */

extern void   srand48();
extern double drand48();

/* Pipeline version of the algorithm... */
/* we really need the velocities as well... */
typedef struct {
    double x, y, z;
    double mass;
    } Particle;

/* Save the forces and old velocities */
typedef struct {
    double xold, yold, zold;
    double fx, fy, fz;
    } ParticleV;

#define MAX_PARTICLES 4000

/* This is 2-D only */
void ComputeForces( particles, pv, npart, 
		    recvbuf, rlen, max_f )
Particle  *particles, *recvbuf;
ParticleV *pv;
int       npart, rlen;
double    *max_f;
{
int i, j;
double xi, yi, mi, rx, ry, mj, r, fx, fy;
double xnew, ynew, rmin;

/* Compute forces (2D only) */
for (i=0; i<npart; i++) {
    rmin = 100.0;
    xi   = particles[i].x;
    yi   = particles[i].y;
    fx   = 0.0;
    fy   = 0.0;
    for (j=0; j<rlen; j++) {
	rx = xi - recvbuf[j].x;
	ry = yi - recvbuf[j].y;
	mj = recvbuf[j].mass;
	r  = rx * rx + ry * ry;
	/* ignore overlap and same particle */
	if (r == 0.0) continue;
	if (r < rmin) rmin = r;
	/* compute forces */
	r  = r * sqrt(r);
	fx += mj * rx / r;
	fy += mj * ry / r;
	}
    pv[i].fx -= fx;
    pv[i].fy -= fy;
    /* Compute a rough estimate of (1/m)|df / dx| */
    fx		      = sqrt(fx*fx + fy*fy)/rmin;
    if (fx > *max_f) *max_f = fx;
    }
}

void PrintParticles( particles, npart, t )
Particle *particles;
int      npart;
double   t;
{
int i;

for (i=0; i<npart; i++) {
/*     printf( "[%f] (%f,%f)\n", t, particles[i].x, particles[i].y ); */
    printf( "%f %f\n", particles[i].x, particles[i].y );
    }
}

int main( argc, argv )
int  argc;
char **argv;
{
Particle  particles[MAX_PARTICLES];   /* Particles on LOCAL node */
ParticleV pv[MAX_PARTICLES];          /* Particle velocity */
Particle  *recvbuf;
int       rank, size, npart, i, j;    /* location of local particles */
int       step, rlen;
int       totpart,                    /* total number of particles */
          cnt;                        /* number of times in loop */
MPI_Datatype particletype;
double    time;                       /* Computation time */
double    dt, dt_old;                 /* Integration time step */
double    t;                          /* Time actually integrated to */
double    a0, a1, a2;
void      *pipe;                      /* Opaque structure for communication 
					 pipe */
int       debug_flag = 1;

MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
MPI_Comm_size( MPI_COMM_WORLD, &size );

/* Everyone COULD have a different size ... */
npart = 2 * size;
if (argc > 1) 
    npart = atoi(argv[1]) / size;
if (npart > MAX_PARTICLES) 
    MPI_Abort( MPI_COMM_WORLD, 1 );

MPI_Allreduce( &npart, &totpart, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD );

cnt    = 100;

MPI_Type_contiguous( 4, MPI_DOUBLE, &particletype );
MPI_Type_commit( &particletype );

/* Generate the initial values */
srand48( rank * 117 );
for (i=0; i<npart; i++) {
    particles[i].x    = drand48();
    particles[i].y    = drand48();
    particles[i].z    = drand48();
    /* Normalize the mass with respect to the gravitational constant 
       (multiply by sqrt(G), G = 6.668E-8 dyne-cm^2/gm^2 or 
        6.668E-16 Joule-m^2/kg^2, sqrt(G) = 2.582e-8 
     */
    particles[i].mass = 1 * 2.582e-8;
    /* This is the zero initial velocity case */
    pv[i].xold	      = particles[i].x;
    pv[i].yold	      = particles[i].y;
    pv[i].zold	      = particles[i].z;
    pv[i].fx	      = 0;
    pv[i].fy	      = 0;
    pv[i].fz	      = 0;
    }

/* Add a particle in the center with much larger mass */
if (rank == 0) {
    particles[0].x    = 0.5;
    particles[0].y    = 0.5;
    particles[0].z    = 0.5;
    particles[0].mass = 1000 * 2.582e-8;
    pv[0].xold	      = particles[0].x;
    pv[0].yold	      = particles[0].y;
    pv[0].zold	      = particles[0].z;
    pv[0].fx	      = 0;
    pv[0].fy	      = 0;
    pv[0].fz	      = 0;
    }

if (debug_flag && rank == 0) 
    PrintParticles( particles, npart, 0.0 );

dt     = 0.001;
dt_old = 0.001;

/* Create a communication pipe */
MPE_Pipe_create( MPI_COMM_WORLD, particletype, npart, &pipe );

time = MPI_Wtime();
t    = 0.0;
while (cnt--) {
    double max_f, dt_est, new_dt, dt_new;
    
    /* Load the initial sendbuffer */
    MPE_Pipe_start( pipe, particles, npart, 1 );

    /* integation is a0 * x^+ + a1 * x + a2 * x^- = f / m */
    a0   = 2.0 / (dt * (dt + dt_old));
    a2   = 2.0 / (dt_old * (dt + dt_old));
    a1   = -(a0 + a2);      /* also -2/(dt*dt_old) */

    /* Compute self forces (on same processor) */
    max_f = 0;
    ComputeForces( particles, pv, npart, particles, npart, &max_f );
    
    /* For other particles, compute interaction forces */
    for (step=1; step<size; step++) {

	/* Push pipe, get new data */
	MPE_Pipe_push( pipe, &recvbuf, &rlen );

	/* Compute forces */
	ComputeForces( particles, pv, npart, recvbuf, rlen, &max_f );
	}

    /* Once we have the forces, we compute the changes in position */
    for (i=0; i<npart; i++) {
	double xi, yi;
	/* Very, very simple leapfrog time integration.  We use a variable 
	   step version to simplify time-step control. */
	xi	       = particles[i].x;
	yi	       = particles[i].y;
	particles[i].x = (pv[i].fx - a1 * xi - a2 * pv[i].xold) / a0;
	particles[i].y = (pv[i].fy - a1 * yi - a2 * pv[i].yold) / a0;
	pv[i].xold     = xi;
	pv[i].yold     = yi;
	pv[i].fx       = 0;
	pv[i].fy       = 0;
	}

    t += dt;

    if (debug_flag && rank == 0) 
	PrintParticles( particles, npart, t );

    /* Recompute a time step. Stability criteria is roughly 
       2 = dt * sqrt(1/m |df/dx|), or
       2/sqrt(1/m |df/dx|) >= dt.  We leave a little room */
    dt_est = 1.0/sqrt(max_f);
    /* Set a minimum: */
    if (dt_est < 1.0e-6) dt_est = 1.0e-6;
    MPI_Allreduce( &dt_est, &dt_new, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD );
    /* Modify time step */
    if (dt_new < dt) {
	dt_old = dt;
	dt     = dt_new;
	if (debug_flag && rank == 0) 
	    printf( "#New time step is %f\n", dt );
	}
    else if (dt_new > 4.0 * dt) {
	dt_old = dt;
	dt    *= 2.0;
	if (debug_flag && rank == 0) 
	    printf( "#New time step is %f\n", dt );
	}

    /* We could do graphics here (move particles on the display) */
    }
time = MPI_Wtime() - time;
if (rank == 0) {
    printf( "#Computed %d particles in %f seconds\n", totpart, time );
    }

MPE_Pipe_free( &pipe );
MPI_Type_free( &particletype );

MPI_Finalize();
}
