/*****************************************************************************
*     FILE: fitness.c: This file contains the routines that have to do with
*                      fitness calculations.
*
*     Authors: David M. Levine, Philip L. Hallstrom, David M. Noelle
*****************************************************************************/

#include "pgapack.h"


/*U****************************************************************************
   PGAEvaluate - Calls a user-specified function to return an evaluation of
   each string in the population. The user-specified function is only called
   if the string has been changed (e.g., by crossover or mutation) or the user
   has explicitly signaled the string's evaluation is out-of-date by a call
   to PGASetEvalUpToDate with argument PGAFALSE

   Category: Fitness

   Inputs:
      ctx  - context variable
      pop  - symbolic constant of the population to be evaluated
      f    - a pointer to a function to evaluate a string.  This function will
             be called once for each string in population pop that requires
             evaluation.  This function must return a double (the evaluation
             function value) and must have an argument list of the exact form
             double f (PGAContext *c, int p, int pop)

   Outputs:
      Evaluates the population via side effect

   Example:
      To evaluate all string in population PGA_NEWPOP using the user-defined
      evaluation function energy use
      
      double energy ( PGAContext *ctx, int p, int pop);
      PGAContext *ctx;
      :
      PGAEvaluate(ctx, PGA_NEWPOP, energy);

****************************************************************************U*/
void PGAEvaluate ( PGAContext *ctx, int pop,
                   double (*f)(PGAContext *c, int p, int pop)  )
{
    int p, fp;
    double e;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAEvaluate", "Entered", 
                   PGA_VOID, NULL );

    for ( p=0; p<ctx->ga.PopSize; p++ )
        if ( !PGAGetEvalUpToDate ( ctx, p, pop ) ) {
            if ( ctx->sys.fortran == PGATRUE ) {   /* calling Fortran */
                fp = p+1;
                e = (*f)((PGAContext *)&ctx, (int)&fp, (int)&pop);
            }
            else                                   /* calling C       */
                e = (*f)(ctx, p, pop);   
            PGASetEvaluate ( ctx, p, pop, e );
        }
        
    PGADebugPrint( ctx, PGADEBUG_EXIT,    "PGAEvaluate", "Exit", 
                   PGA_VOID, NULL );
    return;
}


/*U****************************************************************************
  PGASetEvaluate - updates the evaluation function value and associated
  flag for a string

  Category: Fitness

  Inputs:
     ctx  - context variable
     p    - string index
     pop  - symbolic constant of the population string p is in
     val  - the (user) evaluation value to assign to string p

  Outputs:
     Sets the evaluation function value of string p and the EvalUpToDate
     flag (to PGATRUE) via side effect

  Example:
     To set the evaluation function value of string p in population PGA_NEWPOP
     to 123.456 use
      
     PGAContext *ctx;
     int p;
     :
     PGASetEvaluate(ctx, p, PGA_NEWPOP, 123.456);


****************************************************************************U*/
void PGASetEvaluate ( PGAContext *ctx, int p, int pop, double val )
{
    PGAIndividual *ind;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGASetEvaluate", "Entered", 
                   PGA_VOID, NULL );
    PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGASetEvaluate", "p = ", 
                   PGA_INT, (void *) &p );
    PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGASetEvaluate", "pop = ", 
                   PGA_INT, (void *) &pop );
    PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGASetEvaluate", "val = ", 
                   PGA_DOUBLE, (void *) &val );
    
    ind               = PGAGetIndividual ( ctx, p, pop );
    ind->evalfunc     = val;
    ind->evaluptodate = PGATRUE;

    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGASetEvaluate", "Exit", 
                   PGA_VOID, NULL );
}


/*U****************************************************************************
    PGASetEvalUpToDate - sets the flag associated with a string to PGATRUE or
    PGAFLASE to indicate whether the evaluate function value is out-of-date or
    not.  Note that this flag is always set to PGATRUE when PGASetEvaluate is
    called.
    
    Category: Fitness

    Inputs:
      ctx  - context variable
      p    - string index
      pop  - symbolic constant of the population string p is in
      status - boolean for whether up-to-date

   Outputs:
      Sets the EvalUpToDate associated with the evaluation function value of
      string p via side effect

   Example:
      To set the evaluation function flag for string p in population PGA_NEWPOP
      to PGAFALSE (as might happen after, for example, calling a hill-climbing
      routine that modified this string) use
      
      PGAContext *ctx;
      int p;
      :
      PGASetEvalUpToDate(ctx, p, PGA_NEWPOP, PGAFALSE);

****************************************************************************U*/
void PGASetEvalUpToDate ( PGAContext *ctx, int p, int pop, int status )
{
    PGAIndividual *ind;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGASetEvalUpToDate", "Entered", 
                   PGA_VOID, NULL );
    PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGASetEvalUpToDate", "p = ", 
                   PGA_INT, (void *) &p );
    PGADebugPrint( ctx, PGADEBUG_PRINTVAR,"PGASetEvalUpToDate", "pop = ", 
                   PGA_INT, (void *) &pop );
    
    ind               = PGAGetIndividual ( ctx, p, pop );
    switch (status)
    {
    case PGATRUE:
    case PGAFALSE:
	 ind->evaluptodate = status;
	 break;
    default:
	 PGAError(ctx, "PGASetEvalUpToDate: Invalid value of status:",
		  PGA_FATAL, PGA_INT, (void *) &status);
	 break;
    }

    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGASetEvalUpToDate", "Exit", 
                   PGA_VOID, NULL );
}

