/// <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(CreateActivationScheme(experimentParameters.ActivationScheme, experimentParameters.ActivationIters, experimentParameters.ActivationDeltaThreshold)); // Create the maze decoder _mazeDecoder = new MazeDecoder(experimentParameters.MazeScaleMultiplier); // Initialize evaluation units EvaluationUnits = new List <MazeNavigatorEvaluationUnit>(); // Create maze factory with default dimensions (NEAT factory will be set later based on structure of first // genome encountered) _mazeGenomeFactory = new MazeGenomeFactory(experimentParameters.MazeHeight, experimentParameters.MazeWidth, experimentParameters.MazeQuadrantHeight, experimentParameters.MazeQuadrantWidth); _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> /// Generates the specified number of maze genomes with the specified complexity (i.e. number of interior partitions). /// </summary> /// <param name="numMazeGenomes">The number of maze genomes to generate.</param> /// <param name="numPartitions">The number of initial partitions (the starting complexity of the genome).</param> /// <param name="mazeGenomeFactory">Reference to the maze genome factory.</param> /// <returns></returns> public static List <MazeGenome> GenerateMazeGenomes(int numMazeGenomes, int numPartitions, MazeGenomeFactory mazeGenomeFactory) { var mazeGenomes = new List <MazeGenome>(numMazeGenomes); var rand = RandomDefaults.CreateRandomSource(); for (var curMazeCnt = 0; curMazeCnt < numMazeGenomes; curMazeCnt++) { // Reset innovation IDs mazeGenomeFactory.InnovationIdGenerator.Reset(); // Create a new genome and pass in the requisite factory var mazeGenome = new MazeGenome(mazeGenomeFactory, 0, 0); // Create the specified number of interior partitions (i.e. maze genes) for (var cnt = 0; cnt < numPartitions; cnt++) { // Create new maze gene and add to genome mazeGenome.WallGeneList.Add(new WallGene(mazeGenomeFactory.InnovationIdGenerator.NextId, rand.NextDouble(), rand.NextDouble(), rand.NextDouble() < 0.5)); } mazeGenomes.Add(mazeGenome); } return(mazeGenomes); }
/// <summary> /// Reads a list of maze genomes from an XML file. /// </summary> /// <param name="xr">Reference to the XmlReader.</param> /// <param name="genomeFactory">A MazeGenomeFactory to construct genomes against.</param> /// <returns>Instantiated list of maze genomes.</returns> public static List<MazeGenome> ReadCompleteGenomeList(XmlReader xr, MazeGenomeFactory genomeFactory) { // Find <Root> XmlIoUtils.MoveToElement(xr, false, __ElemRoot); // Find <Mazes> XmlIoUtils.MoveToElement(xr, true, __ElemMazes); // Read mazes List<MazeGenome> genomeList = new List<MazeGenome>(); using (XmlReader xrSubtree = xr.ReadSubtree()) { // Re-scan for the root <Mazes> element XmlIoUtils.MoveToElement(xrSubtree, false); // Move to first Maze element XmlIoUtils.MoveToElement(xrSubtree, true, __ElemMaze); // Read maze elements do { MazeGenome genome = ReadGenome(xrSubtree); genomeList.Add(genome); } while (xrSubtree.ReadToNextSibling(__ElemMaze)); } // Check for empty list if (genomeList.Count == 0) { return genomeList; } // Determine the max genome ID uint maxGenomeId = genomeList.Aggregate<MazeGenome, uint>(0, (current, genome) => Math.Max(current, genome.Id)); // Determine the max gene innovation ID uint maxInnovationId = genomeList.Aggregate<MazeGenome, uint>(0, (curMaxPopulationInnovationId, genome) => genome.GeneList.Aggregate(curMaxPopulationInnovationId, (curMaxGenomeInnovationId, mazeGene) => Math.Max(curMaxGenomeInnovationId, mazeGene.InnovationId))); // Set the genome factory ID generator and innovation ID generator to one more than the max genomeFactory.GenomeIdGenerator.Reset(Math.Max(genomeFactory.GenomeIdGenerator.Peek, maxGenomeId + 1)); genomeFactory.InnovationIdGenerator.Reset(Math.Max(genomeFactory.InnovationIdGenerator.Peek, maxInnovationId + 1)); // Retrospecitively assign the genome factory to the genomes foreach (MazeGenome genome in genomeList) { genome.GenomeFactory = genomeFactory; } return genomeList; }
/// <inheritdoc /> /// <summary> /// Creates the MCC algorithm container using the given agent and maze population sizes. /// </summary> /// <param name="populationSize1">The agent population size.</param> /// <param name="populationSize2">The maze population size.</param> /// <returns>The instantiated MCC algorithm container.</returns> public override IMCCAlgorithmContainer <NeatGenome, MazeGenome> CreateMCCAlgorithmContainer( int populationSize1, int populationSize2) { // Create a genome factory for the NEAT genomes IGenomeFactory <NeatGenome> neatGenomeFactory = new NeatGenomeFactory(AnnInputCount, AnnOutputCount, NeatGenomeParameters); // Create a genome factory for the maze genomes IGenomeFactory <MazeGenome> mazeGenomeFactory = new MazeGenomeFactory(MazeGenomeParameters, MazeHeight, MazeWidth, MazeQuadrantHeight, MazeQuadrantWidth); // Create an initial population of maze navigators var neatGenomeList = neatGenomeFactory.CreateGenomeList(populationSize1, 0); // Create an initial population of mazes // NOTE: the population is set to 1 here because we're just starting with a single, completely open maze space var mazeGenomeList = mazeGenomeFactory.CreateGenomeList(populationSize2, 0); // Create the evolution algorithm container return(CreateMCCAlgorithmContainer(neatGenomeFactory, mazeGenomeFactory, neatGenomeList, mazeGenomeList, false)); }
/// <summary> /// Reads in seed maze genomes used to bootstrap MCC experiments. /// </summary> /// <param name="seedMazePath"> /// The path of the single maze genome or a directory containing multiple XML genome /// definitions. /// </param> /// <param name="mazeGenomeFactory">The maze genome factory to assign to each genome.</param> /// <returns>The list of seed maze genomes.</returns> public static IEnumerable <MazeGenome> ReadSeedMazeGenomes(string seedMazePath, MazeGenomeFactory mazeGenomeFactory) { var mazeGenomes = new List <MazeGenome>(); // Get the maze genome files in the given path var mazeGenomeFiles = GetGenomeFiles(seedMazePath); // Read in all maze genomes and add them to the list foreach (var mazeGenomeFile in mazeGenomeFiles) { using (var xr = XmlReader.Create(mazeGenomeFile)) { // Read in the maze genomes var curMazeGenomes = MazeGenomeXmlIO.ReadCompleteGenomeList(xr, mazeGenomeFactory); // Add the genomes to the overall genome list mazeGenomes.AddRange(curMazeGenomes); } } return(mazeGenomes); }
/// <summary> /// Handles the process of rendering a bitmap file of an existing maze genome. /// </summary> private static void HandleMazeImageReproduction() { MazeGenome mazeGenome = null; // Get the maze genome file path and image output path string mazeGenomeFile = _executionConfiguration[ExecutionParameter.MazeGenomeFile]; string imageOutputPath = _executionConfiguration[ExecutionParameter.BitmapOutputBaseDirectory]; // Create a new (mostly dummy) maze genome factory MazeGenomeFactory mazeGenomeFactory = new MazeGenomeFactory(); // Read in the genome using (XmlReader xr = XmlReader.Create(mazeGenomeFile)) { mazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xr, mazeGenomeFactory); } // Get the maze genome ID uint mazeGenomeId = mazeGenome.Id; // Render the maze phenotype (i.e. the graphical structure0 and print to a bitmap file PrintMazeToFile(mazeGenome, Path.Combine(imageOutputPath, string.Format("EvolvedMaze_ID_{0}.bmp", mazeGenomeId))); }
/// <summary> /// Handles the process of generating maze genomes and rendering bitmap files of their structure. /// </summary> private static void HandleMazeGeneration() { IRandomSource rand = RandomDefaults.CreateRandomSource(); // Get the evolved maze height and width int mazeHeight = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeHeight]); int mazeWidth = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeWidth]); int mazeQuadrantHeight = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeQuadrantHeight]); int mazeQuadrantWidth = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeQuadrantWidth]); // Get the number of interior walls and maze genome output directory int numWaypoints = Int32.Parse(_executionConfiguration[ExecutionParameter.NumWaypoints]); int numInteriorWalls = Int32.Parse(_executionConfiguration[ExecutionParameter.NumWalls]); string mazeGenomeOutputDirectory = _executionConfiguration[ExecutionParameter.MazeGenomeOutputBaseDirectory]; // Get the number of sample mazes to generate (or just 1 maze if not specified) int numMazes = _executionConfiguration.ContainsKey(ExecutionParameter.NumMazes) ? Int32.Parse(_executionConfiguration[ExecutionParameter.NumMazes]) : 1; // Get whether images are being generated for the sample mazes bool generateMazeImages = _executionConfiguration.ContainsKey(ExecutionParameter.OutputMazeBitmap) && Boolean.Parse(_executionConfiguration[ExecutionParameter.OutputMazeBitmap]); // Get whether maze genomes are being serialized to the same file (defaults to false) bool isSingleOutputFile = _executionConfiguration.ContainsKey(ExecutionParameter.SingleGenomeOutputFile) && Boolean.Parse(_executionConfiguration[ExecutionParameter.SingleGenomeOutputFile]); // Create a new maze genome factory MazeGenomeFactory mazeGenomeFactory = new MazeGenomeFactory(mazeHeight, mazeWidth, mazeQuadrantHeight, mazeQuadrantWidth); // Instantiate list to hold generated maze genomes // (only really used when we're writing everything out to one file) List <MazeGenome> mazeGenomeList = new List <MazeGenome>(numMazes); for (int curMazeCnt = 0; curMazeCnt < numMazes; curMazeCnt++) { MazeGenome mazeGenome; // Lay out the base file name string fileBaseName = string.Format("GeneratedMazeGenome_{0}_Height_{1}_Width_{2}_Waypoints_{3}_Walls_{4}", mazeHeight, mazeWidth, numWaypoints, numInteriorWalls, curMazeCnt); // With a single output file, the genomes are likely being used for separate experiments, so we // reset the innovation IDs and assign the maze a constant identifier if (isSingleOutputFile == false) { // Reset innovation IDs mazeGenomeFactory.InnovationIdGenerator.Reset(); // Create a new genome and pass in the requisite factory mazeGenome = new MazeGenome(mazeGenomeFactory, 0, 0); } // Otherwise, we leave the innovation ID generator alone and create a new maze genome with // an identifier that's incremented by one else { mazeGenome = new MazeGenome(mazeGenomeFactory, (uint)curMazeCnt, 0); } // Add the specified number of waypoints (less one because center waypoint is created on maze initialization) for (int cnt = 0; cnt < numWaypoints - 1; cnt++) { Point2DInt waypoint; // Randomly select an orientation IntersectionOrientation newPointOrientation = mazeGenomeFactory.Rng.NextBool() ? IntersectionOrientation.Horizontal : IntersectionOrientation.Vertical; // Iterate until we get a waypoint that's valid do { waypoint = new Point2DInt(mazeGenomeFactory.Rng.Next(mazeGenome.MazeBoundaryWidth - 1), mazeGenomeFactory.Rng.Next(mazeGenome.MazeBoundaryHeight - 1)); } while ( MazeUtils.IsValidWaypointLocation(mazeGenome, waypoint, UInt32.MaxValue, newPointOrientation) == false); mazeGenome.PathGeneList.Add(new PathGene(mazeGenomeFactory.InnovationIdGenerator.NextId, waypoint, newPointOrientation)); } // Create the specified number of interior walls (i.e. maze genes) for (int cnt = 0; cnt < numInteriorWalls; cnt++) { // Create new maze gene and add to genome mazeGenome.WallGeneList.Add(new WallGene(mazeGenomeFactory.InnovationIdGenerator.NextId, rand.NextDouble(), rand.NextDouble(), rand.NextDouble() < 0.5)); } // Only serialize genomes to separate files if single output file option is turned off if (isSingleOutputFile == false) { // Serialize the genome to XML using ( XmlWriter xmlWriter = XmlWriter.Create( Path.Combine(mazeGenomeOutputDirectory, string.Format("{0}.xml", fileBaseName)), new XmlWriterSettings { Indent = true })) { // Get genome XML MazeGenomeXmlIO.WriteComplete(xmlWriter, mazeGenome); } } // Otherwise, just add genome to list to be serialized to single file in bulk else { mazeGenomeList.Add(mazeGenome); } // Print the maze to a bitmap file if that option has been specified if (generateMazeImages) { PrintMazeToFile(mazeGenome, string.Format("{0}_Structure.bmp", fileBaseName)); } } // If serialize to single output file is turned off, go ahead and write everything out to the file if (isSingleOutputFile) { // Serialize all genomes to XML using ( XmlWriter xmlWriter = XmlWriter.Create(Path.Combine(mazeGenomeOutputDirectory, string.Format("GeneratedMaze_{0}_Genomes_{1}_Height_{2}_Width_{3}_Waypoints_{4}_Walls.xml", numMazes, mazeHeight, mazeWidth, numWaypoints, numInteriorWalls)))) { MazeGenomeXmlIO.WriteComplete(xmlWriter, mazeGenomeList); } } }
/// <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> /// Handles per-batch comparison of coevolution-generated mazes on a reference algorithm (e.g. novelty search). /// </summary> /// <param name="batchesWithGenomeData">List of the batches that contain maze genome data.</param> /// <param name="curCoevoExperimentId">Identifier of the coevolution experiment.</param> /// <param name="curCoevoExperimentName">Name of the coevolution experiment.</param> /// <param name="curRun">The current run.</param> /// <param name="numRuns">The total number of runs in the experiment.</param> /// <param name="mazeDecoder">Reference to the maze genome decoder.</param> /// <param name="noveltySearchExperimentConfig">The experiment configuration for the reference (novelty search) experiment.</param> private static void RunPerBatchComparativeAnalysis(IList<int> batchesWithGenomeData, int curCoevoExperimentId, string curCoevoExperimentName, int curRun, int numRuns, MazeDecoder mazeDecoder, ExperimentDictionary noveltySearchExperimentConfig) { // Declare list to hold maze IDs that have already been evaluated using the comparison algorithm List<int> evaluatedMazeGenomeIds = new List<int>(); // Get the total number of initialization evaluations for the current run int coEvoInitEvaluations = ExperimentDataHandler.GetInitializationEvaluationsForRun(curCoevoExperimentId, curRun); // Iterate through each batch and run comparative algorithm on each maze foreach (int curBatch in batchesWithGenomeData) { _executionLogger.Info(string.Format("Executing comparative analysis for batch [{0}] of run [{1}/{2}]", curBatch, curRun, numRuns)); // Get list of all extant maze genomes for this batch IList<string> mazeGenomeXml = ExperimentDataHandler.GetMazeGenomeXml(curCoevoExperimentId, curRun, curBatch); foreach (string genomeXml in mazeGenomeXml) { MazeStructure curMazeStructure = null; MazeGenome curMazeGenome = null; // Create a new (mostly dummy) maze genome factory MazeGenomeFactory curMazeGenomeFactory = new MazeGenomeFactory(); // Convert each genome string to an equivalent genome object using (XmlReader xr = XmlReader.Create(new StringReader(genomeXml))) { curMazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xr, curMazeGenomeFactory); } // If the maze has already been processed, continue on to the next one // (also exclude the initialization maze as a special case) if (evaluatedMazeGenomeIds.Contains((int) curMazeGenome.BirthGeneration) || curMazeGenome.BirthGeneration == 0) { continue; } // Get the total number of evaluations required to solve this maze // (this amounts to the total number of evaluations from both queues at the time of maze birth) int coEvoTotalEvaluations = coEvoInitEvaluations + ExperimentDataHandler.GetTotalPrimaryEvaluationsAtBatch( curCoevoExperimentId, curRun, (int) curMazeGenome.BirthGeneration); // Decode the maze structure curMazeStructure = mazeDecoder.Decode(curMazeGenome); // Instantiate new novelty search experiment for the current experiment/batch/maze NoveltySearchRunner nsRunner = new NoveltySearchRunner(noveltySearchExperimentConfig, curCoevoExperimentName, curMazeStructure, curRun, numRuns, _executionLogger); _executionLogger.Info(string.Format( "Executing novelty search on maze [{0}] with birth batch [{1}]", curMazeGenome.Id, curMazeGenome.BirthGeneration)); // Run novelty search on that maze until the maze is solved or the max evals have been reached INeatEvolutionAlgorithm<NeatGenome> eaFinalState = nsRunner.RunNoveltySearch(); // Write comparison results to output file ExperimentDataHandler.WriteNoveltySearchComparisonResults(curCoevoExperimentId, noveltySearchExperimentConfig.ExperimentDictionaryID, curRun, curBatch, (int) curMazeGenome.Id, (int) curMazeGenome.BirthGeneration, (int) eaFinalState.Statistics._minComplexity, (int) eaFinalState.Statistics._maxComplexity, eaFinalState.Statistics._meanComplexity, coEvoTotalEvaluations, (int) eaFinalState.CurrentEvaluations, !((int) eaFinalState.CurrentEvaluations >= noveltySearchExperimentConfig.MaxEvaluations), false); // Add maze genome ID to the list of processed mazes to its not considered for evaluation again evaluatedMazeGenomeIds.Add((int) curMazeGenome.Id); } } }
/// <summary> /// Computes the natural clustering of the maze genomes along with their respective entropy. /// </summary> /// <param name="mazeGenomeListXml">The list of serialized maze genome XML.</param> /// <param name="isGreedySilhouetteCalculation"> /// Dictates whether optimal clustering is based on number of clusters that /// maximize silhouette score until the first decrease in score (true), or based on the silhouette score calculated for /// a range of clusters with the number of clusters resulting in the maximum score used as the optimal number of /// clusters (false). /// </param> /// <param name="clusterRange">The range of clusters values for which to compute the silhouette width (optional).</param> /// <returns>The cluster diversity unit for maze genomes.</returns> public static ClusterDiversityUnit CalculateMazeClustering(IList <string> mazeGenomeListXml, bool isGreedySilhouetteCalculation, int clusterRange = 0) { // Always start with one cluster const int initClusterCnt = 1; // Initialize list of maze genome objects var mazeGenomes = new List <MazeGenome>(mazeGenomeListXml.Count()); // Convert all maze genome XML strings to maze genome objects foreach (var genomeXml in mazeGenomeListXml) { MazeGenome curMazeGenome; // Create a new, dummy maze genome factory var tempMazeGenomeFactory = new MazeGenomeFactory(); // Convert genome XML to genome object using (var xr = XmlReader.Create(new StringReader(genomeXml))) { curMazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xr, tempMazeGenomeFactory); } // Add to the genome object list mazeGenomes.Add(curMazeGenome); } // Define observation matrix in which to store all gene coordinate vectors for each maze var observationMatrix = new double[mazeGenomes.Count()][]; // Get the maximum observation vector length (max number of maze genes) var maxObservationLength = mazeGenomes.Max(g => g.WallGeneList.Count()); for (var idx = 0; idx < mazeGenomes.Count(); idx++) { // If there are more observations than the total elements in the observation vector, // zero out the rest of the vector if (mazeGenomes[idx].WallGeneList.Count() < maxObservationLength) { observationMatrix[idx] = mazeGenomes[idx].Position.CoordArray.Select(ca => ca.Value) .Concat(Enumerable.Repeat(0.0, maxObservationLength - mazeGenomes[idx].Position.CoordArray.Length)) .ToArray(); } // Otherwise, if the observation and vector length are the same, just set the elements else { observationMatrix[idx] = mazeGenomes[idx].Position.CoordArray.Select(ca => ca.Value).ToArray(); } } // Determine the optimal number of clusters to fit these data var optimalClustering = DetermineOptimalClusters(observationMatrix, initClusterCnt, false, isGreedySilhouetteCalculation, clusterRange); // Compute shannon entropy given optimal clustering var shannonEntropy = ComputeShannonEntropy(optimalClustering); // Compute the silhouette width given optimal clustering var silhouetteWidth = ComputeSilhouetteWidth(optimalClustering.Clusters, observationMatrix, false); // Return the resulting maze genome cluster diversity info return(new ClusterDiversityUnit(optimalClustering.Clusters.Count, silhouetteWidth, shannonEntropy)); }
/// <summary> /// Reads in seed maze genomes used to bootstrap coevolutionary experiments. /// </summary> /// <param name="seedMazePath"> /// The path of the single maze genome or a directory containing multiple XML genome /// definitions. /// </param> /// <param name="mazeGenomeFactory">The maze genome factory to assign to each genome.</param> /// <returns>The list of seed maze genomes.</returns> public static List<MazeGenome> ReadSeedMazeGenomes(string seedMazePath, MazeGenomeFactory mazeGenomeFactory) { string[] mazeGenomeFiles; List<MazeGenome> mazeGenomes = new List<MazeGenome>(); // Get the attributes of the given path/file FileAttributes fileAttributes = File.GetAttributes(seedMazePath); // Determine whether this is a directory or a file if ((fileAttributes & FileAttributes.Directory) == FileAttributes.Directory) { // Get all of the genome files in the directory mazeGenomeFiles = Directory.GetFiles(seedMazePath, "*.xml"); } else { // There's only one file, so make array length 1 and add that file mazeGenomeFiles = new string[1]; mazeGenomeFiles[0] = seedMazePath; } // Read in all maze genomes and add them to the list foreach (string mazeGenomeFile in mazeGenomeFiles) { using (XmlReader xr = XmlReader.Create(mazeGenomeFile)) { // Read in the maze genomes List<MazeGenome> curMazeGenomes = MazeGenomeXmlIO.ReadCompleteGenomeList(xr, mazeGenomeFactory); // Assign the genome factory foreach (MazeGenome curMazeGenome in curMazeGenomes) { curMazeGenome.GenomeFactory = mazeGenomeFactory; } // Add the genomes to the overall genome list mazeGenomes.AddRange(curMazeGenomes); } } return mazeGenomes; }
/// <summary> /// Generates the specified number of maze genomes with the specified complexity (i.e. number of interior partitions). /// </summary> /// <param name="numMazeGenomes">The number of maze genomes to generate.</param> /// <param name="numPartitions">The number of initial partitions (the starting complexity of the genome).</param> /// <param name="mazeGenomeFactory">Reference to the maze genome factory.</param> /// <returns></returns> public static List<MazeGenome> GenerateMazeGenomes(int numMazeGenomes, int numPartitions, MazeGenomeFactory mazeGenomeFactory) { List<MazeGenome> mazeGenomes = new List<MazeGenome>(numMazeGenomes); Random rand = new Random(); for (int curMazeCnt = 0; curMazeCnt < numMazeGenomes; curMazeCnt++) { // Reset innovation IDs mazeGenomeFactory.InnovationIdGenerator.Reset(); // Create a new genome and pass in the requisite factory MazeGenome mazeGenome = new MazeGenome(mazeGenomeFactory, 0, 0); // Create the specified number of interior partitions (i.e. maze genes) for (int cnt = 0; cnt < numPartitions; cnt++) { // Create new maze gene and add to genome mazeGenome.GeneList.Add(new MazeGene(mazeGenomeFactory.InnovationIdGenerator.NextId, rand.NextDouble(), rand.NextDouble(), rand.NextDouble() < 0.5)); } mazeGenomes.Add(mazeGenome); } return mazeGenomes; }
/// <summary> /// Creates the coevolution algorithm container using the given agent and maze population sizes. /// </summary> /// <param name="populationSize1">The agent population size.</param> /// <param name="populationSize2">The maze population size.</param> /// <returns>The instantiated coevolution algorithm container.</returns> public override ICoevolutionAlgorithmContainer<NeatGenome, MazeGenome> CreateCoevolutionAlgorithmContainer( int populationSize1, int populationSize2) { // Create a genome factory for the NEAT genomes IGenomeFactory<NeatGenome> neatGenomeFactory = new NeatGenomeFactory(AnnInputCount, AnnOutputCount, NeatGenomeParameters); // Create a genome factory for the maze genomes IGenomeFactory<MazeGenome> mazeGenomeFactory = new MazeGenomeFactory(MazeGenomeParameters, _mazeHeight, _mazeWidth); // Create an initial population of maze navigators List<NeatGenome> neatGenomeList = neatGenomeFactory.CreateGenomeList(populationSize1, 0); // Create an initial population of mazes // NOTE: the population is set to 1 here because we're just starting with a single, completely open maze space List<MazeGenome> mazeGenomeList = mazeGenomeFactory.CreateGenomeList(populationSize2, 0); // Create the evolution algorithm container return CreateCoevolutionAlgorithmContainer(neatGenomeFactory, mazeGenomeFactory, neatGenomeList, mazeGenomeList); }
/// <summary> /// Handles the process of generating maze genomes and rendering bitmap files of their structure. /// </summary> private static void HandleMazeGeneration() { Random rand = new Random(); // Get the number of interior walls and maze genome output directory int numInteriorWalls = Int32.Parse(_executionConfiguration[ExecutionParameter.NumWalls]); string mazeGenomeOutputDirectory = _executionConfiguration[ExecutionParameter.MazeGenomeOutputBaseDirectory]; // Get the number of sample mazes to generate (or just 1 maze if not specified) int numMazes = _executionConfiguration.ContainsKey(ExecutionParameter.NumMazes) ? Int32.Parse(_executionConfiguration[ExecutionParameter.NumMazes]) : 1; // Get whether images are being generated for the sample mazes bool generateMazeImages = _executionConfiguration.ContainsKey(ExecutionParameter.OutputMazeBitmap) && Boolean.Parse(_executionConfiguration[ExecutionParameter.OutputMazeBitmap]); // Get whether maze genomes are being serialized to the same file (defaults to false) bool isSingleOutputFile = _executionConfiguration.ContainsKey(ExecutionParameter.SingleGenomeOutputFile) && Boolean.Parse(_executionConfiguration[ExecutionParameter.SingleGenomeOutputFile]); // Create a new maze genome factory MazeGenomeFactory mazeGenomeFactory = new MazeGenomeFactory(); // Instantiate list to hold generated maze genomes // (only really used when we're writing everything out to one file) List<MazeGenome> mazeGenomeList = new List<MazeGenome>(numMazes); for (int curMazeCnt = 0; curMazeCnt < numMazes; curMazeCnt++) { MazeGenome mazeGenome = null; // Lay out the base file name string fileBaseName = string.Format("GeneratedMazeGenome_{0}_Walls_{1}", numInteriorWalls, curMazeCnt); // With a single output file, the genomes are likely being used for separate experiments, so we // reset the innovation IDs and assign the maze a constant identifier if (isSingleOutputFile == false) { // Reset innovation IDs mazeGenomeFactory.InnovationIdGenerator.Reset(); // Create a new genome and pass in the requisite factory mazeGenome = new MazeGenome(mazeGenomeFactory, 0, 0); } // Otherwise, we leave the innovation ID generator along and create a new maze genome with // an identifier that's incremented by one else { mazeGenome = new MazeGenome(mazeGenomeFactory, (uint) curMazeCnt, 0); } // Create the specified number of interior walls (i.e. maze genes) for (int cnt = 0; cnt < numInteriorWalls; cnt++) { // Create new maze gene and add to genome mazeGenome?.GeneList.Add(new MazeGene(mazeGenomeFactory.InnovationIdGenerator.NextId, rand.NextDouble(), rand.NextDouble(), rand.NextDouble() < 0.5)); } // Only serialize genomes to separate files if single output file option is turned off if (isSingleOutputFile == false) { // Serialize the genome to XML using ( XmlWriter xmlWriter = XmlWriter.Create( Path.Combine(mazeGenomeOutputDirectory, string.Format("{0}.xml", fileBaseName)), new XmlWriterSettings {Indent = true})) { // Get genome XML MazeGenomeXmlIO.WriteComplete(xmlWriter, mazeGenome); } } // Otherwise, just add genome to list to be serialized to single file in bulk else { mazeGenomeList.Add(mazeGenome); } // Print the maze to a bitmap file if that option has been specified if (generateMazeImages) { PrintMazeToFile(mazeGenome, string.Format("{0}_Structure.bmp", fileBaseName)); } } // If serialize to single output file is turned off, go ahead and write everything out to the file if (isSingleOutputFile) { // Serialize all genomes to XML using ( XmlWriter xmlWriter = XmlWriter.Create(Path.Combine(mazeGenomeOutputDirectory, string.Format("GeneratedMaze_{0}_Genomes_{1}_Walls.xml", numMazes, numInteriorWalls)))) { MazeGenomeXmlIO.WriteComplete(xmlWriter, mazeGenomeList); } } }
private static void Main(string[] args) { // TODO: Refactor all of this to be more configurable - just hard-coding everything now to get results // Base path for maze bitmap output string mazeImageBase = @"F:\User Data\Jonathan\Documents\school\Jonathan\Graduate\PhD\Minimal Criteria Search\Analysis\Coevolution MCS\Images\Final Batch Mazes"; // These are the experiments for which we want to get mazes in the final population List<string> experimentNames = new List<string> { "Coevolution MCS with Maze Initialization 9", "Coevolution MCS with Maze Initialization 10", "Coevolution MCS with Maze Initialization 11", "Coevolution MCS with Maze Initialization 12", "Coevolution MCS with Maze Initialization 13", "Coevolution MCS with Maze Initialization 14", "Coevolution MCS with Maze Initialization 15", "Coevolution MCS with Maze Initialization 16" }; // Setup maze decoder with hard-coded height/width and scale multiplier MazeDecoder mazeDecoder = new MazeDecoder(10, 10, 32); // Create default maze genome factory MazeGenomeFactory mazeGenomeFactory = new MazeGenomeFactory(); foreach (string experimentName in experimentNames) { // Get the current experiment configuration ExperimentDictionary curExperiment = ExperimentDataHandler.LookupExperimentConfiguration(experimentName); // Get the total number of runs of the experiment int numRuns = ExperimentDataHandler.GetNumRuns(curExperiment.ExperimentDictionaryID); for (int runIdx = 1; runIdx <= numRuns; runIdx++) { // Get the total number of batches in the run int numBatches = ExperimentDataHandler.GetNumBatchesForRun(curExperiment.ExperimentDictionaryID, runIdx); // Get the maze population extant in the last batch of the run IList<string> mazePopulationGenomes = ExperimentDataHandler.GetMazeGenomeXml(curExperiment.ExperimentDictionaryID, runIdx, numBatches); // Build output directory string imageOutputDirectory = Path.Combine(mazeImageBase, string.Format("ExperimentName_{0}", curExperiment.ExperimentName), string.Format("Run_{0}", runIdx)); if (Directory.Exists(imageOutputDirectory) == false) { Directory.CreateDirectory(imageOutputDirectory); } // Decode all genomes and render image of structure foreach (string mazeGenomeStr in mazePopulationGenomes) { MazeGenome curMazeGenome; // Unmarshall to maze genome object using (XmlReader xmlReader = XmlReader.Create(new StringReader(mazeGenomeStr))) { curMazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xmlReader, mazeGenomeFactory); } // Generate maze bitmap image ImageGenerationHandler.GenerateMazeStructureImage( Path.Combine(imageOutputDirectory, string.Format("ExperimentID_{0}_Run_{1}_MazeID_{2}.bmp", curExperiment.ExperimentDictionaryID, runIdx, curMazeGenome.Id)), mazeDecoder.Decode(curMazeGenome)); } } } }
/// <summary> /// Reads a single genome from a population from the given XML file. This is typically used in cases where a /// population file is being read in, but it only contains one genome. /// </summary> /// <param name="xr">Reference to the XmlReader.</param> /// <param name="genomeFactory">A MazeGenomeFactory to construct genomes against.</param> /// <returns>Instantiated maze genome.</returns> public static MazeGenome ReadSingleGenomeFromRoot(XmlReader xr, MazeGenomeFactory genomeFactory) { return ReadCompleteGenomeList(xr, genomeFactory)[0]; }