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

#include "pgapack.h"

/*U****************************************************************************
  PGASelect - performs genetic algorithm selection using either the default
  selection scheme or that specified with PGASetSelectType.  Choices are
  PGA_SELECT_PROPORTIONAL, PGA_SELECT_SUS, PGA_SELECT_TOURNAMENT, or 
  PGA_SELECT_PTOURNAMENT for proportional, stochastic universal, tournament,
  or probabilistic tournament selection, respectively.  This function updates
  an internal array with the indices of members of popix selected for
  recombination.  These indices may be accessed with PGASelectNext

  Category: Selection

  Inputs:
    ctx   - context variable
    popix - symbolic constant of population to select from

  Outputs:
    none

  Example:
    PGAContext *ctx,
    :
    PGASelect(ctx, PGA_OLDPOP);

****************************************************************************U*/
void PGASelect( PGAContext *ctx, int popix )
{
    int i;                   /* not to intefere with dummy argument        */
    int j;                   /* random number                              */
    int temp;                /* for shuffling selected indices US          */
    PGAIndividual *pop;      /* pointer to appropriate population          */

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

    pop = PGAPopAddress(ctx, popix);

    switch (ctx->ga.SelectType) {
        
    case PGA_SELECT_PROPORTIONAL:  /* proportional selection             */
        for (i=0; i<ctx->ga.PopSize; i++)
            ctx->ga.selected[i] = PGASelectProportional( ctx, pop );
        break;
    case PGA_SELECT_SUS:           /* stochastic universal selection     */
        PGASelectSUS( ctx, pop );
        break;
    case PGA_SELECT_TOURNAMENT:    /* tournament selection               */
        for (i=0; i<ctx->ga.PopSize; i++)
            ctx->ga.selected[i] = PGASelectTournament( ctx, pop );
        break;
    case PGA_SELECT_PTOURNAMENT:   /* probabilistic tournament selection */
        for (i=0; i<ctx->ga.PopSize; i++)
            ctx->ga.selected[i] = PGASelectPTournament( ctx, pop );
        break;
    default:
        PGAError( ctx,
                 "PGASelect: Invalid value of SelectType:",
                  PGA_FATAL,
                  PGA_INT,
                  (void *) &(ctx->ga.SelectType) );
        break;
    }

    /*if ( ctx->debug.PGADebugLevel >= 30 ) {*/
/*        fprintf(fp, "---selected before sorting\n");
        for (i=0; i<ctx->ga.PopSize; i++)
            fprintf(fp, "selected[%d] = %d\n",i,ctx->ga.selected[i]);*/
    /*}*/

    /* randomize selected string locations */
    
    for (i=0; i<ctx->ga.PopSize; i++) {
        j          = PGARandomInterval(ctx, 0,ctx->ga.PopSize-1);
        temp       = ctx->ga.selected[j];
        ctx->ga.selected[j] = ctx->ga.selected[i];
        ctx->ga.selected[i] = temp;
    }

    /*if ( ctx->debug.PGADebugLevel >= 30 ) {*/
/*        fprintf(fp, "---selected after sorting\n");
        for (i=0; i<ctx->ga.PopSize; i++)
            fprintf(fp, "selected[%d] = %d\n",i,ctx->ga.selected[i]);*/
    /*}*/

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

/*I****************************************************************************
  PGASelectProportional - selects a parent for the next generation using a
  linear search through a (fitness) weighted ``roulette wheel ''.  The
  probability of selection is given by p_i = f_i/sum(i)f_i
  Ref: D. Goldberg, Genetic Algorithms, pg.

  Inputs:
    ctx   - context variable
    popix - symbolic constant of population to select from

  Outputs:
    index of the selected string

  Example:
    PGAContext *ctx,
    int l;
    :
    l = PGASelectProportional(ctx, PGA_OLDPOP);

****************************************************************************I*/
int PGASelectProportional(PGAContext *ctx, PGAIndividual *pop)
{
    double sum, sumfitness, r;
    int i;

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

    sumfitness = 0.0;
    for (i=0; i<ctx->ga.PopSize; i++)
        sumfitness += (pop+i)->fitness;
    
    i = 0;
    sum = (pop+i)->fitness;

    r = sumfitness * PGARandom01(ctx, 0);
    while(r > sum || i==ctx->ga.PopSize) {
        i++;
        sum += (pop+i)->fitness;
    }
    return(i);
}

/*I****************************************************************************
  PGASelectSUS - A select routine using stochastic universal sampling
  Ref:    J. Baker, Reducing Bias and Inefficiency in the Selection Algorithm.
  Second GA conference, pp 14-21 (page 16)

  Inputs:
    ctx   - context variable
    popix - symbolic constant of population to select from

  Outputs:
    the array ga.selected[] created via side effect.  I.e., this routine
    creates the entire selected population with one call

  Example:
    PGAContext *ctx,
    :
    PGASelectSUS(ctx, PGA_OLDPOP);

****************************************************************************I*/
void PGASelectSUS( PGAContext *ctx, PGAIndividual *pop )
{
    int i;
    int k;                          /* index to fill samples array    */
    double davg;                    /* population average fitness     */
    double sum;                     /* running sum of expected values */
    double r;                       /* random number                  */

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

    /* fill the expected value array */
    davg = 0.0;
    for(i=0;i<ctx->ga.PopSize;i++)
        davg += (pop+i)->fitness;
    davg /=  (double) ctx->ga.PopSize;
    for(i=0;i<ctx->ga.PopSize;i++)
        ctx->scratch.dblscratch[i] = (pop+i)->fitness / davg;

    /* select ctx->ga.PopSize as follows */
    sum = 0;
    k   = 0;
    r   = PGARandom01(ctx, 0);
    for(i=0;i<ctx->ga.PopSize;i++)
        for( sum+=ctx->scratch.dblscratch[i]; sum>r; r++ )
            ctx->ga.selected[k++] = i;
}


/*I****************************************************************************
  PGASelectTournament - chooses two strings randomly and returns the one with
  higher fitness
  Ref:    D. Goldberg, Genetic Algorithms, pg. 121

  Inputs:
    ctx   - context variable
    popix - symbolic constant of population to select from

  Outputs:
    index of the selected string

  Example:
    PGAContext *ctx,
    int l;
    :
    l = PGASelectTournament(ctx, PGA_OLDPOP);

****************************************************************************I*/
int PGASelectTournament( PGAContext *ctx, PGAIndividual *pop )
{
    int m1, m2;

    PGADebugPrint( ctx, PGADEBUG_ENTEREDHIGH, "PGASelectTournament", "Entered",
		  PGA_VOID, NULL );

    m1 = PGARandomInterval(ctx, 0, ctx->ga.PopSize-1);
    m2 = PGARandomInterval(ctx, 0, ctx->ga.PopSize-1);

    if ( (pop+m1)->fitness > (pop+m2)->fitness )
        return (m1);
    else
        return (m2);
}

/*I****************************************************************************
  PGASelectPTournament - chooses two strings randomly and returns the one with
  higher fitness with a specified probability
  Ref:    D. Goldberg, Genetic Algorithms, pg. 121

  Inputs:
    ctx   - context variable
    popix - symbolic constant of population to select from

  Outputs:
    index of the selected string

  Example:
    PGAContext *ctx,
    int l;
    :
    l = PGASelectPTournament(ctx, PGA_OLDPOP);

****************************************************************************I*/
int PGASelectPTournament( PGAContext *ctx, PGAIndividual *pop )
{
    int m1, m2;

    PGADebugPrint( ctx, PGADEBUG_ENTEREDHIGH, "PGASelectPTournament",
                   "Entered", PGA_VOID, NULL );
    

    m1 = PGARandomInterval(ctx, 0, ctx->ga.PopSize-1);
    m2 = PGARandomInterval(ctx, 0, ctx->ga.PopSize-1);

    if ( (pop+m1)->fitness > (pop+m2)->fitness )
        if ( (double) PGARandom01(ctx, 0) < ctx->ga.PTournamentProb )
            return (m1);
        else
            return (m2);
    else
        if ( (double) PGARandom01(ctx, 0) < ctx->ga.PTournamentProb )
            return (m2);
        else
            return (m1);
}
   
/*U****************************************************************************
  PGASelectNext - returns the index of next individual in internal array that
  contains the indices determined by PGASelect
  
  Category: Selection

  Inputs:
    ctx   - context variable

  Outputs:
    none

  Example:
    PGAContext *ctx;
    int l;
    :
    l = PGASelectNext(ctx, PGA_OLDPOP);

****************************************************************************U*/
int PGASelectNext ( PGAContext *ctx )
{
    PGADebugPrint( ctx, PGADEBUG_ENTERED, "PGASelectNext", "Entered", 
                   PGA_VOID, NULL );
    
    if (ctx->ga.SelectIndex < ctx->ga.PopSize) {
        PGADebugPrint( ctx, PGADEBUG_EXIT, "PGASelectNext", "Exit", 
                       PGA_VOID, NULL );
        return(ctx->ga.selected[ctx->ga.SelectIndex++]);
    }
    else {
        PGADebugPrint( ctx, PGADEBUG_EXIT, "PGASelectNext", "Exit", 
                       PGA_VOID, NULL );
        PGAError( ctx, "PGASelectNext: Out of data error.",
                  PGA_FATAL, PGA_INT, (void *) &ctx->ga.SelectIndex );
	return(PGA_UNINITIALIZED_INT);
    }
}