/*U****************************************************************************
  PGAFitness - Maps the user's evaluation function value to a fitness value.
  First, the user's evaluation function value is translated to all positive
  values if any are negative.  Next, this positive sequence is translated to
  a maximization problem if the user's optimization direction was minimization.
  This positive sequence is then mapped to a fitness value using linear
  ranking, linear normalization fitness, or the identity (i.e., the evaluation
  function value).  This routine is usually used after PGAEvaluate is called.
  
  Category: Fitness

  Inputs:
    ctx  - context variable
    pop  - symbolic constant of the population to calculate fitness for

  Outputs:
     Calculates the fitness for each string in the population via side effect

  Example:
     To calculate the fitness of all strings in population PGA_NEWPOP after
     calling PGAEvaluate (using the user-defined evaluation function energy)
     use
      
     double energy ( PGAContext *ctx, int p, int pop);
     PGAContext *ctx;
     :
     PGAEvaluate(ctx, PGA_NEWPOP, energy);
     PGAFitness (ctx, PGA_NEWPOP);

****************************************************************************U*/
void PGAFitness ( PGAContext *ctx, int popindex )
{
    int i;
    double mineval;
    PGAIndividual *pop;      

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitness", "Entered", 
                   PGA_VOID, NULL );

    /* set pointer to appropriate population */

    switch (popindex) {
    case PGA_OLDPOP:                
        pop = ctx->ga.oldpop;
        break;
    case PGA_NEWPOP:                
        pop = ctx->ga.newpop;
        break;
    default:
        PGAError( ctx, "PGAFitness: Invalid value of popindex:",
                  PGA_FATAL, PGA_INT, (void *) &popindex );
        break;
    }        

    /* make sure all evaluation function values are up-to-date */

    for( i=0; i<ctx->ga.PopSize; i++ ) {
        /*printf("i = %d, evaluptodate = %d\n",i,(pop+i)->evaluptodate);*/
        if ( (pop+i)->evaluptodate != PGATRUE )
            PGAError( ctx, "PGAFitness: evaluptodate not PGATRUE for:",
                      PGA_FATAL, PGA_INT, (void *) &i );
    }
 
    /* put raw fitness into fitness field */

    for( i=0; i<ctx->ga.PopSize; i++ )
        (pop+i)->fitness = (pop+i)->evalfunc;
 
    /* translate to all positive sequence (if necessary) */

    mineval = ctx->sys.PGAMaxDouble;
    for( i=0; i<ctx->ga.PopSize; i++ )
        if ( (pop+i)->fitness < mineval )
            mineval =(pop+i)->fitness;
    if ( mineval < 0.0 ) {
        mineval = (-1.01) * mineval;
        for( i=0; i<ctx->ga.PopSize; i++ )
           (pop+i)->fitness  = (pop+i)->fitness + mineval;
    }

    /* translate to maximization problem  (if necessary) */

    if ( ctx->ga.optdir == PGA_MINIMIZE ) {
        switch (ctx->ga.FitnessMinType) {
        case PGA_FITNESSMIN_RECIPROCAL:
            PGAFitnessMinRecprl( ctx, pop );
            break;
        case PGA_FITNESSMIN_CMAX:
            PGAFitnessMinCmax      ( ctx, pop );
            break;
        default:
            PGAError( ctx,
                     "PGAFitness: Invalid FitnessMinType:",
                      PGA_FATAL,
                      PGA_INT,
                      (void *) &(ctx->ga.FitnessMinType) );
            break;
        }
    }

    /* last step in fitness calculation */

    switch (ctx->ga.FitnessType) {
    case PGA_FITNESS_RAW:
        break;
    case PGA_FITNESS_NORMAL:
        PGAFitnessLinNor    ( ctx, pop );
        break;
    case PGA_FITNESS_RANKING:
        PGAFitnessLinRank   ( ctx, pop );
        break;
    default:
        PGAError( ctx,
                 "PGAFitness: Invalid FitnessType:",
                  PGA_FATAL,
                  PGA_INT,
                  (void *) &(ctx->ga.FitnessType) );
        break;
    }
    
    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAFitness", "Exit", 
                   PGA_VOID, NULL );
    return;
}

