gt: 			+ "] [" + durationMillis + "]" );
context: /*
   * Copyright (C) 2004 Derek James and Philip Tucker
   * 
   * This file is part of ANJI (Another NEAT Java Implementation).
   * 
   * ANJI 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
   * 
   * created by Philip Tucker on Feb 16, 2003
   */
  package com.anji.neat;
  
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.Calendar;
  import java.util.Date;
  
  import org.apache.log4j.Logger;
  import org.jgap.BulkFitnessFunction;
  import org.jgap.Chromosome;
  import org.jgap.Genotype;
  import org.jgap.event.GeneticEvent;
  
  import com.anji.Copyright;
  import com.anji.integration.LogEventListener;
  import com.anji.integration.PersistenceEventListener;
  import com.anji.integration.PresentationEventListener;
  import com.anji.persistence.Persistence;
  import com.anji.run.Run;
  import com.anji.util.Configurable;
  import com.anji.util.Properties;
  import com.anji.util.Reset;
  
  /**
   * Configures and performs an ANJI evolutionary run.
   * 
   * @author Philip Tucker
   */
  public class Evolver implements Configurable {
  
  private static Logger logger = Logger.getLogger( Evolver.class );
  
  /**
   * properties key, # generations in run
   */
  public static final String NUM_GENERATIONS_KEY = "num.generations";
  
  /**
   * properties key, fitness function class
   */
  public static final String FITNESS_FUNCTION_CLASS_KEY = "fitness_function";
  
  private static final String FITNESS_THRESHOLD_KEY = "fitness.threshold";
  
  private static final String RESET_KEY = "run.reset";
  
  /**
   * properties key, target fitness value - after reaching this run will halt
   */
  public static final String FITNESS_TARGET_KEY = "fitness.target";
  
  private NeatConfiguration config = null;
  
  private Chromosome champ = null;
  
  private Genotype genotype = null;
  
  private int numEvolutions = 0;
  
  private double targetFitness = 0.0d;
  
  private double thresholdFitness = 0.0d;
  
  private int maxFitness = 0;
  
  private Persistence db = null;
  
  /**
   * ctor; must call <code>init()</code> before using this object
   */
  public Evolver() {
  	super();
  }
  
  /**
   * Construct new evolver with given properties. See <a href=" {@docRoot}/params.htm"
   * target="anji_params">Parameter Details </a> for specific property settings.
   * @see com.anji.util.Configurable#init(com.anji.util.Properties)
   */
  public void init( Properties props ) throws Exception {
  	boolean doReset = props.getBooleanProperty( RESET_KEY, false );
  	if ( doReset ) {
  		logger.warn( "Resetting previous run !!!" );
  		Reset resetter = new Reset( props );
  		resetter.setUserInteraction( false );
  		resetter.reset();
  	}
  
  	config = new NeatConfiguration( props );
  
  	// peristence
  	db = (Persistence) props.singletonObjectProperty( Persistence.PERSISTENCE_CLASS_KEY );
  
  	numEvolutions = props.getIntProperty( NUM_GENERATIONS_KEY );
  	targetFitness = props.getDoubleProperty( FITNESS_TARGET_KEY, 1.0d );
  	thresholdFitness = props.getDoubleProperty( FITNESS_THRESHOLD_KEY, targetFitness );
  
  	//
  	// event listeners
  	//
  
  	// run
  	// TODO - hibernate
  	Run run = (Run) props.singletonObjectProperty( Run.class );
  	db.startRun( run.getName() );
  	config.getEventManager().addEventListener( GeneticEvent.GENOTYPE_EVALUATED_EVENT, run );
  
  	// logging
  	LogEventListener logListener = new LogEventListener( config );
  	config.getEventManager().addEventListener( GeneticEvent.GENOTYPE_EVOLVED_EVENT, logListener );
  	config.getEventManager()
  			.addEventListener( GeneticEvent.GENOTYPE_EVALUATED_EVENT, logListener );
  
  	// persistence
  	PersistenceEventListener dbListener = new PersistenceEventListener( config, run );
  	dbListener.init( props );
  	config.getEventManager().addEventListener(
  			GeneticEvent.GENOTYPE_START_GENETIC_OPERATORS_EVENT, dbListener );
  	config.getEventManager().addEventListener(
  			GeneticEvent.GENOTYPE_FINISH_GENETIC_OPERATORS_EVENT, dbListener );
  	config.getEventManager().addEventListener( GeneticEvent.GENOTYPE_EVALUATED_EVENT, dbListener );
  
  	// presentation
  	PresentationEventListener presListener = new PresentationEventListener( run );
  	presListener.init( props );
  	config.getEventManager().addEventListener( GeneticEvent.GENOTYPE_EVALUATED_EVENT,
  			presListener );
  	config.getEventManager().addEventListener( GeneticEvent.RUN_COMPLETED_EVENT, presListener );
  
  	// fitness function
  	BulkFitnessFunction fitnessFunc = (BulkFitnessFunction) props
  			.singletonObjectProperty( FITNESS_FUNCTION_CLASS_KEY );
  	config.setBulkFitnessFunction( fitnessFunc );
  	maxFitness = fitnessFunc.getMaxFitnessValue();
  
  	// load population, either from previous run or random
  	genotype = null; //db.loadGenotype( config );
  	if ( genotype != null ){
  		System.out.println("***WRONG***");
  		logger.info( "genotype from previous run" );
  		}
  	else {
  		System.out.println("***RANDOM***");
  		genotype = Genotype.randomInitialGenotype( config );
  		logger.info( "random genotype" );
  	}
  	System.out.println("Genotype: " + genotype.toString());
  
  }
  
  /**
   * command line usage
   */
  private static void usage() {
  	System.err.println( "usage: <cmd> <properties-file>" );
  }
  
  /**
   * Perform a single run.
   * 
   * @throws Exception
   */
  public void run() throws Exception {
  	// run start time
  	Date runStartDate = Calendar.getInstance().getTime();
  	logger.info( "Run: start" );
  	DateFormat fmt = new SimpleDateFormat( "HH:mm:ss" );
  
  	// initialize result data
  	int generationOfFirstSolution = -1;
  	champ = genotype.getFittestChromosome();
  	double adjustedFitness = ( maxFitness > 0 ? champ.getFitnessValue() / maxFitness : champ
  			.getFitnessValue() );
  
  	// generations
  	for ( int generation = 0; ( generation < numEvolutions && adjustedFitness < targetFitness ); ++generation ) {
  		// generation start time
  		Date generationStartDate = Calendar.getInstance().getTime();
  		logger.info( "Generation " + generation + ": start" );
  
  		// next generation
  		genotype.evolve();
  
  		// result data
  		champ = genotype.getFittestChromosome();
  		adjustedFitness = ( maxFitness > 0 ? (double) champ.getFitnessValue() / maxFitness : champ
  				.getFitnessValue() );
  		if ( adjustedFitness >= thresholdFitness && generationOfFirstSolution == -1 )
  			generationOfFirstSolution = generation;
  
  		// generation finish
  		Date generationEndDate = Calendar.getInstance().getTime();
  		long durationMillis = generationEndDate.getTime() - generationStartDate.getTime();
  		logger.info( "Generation " + generation + ": end [" + fmt.format( generationStartDate )
  				+ " - " + fmt.format( generationEndDate ) + "] [" + durationMillis + "]" );
  	}
  
  	// run finish
  	config.getEventManager().fireGeneticEvent(
  			new GeneticEvent( GeneticEvent.RUN_COMPLETED_EVENT, genotype ) );
  	logConclusion( generationOfFirstSolution, champ );
  	Date runEndDate = Calendar.getInstance().getTime();
  	long durationMillis = runEndDate.getTime() - runStartDate.getTime();
  	logger.info( "Run: end [" + fmt.format( runStartDate ) + " - " + fmt.format( runEndDate )
  