/* $Id: xsh_model_sa.c,v 1.8 2012-06-15 11:11:12 amodigli Exp $
 *
 *Not sure about the copyright stuff here!
 *
 * This program 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 program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * $Author: amodigli $
 * $Date: 2012-06-15 11:11:12 $
 * $Revision: 1.8 $
 * $Name: not supported by cvs2svn $
 */

/* a collection of C routines for general purpose Simulated Annealing
   intended to be the C equivalent of the C++ Simulated Annealing object
   SimAnneal

   Uses Cauchy training

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
/*---------------------------------------------------------------------------*/
/**
 * @addtogroup xsh_model    xsh_model_sa
 *
 * Physical model Simulated Annealing functions
 * See man-page for details.
 */
/*---------------------------------------------------------------------------*/
/**@{*/
/*-----------------------------------------------------------------------------
   								Includes
 -----------------------------------------------------------------------------*/

#include <cpl.h>

#include "xsh_model_kernel.h"
#include "xsh_model_metric.h"

/*----------------------------------------------------------------------------*/

//static char rcsid[] = "@(#)sa.c	1.2 15:54:31 3/30/93   EFC";

#include <stdio.h>
#include <math.h>
#include <float.h>

#include <stdlib.h>		/* for malloc */

/* #include <rands.h> */
#include "xsh_model_r250.h"

#include "xsh_model_sa.h"


/* #define DEBUG */

#ifdef _R250_H_
#define uniform(a,b)    ( a + (b - a) * xsh_dr250() )
#endif

#ifndef HUGE
#define HUGE	HUGE_VAL
#endif

#ifndef PI
#define PI		3.1415626536
#endif

#ifndef PI2
#define PI2		(PI/2.0)
#endif

typedef struct
{
	CostFunction func;
	int dimension, maxit, dwell;
	double *x, *xnew, *xbest;
	float dt, c_jump, K, rho, t0, tscale, range;
	double y, ybest;
} SimAnneal;

static SimAnneal s;

/* iterate a few times at the present temperature */
/* to get to thermal equilibrium */
#ifdef NO_PROTO
static int equilibrate(t, n)
float t;
int n;
#else
static int equilibrate(float t,int n)
#endif
{
	int i, j, equil = 0;
	float ynew, c, delta, tmpval;
	double *xtmp;

	delta = 1.0;
        tmpval = s.rho * t;
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < s.dimension; j++)
		{
			s.xnew[j] = s.x[j] + (tmpval*tan ( uniform( -s.range, s.range ) ));
		}
		/* "energy" */
		ynew = s.func( s.xnew );
		c = ynew - s.y;
		
		if (c < 0.0)		/* keep xnew if energy is reduced */
		{
			xtmp = s.x;
			s.x = s.xnew;
			s.xnew = xtmp;

			s.y = ynew;
			if ( s.y < s.ybest )
			{
				for (j = 0; j < s.dimension; j++)
					s.xbest[j] = s.x[j];
				s.ybest = s.y;
			}

			delta = fabs( c );
			delta = ( s.y != 0.0 ) ? delta / s.y : ( ynew != 0.0 ) ?
					delta / ynew : delta;

			/* equilibrium is defined as a 10% or smaller change
			   in 10 iterations */
			if ( delta < 0.10 )
				equil++;
			else
				equil = 0;


		}
		else
		{
		/* keep xnew with probability, p, if ynew is increased */
/*
			p = exp( - (ynew - s.y) / (s.K * t) );

			if ( p > number(0.0, 1.0) )
			{
				xtmp = s.x;
				s.x = s.xnew;
				s.xnew = xtmp;
				s.y = ynew;
				equil = 0;
			}
			else
*/

				equil++;
		}
		
		if (equil > 9)
			break;



	}

	return i + 1;
}


/* initialize internal variables and define the cost function */
#ifdef NO_PROTO
int xsh_SAInit(f, d)
CostFunction f;
int d;
#else
int xsh_SAInit(CostFunction f, int d)
#endif
{
	int space;
        
	s.func = f;
        s.dimension = d;
        s.t0 = 0.0;
        s.K  = 1.0;
        s.rho = 0.5;
        s.dt  = 0.1;
        s.tscale = 0.1;
        s.maxit = 1;
        s.c_jump = 100.0;
	s.range = PI2;
	s.dwell = 20;

	space = s.dimension * sizeof(double);
        
        if ( (s.x = (double *)cpl_malloc( space ))  == NULL )
        		return 0;
        if ( (s.xnew = (double *)cpl_malloc( space )) == NULL )
        		return 0;
        if ( (s.xbest = (double *)cpl_malloc( space )) == NULL )
        		return 0;

        s.y = s.ybest = HUGE;

#ifdef _R250_H_
	xsh_r250_init( 15256 );
#endif

   	return 1;
        
}

void xsh_SAfree(void)
{
	cpl_free( s.x );
        cpl_free( s.xnew );
        cpl_free( s.xbest );
        s.dimension = 0;
}

#ifdef NO_PROTO
int xsh_SAiterations(m)
int m;
#else
int xsh_SAiterations(int m)
#endif
{
	if ( m > 0 )
        	s.maxit = m;

       return s.maxit;
}

