/// <summary>
        ///     The map evaluator constructor.
        /// </summary>
        /// <param name="experimentParameters">The experiment definition and control parameters.</param>
        /// <param name="agentInputNeuronCount">The number of input neurons in the agent neural controller.</param>
        /// <param name="agentOutputNeuronCount">The number of output neurons in the agent neural controller.</param>
        public MapEvaluator(ExperimentParameters experimentParameters, int agentInputNeuronCount,
            int agentOutputNeuronCount)
        {
            // Create the NEAT genome (agent) decoder - acyclic activation is always used
            _agentDecoder = new NeatGenomeDecoder(NetworkActivationScheme.CreateAcyclicScheme());

            // Create the maze decoder
            _mazeDecoder = new MazeDecoder(experimentParameters.MazeHeight, experimentParameters.MazeWidth,
                experimentParameters.MazeScaleMultiplier);

            // Initialize evaluation units
            EvaluationUnits = new List<MazeNavigatorEvaluationUnit>();

            // Create default maze factory (NEAT factory will be set later based on structure of first genome encountered)
            _mazeGenomeFactory = new MazeGenomeFactory();
            _neatGenomeFactory = new NeatGenomeFactory(agentInputNeuronCount, agentOutputNeuronCount);

            // Set experiment parameters
            _experimentParameters = experimentParameters;

            // Create new agent ID list and maze ID/structure map
            _agentGenomeIds = new List<int>();
            _mazeIdStructureMap = new Dictionary<int, MazeStructure>();
        }
        /// <summary>
        ///     Runs the navigator through its corresponding maze and records data about said evaluation (e.g. solved status, time
        ///     steps, and trajectory).
        /// </summary>
        /// <param name="evaluationUnit">A single unit of evaluation containing a paired maze and navigator.</param>
        /// <param name="experimentParameters">Parameters that control the navigation simulation.</param>
        public static void EvaluateMazeNavigatorUnit(MazeNavigatorEvaluationUnit evaluationUnit,
            ExperimentParameters experimentParameters)
        {
            bool isGoalReached;

            // Build maze configuration
            MazeConfiguration mazeConfiguration =
                new MazeConfiguration(ExtractMazeWalls(evaluationUnit.MazePhenome.Walls),
                    ExtractStartEndPoint(evaluationUnit.MazePhenome.StartLocation),
                    ExtractStartEndPoint(evaluationUnit.MazePhenome.TargetLocation));

            // Create trajectory behavior characterization (in order to capture full trajectory of navigator)
            IBehaviorCharacterization behaviorCharacterization = new TrajectoryBehaviorCharacterization();

            // Create the maze navigation world
            MazeNavigationWorld<BehaviorInfo> world = new MazeNavigationWorld<BehaviorInfo>(mazeConfiguration.Walls,
                mazeConfiguration.NavigatorLocation, mazeConfiguration.GoalLocation,
                experimentParameters.MinSuccessDistance, experimentParameters.MaxTimesteps, behaviorCharacterization);

            // Run a single trial
            BehaviorInfo trialInfo = world.RunTrial(evaluationUnit.AgentPhenome, SearchType.MinimalCriteriaSearch,
                out isGoalReached);

            // Set maze solved status
            evaluationUnit.IsMazeSolved = isGoalReached;

            // The number of time steps is effectively the number of 2-dimensional points in the behaviors array
            evaluationUnit.NumTimesteps = trialInfo.Behaviors.Count()/2;

            // Set the trajectory of the agent
            evaluationUnit.AgentTrajectory = trialInfo.Behaviors;
        }
        /// <summary>
        ///     Runs post-hoc analysis for the entire experiment run in aggregate (i.e. not per batch).  This analyzes the primary
        ///     run phase only.
        /// </summary>
        /// <param name="batchesWithGenomeData">The total number of batches containing genome data.</param>
        /// <param name="experimentParameters">Experiment configuration parameters.</param>
        /// <param name="inputNeuronCount">Count of neurons in controller input layer.</param>
        /// <param name="outputNeuronCount">Count of neurons in controller output layer.</param>
        /// <param name="curRun">The run number.</param>
        /// <param name="numRuns">The total number of runs.</param>
        /// <param name="curExperimentConfiguration">The experiment configuration parameters.</param>
        /// <param name="generateSimulationResults">Indicates whether to write out the results of the batch simulation.</param>
        /// <param name="writeResultsToDatabase">
        ///     Indicates whether to write results directly into a database (if not, results are
        ///     written to a flat file).
        /// </param>
        /// <param name="generateTrajectoryData">Indicates whether the full navigator trajectory should be simulated and persisted.</param>
        /// <param name="generateTrajectoryDiversityScore">
        ///     Indicates whether quantification of navigator trajectory diversity
        ///     should be written out.
        /// </param>
        /// <param name="generateNaturalClustering">Indicates whether the natural population clusters should be analyzed.</param>
        /// <param name="generateMazeBitmaps">Indicates whether bitmap files of the distinct mazes should be written out.</param>
        /// <param name="generateTrajectoryBitmaps">
        ///     Indicates whether bitmap files depicting the navigator trajectory should be
        ///     written out.
        /// </param>
        /// <param name="baseImageOutputDirectory">The path to the output directory for the trajectory images.</param>
        /// <param name="clusterImprovementThreshold">
        ///     The number of cluster additions that are permitted without further maximization of silhouette width.
        /// </param>
        /// <param name="specieSampleSize">The number of genomes sampled from the extant species for clustering analysis.</param>
        private static void ProcessAndLogAggregateRunResults(IList<int> batchesWithGenomeData,
            ExperimentParameters experimentParameters,
            int inputNeuronCount, int outputNeuronCount, int curRun,
            int numRuns, ExperimentDictionary curExperimentConfiguration, bool generateSimulationResults,
            bool generateTrajectoryData, bool generateTrajectoryDiversityScore, bool generateNaturalClustering,
            bool writeResultsToDatabase, bool generateMazeBitmaps, bool generateTrajectoryBitmaps,
            string baseImageOutputDirectory, int clusterImprovementThreshold, int specieSampleSize)
        {
            // Create the maze/navigator map
            MapEvaluator mapEvaluator =
                new MapEvaluator(experimentParameters, inputNeuronCount, outputNeuronCount);

            _executionLogger.Info(string.Format("Executing aggregate run analysis for run [{0}/{1}]", curRun, numRuns));

            // TODO: Might need to consider the option of pinning the mazes in memory and batching through the agents
            // TODO: as pulling both into memory simultaneously is probably going to use too much memory

            // Initialize the evaluator with the maze genomes discovered throughout the entire run
            mapEvaluator.Initialize(
                ExperimentDataHandler.GetMazeGenomeData(curExperimentConfiguration.ExperimentDictionaryID, curRun));

            // Then evaluate each agent against each maze on a batch-by-batch basis
            foreach (int curBatch in batchesWithGenomeData)
            {
                _executionLogger.Info(string.Format("Executing trajectory for batch [{0}] and run [{1}/{2}]", curBatch,
                    curRun, numRuns));

                // Initialize with the agent genomes from the current batch
                mapEvaluator.Initialize(
                    ExperimentDataHandler.GetNavigatorGenomeData(curExperimentConfiguration.ExperimentDictionaryID,
                        curRun, curBatch, RunPhase.Primary));

                // Evaluate all of the maze/navigator combinations in the batch
                mapEvaluator.RunTrajectoryEvaluations();

                if (generateSimulationResults)
                {
                    // Save the evaluation results
                    // TODO: Implement - not sure if this adds a whole lot of value beyond per-batch approach
                }

                if (generateMazeBitmaps)
                {
                    // Generate bitmaps of distinct mazes extant throughout the run
                    // TODO: Implement - not sure if this adds a whole lot of value beyond per-batch approach
                }

                if (generateTrajectoryBitmaps)
                {
                    // Generate bitmaps of trajectory for all successful trials
                    // TODO: Implement - not sure if this adds a whole lot of value beyond per-batch approach
                }

                if (generateTrajectoryData)
                {
                    // Write out the full trajectory of all agents through all solved mazes
                    // TODO: Implement - may end up removing this as it takes up an enormous amount of space
                }

                // Compare trajectories of agents through maze to get quantitative sense of solution diversity
                // Mean euclidean distance will be calculated for selected trajectory against:
                // 1. Other agent trajectories in the current maze only
                // 2. Other agent trajectories on *another* maze only
                // 3. All other agent trajectories (regardless of maze)
                if (generateTrajectoryDiversityScore)
                {
                    // TODO: Implement
                    ExperimentDataHandler.WriteTrajectoryDiversityData(
                        curExperimentConfiguration.ExperimentDictionaryID, curRun,
                        EvaluationHandler.CalculateTrajectoryDiversity(mapEvaluator.EvaluationUnits),
                        writeResultsToDatabase);
                }

                if (generateNaturalClustering)
                {
                    // Calculate natural clustering of the population trajectories throughout the run and persist
                    ExperimentDataHandler.WriteClusteringDiversityData(
                        curExperimentConfiguration.ExperimentDictionaryID, curRun,
                        EvaluationHandler.CalculateNaturalClustering(mapEvaluator.EvaluationUnits,
                            clusterImprovementThreshold), writeResultsToDatabase);
                }
            }
        }
        /// <summary>
        ///     Runs post-hoc analysis for all batches in the given experiment/run.  This can be part of either the initialization
        ///     or primary run phase.
        /// </summary>
        /// <param name="batchesWithGenomeData">The total number of batches containing genome data.</param>
        /// <param name="runPhase">
        ///     Indicates whether this is part of the initialization or primary run phase.
        /// </param>
        /// <param name="experimentParameters">Experiment configuration parameters.</param>
        /// <param name="inputNeuronCount">Count of neurons in controller input layer.</param>
        /// <param name="outputNeuronCount">Count of neurons in controller output layer.</param>
        /// <param name="curRun">The run number.</param>
        /// <param name="numRuns">The total number of runs.</param>
        /// <param name="curExperimentConfiguration">The experiment configuration parameters.</param>
        /// <param name="generateSimulationResults">Indicates whether to write out the results of the batch simulation.</param>
        /// <param name="writeResultsToDatabase">
        ///     Indicates whether to write results directly into a database (if not, results are
        ///     written to a flat file).
        /// </param>
        /// <param name="generateTrajectoryData">Indicates whether the full navigator trajectory should be simulated and persisted.</param>
        /// <param name="generateTrajectoryDiversityScore">
        ///     Indicates whether quantification of navigator trajectory diversity
        ///     should be written out.
        /// </param>
        /// <param name="generateNaturalClustering">Indicates whether the natural population clusters should be analyzed.</param>
        /// <param name="generateMazeBitmaps">Indicates whether bitmap files of the distinct mazes should be written out.</param>
        /// <param name="generateTrajectoryBitmaps">
        ///     Indicates whether bitmap files depicting the navigator trajectory should be
        ///     written out.
        /// </param>
        /// <param name="baseImageOutputDirectory">The path to the output directory for the trajectory images.</param>
        /// <param name="clusterImprovementThreshold">
        ///     The number of cluster additions that are permitted without further maximization of silhouette width.
        /// </param>
        /// <param name="sampleSize">The number of genomes sampled from the extant species for clustering analysis.</param>
        /// <param name="sampleClusterObservationsFromSpecies">
        ///     Indicates whether to sample observations used in clustering analysis
        ///     from species or from the population as a whole.
        /// </param>
        private static void ProcessAndLogPerBatchResults(IList<int> batchesWithGenomeData, RunPhase runPhase,
            ExperimentParameters experimentParameters, int inputNeuronCount, int outputNeuronCount, int curRun,
            int numRuns, ExperimentDictionary curExperimentConfiguration, bool generateSimulationResults,
            bool generateTrajectoryData, bool generateTrajectoryDiversityScore, bool generateNaturalClustering,
            bool writeResultsToDatabase, bool generateMazeBitmaps, bool generateTrajectoryBitmaps,
            string baseImageOutputDirectory, int clusterImprovementThreshold, int sampleSize,
            bool sampleClusterObservationsFromSpecies)
        {
            IList<CoevolutionMCSMazeExperimentGenome> staticInitializationMazes = null;

            // If this invocation is processing initialization results, just get the maze up front as it will remain
            // the same throughout the initialization process
            if (runPhase == RunPhase.Initialization)
            {
                staticInitializationMazes =
                    ExperimentDataHandler.GetMazeGenomeData(curExperimentConfiguration.ExperimentDictionaryID,
                        curRun, 0);
            }

            // Iterate through each batch and evaluate maze/navigator combinations
            foreach (int curBatch in batchesWithGenomeData)
            {
                // Create the maze/navigator map
                MapEvaluator mapEvaluator =
                    new MapEvaluator(experimentParameters, inputNeuronCount, outputNeuronCount);

                _executionLogger.Info(string.Format("Executing {0} run phase analysis for batch [{1}] of run [{2}/{3}]",
                    runPhase, curBatch, curRun, numRuns));

                // TODO: Check first to see if trajectory evaluations already exist

                // Get any existing navigation results (this avoids rerunning failed combinations)
                var successfulNavigations =
                    ExperimentDataHandler.GetSuccessfulNavigations(curExperimentConfiguration.ExperimentDictionaryID,
                        curRun, curBatch);

                // If successful navigation results were found and we're not re-running the simulations,
                // initialize the map evaluator with only those combinations known to be successful
                if (generateSimulationResults == false && successfulNavigations != null &&
                    successfulNavigations.Count > 0)
                {
                    List<Tuple<CoevolutionMCSMazeExperimentGenome, CoevolutionMCSNavigatorExperimentGenome>>
                        successfulGenomeCombos =
                            new List<Tuple<CoevolutionMCSMazeExperimentGenome, CoevolutionMCSNavigatorExperimentGenome>>
                                (successfulNavigations.Count());

                    // Get distinct maze and navigator genomes
                    var mazeGenomeData =
                        ExperimentDataHandler.GetMazeGenomeData(curExperimentConfiguration.ExperimentDictionaryID,
                            curRun, curBatch, successfulNavigations.Select(n => n.MazeGenomeID).Distinct().ToList());
                    var navigatorGenomeData =
                        ExperimentDataHandler.GetNavigatorGenomeData(curExperimentConfiguration.ExperimentDictionaryID,
                            curRun, curBatch, runPhase,
                            successfulNavigations.Select(n => n.NavigatorGenomeID).Distinct().ToList());

                    // Build list of successful maze/navigator combinations
                    successfulGenomeCombos.AddRange(
                        successfulNavigations.Select(
                            successfulNav =>
                                new Tuple<CoevolutionMCSMazeExperimentGenome, CoevolutionMCSNavigatorExperimentGenome>(
                                    mazeGenomeData.First(gd => successfulNav.MazeGenomeID == gd.GenomeID),
                                    navigatorGenomeData.First(gd => successfulNav.NavigatorGenomeID == gd.GenomeID))));

                    // Initialize the maze/navigator map with combinations that are known to be successful
                    mapEvaluator.Initialize(successfulGenomeCombos);
                }
                // Otherwise, just initialize with all combinations
                else
                {
                    // Initialize the maze/navigator map with the serialized maze and navigator data (this does the parsing)
                    mapEvaluator.Initialize(
                        runPhase == RunPhase.Initialization
                            ? staticInitializationMazes
                            : ExperimentDataHandler.GetMazeGenomeData(curExperimentConfiguration.ExperimentDictionaryID,
                                curRun, curBatch), ExperimentDataHandler.GetNavigatorGenomeData(
                                    curExperimentConfiguration.ExperimentDictionaryID, curRun, curBatch, runPhase));
                }

                // Evaluate all of the maze/navigator combinations in the batch
                mapEvaluator.RunTrajectoryEvaluations();

                if (generateSimulationResults)
                {
                    // Save the evaluation results
                    ExperimentDataHandler.WriteNavigatorMazeEvaluationData(
                        curExperimentConfiguration.ExperimentDictionaryID, curRun, curBatch, runPhase,
                        mapEvaluator.EvaluationUnits, CommitPageSize, writeResultsToDatabase);
                }

                if (generateMazeBitmaps)
                {
                    // Generate bitmaps of distinct mazes extant at the current point in time
                    ImageGenerationHandler.GenerateMazeBitmaps(baseImageOutputDirectory,
                        curExperimentConfiguration.ExperimentName, curExperimentConfiguration.ExperimentDictionaryID,
                        curRun, curBatch, mapEvaluator.EvaluationUnits);
                }

                if (generateTrajectoryBitmaps)
                {
                    // Generate bitmaps of trajectory for all successful trials
                    ImageGenerationHandler.GenerateBitmapsForSuccessfulTrials(
                        baseImageOutputDirectory, curExperimentConfiguration.ExperimentName,
                        curExperimentConfiguration.ExperimentDictionaryID,
                        curRun, curBatch, mapEvaluator.EvaluationUnits, runPhase);
                }

                if (generateTrajectoryData && runPhase != RunPhase.Initialization)
                {
                    // Write out the full trajectory of all agents through all solved mazes
                    ExperimentDataHandler.WriteTrajectoryData(curExperimentConfiguration.ExperimentDictionaryID, curRun,
                        curBatch, mapEvaluator.EvaluationUnits, CommitPageSize, writeResultsToDatabase);
                }

                // Compare trajectories of agents through maze to get quantitative sense of solution diversity
                // Mean euclidean distance will be calculated for selected trajectory against:
                // 1. Other agent trajectories in the current maze only
                // 2. Other agent trajectories on *another* maze only
                // 3. All other agent trajectories (regardless of maze)
                if (generateTrajectoryDiversityScore && runPhase != RunPhase.Initialization)
                {
                    ExperimentDataHandler.WriteTrajectoryDiversityData(
                        curExperimentConfiguration.ExperimentDictionaryID, curRun, curBatch,
                        EvaluationHandler.CalculateTrajectoryDiversity(mapEvaluator.EvaluationUnits),
                        writeResultsToDatabase);
                }

                // Only write clustering results for primary runs when the number of trajectories have surpassed
                // the minimum cluster count of 3
                if (generateNaturalClustering && runPhase != RunPhase.Initialization)
                {
                    // Extract uniform samples of maze and navigator genomes from each extant specie
                    // on which to run clustering analysis
                    var evaluationSamples = sampleClusterObservationsFromSpecies
                        ? DataManipulationUtil.ExtractEvaluationUnitSamplesFromSpecies(
                            curExperimentConfiguration.ExperimentDictionaryID, curRun, curBatch,
                            mapEvaluator.EvaluationUnits.Where(eu => eu.IsMazeSolved).ToList(), sampleSize)
                        : DataManipulationUtil.ExtractEvaluationUnitSamplesFromPopulation(
                            mapEvaluator.EvaluationUnits.Where(eu => eu.IsMazeSolved).ToList(),
                            sampleSize);

                    // Calculate natural clustering of the population trajectories at each point in time and persist
                    ExperimentDataHandler.WriteClusteringDiversityData(
                        curExperimentConfiguration.ExperimentDictionaryID, curRun, curBatch,
                        EvaluationHandler.CalculateNaturalClustering(evaluationSamples, clusterImprovementThreshold),
                        writeResultsToDatabase);
                }
            }
        }
        private static void Main(string[] args)
        {
            string baseImageOutputDirectory = null;

            // Initialise log4net (log to console and file).
            XmlConfigurator.Configure(new FileInfo("log4net.properties"));

            // Instantiate the execution logger
            _executionLogger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

            // Extract the execution parameters and check for any errors (exit application if any found)
            if (ParseAndValidateConfiguration(args) == false)
                Environment.Exit(0);

            _executionLogger.Info("Invocation parameters validated - continuing with experiment execution.");

            // Get boolean indicator dictating whether to analyze the whole run or just the last batch (default is true - full run)
            AnalysisScope analysisScope =
                AnalysisScopeUtil.ConvertStringToAnalysisScope(_executionConfiguration[ExecutionParameter.AnalysisScope]);

            // Get input and output neurons counts for navigator agent
            int inputNeuronCount = Int32.Parse(_executionConfiguration[ExecutionParameter.AgentNeuronInputCount]);
            int outputNeuronCount = Int32.Parse(_executionConfiguration[ExecutionParameter.AgentNeuronOutputCount]);

            // Get boolean indicator dictating whether to generate trajectory data (default is false)
            bool generateTrajectoryData =
                _executionConfiguration.ContainsKey(ExecutionParameter.GenerateTrajectoryData) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateTrajectoryData]);

            // Get boolean indicator dictating whether to write out numeric results of the batch simulations (default is true)
            bool generateSimulationResults =
                _executionConfiguration.ContainsKey(ExecutionParameter.GenerateSimulationResults) == false ||
                Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateSimulationResults]);

            // Get boolean indicator dictating whether to generate bitmaps of mazes (default is true)
            bool generateMazeBitmaps = _executionConfiguration.ContainsKey(ExecutionParameter.GenerateMazeBitmaps) ==
                                       false ||
                                       Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateMazeBitmaps]);

            // Get boolean indicator dictating whether to generate bitmaps of agent trajectories (default is true)
            bool generateTrajectoryBitmaps =
                _executionConfiguration.ContainsKey(ExecutionParameter.GenerateAgentTrajectoryBitmaps) == false ||
                Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateAgentTrajectoryBitmaps]);

            // Get boolean indicator dictating whether to write simulation results to database (default is false)
            bool writeResultsToDatabase =
                _executionConfiguration.ContainsKey(ExecutionParameter.WriteResultsToDatabase) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.WriteResultsToDatabase]);

            // Determine whether this is a distributed execution
            bool isDistributedExecution =
                _executionConfiguration.ContainsKey(ExecutionParameter.IsDistributedExecution) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.IsDistributedExecution]);

            // Get boolean indicator dictating whether to write out trajectory diversity scores (default is false)
            bool generateTrajectoryDiversityScores =
                _executionConfiguration.ContainsKey(ExecutionParameter.GenerateDiversityScores) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateDiversityScores]);

            // Get boolean indicator dictating whether to write out natural population clusters (default is false)
            bool generateNaturalClusters =
                _executionConfiguration.ContainsKey(ExecutionParameter.GenerateNaturalClusters) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.GenerateNaturalClusters]);

            // If the generate natural clusters flag is set, get the cluster improvement threshold and specie sample size
            int clusterImprovementThreshold = generateNaturalClusters
                ? Int32.Parse(_executionConfiguration[ExecutionParameter.ClusterImprovementThreshold])
                : 0;
            int specieSampleSize = generateNaturalClusters
                ? Int32.Parse(_executionConfiguration[ExecutionParameter.ClusterSampleSize])
                : 0;

            // Get boolean indicator dictating whether to sample the clustering data points from
            // species or from the population (default is false, meaning sample from population)
            bool sampleClusterObservationsFromSpecies =
                _executionConfiguration.ContainsKey(ExecutionParameter.SampleFromSpecies) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.SampleFromSpecies]);

            // Get boolean indicator dictating whether to execute initialization trial analysis (default is false)
            bool runInitializationAnalysis =
                _executionConfiguration.ContainsKey(ExecutionParameter.ExecuteInitializationTrials) &&
                Boolean.Parse(_executionConfiguration[ExecutionParameter.ExecuteInitializationTrials]);

            // If bitmap generation was enabled, grab the base output directory
            if (generateTrajectoryBitmaps)
            {
                baseImageOutputDirectory = _executionConfiguration[ExecutionParameter.BitmapOutputBaseDirectory];
            }

            // Extract the experiment names
            string[] experimentNames = _executionConfiguration[ExecutionParameter.ExperimentNames].Split(',');

            _executionLogger.Info(string.Format("[{0}] experiments specified for analysis.", experimentNames.Count()));

            // Process each experiment
            foreach (string experimentName in experimentNames)
            {
                // Get the run from which to start execution (if specified)
                int startingRun = _executionConfiguration.ContainsKey(ExecutionParameter.StartFromRun)
                    ? Int32.Parse(_executionConfiguration[ExecutionParameter.StartFromRun])
                    : 1;

                // Lookup the current experiment configuration
                ExperimentDictionary curExperimentConfiguration =
                    ExperimentDataHandler.LookupExperimentConfiguration(experimentName);

                // Ensure that experiment configuration was found
                if (curExperimentConfiguration == null)
                {
                    _executionLogger.Error(
                        string.Format("Unable to lookup experiment configuration for experiment with name [{0}]",
                            experimentName));
                    Environment.Exit(0);
                }

                // Construct the experiment parameters
                ExperimentParameters experimentParameters =
                    new ExperimentParameters(curExperimentConfiguration.MaxTimesteps,
                        curExperimentConfiguration.MinSuccessDistance,
                        curExperimentConfiguration.Primary_Maze_MazeHeight,
                        curExperimentConfiguration.Primary_Maze_MazeWidth,
                        curExperimentConfiguration.Primary_Maze_MazeScaleMultiplier);

                // Get the number of runs in the experiment. Note that if this is a distributed execution, each node
                // will only execute a single run analysis, so the number of runs will be equivalent to the run
                // to start from (this ensures that the ensuing loop that executes all of the runs executes exactly once)
                int numRuns = isDistributedExecution
                    ? startingRun
                    : ExperimentDataHandler.GetNumRuns(curExperimentConfiguration.ExperimentDictionaryID);

                _executionLogger.Info(string.Format("Preparing to execute analysis for [{0}] runs of experiment [{1}]",
                    numRuns,
                    curExperimentConfiguration.ExperimentName));

                // Process each experiment run
                for (int curRun = startingRun; curRun <= numRuns; curRun++)
                {
                    // If simulation result generation is enabled and we're not writing to
                    // the database, open the simulation result file writer
                    if (generateSimulationResults && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.OpenFileWriter(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - Run{1}.csv", experimentName, curRun)),
                            OutputFileType.NavigatorMazeEvaluationData);
                    }

                    // If trajectory data generation is enabled and we're not writing
                    // to the database, open the trajectory data file writer
                    if (generateTrajectoryData && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.OpenFileWriter(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - TrajectoryData - Run{1}.csv", experimentName, curRun)),
                            OutputFileType.TrajectoryData);
                    }

                    // If trajectory diversity score generation is enabled and we're not writing to
                    // the database, open the trajectory diversity score file writer
                    if (generateTrajectoryDiversityScores && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.OpenFileWriter(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - TrajectoryDiversity - {1} - Run{2}.csv", experimentName,
                                    analysisScope, curRun)),
                            OutputFileType.TrajectoryDiversityData);
                    }

                    if (generateNaturalClusters && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.OpenFileWriter(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - NaturalClusters - {1} - Run{2}.csv", experimentName, analysisScope,
                                    curRun)),
                            OutputFileType.NaturalClusterData);
                    }

                    // If we're analyzing the entire run, go ahead and process through the initialization phase
                    // and all primary phase batch results
                    if (AnalysisScope.Full == analysisScope)
                    {
                        // Get the number of initialization batches in the current run
                        IList<int> initializationBatchesWithGenomeData =
                            ExperimentDataHandler.GetBatchesWithGenomeData(
                                curExperimentConfiguration.ExperimentDictionaryID, curRun, RunPhase.Initialization);

                        // If we're running initialization analysis and there was an initialization phase, analyze those results
                        if (runInitializationAnalysis && initializationBatchesWithGenomeData.Count > 0)
                        {
                            _executionLogger.Info(
                                string.Format(
                                    "Executing initialization phase analysis for run [{0}/{1}] with [{2}] batches",
                                    curRun,
                                    numRuns, initializationBatchesWithGenomeData.Count));

                            // Begin initialization phase results processing
                            ProcessAndLogPerBatchResults(initializationBatchesWithGenomeData,
                                RunPhase.Initialization,
                                experimentParameters, inputNeuronCount, outputNeuronCount, curRun, numRuns,
                                curExperimentConfiguration, generateSimulationResults, generateTrajectoryData,
                                generateTrajectoryDiversityScores, generateNaturalClusters, writeResultsToDatabase,
                                generateMazeBitmaps, generateTrajectoryBitmaps, baseImageOutputDirectory,
                                clusterImprovementThreshold, specieSampleSize, sampleClusterObservationsFromSpecies);
                        }

                        // Get the number of primary batches in the current run
                        IList<int> batchesWithGenomeData =
                            ExperimentDataHandler.GetBatchesWithGenomeData(
                                curExperimentConfiguration.ExperimentDictionaryID, curRun, RunPhase.Primary);

                        _executionLogger.Info(
                            string.Format("Executing primary phase analysis for run [{0}/{1}] with [{2}] batches",
                                curRun,
                                numRuns, batchesWithGenomeData.Count));

                        // Begin primary phase results processing
                        ProcessAndLogPerBatchResults(batchesWithGenomeData, RunPhase.Primary, experimentParameters,
                            inputNeuronCount, outputNeuronCount, curRun, numRuns, curExperimentConfiguration,
                            generateSimulationResults, generateTrajectoryData, generateTrajectoryDiversityScores,
                            generateNaturalClusters, writeResultsToDatabase, generateMazeBitmaps,
                            generateTrajectoryBitmaps, baseImageOutputDirectory, clusterImprovementThreshold,
                            specieSampleSize, sampleClusterObservationsFromSpecies);
                    }
                    // If we want to analyze the entirety of the run as a whole
                    else if (AnalysisScope.Aggregate == analysisScope)
                    {
                        _executionLogger.Info(
                            string.Format("Executing aggregate analysis for run [{0}/{1}]", curRun, numRuns));

                        // Get the number of primary batches in the current run
                        IList<int> batchesWithGenomeData =
                            ExperimentDataHandler.GetBatchesWithGenomeData(
                                curExperimentConfiguration.ExperimentDictionaryID, curRun, RunPhase.Primary);

                        // Process aggregate run
                        ProcessAndLogAggregateRunResults(batchesWithGenomeData, experimentParameters, inputNeuronCount,
                            outputNeuronCount,
                            curRun, numRuns, curExperimentConfiguration, generateSimulationResults,
                            generateTrajectoryData, generateTrajectoryDiversityScores, generateNaturalClusters,
                            writeResultsToDatabase, generateMazeBitmaps, generateTrajectoryBitmaps,
                            baseImageOutputDirectory, clusterImprovementThreshold, specieSampleSize);
                    }
                    // Otherwise, we're just analyzing the ending population
                    else
                    {
                        // Get the last batch in the current run
                        int finalBatch =
                            ExperimentDataHandler.GetNumBatchesForRun(
                                curExperimentConfiguration.ExperimentDictionaryID, curRun);

                        _executionLogger.Info(
                            string.Format(
                                "Executing analysis of end-stage mazes and navigator trajectories for run [{0}/{1}] batch [{2}]",
                                curRun, numRuns, finalBatch));

                        // Begin maze/navigator trajectory image generation
                        ProcessAndLogPerBatchResults(new List<int>(1) {finalBatch}, RunPhase.Primary,
                            experimentParameters, inputNeuronCount, outputNeuronCount, curRun, numRuns,
                            curExperimentConfiguration, generateSimulationResults, generateTrajectoryData,
                            generateTrajectoryDiversityScores, generateNaturalClusters, writeResultsToDatabase,
                            generateMazeBitmaps, generateTrajectoryBitmaps, baseImageOutputDirectory,
                            clusterImprovementThreshold, specieSampleSize, sampleClusterObservationsFromSpecies);
                    }

                    // If we're not writing to the database, close the simulation result file writer
                    // and write the sentinel file for the run
                    if (generateSimulationResults && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.CloseFileWriter(OutputFileType.NavigatorMazeEvaluationData);
                        ExperimentDataHandler.WriteSentinelFile(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                experimentName), curRun);
                    }

                    // If we're not writing to the database, close the trajectory data file writer
                    // and write the sentinel file for the run
                    if (generateTrajectoryData && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.CloseFileWriter(OutputFileType.TrajectoryData);
                        ExperimentDataHandler.WriteSentinelFile(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - TrajectoryData", experimentName)), curRun);
                    }

                    // If we're not writing to the database, close the trajectory diversity
                    // score file writer and write the sentinel file for the run
                    if (generateTrajectoryDiversityScores && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.CloseFileWriter(OutputFileType.TrajectoryDiversityData);
                        ExperimentDataHandler.WriteSentinelFile(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - TrajectoryDiversity - {1}", experimentName, analysisScope)), curRun);
                    }

                    // If we're not writing to the database, close the natural clustering file writer
                    // and write the sentinel file for the run
                    if (generateNaturalClusters && writeResultsToDatabase == false)
                    {
                        ExperimentDataHandler.CloseFileWriter(OutputFileType.NaturalClusterData);
                        ExperimentDataHandler.WriteSentinelFile(
                            Path.Combine(_executionConfiguration[ExecutionParameter.DataFileOutputDirectory],
                                string.Format("{0} - NaturalClusters - {1}", experimentName, analysisScope)), curRun);
                    }
                }
            }
        }