/*I****************************************************************************
  PGAFitnessLinNor - Calculates fitness using a ranking method and linear`
  ordering. The fitness function is of the form u(x) = K - ( rank * sigma )
  with the constant K equal to the mean of the evaluation functions, and the
  decrement sigma equal to the standard deviation of the same.
  Ref:    L. Davis, Handbook of Genetic Algorithms, pg. 33

  Inputs:
    ctx  - context variable
    pop  - population pointer to calculate fitness for

  Outputs:
     Calculates the fitness for each string in the population via side effect

  Example:

****************************************************************************I*/
void PGAFitnessLinNor ( PGAContext *ctx, PGAIndividual *pop )
{

    int i;
    double K, sigma, mean;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitnessLinNor", "Entered", 
                   PGA_VOID, NULL );

    /* fill arrays for sorting */
    
    for(i=0;i<ctx->ga.PopSize;i++) {
        ctx->scratch.dblscratch[i] = (pop+i)->fitness;
        ctx->scratch.intscratch[i] =                i;
    }

    /* calculate parameters for linear normalization */

    mean  = PGAMean   ( ctx, ctx->scratch.dblscratch, ctx->ga.PopSize  );
    sigma = PGAStddev ( ctx, ctx->scratch.dblscratch, ctx->ga.PopSize, mean );
    if (sigma == 0)
	 sigma = 1;
    K     = sigma * (double) ctx->ga.PopSize;
    PGADblHeapSort ( ctx, ctx->scratch.dblscratch,
		  ctx->scratch.intscratch,
		  ctx->ga.PopSize);

    for( i=0; i<ctx->ga.PopSize; i++ )
        (pop+i)->fitness = K - ( sigma *
            (double) PGARank(ctx,i,ctx->scratch.intscratch,ctx->ga.PopSize) );

    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAFitnessLinNor", "Exit", 
                   PGA_VOID, NULL );
    return;
}

/*I****************************************************************************
  PGAFitnessLinRank - Calculates fitness using linear ranking. The fitness
  function is of the form 1/N * ( max - (max-min) * ( (i-1)/(N-1) ) ) where
  min = 2-max and 1 <= max <= 2.
  Ref:    J. Baker: Adaptive selection methods for GAs
  Ref:    J. Baker: Extended selection mechanism in GAs
  Ref:    J. Grefenstte: A critical look at implicit parallelism
  Ref:    D. Whitley's linear() function on pp. 121 of ICGA

  Inputs:
    ctx  - context variable
    pop  - population pointer to calculate fitness for

  Outputs:
     Calculates the fitness for each string in the population via side effect

  Example:
****************************************************************************I*/
void PGAFitnessLinRank ( PGAContext *ctx, PGAIndividual *pop )
{
    double max, min, popsize, rpopsize;
    int i;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitnessLinRank", "Entered", 
                   PGA_VOID, NULL );

    max      = ctx->ga.FitnessRankMax;
    min      = 2. - max;
    popsize  = (double) ctx->ga.PopSize;
    rpopsize = 1.0/popsize;

    for(i=0;i<ctx->ga.PopSize;i++) {
        ctx->scratch.dblscratch[i] = (pop+i)->fitness;
        ctx->scratch.intscratch[i] =                i;
    }

    PGADblHeapSort ( ctx, ctx->scratch.dblscratch,
		  ctx->scratch.intscratch,
		  ctx->ga.PopSize);

    for(i=0;i<ctx->ga.PopSize;i++) {
        (pop+i)->fitness = rpopsize * ( max -
        ( (max - min) *
        ( ( (double) PGARank(ctx,i,ctx->scratch.intscratch,ctx->ga.PopSize)
	     - 1. ) / ( popsize - 1. ) ) ) );
                     
    }

    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAFitnessLinRank", "Exit", 
                   PGA_VOID, NULL );
    return;
}


/*I****************************************************************************
  PGAFitnessMinRecprl - Calculates fitness in the case of a minimization
  problem using the reciprocal of the evaluation function. This is a power law
  u(x) = ( a f(x) + b )^k with a=1, b=0, k=-1

  Inputs:
    ctx  - context variable
    pop  - population pointer to calculate fitness for

  Outputs:
     Calculates the fitness for each string in the population via side effect

  Example:
****************************************************************************I*/
void PGAFitnessMinRecprl ( PGAContext *ctx, PGAIndividual *pop )
{
    int i;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitnessMinRecprl", "Entered", 
                   PGA_VOID, NULL );

    for( i=0; i<ctx->ga.PopSize; i++ ) {
        if ( (pop+i)->fitness != 0. )
            (pop+i)->fitness = 1. / (pop+i)->fitness;
        else 
            PGAError( ctx,
                     "PGAFitnessReciprocal: Value 0.0 for fitness member:",
                      PGA_FATAL,
                      PGA_INT,
                     (void *) &i );
    }

    PGADebugPrint( ctx, PGADEBUG_EXIT, "PGAFitnessMinRecprl", "Exit", 
                   PGA_VOID, NULL );
    return;
}


