/*
   Project: Adun

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

   Author: 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 "AdunKernel/AdunSimulator.h"

@class NewtonianSimulator;
@class LangevinSimulator;

@implementation AdSimulator

//Subclasses should override this method if they need to peform 
//update related tasks when a systems status changes

- (void) handleChangeInSystemStatus: (NSNotification*) aNotification
{
	NSString* previousStatus, *currentStatus;
	NSDictionary* infoDict;

	//We are interested in systems who change from "active" or to "active"
	
	infoDict = [aNotification userInfo];
	previousStatus = [infoDict objectForKey:@"PreviousStatus"];
	currentStatus = [infoDict objectForKey:@"CurrentStatus"];

	if(!([previousStatus isEqual: @"Active"] || 
		[currentStatus isEqual: @"Active"]))
	{
		return;
	}
	
	//It is simplest to just reacquire the new active (non interaction) subsystems
	
	subsystems = [system systemsOfType: @"Standard" withStatus: @"Active"];
}

- (void) emptyPool
{
	[pool release];
	pool = [[NSAutoreleasePool alloc] init];
}

- (void) endSimulation
{
	endSimulation = YES;
}

- (void) checkFloatingPointErrors
{
	int raised;
	NSMutableDictionary* errorInfo;
	NSError* error;

	/*
	 * We want to detect floating point errors since they
	 * will affect the stability of our simulation.
	 * The errors we are detecting are based on IEEE754 standard
	 * 1 - Invalid Operation
	 * 2 - Division by Zero
	 * 3 - Overflow
	 * 4 - Underflow
	 * 5 - Inexact
	 * However error 5 is common since all irrational and transcendental
	 * numbers are inexact and we may be adding many numbers who differ by more than 
	 * DBL_EPSILON so we wont do anything in this case .
	 * Error 4 leads to a slow loss of precision - however it may occur 
	 * that tiny forces and energies are calculated in the course of
	 * a simulation. In this case we will just log when this happens and 
	 * let Error 2 handle cases where this will lead to a catastrophic error
	 * (due to the result being zero).
	 * See the Arithmetic section of the libc manual for more detail.
	 */

	raised = fetestexcept(floatingPointExceptionMask);

	#ifdef FE_INVALID
	if(raised & FE_INVALID)	
	{
		errorInfo = [NSMutableDictionary dictionary];
		[errorInfo setObject: @"Detected floating point exception during simulation."
			forKey: @"NSLocalizedDescriptionKey"];
		[errorInfo setObject: @"Exception due to an invalid operation."
			forKey: @"AdDetailedDescriptionKey"];
		[errorInfo setObject: @"This is a critical error and likely due to a bug in an underlying algorithm.\n\
Please contact the Adun developers with information regarding the simulation you were running when this occurred.\n\
(Options, System, Results etc)\n"
			forKey: @"NSRecoverySuggestionKey"];
		error = [NSError errorWithDomain: @"AdKernelErrorDomain"
					code: 2
					userInfo: errorInfo];
		feclearexcept(FE_ALL_EXCEPT);
		[[NSException exceptionWithName: @"AdFloatingPointException"
			reason: @"Caught a IEEE74 floating point exception"
			userInfo: [NSDictionary dictionaryWithObject: error
					forKey: @"AdKnownExceptionError"]]
			raise];
	}
	#endif

	#ifdef FE_OVERFLOW
	if(raised & FE_OVERFLOW)
	{
		errorInfo = [NSMutableDictionary dictionary];
		[errorInfo setObject: @"Detected floating point exception during simulation."
			forKey: @"NSLocalizedDescriptionKey"];
		[errorInfo setObject: @"Exception due to overflow"
			forKey: @"AdDetailedDescriptionKey"];
		[errorInfo setObject: @"This error indicates infinities entering the simulation.\n\
This is likely an indication of the simulation exploding due to excessive forces.\nUnrelaxed starting structures are\
a possible explanation.\nIn this case it is recommended you run an initial simulation with a smaller time step to relax\
the molecule.\nIt is also recommened that you examine the data collected so far which will provide more information on\
the cause.\n)"
			forKey: @"NSRecoverySuggestionKey"];
		error = [NSError errorWithDomain: @"AdKernelErrorDomain"
					code: 2
					userInfo: errorInfo];
		feclearexcept(FE_ALL_EXCEPT);
		[[NSException exceptionWithName: @"AdFloatingPointException"
			reason: @"Caught a IEEE74 floating point exception"
			userInfo: [NSDictionary dictionaryWithObject: error
					forKey: @"AdKnownExceptionError"]]
			raise];
	}
	#endif

	#ifdef FE_DIVBYZERO
	if(raised & FE_DIVBYZERO)
	{
		errorInfo = [NSMutableDictionary dictionary];
		[errorInfo setObject: @"Detected floating point exception during simulation."
			forKey: @"NSLocalizedDescriptionKey"];
		[errorInfo setObject: @"Exception due to divide by zero"
			forKey: @"AdDetailedDescriptionKey"];
		[errorInfo setObject: @"This error could be due to a underflow event or a programming bug.\n\
Please contact the Adun developers with information regarding the simulation you were running when this occurred.\n\
(Options, System, Results etc)\n"
			forKey: @"NSRecoverySuggestionKey"];
		error = [NSError errorWithDomain: @"AdKernelErrorDomain"
					code: 2
					userInfo: errorInfo];
		feclearexcept(FE_ALL_EXCEPT);
		[[NSException exceptionWithName: @"AdFloatingPointException"
			reason: @"Caught a IEEE74 floating point exception"
			userInfo: [NSDictionary dictionaryWithObject: error
					forKey: @"AdKnownExceptionError"]]
			raise];
	}
	#endif

	#ifdef FE_UNDERFLOW 
	if(raised & FE_UNDERFLOW)
	{
		NSWarnLog(@"Detected an underflow exception.");
		NSWarnLog(@"This could be the result of extremly small forces and/or energies being calculated.");
		NSWarnLog(@"Such events are not uncommon but will lead to a loss of precision.");
		NSWarnLog(@"It is possible that underflows could lead to zeros entering the calculation and\
'divide by zero' errors as a result\n. However these will be caught independantly if they occur");
		NSWarnLog(@"Continuing simulation");
		feclearexcept(FE_ALL_EXCEPT);
	}
	#endif

	//clear any FE_INEXACT exceptions

	feclearexcept(FE_ALL_EXCEPT);
}


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

