/*
   Project: Adun

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   Created: 2005-06-23 11:06:55 +0200 by michael johnston

   This application 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 2 of the License, or (at your option) any later version.

   This application 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
   Library General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include "NewtonianSimulator.h"

inline static double temp_factor_calc(double, double, double);

@implementation NewtonianSimulator

/**************************

Object Creation and Maintainence

****************************/

- (id) init
{
	return [self initWithEnvironment: nil];
}

- (id) initWithEnvironment: (id) object
{
	return [self initWithEnvironment: object observe: YES];
}

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(self = [super initWithEnvironment: object observe: value])
	{
		if(environment != nil)
			coupling_fac = [[environment valueForKey: @"CouplingFactor"] doubleValue];
		else
			coupling_fac = 100;
	
		time_p_coup = (double)time_step/coupling_fac;
	}

	return self;
}

- (void) dealloc
{
	[super dealloc];
}

/*************************

Public Methods

*************************/

- (void) production
{
	register int j, k;
	int no_of_atoms;
	double temp_factor;
	double** coords, **vel, **accel;
	AdMatrix* coordinates, *accelerations, *velocities;
	NSEnumerator* subsystemEnum;
	id subsystem;
	
	pool = [[NSAutoreleasePool alloc] init];
	
	GSPrintf(stderr, @"Initial energy calculation\n");
	[forceField calculateAccelerations];
	
	//Zero the accelerations
	//FIXME: Should we use these initial accelerations?
	
	subsystemEnum = [subsystems objectEnumerator];
	while(subsystem = [subsystemEnum nextObject])
		[subsystem zeroAccelerations];

	[system update];

	GSPrintf(stderr, @"Begining Simulation\n");

	for(currentStep=0; currentStep < no_of_steps; currentStep++)
	{
		subsystemEnum = [subsystems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
		{
			coordinates = [[subsystem coordinates] pointerValue];
			coords = coordinates->matrix;
			velocities = [[subsystem velocities] pointerValue];
			vel = velocities->matrix;
			accelerations = [[subsystem accelerations] pointerValue];
			accel = accelerations->matrix;
			no_of_atoms = coordinates->no_rows;

			for(k=0; k < no_of_atoms; k++)
				for(j=0; j< 3; j++)	
					coords[k][j] += vel[k][j]*time_step + accel[k][j]*timefac_sq;

			for(j=0; j < no_of_atoms; j++)
				for(k=0; k<3; k++)
					vel[j][k] +=  accel[j][k]*timefac;
		}

		[forceField calculateAccelerations];
		
		subsystemEnum = [subsystems objectEnumerator];
		while(subsystem = [subsystemEnum nextObject])
		{
			coordinates = [[subsystem coordinates] pointerValue];
			coords = coordinates->matrix;
			velocities = [[subsystem velocities] pointerValue];
			vel = velocities->matrix;
			accelerations = [[subsystem accelerations] pointerValue];
			accel = accelerations->matrix;
			no_of_atoms = coordinates->no_rows;

			for(j=0; j < no_of_atoms; j++)
				for(k=0; k<3; k++)
					vel[j][k] += accel[j][k]*timefac;
			//scale velocities		
			
			temp_factor = temp_factor_calc(time_p_coup, 
					target_temperature, 
					[subsystem temperature]);
			
			for(j=0; j < no_of_atoms; j++)
				for(k=0; k<3; k++)
					vel[j][k] *= temp_factor;
		}
		
		[system frameUpdate];
		[timer increment];

		//Don't like this but it seems to be the simplest
		//and cleanest way to get the loop to exit when 
		//you want it to
		if(endSimulation)
			break;
	}

	[pool release];
}


- (void) setTimeStep: (double) stepSize
{
	[super setTimeStep: stepSize];
	time_p_coup = (double)time_step/coupling_fac;
}

- (void) setCouplingFactor: (double) value
{
	coupling_fac = value;
	time_p_coup = (double)time_step/coupling_fac;
}

- (double) couplingFactor
{
	return coupling_fac;
}
	
@end

inline static double temp_factor_calc(double coupling, double target_temp, double temp)
{
	double factor;
	
	if(temp == 0)
		return 0;
	else
		factor = 1 - coupling*(1 - (target_temp/temp));
	
	return sqrt(factor);
}