/*I****************************************************************************
  PGAFitnessMinCmax - Calculates fitness in the case of a minimization
  problem by subtracting the worst evaluation function value from each
  evaluation function.  This is a dynamic linear fitness function
  u(x) = a f(x) + b(t) with a=-1, b(t) = 1.1 * max f(x)

  Inputs:
    ctx  - context variable
    pop  - population pointer to calculate fitness for

  Outputs:
     Calculates the fitness for each string in the population via side effect

  Example:
****************************************************************************I*/
void PGAFitnessMinCmax ( PGAContext *ctx, PGAIndividual *pop )
{
    int i;
    double cmax;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitnessMinCmax", "Entered", 
                   PGA_VOID, NULL );

    cmax = 0.;

    for(i=0; i<ctx->ga.PopSize; i++)
        if ( (pop+i)->evalfunc > cmax )
            cmax = (pop+i)->evalfunc;

    cmax *= 1.01;  /* so worst string has nonzero fitness */

    for(i=0;i<ctx->ga.PopSize;i++)
        (pop+i)->fitness = cmax - (pop+i)->evalfunc;
    
    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGAFitnessMinCmax", "Entered", 
                   PGA_VOID, NULL );

    return;
}


/*U****************************************************************************
  PGAMean - calculates the mean value of an array of elements

  Category: System

  Inputs:
    ctx  - context variable
    a    - array to take the mean of
    n    - number of elements in array a

  Outputs:
    The mean of the n elements in array a

  Example:
    PGAContext *ctx;
    double a[n],mean;
    :
    mean = PGAMean(ctx,a,n);

****************************************************************************U*/
double PGAMean ( PGAContext *ctx, double *a, int n)
{
    int i;
    double result;

    result = 0.;
    for( i=0; i<n; i++ )
        result += a[i];

    return (result / (double) n );
}


/*U****************************************************************************
  PGAStddev - calculates the standard deviation of an array of elements

  Category: System

  Inputs:
    ctx  - context variable
    a    - array to take the standard deviation of
    n    - number of elements in array a
    mean - the mean of the elements in array a

  Outputs:
    The standard deviation of the n elements in array a

  Example:
    PGAContext *ctx;
    double a[n], mean, sigma;
    :
    mean  = PGAMean(ctx,a,n);
    sigma = PGAStddev(ctx,a,n,mean);

****************************************************************************U*/
double PGAStddev ( PGAContext *ctx, double *a, int n, double mean)
{
    int i;
    double result;

    result = 0.;
    for( i=0; i<n; i++ )
        result += (a[i] - mean) * (a[i] - mean);
    result /= n;
    result  = sqrt(result);

    return (result);
}


/*U****************************************************************************
  PGARank - returns the rank of a string in a population.  This is a value
  between 1,...,N (the population size).  The most fit string has rank 1.
  The least fit string has rank N.

  Category: System

  Inputs:
    ctx   - context variable
    p     - the index of the string whose rank is desired
    order - an array containing a unique rank for each string
    n     - the size of the array order

  Outputs:
    The rank of string p

  Example:
    To determine the rank of string p use
        
    PGAContext *ctx;
    int i, popsize, rank, *order;
    double *fitness;

    popsize = PGAGetPopsize(ctx);
    order   = (int *)   malloc(sizeof(int)    * popsize);
    fitness = (double *)malloc(sizeof(double) * popsize);

    for(i=0;i<popsize; i++) {
        fitness[i] = PGAGetFitness(ctx,p,PGA_OLDPOP);
        order[i]   = i;
    }
        
    PGADblHeapSort ( ctx, fitness, order, popsize);
    rank = PGARank(ctx,p,order,n)
****************************************************************************U*/
int PGARank( PGAContext *ctx, int p, int *order, int n )
{
    int i;

    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGARank", "Entered", 
                   PGA_VOID, NULL );

    for(i=0; i<n; i++)
        if ( order[i] == p )
            return(i+1);
    PGAError( ctx,
              "PGARank: Bottom of loop in rank, p = ",
                      PGA_FATAL,
                      PGA_INT,
                     (void *) &p );
    return(-1);
}