Object Creation

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

+ (id) objectForEnvironment: (id) object observes: (BOOL) value
{
	id simulator;

	simulator = nil;
	if(object != nil)
	{
		if([[object valueForKey: @"ExplicitSolvent"] boolValue])
			simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: value];
		else if([[object valueForKey: @"SimulationType"] isEqual: @"Langevin"])
			simulator =  [[LangevinSimulator alloc] initWithEnvironment: object observe: value];
		else if([[object valueForKey: @"SimulationType"] isEqual: @"Newtonian"])
			simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: value];
		else
			[NSException raise: NSInvalidArgumentException
				format: [NSString stringWithFormat: 
				@"Environment contains invalid value for SimulationType - %@." ,
				 [object valueForKey: @"SimulationType"]]];
	}
	else
		simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: value];

	return [simulator autorelease];
}


+ (id) objectForEnvironment: (id) object
{
	id simulator;

	simulator = nil;
	if(object != nil)
	{
		if([[object valueForKey: @"ExplicitSolvent"] boolValue])
			simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: YES];
		else if([[object valueForKey: @"SimulationType"] isEqual: @"Langevin"])
			simulator =  [[LangevinSimulator alloc] initWithEnvironment: object observe: YES];
		else if([[object valueForKey: @"SimulationType"] isEqual: @"Newtonian"])
			simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: YES];
		else
			[NSException raise: NSInvalidArgumentException
				format: [NSString stringWithFormat: 
				@"Environment contains invalid value for SimulationType - %@." ,
				 [object valueForKey: @"SimulationType"]]];
	}
	else
		simulator = [[NewtonianSimulator alloc] initWithEnvironment: object observe: YES];

	return [simulator autorelease];
}

- (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])
	{
		endSimulation = NO;
		if(environment == nil)
		{
			no_of_steps = 1000;
			target_temperature = 300;
			time_step = 1.0;
			checkFPErrorInterval = 10;
		}
		else
		{
			no_of_steps = [[environment valueForKey: @"NumberConfigurations"] intValue];
			target_temperature = [[environment valueForKey: @"TargetTemperature"] doubleValue];
			time_step = [[environment valueForKey: @"TimeStep"] doubleValue];
			checkFPErrorInterval = [[environment valueForKey: @"CheckFPErrorInterval"] intValue];
		}

		NSDebugLLog(@"AdunSimulator", @"The number of steps is %d", no_of_steps);
		timefac = time_step*0.5;
		timefac_sq = timefac*time_step;

		timer = [AdTimer new];	
		[timer sendMessage: @selector(emptyPool)
			toObject: self
			interval: 200
			name: @"Autorelease"];
		[timer sendMessage: @selector(checkFloatingPointErrors)
			toObject: self
			interval: checkFPErrorInterval
			name: @"FloatingPointErrors"];
	}	
	
	return self;
}

- (void) dealloc
{
	[subsystems release];
	[timer release];
	[system release];
	[forceField release];
	[super dealloc];
}

//FIXME: Temporary method
- (void) clearTimer
{
	[timer removeMessageWithName: @"Autorelease"];
	[timer removeMessageWithName: @"FloatingPointErrors"];
}

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

Object Accessors

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

- (void) production
{
	[NSException raise: NSInternalInconsistencyException
		format: [NSString stringWithFormat:
		@"(%@) %@ is an abstract method. You need to use a concrete subclass of this class", 
		NSStringFromClass([self class]), NSStringFromSelector(_cmd)]];
}

- (AdSystemNode*) system
{
	return system;
}

- (void) setSystem: (AdSystemNode*) object 
{
	[notificationCenter removeObserver: self
		name: @"AdSystemStatusDidChangeNotification"
		object: system];

	[system release];
        system = [object retain];
	if(subsystems != nil)
		[subsystems release];
	subsystems = [system systemsOfType: @"Standard"
			withStatus: @"Active"];
	[subsystems retain];		

	[notificationCenter addObserver: self
		selector: @selector(handleChangeInSystemStatus:)
		name: @"AdSystemStatusDidChangeNotification"
		object: system];
		
}

- (AdForceFieldManager*) forceField
{
	return forceField;
}

- (void) setForceField: (AdForceFieldManager*) object 
{
	[forceField release];
	forceField = [object retain];
}

- (void) setNumberOfSteps: (int) aNumber
{
	no_of_steps = aNumber;
	[self updateDependantsOfKey: @"NumberOfConfigurations"];	
}

- (void) setTargetTemperature: (double) aValue
{
	target_temperature = aValue;
}

- (double) targetTemperature
{
	return target_temperature;
}

- (double) timeStep
{
	return time_step;
}

- (void) setTimeStep: (double) stepSize
{
	time_step = stepSize;
	timefac = time_step*0.5;
	timefac_sq = time_step*time_step*0.5;
}

- (int) currentStep
{
	return currentStep;
}

- (int) numberOfSteps
{
	return no_of_steps;
}

- (AdTimer*) timer
{
	return timer;
}

@end
 
