/* This file is part of Cloudy and is copyright (C) 1978-2004 by Gary J. Ferland.
 * For conditions of distribution and use, see copyright notice in license.txt */
/* optimize_amoeba routine for optimizing model parameters */
#include "cddefines.h"
#define	NMAX	20
#include "optimize.h"
/*helper routine for optimize_amoeba */
double AmoebaHelper(float *p, 
  float y[], 
  float SumOfP[], 
  long int mp, 
  long int ndim, 
  double (*optimize_func)(float[]), 
  long int ihi, 
  double fac);

void optimize_amoeba(float *p, 
  float y[], 
  long int mp, 
  long int ndim, 
  double toler, 
  double (*optimize_func)(float[]), 
  long int *iteram)
{
#define P(I_,J_)	(*(p+(I_)*(mp)+(J_)))
	long int i, 
	  ihi, 
	  ilo, 
	  inhi, 
	  j, 
	  m, 
	  n;
	float SumOfP[NMAX]; 
	double  RTolerance, 
	  sum, 
	  swap, 
	  xmin,
	  xmax,
	  ysave, 
	  ytry;

#	ifdef DEBUG_FUN
	fputs( "<+>optimize_amoeba()\n", debug_fp );
#	endif

	/*uses AmoebaHelper,optimize_func */
	*iteram = 0;

L_1:
	for( n=0; n < ndim; n++ )
	{
		sum = 0.;
		for( m=0; m < (ndim + 1); m++ )
		{
			sum += P(n,m);
		}
		SumOfP[n] = (float)sum;
	}

L_2:
	ilo = 1;
	if( y[0] > y[1] )
	{
		ihi = 1;
		inhi = 2;
	}
	else
	{
		ihi = 2;
		inhi = 1;
	}
	for( i=0; i < (ndim + 1); i++ )
	{
		if( y[i] <= y[ilo-1] )
			ilo = i+1;
		if( y[i] > y[ihi-1] )
		{
			inhi = ihi;
			ihi = i+1;
		}
		else if( y[i] > y[inhi-1] )
		{
			if( (i+1) == ihi )
				inhi = i+1;
		}
	}

	/* determine largest dimension of simplex */
	RTolerance = 0.;
	for( i=0; i < ndim; i++ )
	{
		xmin = DBL_MAX;
		xmax = -DBL_MAX;
		for( j=0; j <= ndim; j++ )
		{
			xmax = MAX2(xmax,(double)P(i,j));
			xmin = MIN2(xmin,(double)P(i,j));
		}
		RTolerance = MAX2(RTolerance,xmax-xmin);
	}

	if( RTolerance < toler )
	{
		swap = y[0];
		y[0] = y[ilo-1];
		y[ilo-1] = (float)swap;
		for( n=0; n < ndim; n++ )
		{
			swap = P(n,0);
			P(n,0) = P(n,ilo-1);
			P(n,ilo-1) = (float)swap;
		}
		
#		ifdef DEBUG_FUN
		fputs( " <->optimize_amoeba()\n", debug_fp );
#		endif
		return;
	}

	if( optimize.nOptimiz >= optimize.nIterOptim )
	{
		fprintf( ioQQQ, " Optimizer exceeding maximum iterations.\n This can be reset with the OPTIMZE ITERATIONS command.\n" );
		
#		ifdef DEBUG_FUN
		fputs( " <->optimize_amoeba()\n", debug_fp );
#		endif
		return;
	}

	*iteram += 2;
	ysave = 1.;/* this needed to get through lint */
	ysave = -ysave;
	ytry = AmoebaHelper(p,y,SumOfP,mp,ndim,optimize_func,ihi,ysave);
	if( ytry <= y[ilo-1] )
	{
		ytry = AmoebaHelper(p,y,SumOfP,mp,ndim,optimize_func,ihi,2.0);
	}
	else if( ytry >= y[inhi-1] )
	{
		ysave = y[ihi-1];
		ytry = AmoebaHelper(p,y,SumOfP,mp,ndim,optimize_func,ihi,0.5);
		if( ytry >= ysave )
		{
			for( i=0; i < (ndim + 1); i++ )
			{
				if( (i+1) != ilo )
				{
					for( j=0; j < ndim; j++ )
					{
						SumOfP[j] = (float)(0.5*(P(j,i) + P(j,ilo-1)));

						optimize.vparm[0][j] = (float)SumOfP[j];

						P(j,i) = (float)SumOfP[j];
					}
					y[i] = (float)(*optimize_func)(SumOfP);
				}
			}
			*iteram += ndim;
			goto L_1;
		}
	}
	else
	{
		*iteram -= 1;
	}
	goto L_2;
#undef	P
}

/*helper routine for optimize_amoeba */
double AmoebaHelper(float *p, 
  float y[], 
  float SumOfP[], 
  long int mp, 
  long int ndim, 
  double (*optimize_func)(float[]), 
  long int ihi, 
  double fac)
{
#define P(I_,J_)	(*(p+(I_)*(mp)+(J_)))
	long int j;
	double amotry_v, 
	  fac1, 
	  fac2, 
	  ytry;
	float ptry[NMAX];

#	ifdef DEBUG_FUN
	fputs( "<+>AmoebaHelper()\n", debug_fp );
#	endif
	/*U    USES optimize_func */
	fac1 = (1. - fac)/(double)ndim;
	fac2 = fac1 - fac;
	for( j=0; j < ndim; j++ )
	{
		ptry[j] = (float)(SumOfP[j]*fac1 - P(j,ihi-1)*fac2);
	}
	ytry = (*optimize_func)(ptry);

	if( ytry < y[ihi-1] )
	{
		y[ihi-1] = (float)ytry;
		for( j=0; j < ndim; j++ )
		{
			SumOfP[j] += (float)(-P(j,ihi-1) + ptry[j]);
			P(j,ihi-1) = (float)ptry[j];
		}
	}
	amotry_v = ytry;
	
#	ifdef DEBUG_FUN
	fputs( " <->AmoebaHelper()\n", debug_fp );
#	endif
	return( amotry_v );
#	undef	P
}

