/*
 * @(#) Statistician.java 2003/03/22
 * 
 */

import java.util.Properties;



/**
 * Gather population statistics and linearly scale population.
 * 
 * @author Sushil J. Louis
 */
public class Statistician implements java.io.Serializable {

    /**
     * Scaling factor to be used when scaling the population. Scaling
     * tries to maintain a constant selection pressure on the population.
     */
    public double scaleFactor;

    /**
     * Coefficients of linear equation used to scale fitness. 
     * scaledFitness = scaleConstA * fitness + scaleConstB.
     */
    public double scaleConstA, scaleConstB;

    /**
     * Constructor sets scaleFactor from the GA properties object.
     *
     * @param props properties object that contains the scaling factor.
     */
    public Statistician(Properties props) {
	scaleFactor = Double.parseDouble(props.getProperty("ScaleFactor", 
							   "1.1"));
    }


    /**
     * Calculates min, max, and avg fitness of the current population. Also 
     * updates values of bigMax, bigIndex, and bigGen in {@link Population}.
     * Linearly scales the population according to the algorithm in 
     * Goldberg's book. Thus the scaled fitness = fitness * A + B where
     * A and B are the two scale coefficients.
     *
     * @param pop the population for which statistics need to calculated
     */
    public void statistics(Population pop) {
	double fit, sfit;
	pop.mini = 0;
	pop.maxi = 0;
	
	pop.max = pop.currentPop[0].fitness;
	pop.min = pop.max;
	pop.sumFitness = pop.min;

	for(int i = 1; i < pop.popSize; i++){
	    fit = pop.currentPop[i].fitness;
	    pop.sumFitness += fit;
	    if(pop.max < fit){
		pop.max = fit;
		pop.maxi = i;
	    }
	    if(pop.min > fit){
		pop.min = fit;
		pop.mini = i;
	    }
	}
	if(pop.bigMax < pop.max){
	    pop.bigMax = pop.max;
	    pop.bigGen = pop.generation;
	    pop.bigIndex = pop.maxi;
	}
	pop.avg = pop.sumFitness/(double) pop.popSize;
	if(scaleFactor > 0){
	    scaleFitness(pop, scaleFactor);
	}
    }

    /**
     * Linearly scales the population's fitness.
     * scaledFitness = scaleConstA * fitness + scaleConstB. The two linear
     * coefficients are calculated anew every generation. Also sets the 
     * population statistics of smax, smin,and scaledSumFitness in the first
     * argument.
     *
     * @param pop the population for which statistics need to calculated
     * @param sf  the scaling factor to be used where p.smax = sf * p.avg.
     */
    void scaleFitness(Population pop, double sf){

	double sfit;
	double scaledSF;

	scaledSF = 0.0;

	findCoefficients(pop, sf); // find linear coeffs to scale population

	for(int i = 0; i < pop.popSize; i++){
	    sfit = scaleConstA * pop.currentPop[i].fitness + scaleConstB;
	    if (sfit < 0.0) {
		sfit = 0.0;
	    }
	    pop.currentPop[i].fitness = sfit; /* use sf < 0 to not scale */
	    scaledSF += sfit;
	}
	pop.max = scaleConstA * pop.max + scaleConstB;
	pop.min = scaleConstA * pop.min + scaleConstB;
	if(pop.min < 0){
	    System.err.println("Warning scaled minimum is going below 0");
	    pop.min = 0.0;
	}
	pop.sumFitness = scaledSF;
    }

    /**
     * finds the coefficients of the linear equation used to scale the 
     * population. 
     *
     * @param pop the population for which statistics need to calculated
     * @param sf  the scaling factor to be used where p.smax = sf * p.avg.
     */
    private void findCoefficients(Population p, double sf){
	double d;

	if(sf <= 1.0) {
	    scaleConstA = 1.0;
	    scaleConstB = 0.0;
	    return;
	}

	// sf > 1.0
	if(p.min > (sf * p.avg - p.max)/(sf - 1.0)) { 
	    // if nonnegative smin 
	    d = p.max - p.avg;
	    scaleConstA = (sf - 1.0) * p.avg / d;
	    scaleConstB = p.avg * (p.max - (sf * p.avg))/d;
	} else {  /* if smin becomes negative on scaling */
	    d = p.avg - p.min;
	    scaleConstA = p.avg/d;
	    scaleConstB = -p.min * p.avg/d;
	}
	if(d < 0.00001 && d > -0.00001) { /* if converged */
	    scaleConstA = 1.0;
	    scaleConstB = 0.0;
	}
    }
}