#ifdef NO_PROTO
int xsh_SAdwell(m)
int m;
#else
int xsh_SAdwell(int m)
#endif
{
        if ( m > 0 )
                s.dwell = m;

       return s.dwell;
}



#ifdef NO_PROTO
float xsh_SABoltzmann(k)
float k;
#else
float xsh_SABoltzmann(float k)
#endif
{
	if ( k > 0.0 )
        	s.K = k;

       return s.K;
}

#ifdef NO_PROTO
float xsh_SAtemperature(t)
float t;
#else
float xsh_SAtemperature(float t)
#endif
{
	if ( t > 0.0 )
        	s.t0 = t;

       return s.t0;
}

#ifdef NO_PROTO
float xsh_SAlearning_rate(r)
float r;
#else
float xsh_SAlearning_rate(float r)
#endif
{
	if ( r > 0.0 )
        	s.rho = r;

       return s.rho;
}

#ifdef NO_PROTO
float xsh_SAjump(j)
float j;
#else
float xsh_SAjump(float j)
#endif
{
	if ( j > 0.0 )
        	s.c_jump = j;

       return s.c_jump;
}

#ifdef NO_PROTO
float xsh_SArange(r)
float r;
#else
float xsh_SArange(float r)
#endif
{
        if ( r > 0.0 )
                s.range = r;

       return s.range;
}


#ifdef NO_PROTO
void xsh_SAinitial(xi)
float* xi;
#else
void xsh_SAinitial(double* xi)
#endif
{
	int k;
	for (k = 0; k < s.dimension; k++)
		s.x[k] = xi[k];
}

#ifdef NO_PROTO
void xsh_SAcurrent(xc)
float* xc;
#else
void xsh_SAcurrent(double* xc)
#endif
{
	int k;
        
	for (k = 0; k < s.dimension; k++)
		xc[k] = s.x[k];
}

#ifdef NO_PROTO
void xsh_SAoptimum(xb)
float* xb;
#else
void xsh_SAoptimum(double* xb)
#endif
{
	int k;
        
	for (k = 0; k < s.dimension; k++)
		xb[k] = s.xbest[k];
}




/* increase the temperature until the system "melts" */
#ifdef NO_PROTO
float xsh_SAmelt(iters)
int iters;
#else
float xsh_SAmelt(int iters)	
#endif
{
	int i, j, ok = 0;
	float ynew, t, cold = 0.0, c = 0.0, tmpval;

	int n = iters;
	if ( n < 1 )
		n = s.maxit;

	t = s.t0;

	for (i = 0; i < n; i++)
	{
		if (i > 0 && c > 0.0)
		{
			cold = c;
			ok = 1;
		}
		
		t += s.dt;

                tmpval = s.rho*t;
		for (j = 0; j < s.dimension; j++)
		{
			s.x[j] += tmpval * tan ( uniform( -s.range, s.range ) );
		}

		equilibrate( t, s.dwell);
		
		/* "energy" */
		ynew = s.func( s.x );
		c = ynew - s.y;

		if ( c < 0.0 && ynew < s.ybest)
		{
			for (j = 0; j < s.dimension; j++)
				s.xbest[j] = s.x[j];
			s.ybest = ynew;
		}

		s.y = ynew;

		if ( ok && c > (s.c_jump * cold) )	/* phase transition */
			break;

	}

	return s.t0 = t;
	
}

/* cool the system with annealing */
#ifdef NO_PROTO
float xsh_SAanneal(iters)
#else
float xsh_SAanneal(int iters)
#endif
{
	int i, j;
	float p, ynew, t=0.0, c, dt, told, tmpval;
	double *xtmp;


	int n = iters;
	if ( n < 1 )
		n = s.maxit;

	equilibrate( s.t0, 10 * s.dwell );

	told = s.t0;
	for (i = 0; i < n; i++)
	{
		t = s.t0 /(1.0 + i * s.tscale);
		dt = t - told;
		told = t;

		equilibrate(t, s.dwell);

                tmpval = s.rho * t;
		for (j = 0; j < s.dimension; j++)
	        {
/* 			s.xnew[j] = s.x[j] + xc; */
		        s.xnew[j] = tmpval * tan ( uniform( -s.range, s.range ) ); 
		}      
		/* "energy" */
           
		ynew = s.func( s.xnew );
		c = ynew - s.y;
		
		if (ynew <= s.y)	/* keep xnew if energy is reduced */
		{
			xtmp = s.x;
			s.x = s.xnew;
			s.xnew = xtmp;
			s.y = ynew;

			if ( s.y < s.ybest )
			{
				for (j = 0; j < s.dimension; j++)
					s.xbest[j] = s.x[j];
				s.ybest = s.y;
			}
			continue;
		}
		else
		{
		/* keep xnew with probability, p, if ynew is increased */
			p = exp( - (ynew - s.y) / (s.K * t) );

			if ( p > uniform(0.0, 1.0) )
			{
				xtmp = s.x;
				s.x = s.xnew;
				s.xnew = xtmp;
				s.y = ynew;
			}
		}

	}

	equilibrate( t, 10 * s.dwell );
	
	return s.t0 = t;
}

/**@}*/
