diff --git a/CMakeLists.txt b/CMakeLists.txt index 828a8770..9bdd123c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,24 +78,27 @@ if (BUILD_GP_ONLY) set(BUILD_MAIN 0) set(BUILD_TESTS 0) - # Configure all of tinyxml2 set(XML_SRC_DIR third_party/tinyxml2) set(XML_BUILD_DIR xml_build) add_subdirectory(${XML_SRC_DIR} ${XML_BUILD_DIR}) - #added the executable - add_executable(gp_train_main source/gp_train_main.cpp) + # List of executables + set(EXECUTABLES gp_train_main gp_selective_runner_main gp_train_cgp_main gp_train_lgp_main) - #linking the targets - target_sources(gp_train_main PRIVATE source/core/Entity.cpp) - target_include_directories(gp_train_main - PRIVATE ${CMAKE_SOURCE_DIR}/source/core - ${CMAKE_SOURCE_DIR}/source/Agents - ) - target_link_libraries(gp_train_main - PRIVATE tinyxml2 - PRIVATE pthread - ) + # Common source files and include directories + set(COMMON_SOURCES source/core/Entity.cpp) + set(COMMON_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/source/core ${CMAKE_SOURCE_DIR}/source/Agents) + + # Common libraries to link with + set(COMMON_LIBRARIES tinyxml2 pthread) + + # Loop to configure each executable + foreach(EXECUTABLE IN LISTS EXECUTABLES) + add_executable(${EXECUTABLE} source/${EXECUTABLE}.cpp) + target_sources(${EXECUTABLE} PRIVATE ${COMMON_SOURCES}) + target_include_directories(${EXECUTABLE} PRIVATE ${COMMON_INCLUDE_DIRS}) + target_link_libraries(${EXECUTABLE} PRIVATE ${COMMON_LIBRARIES}) + endforeach() endif () diff --git a/source/Agents/GP/CGPAgent.hpp b/source/Agents/GP/CGPAgent.hpp index 3e33ae50..dc2ea3fa 100644 --- a/source/Agents/GP/CGPAgent.hpp +++ b/source/Agents/GP/CGPAgent.hpp @@ -1,7 +1,6 @@ /** * This file is part of the Fall 2023, CSE 491 course project. * @brief An Agent based on genetic programming. - * @note Status: PROPOSAL **/ #pragma once @@ -17,13 +16,13 @@ namespace cowboys { /// Don't know the maximum size a state can be, arbitrary large number - constexpr size_t INPUT_SIZE = 6; + constexpr size_t INPUT_SIZE = 9; /// Number of computational layers for each agent constexpr size_t NUM_LAYERS = 3; /// The number of nodes in each layer - constexpr size_t NUM_NODES_PER_LAYER = 5; + constexpr size_t NUM_NODES_PER_LAYER = 2; /// The number of layers preceding a node's layer that the node can reference constexpr size_t LAYERS_BACK = 2; @@ -65,8 +64,6 @@ namespace cowboys { genotype = CGPGenotype({INPUT_SIZE, action_map.size(), NUM_LAYERS, NUM_NODES_PER_LAYER, LAYERS_BACK}); } - genotype.SetSeed(rand()); - // Mutate the beginning genotype, might not want this. MutateAgent(0.2); @@ -80,8 +77,11 @@ namespace cowboys { return action_to_take; } - - void Serialize(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* parentElem, double fitness = -1) override { + /// @brief Serialize this agent to XML. + /// @param doc The XML document to serialize to. + /// @param parentElem The parent element to serialize to. + /// @param fitness The fitness of this agent to write to the XML. + void SerializeGP(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentElem, double fitness = -1) override { auto agentElem = doc.NewElement("CGPAgent"); parentElem->InsertEndChild(agentElem); @@ -96,6 +96,16 @@ namespace cowboys { } + /// @brief Export the genotype for this agent. + /// @return The string representation of the genotype for this agent. + std::string Export() override { return genotype.Export(); } + + /// @brief Load in the string representation of a genotype and configure this agent based on it. + /// @param genotype The string representation of a genotype. + void Import(const std::string &genotype) override { + this->genotype.Configure(genotype); + decision_graph = GraphBuilder().CartesianGraph(this->genotype, FUNCTION_SET, this); + } /// @brief Get the genotype for this agent. /// @return A const reference to the genotype for this agent. @@ -115,6 +125,21 @@ namespace cowboys { assert(dynamic_cast(&other) != nullptr); Configure(dynamic_cast(other)); } + + /// @brief The complexity of this agent. Used for fitness. + /// @return The complexity of this agent. + double GetComplexity() const { + double connection_complexity = + static_cast(genotype.GetNumConnections()) / genotype.GetNumPossibleConnections(); + + double functional_nodes = genotype.GetNumFunctionalNodes(); + + // Just needed some function such that connection_complexity + node_complexity grows as the number of nodes grows, this function makes the increase more gradual. + double node_complexity = std::log(functional_nodes) / 5; + + double complexity = connection_complexity + node_complexity; + return complexity; + } }; } // End of namespace cowboys diff --git a/source/Agents/GP/CGPGenotype.hpp b/source/Agents/GP/CGPGenotype.hpp index a286d277..ca9495ec 100644 --- a/source/Agents/GP/CGPGenotype.hpp +++ b/source/Agents/GP/CGPGenotype.hpp @@ -232,9 +232,6 @@ namespace cowboys { /// The node configurations. std::vector nodes; - /// The random number generator. - std::mt19937 rng; - private: /// @brief Encodes the header into a string. /// @return The encoded header. @@ -512,11 +509,8 @@ namespace cowboys { /// @return Bool value to indicate if any input connections non-zero. bool HasInputConnections() const { for (auto it = begin(); it != end(); ++it) { - if (std::any_of( - it->input_connections.begin(), - it->input_connections.end(), - [](char c) { return c != '0'; } - )) return true; + if (std::any_of(it->input_connections.begin(), it->input_connections.end(), [](char c) { return c != '0'; })) + return true; } return false; } @@ -563,22 +557,15 @@ namespace cowboys { return header + HEADER_END + genotype; } - /// @brief Sets the seed of the random number generator. - CGPGenotype &SetSeed(size_t seed) { - rng.seed(seed); - return *this; - } - /// @brief Mutates the genotype. /// @param mutation_rate Value between 0 and 1 representing the probability of mutating a value. /// @param mutation The function to use for mutating the output. The function will receive the node gene as a /// parameter. /// @return This genotype. - CGPGenotype &Mutate(double mutation_rate, std::function mutation) { + CGPGenotype &Mutate(double mutation_rate, GPAgentBase &agent, std::function mutation) { assert(mutation_rate >= 0.0 && mutation_rate <= 1.0); - std::uniform_real_distribution dist_mutation(0.0, 1.0); for (CGPNodeGene &node : nodes) - if (dist_mutation(rng) < mutation_rate) + if (agent.GetRandom() < mutation_rate) mutation(node); return *this; } @@ -590,7 +577,7 @@ namespace cowboys { /// @return This genotype. CGPGenotype &MutateConnections(double mutation_rate, GPAgentBase &agent) { std::uniform_int_distribution dist(0, 1); - Mutate(mutation_rate, [&agent](CGPNodeGene &node) { + Mutate(mutation_rate, agent, [&agent](CGPNodeGene &node) { for (char &con : node.input_connections) { con = agent.GetRandomULL(2) == 0 ? '0' : '1'; } @@ -603,7 +590,7 @@ namespace cowboys { /// @param num_functions The number of functions available to the nodes. /// @return This genotype. CGPGenotype &MutateFunctions(double mutation_rate, size_t num_functions, GPAgentBase &agent) { - Mutate(mutation_rate, + Mutate(mutation_rate, agent, [num_functions, &agent](CGPNodeGene &node) { node.function_idx = agent.GetRandomULL(num_functions); }); return *this; } @@ -615,7 +602,7 @@ namespace cowboys { /// @return This genotype. CGPGenotype &MutateOutputs(double mutation_rate, double mean, double std, GPAgentBase &agent, bool additive = true) { - Mutate(mutation_rate, [mean, std, &agent, additive](CGPNodeGene &node) { + Mutate(mutation_rate, agent, [mean, std, &agent, additive](CGPNodeGene &node) { double mutation = agent.GetRandomNormal(mean, std); if (additive) { node.default_output += mutation; diff --git a/source/Agents/GP/GPAgentAnalyze.h b/source/Agents/GP/GPAgentAnalyze.h new file mode 100644 index 00000000..bcf3569b --- /dev/null +++ b/source/Agents/GP/GPAgentAnalyze.h @@ -0,0 +1,104 @@ +// +// A class that analyzes the data of the best agent in the GP algorithm +// and saves it to a csv file +// + +#pragma once + +#include +#include +#include +#include + +namespace cowboys { + + class GPAgentAnalyzer { + private: + /// The average fitness of the best agent + std::vector average_fitness; + + /// The max fitness of the best agent + std::vector max_fitness; + + /// The weighted score of the best agent + std::vector elite_score; + + /// The average score of the best agent + std::vector average_score; + + /// The number of agents with the max fitness + std::vector max_agents; + + public: + /** + * @brief Construct a new GP Agent Analyzer object + */ + GPAgentAnalyzer() = default; + + /** + * @brief Destroy the GP Agent Analyzer object + */ + ~GPAgentAnalyzer() = default; + + /** + * @brief Adds the average fitness of the best agent + * @param fitness + */ + void addAverageFitness(double fitness) { + average_fitness.push_back(fitness); + } + + /** + * @brief Adds the max fitness of the best agent + * @param fitness + */ + void addMaxFitness(double fitness) { + max_fitness.push_back(fitness); + } + + /** + * @brief Adds the weighted score of the best agent + * @param score + */ + void addEliteScore(double score) { + elite_score.push_back(score); + } + + /** + * @brief Adds the average score of the best agent + * @param score + */ + void addAverageScore(double score) { + average_score.push_back(score); + } + + /** + * @brief Adds the number of agents with the max fitness + * @param num_agents + */ + void addNumAgentsWithMaxFitness(double num_agents) { + max_agents.push_back(num_agents); + } + + /** + * @brief Saves the data to a csv file + */ + void saveToFile() { + // create a new file + std::ofstream file("gp_agent_analyzer.csv"); + + // write the data to the file + file << "average_fitness,max_fitness,average_elite_score,best_agent_weighted_score,agents_with_max_fitness\n"; + + for (int i = 0; i < average_fitness.size(); i++) { + file << average_fitness[i] << "," << max_fitness[i] << "," << elite_score[i] << "," << average_score[i] << "," << max_agents[i] << "\n"; + } + std::cout << "Saved GP Agent Analyzer data to gp_agent_analyzer.csv" << std::endl; + + // close the file + file.close(); + } + }; + +} // namespace cowboys + diff --git a/source/Agents/GP/GPAgentBase.hpp b/source/Agents/GP/GPAgentBase.hpp index 962d48f6..16e192b7 100644 --- a/source/Agents/GP/GPAgentBase.hpp +++ b/source/Agents/GP/GPAgentBase.hpp @@ -26,19 +26,28 @@ namespace cowboys { std::normal_distribution norm_dist; ///< Normal distribution. public: - GPAgentBase(size_t id, const std::string &name) : AgentBase(id, name) { extra_state["previous_action"] = 0; } + GPAgentBase(size_t id, const std::string &name) : AgentBase(id, name) { + Reset(); + } ~GPAgentBase() = default; /// @brief Setup graph. /// @return Success. - bool Initialize() override { return true; } + bool Initialize() override { return true; } /// Choose the action to take a step in the appropriate direction. size_t SelectAction(const cse491::WorldGrid &grid, const cse491::type_options_t &type_options, const cse491::item_map_t &item_set, const cse491::agent_map_t &agent_set) override { + // Update extra state information before action + if (extra_state["starting_x"] == std::numeric_limits::max()) { + auto pos = GetPosition(); + extra_state["starting_x"] = pos.GetX(); + extra_state["starting_y"] = pos.GetY(); + } + size_t action = GetAction(grid, type_options, item_set, agent_set); - // Update extra state information. + // Update extra state information after action extra_state["previous_action"] = action; return action; @@ -63,14 +72,18 @@ namespace cowboys { }; - virtual void Serialize(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentElem, double fitness = -1) = 0; + virtual void SerializeGP(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentElem, double fitness = -1) = 0; virtual std::string Export() { return ""; } - virtual void Reset(bool /*hard*/ = false) { extra_state["previous_action"] = 0; }; + virtual void Reset(bool /*hard*/ = false) { + extra_state["previous_action"] = 0; + extra_state["starting_x"] = std::numeric_limits::max(); + extra_state["starting_y"] = std::numeric_limits::max(); + }; // virtual void crossover(const GPAgentBase &other) {}; - // virtual void Import(const std::string &genotype) {}; + virtual void Import(const std::string &genotype) = 0; // -- Random Number Generation -- diff --git a/source/Agents/GP/GPTrainingLoop.hpp b/source/Agents/GP/GPTrainingLoop.hpp index ada100d0..523b20b0 100644 --- a/source/Agents/GP/GPTrainingLoop.hpp +++ b/source/Agents/GP/GPTrainingLoop.hpp @@ -8,6 +8,7 @@ #include "CGPAgent.hpp" +#include "LGPAgent.hpp" #include #include @@ -21,7 +22,7 @@ #include "tinyxml2.h" - +#include "GPAgentAnalyze.h" namespace cowboys { @@ -36,6 +37,7 @@ namespace cowboys { std::vector> agents; std::vector> TEMPAgentFitness; + GPAgentAnalyzer analyzer; tinyxml2::XMLDocument topAgentsDoc; tinyxml2::XMLDocument lastGenerationsTopAgentsDoc; // <- Saves the last 5 generations @@ -75,18 +77,25 @@ namespace cowboys { std::vector>> endPositions = std::vector>>(); std::vector>> independentAgentFitness = std::vector>>(); + int global_max_threads = std::thread::hardware_concurrency(); + int rollingRandomSeed = 0; + + double ELITE_POPULATION_PERCENT = 0.1; + double UNFIT_POPULATION_PERCENT = 0.2; + public: + bool ScavengerQueuing = false; /** * @brief: constructor */ - GPTrainingLoop() { + GPTrainingLoop(const bool scavengerQueuing = false) : ScavengerQueuing(scavengerQueuing) { topAgentsDoc.InsertFirstChild(rootTopAllGenerations); rootMetaData = metaData.NewElement("GPLoopMetaData"); metaData.InsertFirstChild(rootMetaData); - + srand(TRAINING_SEED); ResetMainTagLastGenerations(); } @@ -146,13 +155,15 @@ namespace cowboys { } + loadLastGeneration(); + Printgrid(STARTPOSITIONS); const size_t numAgents = numArenas * NumAgentsForArena; std::stringstream ss; - ss.imbue(std::locale("")); + // ss.imbue(std::locale("")); ss << std::fixed << numAgents; std::cout << "number of agents " << std::fixed << ss.str() << std::endl; @@ -160,6 +171,99 @@ namespace cowboys { } + void loadLastGeneration() { + if (ScavengerQueuing) + { + + auto lastFile = FullLoadGrabLatestGeneration(); + + if (lastFile == "NOTTAFILE") + { + std::cout << "No last file found" << std::endl; + return; + } + + allOfLastGeneration.LoadFile(lastFile.string().c_str()); + rootAllOfLastGeneration = allOfLastGeneration.FirstChildElement("GPLoopAllOfLastGeneration"); + + std::cout << "Loaded last file" << std::endl; + +// if (auto agentType = dynamic_cast(&agents[0][0])) { +// std::cout << "Agent Type is CGPAgent" << std::endl; +// agentType->Import(lastFile.string()); +// } +// else if (auto agentType = dynamic_cast(&agents[0][0])) { +// std::cout << "Agent Type is LGPAgent" << std::endl; +// agentType->Import(lastFile.string()); +// } +// else { +// std::cout << "Agent Type is not CGPAgent or LGPAgent" << std::endl; +// } +// use typetraits to check the type of the agent + if (std::is_same::value) { + std::cout << "Agent Type is CGPAgent" << std::endl; + } + else if (std::is_same::value) { + std::cout << "Agent Type is LGPAgent" << std::endl; + assert(false); //TODO: Agent not implemented for import + } + else { + std::cout << "Agent Type is not CGPAgent or LGPAgent" << std::endl; + } + + + auto *latestGenerationElem = rootAllOfLastGeneration->FirstChildElement(); + std::cout << latestGenerationElem->Name() << std::endl; + auto *agentElem = latestGenerationElem->FirstChildElement(); + tinyxml2::XMLElement* generationElem = nullptr; + + std::cout << agentElem->Name() << std::endl; + size_t numArenas = agents.size(); + size_t NumAgentsForArena = agents.at(0).size(); + for (size_t i = 0; i < numArenas; ++i) { + for (size_t j = 0; j < NumAgentsForArena; ++j) { + + generationElem = agentElem->FirstChildElement(); + const char *genotypeData = generationElem->GetText(); + agents.at(i).at(j)->Import(genotypeData); + agentElem = agentElem->NextSiblingElement(); + } + } + + std::cout << "Agents LoadComplete" << std::endl; + + } + } + + std::filesystem::path FullLoadGrabLatestGeneration() { + std::filesystem::path normalizedAbsolutePath = getSystemPath(); + + std::string lastGenerationsPrefix = "allAgentData_"; + std::string lastGenerationsFilenameExtension = ".xml"; + + std::vector matchingFiles; + for (const auto & entry : std::filesystem::directory_iterator(normalizedAbsolutePath)) + { + if (entry.path().extension() == lastGenerationsFilenameExtension && entry.path().filename().string().find(lastGenerationsPrefix) != std::string::npos) + { + matchingFiles.push_back(entry.path()); + } + } + std::sort(matchingFiles.begin(), matchingFiles.end()); + + if (matchingFiles.empty()) + { + return "NOTTAFILE"; + } + + std::filesystem::path lastFile = matchingFiles.back(); + + std::cout << "Last File: " << lastFile << std::endl; + + return lastFile; + } + + /** * Simple and temporary fitness function * @param agent @@ -180,15 +284,32 @@ namespace cowboys { // Agent complexity, temporarily doing this in a bad way if (auto *cgp = dynamic_cast(&agent)) { - auto genotype = cgp->GetGenotype(); - double connection_complexity = - static_cast(genotype.GetNumConnections()) / genotype.GetNumPossibleConnections(); + fitness -= cgp->GetComplexity(); + } - double functional_nodes = genotype.GetNumFunctionalNodes(); - double node_complexity = functional_nodes / (functional_nodes + 1); + return fitness; + } + +// STARTPOSITIONS[startPos_idx], agents[arena][a]->GetPosition(), arena, a + double AStarFitnessFunction(const cse491::GridPosition & startpos, const cse491::GridPosition & endpos, int arena, int a) + { + double fitness = 0; + + // Euclidean distance + cse491::GridPosition currentPosition = endpos; + double distance = static_cast( + Get_A_StarDistance(startpos, currentPosition, arena, a) + ); + + + double score = distance; + + fitness += score; - double complexity = connection_complexity + node_complexity; - fitness -= complexity; + cse491::AgentBase &agent = *agents[arena][a]; + // Agent complexity, temporarily doing this in a bad way + if (auto *cgp = dynamic_cast(&agent)){ + fitness -= cgp->GetComplexity(); } return fitness; @@ -234,6 +355,8 @@ namespace cowboys { size_t threadsComplete = 0; + + for (size_t arena = 0; arena < environments.size(); ++arena) { if (maxThreads == 0 || threads.size() < maxThreads) { threads.emplace_back(&GPTrainingLoop::RunArena, this, arena, numberOfTurns); @@ -274,6 +397,8 @@ namespace cowboys { } + + /** * @brief: runs the Genetic Programming training loop for a number of generations to evolve the agents * @@ -287,15 +412,50 @@ namespace cowboys { auto startTime = std::chrono::high_resolution_clock::now(); + global_max_threads = maxThreads; + + SaveDataParams saveDataParams(0); saveDataParams.save = saveData; saveDataParams.saveMetaData = true; -// saveDataParams.saveAllAgentData = true; + saveDataParams.saveAllAgentData = true; // // saveDataParams.saveTopAgents = true; // saveDataParams.saveLastGenerations = true; - for (size_t generation = 0; generation < numGenerations; ++generation) { + + +// check to see if meta data exists + std::filesystem::path normalizedAbsolutePath = getSystemPath(); + std::string metaDataFilename = "metaData.xml"; +// check to see if the file exists + std::filesystem::path metaDataFullPath = normalizedAbsolutePath / metaDataFilename; + + + size_t generation = 0; // <- the generation to start at + + if (std::filesystem::exists(metaDataFullPath) && ScavengerQueuing) { + std::cout << "MetaData file exists" << std::endl; + metaData.LoadFile(metaDataFullPath.string().c_str()); + rootMetaData = metaData.FirstChildElement("GPLoopMetaData"); + auto *generationTag = rootMetaData->FirstChildElement(); + generation = generationTag->UnsignedAttribute("generation") + 1; + rollingRandomSeed = generationTag->UnsignedAttribute("randSeed"); + std::cout << "Starting at generation " << generation << std::endl; + + } else { + if (ScavengerQueuing) + { + std::cout << "MetaData file does not exist Starting a new Scavenger Queue" << std::endl; + } + rollingRandomSeed = TRAINING_SEED; + rootMetaData = metaData.NewElement("GPLoopMetaData"); + metaData.InsertFirstChild(rootMetaData); + } + + for (; generation < numGenerations; ++generation) { + srand(rollingRandomSeed); + rollingRandomSeed = rand(); auto generationStartTime = std::chrono::high_resolution_clock::now(); saveDataParams.updateGeneration(generation); @@ -313,6 +473,7 @@ namespace cowboys { saveDataParams.countMaxAgents = countMaxAgents; SaveDataCheckPoint(saveDataParams); + GpLoopMutateHelper(); resetEnvironments(); @@ -321,6 +482,7 @@ namespace cowboys { generationEndTime - generationStartTime); std::cout << "Generation " << generation << " took " << generationDuration.count() / 1000000.0 << " seconds" << std::endl; + analyzer.saveToFile(); } @@ -423,6 +585,7 @@ namespace cowboys { std::cout << "@@@@@@@@@@@@@@@@@@@@@@ " << "DataSaved" << " @@@@@@@@@@@@@@@@@@@@@@" << std::endl; +// analyzer.saveToFile(getSystemPath() / "fitness.csv"); lastGenerationsTopAgentsDoc.Clear(); ResetMainTagLastGenerations(); } @@ -440,6 +603,15 @@ namespace cowboys { return ss.str(); } + size_t Get_A_StarDistance(const cse491::GridPosition & startpos, const cse491::GridPosition & endpos, int arenaIDX, int agentIDX) { + auto& agent = agents[arenaIDX][agentIDX]; + auto& world = environments[arenaIDX]; + + + auto distance = walle::GetShortestPath(startpos, endpos, *world, *agent).size() - 1; + assert(distance >= 0); + return distance; + } /** @@ -480,16 +652,23 @@ namespace cowboys { std::cout << "Generation " << generation << " complete" << std::endl; std::cout << "Average fitness: " << averageFitness << " "; + analyzer.addAverageFitness(averageFitness); + std::cout << "Max fitness: " << maxFitness << std::endl; + analyzer.addMaxFitness(maxFitness); + - const char *tagName = ("generation_" + std::to_string(generation)).c_str(); - auto *generationTag = metaData.NewElement(tagName); + std::string tagName = "generation_" + std::to_string(generation); + auto *generationTag = metaData.NewElement(tagName.c_str()); + generationTag->SetAttribute("generation", generation); generationTag->SetAttribute("averageFitness", averageFitness); generationTag->SetAttribute("maxFitness", maxFitness); generationTag->SetAttribute("bestAgentIDX", bestAgent.second); + generationTag->SetAttribute("Rand", rollingRandomSeed); + rootMetaData->InsertFirstChild(generationTag); @@ -501,6 +680,11 @@ namespace cowboys { Printgrid(endPositions[bestAgent.first][bestAgent.second], 'A'); + auto& agent = agents[bestAgent.first][bestAgent.second]; + auto& startPosition = STARTPOSITIONS; + auto& endPosition = endPositions[bestAgent.first][bestAgent.second]; + + auto& world = environments[bestAgent.first]; auto calculateDistance = [](const cse491::GridPosition& startPosition, const cse491::GridPosition & currentPosition) { @@ -512,25 +696,33 @@ namespace cowboys { std::cout << std::left << std::setw(columnWidth) << "Start" << std::setw(columnWidth) << "Final" - << "Distance\n"; + << std::setw(columnWidth) << "Distance" + << "A* Distance\n"; for (size_t i = 0; i < STARTPOSITIONS.size(); ++i) { std::cout << std::setw(columnWidth) << FormatPosition(STARTPOSITIONS[i]) << std::setw(columnWidth) << FormatPosition(endPositions[bestAgent.first][bestAgent.second][i]); - double distance = calculateDistance(STARTPOSITIONS[i], endPositions[bestAgent.first][bestAgent.second][i]); - std::cout << std::fixed << std::setprecision(2) << std::setw(6) << distance; + // Calculate and print Euclidean distance + double distance = calculateDistance(STARTPOSITIONS[i], endPositions[bestAgent.first][bestAgent.second][i]); + std::cout << std::fixed << std::setprecision(2) << std::setw(12) << distance; + + + // Calculate and print A* distance + size_t astarDistance = Get_A_StarDistance(startPosition[i], endPosition[i], bestAgent.first, bestAgent.second); + std::cout << std::setw(12) << astarDistance; std::cout << std::endl; } - std::cout << "with an average score of " << TEMPAgentFitness[bestAgent.first][bestAgent.second] << std::endl; + std::cout << "with best agent weighted score of " << TEMPAgentFitness[bestAgent.first][bestAgent.second] << std::endl; std::cout << std::endl; - + analyzer.addAverageScore(TEMPAgentFitness[bestAgent.first][bestAgent.second]); std::cout << "Number of agents with max fitness: " << countMaxAgents << std::endl; std::cout << "------------------------------------------------------------------" << std::endl; + analyzer.addNumAgentsWithMaxFitness(countMaxAgents); return countMaxAgents; } @@ -573,15 +765,15 @@ namespace cowboys { void SerializeAgents(int generation, tinyxml2::XMLElement *rootElement, tinyxml2::XMLDocument ¶mDocument, size_t topN = 5) { - const char *tagName = ("generation_" + std::to_string(generation)).c_str(); + std::string tagName = "generation_" + std::to_string(generation); + auto *generationTag = paramDocument.NewElement(tagName.c_str()); - auto *generationTag = paramDocument.NewElement(tagName); rootElement->InsertFirstChild(generationTag); for (size_t i = 0; i < std::min(sortedAgents.size(), topN); ++i) { auto [arenaIDX, agentIDX] = sortedAgents[i]; - agents[arenaIDX][agentIDX]->Serialize(paramDocument, generationTag, TEMPAgentFitness[arenaIDX][agentIDX]); + agents[arenaIDX][agentIDX]->SerializeGP(paramDocument, generationTag, TEMPAgentFitness[arenaIDX][agentIDX]); } @@ -653,8 +845,8 @@ namespace cowboys { */ void GpLoopMutateHelper() { - constexpr double ELITE_POPULATION_PERCENT = 0.1; - constexpr double UNFIT_POPULATION_PERCENT = 0.2; +// constexpr double ELITE_POPULATION_PERCENT = 0.1; +// constexpr double UNFIT_POPULATION_PERCENT = 0.2; const int ELITE_POPULATION_SIZE = int(ELITE_POPULATION_PERCENT * sortedAgents.size()); @@ -668,13 +860,14 @@ namespace cowboys { averageEliteFitness /= ELITE_POPULATION_SIZE; std::cout << " --- average elite score " << averageEliteFitness << "------ " << std::endl; - + analyzer.addEliteScore(averageEliteFitness); const int MIDDLE_MUTATE_ENDBOUND = int(sortedAgents.size() * (1 - UNFIT_POPULATION_PERCENT)); const int MIDDLE_MUTATE_STARTBOUND = int(ELITE_POPULATION_PERCENT * sortedAgents.size()); // Determine the number of threads to use - const int num_threads = std::thread::hardware_concurrency(); + const int num_threads = global_max_threads; +// const int num_threads = std::min(static_cast(std::thread::hardware_concurrency()), global_max_threads); std::vector threads; @@ -831,6 +1024,9 @@ namespace cowboys { void RunArena(size_t arena, size_t numberOfTurns) { for (size_t startPos_idx = 0; startPos_idx < STARTPOSITIONS.size(); ++startPos_idx) { for(size_t a = 0; a < agents[arena].size(); ++a) { + // Reset the agent before each run + agents[arena][a]->Reset(); + // Set the starting position agents[arena][a]->SetPosition(STARTPOSITIONS[startPos_idx]); } @@ -839,7 +1035,8 @@ namespace cowboys { environments[arena]->UpdateWorld(); } for (size_t a = 0; a < agents[arena].size(); ++a) { - double tempscore = SimpleFitnessFunction(*agents[arena][a], STARTPOSITIONS[startPos_idx]); +// double tempscore = SimpleFitnessFunction(*agents[arena][a], STARTPOSITIONS[startPos_idx]); + double tempscore = AStarFitnessFunction(STARTPOSITIONS[startPos_idx], agents[arena][a]->GetPosition(), arena, a); auto tempEndPosition = agents[arena][a]->GetPosition(); endPositions[arena][a][startPos_idx] = tempEndPosition; independentAgentFitness[arena][a][startPos_idx] = tempscore; @@ -863,9 +1060,10 @@ namespace cowboys { // TEMPAgentFitness[arena][a] /= STARTPOSITIONS.size(); // TEMPAgentFitness[arena][a] += computeMedian(); double min = *std::min_element(scores.begin(), scores.end()); - double avg = TEMPAgentFitness[arena][a] / STARTPOSITIONS.size(); -// TEMPAgentFitness[arena][a] = 0.7 * min + 0.3 * avg; - TEMPAgentFitness[arena][a] = min; + [[maybe_unused]] double avg = TEMPAgentFitness[arena][a] / STARTPOSITIONS.size(); + // TEMPAgentFitness[arena][a] = 0.7 * min + 0.3 * avg; + // TEMPAgentFitness[arena][a] = min; + TEMPAgentFitness[arena][a] = avg; } } @@ -884,4 +1082,4 @@ namespace cowboys { ~GPTrainingLoop() = default; }; -} \ No newline at end of file +} diff --git a/source/Agents/GP/Graph.hpp b/source/Agents/GP/Graph.hpp index 458710ba..4580ab7f 100644 --- a/source/Agents/GP/Graph.hpp +++ b/source/Agents/GP/Graph.hpp @@ -8,6 +8,7 @@ #include #include "../../core/AgentBase.hpp" +#include "../AgentLibary.hpp" #include "GraphNode.hpp" namespace cowboys { @@ -115,7 +116,7 @@ namespace cowboys { /// @return A vector of doubles for the decision graph. std::vector EncodeState(const cse491::WorldGrid &grid, const cse491::type_options_t & /*type_options*/, const cse491::item_map_t & /*item_set*/, const cse491::agent_map_t & /*agent_set*/, - const cse491::Entity *agent, + const cse491::AgentBase *agent, const std::unordered_map &extra_agent_state) { /// TODO: Implement this function properly. std::vector inputs; @@ -129,8 +130,13 @@ namespace cowboys { double right_state = grid.IsValid(current_position.ToRight()) ? grid.At(current_position.ToRight()) : 0.; double prev_action = extra_agent_state.at("previous_action"); + double starting_x = extra_agent_state.at("starting_x"); + double starting_y = extra_agent_state.at("starting_y"); + auto starting_pos = cse491::GridPosition(starting_x, starting_y); + auto path = walle::GetShortestPath(agent->GetPosition(), starting_pos, agent->GetWorld(), *agent); + double distance_from_start = path.size(); - inputs.insert(inputs.end(), {prev_action, current_state, above_state, below_state, left_state, right_state}); + inputs.insert(inputs.end(), {prev_action, starting_x, starting_y, distance_from_start, current_state, above_state, below_state, left_state, right_state}); return inputs; } diff --git a/source/Agents/GP/GraphNode.hpp b/source/Agents/GP/GraphNode.hpp index 3aa213a5..93327dac 100644 --- a/source/Agents/GP/GraphNode.hpp +++ b/source/Agents/GP/GraphNode.hpp @@ -387,6 +387,14 @@ namespace cowboys { /// @param agent The agent that the node belongs to. /// @return The distance to the grid position using A* double AStarDistance(const GraphNode &node, const cse491::AgentBase &agent) { + // + // The outputs of the first two connections are the x and y coordinates of the goal position. It'd probably be rare + // for agents to randomly use it in a useful way. Most of the time when it IS used, there is no input connections + // and thus the default output is used, so it isn't REALLY being used. Other times when it does have input + // connections, the agent has a lower fitness, so it probably wasn't making good use of it. + // + // Decided to make an easier way A* can be used by agents by giving the A* distance from the agent's start position + // as an input. This can still be used in the off chance it is useful. auto vals = node.GetInputValues<2>(std::array{0, 1}); if (!vals.has_value()) return node.GetDefaultOutput(); diff --git a/source/Agents/GP/LGPAgent.hpp b/source/Agents/GP/LGPAgent.hpp index 427927ce..16085cf3 100644 --- a/source/Agents/GP/LGPAgent.hpp +++ b/source/Agents/GP/LGPAgent.hpp @@ -1,3 +1,7 @@ +/** + * This file is part of the Fall 2023, CSE 491 course project. + * @brief An Agent based on linear genetic programming. + **/ #pragma once @@ -19,14 +23,14 @@ namespace cowboys { protected: // A dictionary of actions and a dictionary of sensors - // A sensor is a function that takes in a grid and returns a value (e.g. distance to nearest agent) - - // For example group 1 has a function for the shortest path + // A sensor is a function that takes in a grid and returns a value (e.g. distance to left wall) std::vector possibleInstructionsList = {}; std::vector actionsList = {}; std::vector operationsList = {"lessthan", "greaterthan", "equals"}; std::vector sensorsNamesList = {"getLeft", "getRight", "getUp", "getDown"}; + + // A list that stores the results of executed instructions std::vector resultsList; std::vector> instructionsList = {}; @@ -46,9 +50,7 @@ namespace cowboys } } - - - /// @brief This agent needs a specific set of actions to function. + /// @brief Initialize the agent after being given an action map /// @return Success. bool Initialize() override { @@ -57,9 +59,10 @@ namespace cowboys return true; } + /// @brief Generate a random list of instructions from a list of possible instructions + /// @return Success. void GenerateRandomActionList() { - // generate a random list of actions std::uniform_int_distribution dist(0, possibleInstructionsList.size() - 1); std::uniform_int_distribution dist2(0, LISTSIZE - 1); for (int i = 0; i < LISTSIZE; i++) @@ -71,8 +74,12 @@ namespace cowboys /// @brief Encodes the actions from an agent's action map into a vector of string, representing action names. /// @param action_map The action map from the agent. + /// @param sensorsNamesList The list of sensors from the agent. + /// @param operationsList The list of operations from the agent. + /// @param actionsList The list of actions from the agent. /// @return A vector of strings, representing action names. - static std::vector EncodeActions(const std::unordered_map &action_map, const std::vector &sensorsNamesList, const std::vector &operationsList, std::vector &actionsList) + static std::vector EncodeActions(const std::unordered_map &action_map, const std::vector &sensorsNamesList, + const std::vector &operationsList, std::vector &actionsList) { std::vector instructions; for (const auto &[action_name, action_id] : action_map) @@ -93,7 +100,8 @@ namespace cowboys return instructions; } - + /// @brief Mutate this agent. + /// @param mutation_rate The probability of any instruction being changed void MutateAgent(double mutation_rate = 0.01) override { std::uniform_int_distribution rnd_mutate(1, 100); @@ -108,8 +116,6 @@ namespace cowboys } } - -// resultsList = std::vector(LISTSIZE); resultsList.clear(); resultsList.resize(LISTSIZE); currentInstructionIndex = 0; @@ -139,10 +145,13 @@ namespace cowboys Configure(dynamic_cast(other)); } - std::string Export() override { - return ""; - } + /// @brief Get the action to take. + /// @param grid The world grid. + /// @param type_options The available types of cells in the grid. + /// @param item_set The set of items in the world. + /// @param agent_set The set of agents in the world. + /// @return A size_t corresponding to the action chosen size_t GetAction([[maybe_unused]] const cse491::WorldGrid &grid, [[maybe_unused]] const cse491::type_options_t &type_options, [[maybe_unused]] const cse491::item_map_t &item_set, @@ -167,6 +176,7 @@ namespace cowboys { if (std::find(actionsList.begin(), actionsList.end(), std::get<0>(instruction)) != actionsList.end()) { + // the instruction is in the action list (provided by the world) action = std::get<0>(instruction); } else if (std::find(sensorsNamesList.begin(), sensorsNamesList.end(), std::get<0>(instruction)) != sensorsNamesList.end()) @@ -240,14 +250,121 @@ namespace cowboys return 0; } - void Serialize(tinyxml2::XMLDocument &, tinyxml2::XMLElement *, double fitness = -1) override {} + /// @brief Export the agent to a string + /// @return The string representation of the agent + std::string Export() override { + std::string encodedLists = ""; + + for (auto instruction : instructionsList) + { + encodedLists += std::get<0>(instruction); + encodedLists += "."; + encodedLists += std::to_string(std::get<1>(instruction)); + encodedLists += "."; + encodedLists += std::to_string(std::get<2>(instruction)); + encodedLists += ","; + } + + encodedLists += ";"; + + for (auto possInstruction : possibleInstructionsList) + { + encodedLists += possInstruction; + encodedLists += "."; + } + + encodedLists += ";"; + + for (auto action : actionsList) + { + encodedLists += action; + encodedLists += "."; + } + + return encodedLists; + } + + /// @brief Serialize this agent to XML. + /// @param doc The XML document to serialize to. + /// @param parentElem The parent element to serialize to. + /// @param fitness The fitness of this agent to write to the XML. + void SerializeGP(tinyxml2::XMLDocument & doc, tinyxml2::XMLElement* parentElem, double fitness = -1) override + { + auto agentElem = doc.NewElement("LGPAgent"); + parentElem->InsertEndChild(agentElem); + + auto listElem = doc.NewElement("instruction list"); + listElem->SetText(Export().c_str()); + if (fitness != -1) + listElem->SetAttribute("fitness", fitness); + agentElem->InsertEndChild(listElem); + } + + /// @brief Load in the string representation of an LGP agent and configure this agent based on it. + /// @param genotype The string representation of an LGP agent. + void Import(const std::string & encodedLists) override { + std::vector> decodedInstructionsList = {}; + std::string decodedInstruction; + size_t start_pos = 0; + size_t first_period_pos; + size_t second_period_pos; + size_t comma_pos = encodedLists.find(","); + + // Load the instruction list + while (comma_pos != std::string::npos) { + decodedInstruction = encodedLists.substr(start_pos, comma_pos - start_pos); + first_period_pos = decodedInstruction.find("."); + second_period_pos = decodedInstruction.find(".", first_period_pos + 1); + decodedInstructionsList.push_back(std::make_tuple(decodedInstruction.substr(0, first_period_pos), + std::stoi(decodedInstruction.substr(first_period_pos+1, second_period_pos-first_period_pos+1)), std::stoi(decodedInstruction.substr(second_period_pos+1)))); + + start_pos = comma_pos + 1; + comma_pos = encodedLists.find(",", start_pos); + } + + std::vector decodedPossInstructionsList = {}; + std::vector decodedActionsList = {}; + size_t first_semicolon_pos = encodedLists.find(";"); + size_t second_semicolon_pos = encodedLists.find(";", first_semicolon_pos+1); + std::string unseparated_instruction_list = encodedLists.substr(first_semicolon_pos+1, second_semicolon_pos-first_semicolon_pos+1); + std::string unseparated_action_list = encodedLists.substr(second_semicolon_pos+1); + + // Load the list of possible instructions + size_t period_pos = unseparated_instruction_list.find("."); + start_pos = 0; + while (period_pos != std::string::npos) { + decodedPossInstructionsList.push_back(unseparated_instruction_list.substr(start_pos, period_pos)); + + start_pos = period_pos + 1; + period_pos = encodedLists.find(".", start_pos); + } + + // Load the list of actions + period_pos = unseparated_action_list.find("."); + start_pos = 0; + while (period_pos != std::string::npos) { + decodedActionsList.push_back(unseparated_action_list.substr(start_pos, period_pos)); + + start_pos = period_pos + 1; + period_pos = encodedLists.find(".", start_pos); + } + + instructionsList = decodedInstructionsList; + possibleInstructionsList = decodedPossInstructionsList; + actionsList = decodedActionsList; + resultsList.clear(); + resultsList.resize(LISTSIZE); + currentInstructionIndex = 0; + } + + /// @brief Print the agent void PrintAgent() override { - for (auto i = 0; i < LISTSIZE; i++) - { - std::cout << std::get<0>(instructionsList[i]) << " "; - } - std::cout << std::endl; + for (auto i = 0; i < LISTSIZE; i++) + { + std::cout << std::get<0>(instructionsList[i]) << " "; + } + std::cout << std::endl; } }; -} \ No newline at end of file +} diff --git a/source/Worlds/MazeWorld.hpp b/source/Worlds/MazeWorld.hpp index f7089df2..d37d82d2 100644 --- a/source/Worlds/MazeWorld.hpp +++ b/source/Worlds/MazeWorld.hpp @@ -30,6 +30,7 @@ class MazeWorld : public WorldBase { public: MazeWorld(unsigned int seed = 0) : WorldBase(seed) { // Create cell types + floor_id = AddCellType("floor", "Floor that you can easily walk over.", ' '); wall_id = AddCellType( diff --git a/source/gp_selective_runner_main.cpp b/source/gp_selective_runner_main.cpp new file mode 100644 index 00000000..577e2937 --- /dev/null +++ b/source/gp_selective_runner_main.cpp @@ -0,0 +1,111 @@ +/** + * This file is part of the Fall 2023, CSE 491 course project. + * @brief A simplistic main file to demonstrate a system. + * @note Status: PROPOSAL + **/ + +// Include the modules that we will be using. +#include "Agents/PacingAgent.hpp" +#include "Interfaces/TrashInterface.hpp" +#include "Worlds/MazeWorld.hpp" + +#include "Agents/GP/GPAgentBase.hpp" +#include "Agents/GP/LGPAgent.hpp" +#include "Agents/GP/CGPAgent.hpp" + +#include + + +void Printgrid(const cse491::MazeWorld &mazeWorld, char symbol = 'S', std::vector agents = {}) { + + + size_t arena = 0; + auto &grid = mazeWorld.GetGrid(arena); + std::vector symbol_grid(grid.GetHeight()); + + + + + + const auto &type_options = mazeWorld.GetCellTypes(); + // Load the world into the symbol_grid; + for (size_t y = 0; y < grid.GetHeight(); ++y) { + symbol_grid[y].resize(grid.GetWidth()); + for (size_t x = 0; x < grid.GetWidth(); ++x) { + symbol_grid[y][x] = type_options[grid.At(x, y)].symbol; + } + } + + + // Load the agents into the symbol_grid; + for (auto agent : agents) { + auto position = agent->GetPosition(); + symbol_grid[position.GetY()][position.GetX()] = symbol; + } + + std::cout << " "; + for (size_t x = 0; x < grid.GetWidth(); ++x) { + if (x % 10 == 0 && x != 0) { + std::cout << x / 10; // Print the ten's place of the column number + } else { + std::cout << " "; // Space for non-marker columns + } + } + std::cout << "\n"; + + // Print column numbers + std::cout << " "; // Space for row numbers + for (size_t x = 0; x < grid.GetWidth(); ++x) { + std::cout << x % 10; // Print only the last digit of the column number + } + std::cout << "\n"; + + // Print out the symbol_grid with a box around it. + std::cout << " +" << std::string(grid.GetWidth(), '-') << "+\n"; + for (size_t y = 0; y < grid.GetHeight(); ++y) { + + if (y % 10 == 0 && y != 0) { + std::cout << y / 10 << " "; // Print the ten's place of the row number + } else { + std::cout << " "; // Space for non-marker rows + } + + // Print row number + std::cout << y % 10 << "|"; // Print only the last digit of the row number + for (char cell: symbol_grid[y]) { + std::cout << cell; + } + std::cout << "|\n"; + } + + std::cout << " +" << std::string(grid.GetWidth(), '-') << "+\n"; + std::cout << std::endl; +} + + +int main() { + cse491::MazeWorld world; +/** + + 6,4,3,9,6;0.j.606uA3:0.0.61duDu:0.3.600:0.0.60iLO:0.5.602j4n:0.n.603vSU:0.6.614Prx:0.g.61xFL:0.l.602iIM:0.h.60185j:00w.3.619bwS:40g.n.619V/2:0.3.611Z3a:2w0.k.601p/w:40g.l.61iZK:0.m.602U29:0.0.601RtI:1g0.8.603nKR:0.4.604iMQ:0.m.61g6tL:0.j.602df1:a8j8.3.61bYRZ:0.f.612vfs:a084.3.6114Xe:0A60.e.614pTw:0.c.613CKA:0.j.611NQG:0.3.60765e:000e1w.f.602oxB:00085g.l.601xVk:000i1c.m.600: + + */ + + //GP agent + auto& GPagent = static_cast(world.AddAgent("GP 1").SetProperty("symbol", 'G')); + GPagent.Import("6,4,3,9,6;0.j.606uA3:0.0.61duDu:0.3.600:0.0.60iLO:0.5.602j4n:0.n.603vSU:0.6.614Prx:0.g.61xFL:0.l.602iIM:0.h.60185j:00w.3.619bwS:40g.n.619V/2:0.3.611Z3a:2w0.k.601p/w:40g.l.61iZK:0.m.602U29:0.0.601RtI:1g0.8.603nKR:0.4.604iMQ:0.m.61g6tL:0.j.602df1:a8j8.3.61bYRZ:0.f.612vfs:a084.3.6114Xe:0A60.e.614pTw:0.c.613CKA:0.j.611NQG:0.3.60765e:000e1w.f.602oxB:00085g.l.601xVk:000i1c.m.600:"); + GPagent.SetPosition(0, 1); + for (int i = 0; i < 100; ++i) { + world.RunAgents(); + + world.UpdateWorld(); + std::cout << "Generation: " << i << std::endl; + Printgrid(world, 'G', {&GPagent}); + + + constexpr const int kMicrosecondsPerSecond = 170000; + usleep(kMicrosecondsPerSecond); + + } + +} diff --git a/source/gp_train_cgp_main.cpp b/source/gp_train_cgp_main.cpp new file mode 100644 index 00000000..57c29219 --- /dev/null +++ b/source/gp_train_cgp_main.cpp @@ -0,0 +1,38 @@ + +#include "Agents/GP/GPTrainingLoop.hpp" +#include "Agents/GP/LGPAgent.hpp" +#include "Worlds/MazeWorld.hpp" + +#include "Interfaces/TrashInterface.hpp" +#include "Worlds/ManualWorld.hpp" + + +#include + + +#include + +int main() { + +// const int num_threads = std::thread::hardware_concurrency(); + const int num_threads = 6; + std::cout << "Number of threads: " << num_threads << std::endl; + + + auto start_time = std::chrono::high_resolution_clock::now(); + + cowboys::GPTrainingLoop loop(true); + + + loop.Initialize(50, 5000); + loop.Run(200, 70, num_threads, true); + + + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + + auto seconds = duration.count() / 1000000.0; + std::cout << "Time taken by Training: " << seconds << " seconds" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/source/gp_train_lgp_main.cpp b/source/gp_train_lgp_main.cpp new file mode 100644 index 00000000..00426f99 --- /dev/null +++ b/source/gp_train_lgp_main.cpp @@ -0,0 +1,38 @@ + +#include "Agents/GP/GPTrainingLoop.hpp" +#include "Agents/GP/LGPAgent.hpp" +#include "Worlds/MazeWorld.hpp" + +#include "Interfaces/TrashInterface.hpp" +#include "Worlds/ManualWorld.hpp" + + +#include + + +#include + +int main() { + +// const int num_threads = std::thread::hardware_concurrency(); + const int num_threads = 6; + std::cout << "Number of threads: " << num_threads << std::endl; + + + auto start_time = std::chrono::high_resolution_clock::now(); + + cowboys::GPTrainingLoop loop(false); + + + loop.Initialize(50, 5000); + loop.Run(150, 70, num_threads, false); + + + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time); + + auto seconds = duration.count() / 1000000.0; + std::cout << "Time taken by Training: " << seconds << " seconds" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/source/gp_train_main.cpp b/source/gp_train_main.cpp index 2544b48d..20454975 100644 --- a/source/gp_train_main.cpp +++ b/source/gp_train_main.cpp @@ -15,17 +15,23 @@ int main() { const int num_threads = std::thread::hardware_concurrency(); + +// const int num_threads = 6; + + + std::cout << "Number of threads: " << num_threads << std::endl; auto start_time = std::chrono::high_resolution_clock::now(); -// for (size_t i = 0; i < 20; ++i){ - cowboys::GPTrainingLoop loop; + + cowboys::GPTrainingLoop loop(false); + + loop.Initialize(13, 200); + loop.Run(6, 50, num_threads, true); + - loop.Initialize(100, 1000); - loop.Run(11, 100, num_threads, true); -// } auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end_time - start_time); @@ -33,7 +39,5 @@ int main() { auto seconds = duration.count() / 1000000.0; std::cout << "Time taken by Training: " << seconds << " seconds" << std::endl; - - return 0; -} \ No newline at end of file +} diff --git a/tests/unit/Agents/GP/CGPAgent.cpp b/tests/unit/Agents/GP/CGPAgent.cpp index a8bef79c..e844e7d7 100644 --- a/tests/unit/Agents/GP/CGPAgent.cpp +++ b/tests/unit/Agents/GP/CGPAgent.cpp @@ -11,6 +11,12 @@ using namespace cowboys; +struct MockWorld : cse491::WorldBase { + int DoAction(cse491::AgentBase &, size_t) { return 0; } +}; + +MockWorld world; + TEST_CASE("CGPAgent construction", "[group7][agent]") { SECTION("CGPAgent construction") { CGPAgent agent(0, "agent"); diff --git a/tests/unit/Agents/GP/GraphBuilder.cpp b/tests/unit/Agents/GP/GraphBuilder.cpp index c3f3d06c..4c227c28 100644 --- a/tests/unit/Agents/GP/GraphBuilder.cpp +++ b/tests/unit/Agents/GP/GraphBuilder.cpp @@ -50,7 +50,7 @@ TEST_CASE("Cartesian Graph", "[group7][graph][cartesian]") { size_t iterations = 100; for (size_t i = 0; i < iterations; ++i) { CGPGenotype genotype({INPUT_SIZE, NUM_OUTPUTS, NUM_LAYERS, NUM_NODES_PER_LAYER, LAYERS_BACK}); - genotype.SetSeed(i).MutateDefault(1, mock_agent, NODE_FUNCTION_SET.size()); + genotype.MutateDefault(1, mock_agent, NODE_FUNCTION_SET.size()); auto graph = builder.CartesianGraph(genotype, NODE_FUNCTION_SET); auto new_action = graph->MakeDecision(inputs, actions); choose_same_action = choose_same_action && (new_action == action); @@ -75,7 +75,7 @@ TEST_CASE("Cartesian Graph", "[group7][graph][cartesian]") { size_t iterations = 100; for (size_t i = 0; i < iterations; ++i) { auto copy = base; - copy.SetSeed(i).MutateHeader(1, mock_agent); + copy.MutateHeader(1, mock_agent); CHECK_FALSE(copy == base); auto expanded_graph = builder.CartesianGraph(copy, NODE_FUNCTION_SET); auto new_action = expanded_graph->MakeDecision(inputs, actions);