From 53c8aa102cb5ac75268ea0147da2934546de0c88 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 11 Jun 2021 23:48:24 -0400 Subject: [PATCH 001/445] Started building an evaluator to evolve organisms to play Mancala. --- source/evaluate/games/EvalMancala.hpp | 183 ++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 source/evaluate/games/EvalMancala.hpp diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp new file mode 100644 index 00000000..c38c3dea --- /dev/null +++ b/source/evaluate/games/EvalMancala.hpp @@ -0,0 +1,183 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file EvalMancala.hpp + * @brief MABE Evaluation module that has organisms play Mancala. + */ + +#ifndef MABE_EVAL_MANCALA_HPP +#define MABE_EVAL_MANCALA_HPP + +#include "emp/games/Mancala.hpp" + +#include "../../core/MABE.hpp" +#include "../../core/Module.hpp" + +namespace mabe { + + class EvalMancala : public Module { + private: + Collection target_collect; // Which organisms should we evaluate? + + std::string input_trait; // Name of trait to put input values. + std::string output_trait; // Name of trait to find output values. + std::string score_trait; // Trait to indicate game results. + + /// What type of opponent should we use? + enum Opponent { + RANDOM_MOVES, // Opponent will always choose a random, legal move. + AI, // Opponent is a human-crafted AI. + RANDOM_ORG, // Opponent is a random organism from the population. + UNKNOWN + }; + + Opponent opponent_type; + + public: + EvalMancala(mabe::MABE & control, + const std::string & name="EvalMancala", + const std::string & desc="Evaluate organisms by having them play Mancala.", + const std::string & _itrait="input", + const std::string & _otrait="output", + const std::string & _strait="score") + : Module(control, name, desc) + , target_collect(control.GetPopulation(0)) + , input_trait(_itrait) + , output_trait(_otrait) + , score_trait(_strait) + { + SetEvaluateMod(true); + } + ~EvalMancala() { } + + void SetupConfig() override { + LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); + LinkVar(input_trait, "input_trait", "Into which trait should input values be placed?"); + LinkVar(output_trait, "output_trait", "Out of which trait should output values be read?"); + LinkVar(score_trait, "score_trait", "Which trait should we store success rating?"); + LinkMenu(opponent_type, "opponent_type", "Which type of opponent should organisms face?", + RANDOM_MOVES, "random_moves", "Always choose a random, legal move.", + AI, "ai", "Human supplied (but not very good) AI", + RANDOM_ORG, "random_org", "Pick another random organism from collection." + ); + } + + void SetupModule() override { + AddOwnedTrait>(input_trait, "Input values (curret board state)", emp::vector({0.0})); + AddRequiredTrait>(output_trait); // Output values (move to make) + AddOwnedTrait(score_trait, "Play success", 0.0); + } + + + // Determine the next move of an organism. + size_t EvalMove(emp::Mancala & game, Organism & org) { + // Setup the hardware with proper inputs. + org.GetVar>(input_trait) = game.AsVectorInput(game.GetCurPlayer()); + + // Run the code. + org.GenerateOutput(); + + emp::vector results = org.GetVar>(output_trait); + + // Determine the chosen move. + size_t best_move = 0; + for (int i = 1; i < 6; i++) { + if (results[best_move] < results[i]) { best_move = i; } + } + + return best_move; + } + + + void OnUpdate(size_t /* update */) override { + emp_assert(control.GetNumPopulations() >= 1); + + // Loop through the living organisms in the target collection to evaluate each. + mabe::Collection alive_collect( target_collect.GetAlive() ); + for (Organism & org : alive_collect) { + + // Make sure this organism has its values ready for us to access. + org.GenerateOutput(); + + // Get access to the data_map elements that we need. + const emp::vector & vals = org.GetVar>(vals_trait); + emp::vector & scores = org.GetVar>(scores_trait); + double & total_score = org.GetVar(total_trait); + + // Initialize output values. + scores.resize(vals.size()); + total_score = 0.0; + size_t pos = 0; + + // Determine the scores based on the diagnostic type that we're using. + switch (diagnostic_id) { + case EXPLOIT: + scores = vals; + for (double x : scores) total_score += x; + break; + case STRUCT_EXPLOIT: + total_score = scores[0] = vals[0]; + + // Use values as long as they are monotonically decreasing. + for (pos = 1; pos < vals.size() && vals[pos] <= vals[pos-1]; ++pos) { + total_score += (scores[pos] = vals[pos]); + } + + // Clear out the remaining values. + while (pos < scores.size()) { scores[pos] = 0.0; ++pos; } + break; + case EXPLORE: + // Start at highest value (clearing everything before it) + pos = emp::FindMaxIndex(vals); // Find the position to start. + for (size_t i = 0; i < pos; i++) scores[i] = 0.0; + + total_score = scores[pos] = vals[pos]; + pos++; + + // Use values as long as they are monotonically decreasing. + while (pos < vals.size() && vals[pos] <= vals[pos-1]) { + total_score += (scores[pos] = vals[pos]); + pos++; + } + + // Clear out the remaining values. + while (pos < scores.size()) { scores[pos] = 0.0; ++pos; } + + break; + case DIVERSITY: + // Only count highest value + pos = emp::FindMaxIndex(vals); // Find the position to start. + total_score = scores[pos] = vals[pos]; + + // All others are subtracted from max and divided by two, creating a + // pressure to minimize. + for (size_t i = 0; i < vals.size(); i++) { + if (i != pos) total_score += (scores[i] = (vals[pos] - vals[i]) / 2.0); + } + + break; + case WEAK_DIVERSITY: + // Only count highest value + pos = emp::FindMaxIndex(vals); // Find the position to start. + total_score = scores[pos] = vals[pos]; + + // Clear all other schores. + for (size_t i = 0; i < vals.size(); i++) { + if (i != pos) scores[i] = 0.0; + } + + break; + default: + emp_error("Unknown Diganostic."); + } + + } + } + }; + + MABE_REGISTER_MODULE(EvalMancala, "Evaluate organisms on their ability to play Mancala."); +} + +#endif From fd469928a1e971e728477309def466a0cfb6908a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 12 Jun 2021 23:56:22 -0400 Subject: [PATCH 002/445] Added a base-class for Genomes in MABE. --- source/core/Genome.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 source/core/Genome.hpp diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp new file mode 100644 index 00000000..d22b934f --- /dev/null +++ b/source/core/Genome.hpp @@ -0,0 +1,24 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file Genome.hpp + * @brief Base genome representation for organisms. + */ + +#ifndef MABE_GENOME_HPP +#define MABE_GENOME_HPP + +#include "emp/bits/BitVector.hpp" + +namespace mabe { + + // Base class for all genome types. + class Genome : public BitVector { + + }; + +} + +#endif From 26737c71c9de219863de71d6946cb5d0bf40203f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 13 Jun 2021 00:57:40 -0400 Subject: [PATCH 003/445] Fleshed out more Genome functionality. --- source/core/Genome.hpp | 81 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp index d22b934f..e604ede0 100644 --- a/source/core/Genome.hpp +++ b/source/core/Genome.hpp @@ -11,12 +11,91 @@ #define MABE_GENOME_HPP #include "emp/bits/BitVector.hpp" +#include "emp/math/Random.hpp" +#include "emp/meta/TypeID.hpp" namespace mabe { // Base class for all genome types. - class Genome : public BitVector { + class GenomeBase : public emp::BitVector { + /// Set all bits randomly, with a 50/50 probability. + virtual GenomeBase & Randomize(emp::Random & random) = 0; + /// Set all bits randomly, with a given probability of being a one. + virtual GenomeBase & Randomize(emp::Random & random, const double p) = 0; + + /// Identify the locus type used in this genome. + virtual emp::TypeID GetLocusType() const = 0; + + /// Test to see if this genome has a specified locus type. + template + bool HasLocusType() const { return GetLocusType() == GetTypeID(); } + + /// Get entry at a given indexof a specified type (in steps of that type size) + template + [[nodiscard]] LOCUS_T Get(const size_t index) { + return emp::BitVector::GetValueAtIndex(index) const; + } + + /// Set entry at a given indexof a specified type (in steps of that type size) + template + void Set(const size_t index, LOCUS_T value) { + emp::BitVector::SetValueAtIndex(index, value); + } + + /// Get entry at a given bit + template + [[nodiscard]] LOCUS_T GetAtBit(const size_t index) { + return emp::BitVector::GetValueAtBit(index) const; + } + + /// Set entry at a given bit + template + void SetAtBit(const size_t index, LOCUS_T value) { + emp::BitVector::SetValueAtBit(index, value); + } + + /// Get entry at a given byte + template + [[nodiscard]] LOCUS_T GetAtByte(const size_t index) { + return emp::BitVector::GetValueAtBit(index*8) const; + } + + /// Set entry at a given byte + template + void SetAtByte(const size_t index, LOCUS_T value) { + emp::BitVector::SetValueAtBit(index*8, value); + } + }; + + template + class Genome : public GenomeBase { + using this_t = Genome; + + /// Set all bits randomly, with a 50/50 probability. + this_t & Randomize(emp::Random & random) override { + emp::BitVector::Randomize(random); + return *this; + } + + /// Set all bits randomly, with a given probability of being a one. + this_t & Randomize(emp::Random & random, const double p) override { + emp::BitVector::Randomize(random, p); + return *this; + } + + emp::TypeID GetLocusType() const override { return emp::GetTypeID(); } + + /// Get entry at a given index (in steps of that type size) + [[nodiscard]] virtual LOCUS_T Get(const size_t index) { + return emp::BitVector::GetValueAtIndex(index) const; + } + + /// Set entry at a given index (in steps of that type size) + virtual void Set(const size_t index, LOCUS_T value) { + emp::BitVector::SetValueAtIndex(index, value); + } + }; } From 0e68a0de6a4b5860a970623d850504e568595023 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 14 Jun 2021 13:01:00 -0400 Subject: [PATCH 004/445] Renamed Genome classes. --- source/core/Genome.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp index e604ede0..b38c3602 100644 --- a/source/core/Genome.hpp +++ b/source/core/Genome.hpp @@ -17,12 +17,12 @@ namespace mabe { // Base class for all genome types. - class GenomeBase : public emp::BitVector { + class Genome : public emp::BitVector { /// Set all bits randomly, with a 50/50 probability. - virtual GenomeBase & Randomize(emp::Random & random) = 0; + virtual Genome & Randomize(emp::Random & random) = 0; /// Set all bits randomly, with a given probability of being a one. - virtual GenomeBase & Randomize(emp::Random & random, const double p) = 0; + virtual Genome & Randomize(emp::Random & random, const double p) = 0; /// Identify the locus type used in this genome. virtual emp::TypeID GetLocusType() const = 0; @@ -69,8 +69,8 @@ namespace mabe { }; template - class Genome : public GenomeBase { - using this_t = Genome; + class TypedGenome : public Genome { + using this_t = TypedGenome; /// Set all bits randomly, with a 50/50 probability. this_t & Randomize(emp::Random & random) override { From a22ee426c5afd6fbc8c91d8407af1f18061d30fb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 15 Jun 2021 17:47:20 -0400 Subject: [PATCH 005/445] Added an EvalGame() helper function for EvalMancala. --- source/evaluate/games/EvalMancala.hpp | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index c38c3dea..10229c46 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -90,7 +90,49 @@ namespace mabe { return best_move; } + using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; + + // Setup the fitness function for a whole game. + double EvalGame(mancala_ai_t & player0, mancala_ai_t & player1, + bool cur_player=0, bool verbose=false) { + emp::Mancala game(cur_player==0); + size_t round = 0, errors = 0; + while (game.IsDone() == false) { + // Determine the current player and their move. + auto & play_fun = (cur_player == 0) ? player0 : player1; + size_t best_move = play_fun(game); + + if (verbose) { + std::cout << "round = " << round++ << " errors = " << errors << std::endl; + game.Print(); + char move_sym = (char) ('A' + best_move); + std::cout << "Move = " << move_sym; + if (game.GetCurSide()[best_move] == 0) { + std::cout << " (illegal!)"; + } + std::cout << std::endl << std::endl; + } + // If the chosen move is illegal, shift through other options. + while (game.GetCurSide()[best_move] == 0) { // Cannot make a move into an empty pit! + if (cur_player == 0) errors++; + if (++best_move > 5) best_move = 0; + } + + // Do the move and determine who goes next. + bool go_again = game.DoMove(cur_player, best_move); + if (!go_again) cur_player = !cur_player; + } + + if (verbose) { + std::cout << "Final scores -- A: " << game.ScoreA() + << " B: " << game.ScoreB() + << std::endl; + } + + return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); + } + void OnUpdate(size_t /* update */) override { emp_assert(control.GetNumPopulations() >= 1); From 1ff5a585bbaf81cfd43c4275bc6cb426c3c61cbf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 16 Jun 2021 15:59:41 -0400 Subject: [PATCH 006/445] Added an EvalMove() to work with human input and simpler versions of EvalGame() --- source/evaluate/games/EvalMancala.hpp | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 10229c46..55bc3fa5 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -90,6 +90,27 @@ namespace mabe { return best_move; } + // Determine the next move with human IO. + size_t EvalMove(emp::Mancala & game, std::ostream & os=std::cout, std::istream & is=std::cin) { + // Present the current board. + game.Print(); + + // Request a move from the human. + char move; + os << "Move?" << std::endl; + is >> move; + + while (move < 'A' || move > 'F' || game.GetCurSide()[(size_t)(move-'A')] == 0) { + os << "Invalid move! (choose a value 'A' to 'F')" << std::endl; + is.clear(); + is.ignore(std::numeric_limits::max(), '\n'); + is >> move; + } + + return (size_t) (move - 'A'); + } + + using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; // Setup the fitness function for a whole game. @@ -132,7 +153,23 @@ namespace mabe { return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); } - + + // Build wrappers for Organisms + double EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool cur_player=0, bool verbose=false) { + mancala_ai_t org_fun0 = [this,&org0](emp::Mancala & game){ return EvalMove(game, org0); }; + mancala_ai_t org_fun1 = [this,&org1](emp::Mancala & game){ return EvalMove(game, org1); }; + return EvalGame(org_fun0, org_fun1, cur_player, verbose); + } + + // Otherwise assume a human opponent! + double EvalGame(mabe::Organism & org, bool cur_player=0) { + mancala_ai_t fun0 = [this,&org](emp::Mancala & game){ return EvalMove(game, org); }; + mancala_ai_t fun1 = [this](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; + return EvalGame(fun0, fun1, cur_player, true); + } + + + void OnUpdate(size_t /* update */) override { emp_assert(control.GetNumPopulations() >= 1); From 1e5c3eed4764bfa92b26b084334d079c6b94c4a8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 17 Jun 2021 11:49:59 -0400 Subject: [PATCH 007/445] Added a random Mancala move generator (for random mode). --- source/evaluate/games/EvalMancala.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 55bc3fa5..f7c0bdd8 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -173,6 +173,14 @@ namespace mabe { void OnUpdate(size_t /* update */) override { emp_assert(control.GetNumPopulations() >= 1); + // Setup a player that make random moves. + emp::Random & random = control.GetRandom(); + mancala_ai_t random_player = [&random](emp::Mancala & game) { + size_t move_id = 6; + while (!game.IsMoveValid(move_id)) move_id = random.GetUInt(6); + return move_id; + }; + // Loop through the living organisms in the target collection to evaluate each. mabe::Collection alive_collect( target_collect.GetAlive() ); for (Organism & org : alive_collect) { From 6c4f6bd6ddc9796caf4e3890b00b111db11b3bdc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 18 Jun 2021 15:38:06 -0400 Subject: [PATCH 008/445] Restructured Mancala game evaluation interface. --- source/evaluate/games/EvalMancala.hpp | 112 ++++++-------------------- 1 file changed, 26 insertions(+), 86 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index f7c0bdd8..ebbbfb23 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -114,7 +114,7 @@ namespace mabe { using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; // Setup the fitness function for a whole game. - double EvalGame(mancala_ai_t & player0, mancala_ai_t & player1, + double EvalGame(const mancala_ai_t & player0, const mancala_ai_t & player1, bool cur_player=0, bool verbose=false) { emp::Mancala game(cur_player==0); size_t round = 0, errors = 0; @@ -154,25 +154,39 @@ namespace mabe { return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); } - // Build wrappers for Organisms + mancala_ai_t ToOrgFun(mabe::Organism & org) { + return [this,&org](emp::Mancala & game){ return EvalMove(game, org); }; + } + + // Wrapper for two Organisms competing double EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool cur_player=0, bool verbose=false) { - mancala_ai_t org_fun0 = [this,&org0](emp::Mancala & game){ return EvalMove(game, org0); }; - mancala_ai_t org_fun1 = [this,&org1](emp::Mancala & game){ return EvalMove(game, org1); }; - return EvalGame(org_fun0, org_fun1, cur_player, verbose); + return EvalGame(ToOrgFun(org0), ToOrgFun(org1), cur_player, verbose); } - // Otherwise assume a human opponent! - double EvalGame(mabe::Organism & org, bool cur_player=0) { - mancala_ai_t fun0 = [this,&org](emp::Mancala & game){ return EvalMove(game, org); }; - mancala_ai_t fun1 = [this](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; - return EvalGame(fun0, fun1, cur_player, true); + // Wrapper for organism vs. random opponent. + double EvalGame(mabe::Organism & org, emp::Random & random, bool cur_player=0, bool verbose=false) { + mancala_ai_t rand_fun = [&random](emp::Mancala & game) { + size_t move_id = random.GetUInt(6); + while (!game.IsMoveValid(move_id)) move_id = random.GetUInt(6); + return move_id; + }; + return EvalGame(ToOrgFun(org), rand_fun, cur_player, verbose); } + // Wrapper for organism vs. human + double EvalGame(mabe::Organism & org, bool cur_player=0) { + mancala_ai_t human_fun = [this](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; + return EvalGame(ToOrgFun(org), human_fun, cur_player, true); + } void OnUpdate(size_t /* update */) override { emp_assert(control.GetNumPopulations() >= 1); + // Determine the type of competitions to perform. + + // @CAO: For the moment, just doing a random opponent!! + // Setup a player that make random moves. emp::Random & random = control.GetRandom(); mancala_ai_t random_player = [&random](emp::Mancala & game) { @@ -184,82 +198,8 @@ namespace mabe { // Loop through the living organisms in the target collection to evaluate each. mabe::Collection alive_collect( target_collect.GetAlive() ); for (Organism & org : alive_collect) { - - // Make sure this organism has its values ready for us to access. - org.GenerateOutput(); - - // Get access to the data_map elements that we need. - const emp::vector & vals = org.GetVar>(vals_trait); - emp::vector & scores = org.GetVar>(scores_trait); - double & total_score = org.GetVar(total_trait); - - // Initialize output values. - scores.resize(vals.size()); - total_score = 0.0; - size_t pos = 0; - - // Determine the scores based on the diagnostic type that we're using. - switch (diagnostic_id) { - case EXPLOIT: - scores = vals; - for (double x : scores) total_score += x; - break; - case STRUCT_EXPLOIT: - total_score = scores[0] = vals[0]; - - // Use values as long as they are monotonically decreasing. - for (pos = 1; pos < vals.size() && vals[pos] <= vals[pos-1]; ++pos) { - total_score += (scores[pos] = vals[pos]); - } - - // Clear out the remaining values. - while (pos < scores.size()) { scores[pos] = 0.0; ++pos; } - break; - case EXPLORE: - // Start at highest value (clearing everything before it) - pos = emp::FindMaxIndex(vals); // Find the position to start. - for (size_t i = 0; i < pos; i++) scores[i] = 0.0; - - total_score = scores[pos] = vals[pos]; - pos++; - - // Use values as long as they are monotonically decreasing. - while (pos < vals.size() && vals[pos] <= vals[pos-1]) { - total_score += (scores[pos] = vals[pos]); - pos++; - } - - // Clear out the remaining values. - while (pos < scores.size()) { scores[pos] = 0.0; ++pos; } - - break; - case DIVERSITY: - // Only count highest value - pos = emp::FindMaxIndex(vals); // Find the position to start. - total_score = scores[pos] = vals[pos]; - - // All others are subtracted from max and divided by two, creating a - // pressure to minimize. - for (size_t i = 0; i < vals.size(); i++) { - if (i != pos) total_score += (scores[i] = (vals[pos] - vals[i]) / 2.0); - } - - break; - case WEAK_DIVERSITY: - // Only count highest value - pos = emp::FindMaxIndex(vals); // Find the position to start. - total_score = scores[pos] = vals[pos]; - - // Clear all other schores. - for (size_t i = 0; i < vals.size(); i++) { - if (i != pos) scores[i] = 0.0; - } - - break; - default: - emp_error("Unknown Diganostic."); - } - + double & score = org.GetVar(score_trait); + score = EvalGame(org, ) } } }; From 9c6d57931753e2582f60e239f2252503adb48ec2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 19 Jun 2021 23:22:43 -0400 Subject: [PATCH 009/445] Cleaned up OnUpdate() in EvalMancala; will now compile. --- source/evaluate/games/EvalMancala.hpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index ebbbfb23..12ef2114 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -184,22 +184,14 @@ namespace mabe { emp_assert(control.GetNumPopulations() >= 1); // Determine the type of competitions to perform. - // @CAO: For the moment, just doing a random opponent!! - // Setup a player that make random moves. - emp::Random & random = control.GetRandom(); - mancala_ai_t random_player = [&random](emp::Mancala & game) { - size_t move_id = 6; - while (!game.IsMoveValid(move_id)) move_id = random.GetUInt(6); - return move_id; - }; - // Loop through the living organisms in the target collection to evaluate each. mabe::Collection alive_collect( target_collect.GetAlive() ); for (Organism & org : alive_collect) { double & score = org.GetVar(score_trait); - score = EvalGame(org, ) + score = EvalGame(org, control.GetRandom()); // Start first. + score += EvalGame(org, control.GetRandom(), 1); // Start second. } } }; From 249ae4a7690ec519bc896ddf9adf18d4c0bf7b37 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 20 Jun 2021 15:53:33 -0400 Subject: [PATCH 010/445] Hooked EvalMancala into modules.hpp --- source/modules.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/modules.hpp b/source/modules.hpp index fe826bb6..6befade1 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -8,6 +8,7 @@ */ // Evaluation Modules +#include "evaluate/games/EvalMancala.hpp" #include "evaluate/static/EvalCountBits.hpp" #include "evaluate/static/EvalDiagnostic.hpp" #include "evaluate/static/EvalMatchBits.hpp" From 3c3f260c6a9fc473fd30fa9d4ca43481db04257f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 20 Jun 2021 20:32:12 -0400 Subject: [PATCH 011/445] In ConfigScope, allow LinkVar() and LinkFuns() to spicify built-ins. --- source/config/ConfigScope.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/config/ConfigScope.hpp b/source/config/ConfigScope.hpp index 393b9a0b..8c414c24 100644 --- a/source/config/ConfigScope.hpp +++ b/source/config/ConfigScope.hpp @@ -127,7 +127,9 @@ namespace mabe { template ConfigEntry_Linked & LinkVar(const std::string & name, VAR_T & var, - const std::string & desc) { + const std::string & desc, + bool is_builtin = false) { + if (is_builtin) return AddBuiltin>(name, var, desc, this); return Add>(name, var, desc, this); } @@ -137,7 +139,9 @@ namespace mabe { ConfigEntry_Functions & LinkFuns(const std::string & name, std::function get_fun, std::function set_fun, - const std::string & desc) { + const std::string & desc, + bool is_builtin = false) { + if (is_builtin) return AddBuiltin>(name, get_fun, set_fun, desc, this); return Add>(name, get_fun, set_fun, desc, this); } From dc12d3694d7dfba5e4054646f12ccdab58d77835 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 20 Jun 2021 20:32:36 -0400 Subject: [PATCH 012/445] In ConfigType, allow LinkVar() and LinkFuns() to spicify built-ins. --- source/config/ConfigType.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 7f856a66..32bc3d91 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -43,8 +43,9 @@ namespace mabe { template ConfigEntry_Linked & LinkVar(VAR_T & var, const std::string & name, - const std::string & desc) { - return GetScope().LinkVar(name, var, desc); + const std::string & desc, + bool is_builtin = false) { + return GetScope().LinkVar(name, var, desc, is_builtin); } /// Link a configuration entry to a pair of functions - it automatically calls the set @@ -53,8 +54,9 @@ namespace mabe { ConfigEntry_Functions & LinkFuns(std::function get_fun, std::function set_fun, const std::string & name, - const std::string & desc) { - return GetScope().LinkFuns(name, get_fun, set_fun, desc); + const std::string & desc, + bool is_builtin = false) { + return GetScope().LinkFuns(name, get_fun, set_fun, desc, is_builtin); } // Helper functions and info. From a9d618e8ef66d1a1f5478420dfc91492f937faeb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 20 Jun 2021 20:33:23 -0400 Subject: [PATCH 013/445] Add an input specification for AvidaGPOrg. --- source/orgs/AvidaGPOrg.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/orgs/AvidaGPOrg.hpp b/source/orgs/AvidaGPOrg.hpp index 5f4a4e53..58fa70d3 100644 --- a/source/orgs/AvidaGPOrg.hpp +++ b/source/orgs/AvidaGPOrg.hpp @@ -38,7 +38,8 @@ namespace mabe { size_t init_length = 100; ///< Length of new organisms. bool init_random = true; ///< Should we randomize ancestor? (false = all zeros) size_t eval_time = 500; ///< How long should the CPU be given on each evaluate? - std::string output_name = "output"; ///< Name of trait that should be used to access bits. + std::string input_name = "input"; ///< Name of trait that should be used load input values + std::string output_name = "output"; ///< Name of trait that should be used store output values // Internal use emp::Binomial mut_dist; ///< Distribution of number of mutations to occur. @@ -106,8 +107,10 @@ namespace mabe { "Should we randomize ancestor? (0 = \"blank\" default)"); GetManager().LinkVar(SharedData().eval_time, "eval_time", "How many CPU cycles should we give organisms to run?"); + GetManager().LinkVar(SharedData().input_name, "input_name", + "Name of variable to load inputs from."); GetManager().LinkVar(SharedData().output_name, "output_name", - "Name of variable to contain bit sequence."); + "Name of variable to output results."); } /// Setup this organism type with the traits it need to track. @@ -115,7 +118,8 @@ namespace mabe { // Setup the default vector to indicate mutation positions. SharedData().mut_sites.Resize(hardware.GetSize()); - // Setup the output trait. + // Setup the input and output traits. + GetManager().AddRequiredTrait>(SharedData().input_name); GetManager().AddSharedTrait(SharedData().output_name, "Value map output from organism.", std::unordered_map()); From 6ca74b45c56f8431d00244e2ef3c73f99611e549 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 20 Jun 2021 23:15:41 -0400 Subject: [PATCH 014/445] Changed _active and _desc to be builtin variables, not auto-included in config files. --- source/config/Config.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index ba0eb4c0..a279e953 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -577,8 +577,8 @@ namespace mabe { ConfigScope & new_scope = scope.AddScope(var_name, type_map[type_name].desc, type_name); ConfigType & new_obj = type_map[type_name].init_fun(var_name); new_obj.SetupScope(new_scope); - new_obj.LinkVar(new_obj._active, "_active", "Should we activate this module? (0=off, 1=on)"); - new_obj.LinkVar(new_obj._desc, "_desc", "Special description for those object."); + new_obj.LinkVar(new_obj._active, "_active", "Should we activate this module? (0=off, 1=on)", true); + new_obj.LinkVar(new_obj._desc, "_desc", "Special description for those object.", true); new_obj.SetupConfig(); return new_scope; From e5f2b8cbd88e638d7e7218727943fccf9267c20a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 21 Jun 2021 23:48:15 -0400 Subject: [PATCH 015/445] Cleanup on EvalMancala --- source/evaluate/games/EvalMancala.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 12ef2114..43fcdb08 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -58,7 +58,7 @@ namespace mabe { LinkVar(output_trait, "output_trait", "Out of which trait should output values be read?"); LinkVar(score_trait, "score_trait", "Which trait should we store success rating?"); LinkMenu(opponent_type, "opponent_type", "Which type of opponent should organisms face?", - RANDOM_MOVES, "random_moves", "Always choose a random, legal move.", + RANDOM_MOVES, "random", "Always choose a random, legal move.", AI, "ai", "Human supplied (but not very good) AI", RANDOM_ORG, "random_org", "Pick another random organism from collection." ); @@ -83,7 +83,8 @@ namespace mabe { // Determine the chosen move. size_t best_move = 0; - for (int i = 1; i < 6; i++) { + const size_t move_cap = std::min(results.size(), 6); + for (size_t i = 1; i < move_cap; i++) { if (results[best_move] < results[i]) { best_move = i; } } From 687fa4ae10e92c0f5193e2578b56bcb472a1c243 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 22 Jun 2021 23:50:59 -0400 Subject: [PATCH 016/445] Removed shift in tournament size from Diagnostics generator. --- build/settings/Diagnostics.gen | 1 - 1 file changed, 1 deletion(-) diff --git a/build/settings/Diagnostics.gen b/build/settings/Diagnostics.gen index 0d08c467..b22f7051 100644 --- a/build/settings/Diagnostics.gen +++ b/build/settings/Diagnostics.gen @@ -18,5 +18,4 @@ ValsOrg vals_org; @start() print("random_seed = ", random_seed, "\n"); @start() inject("vals_org", "main_pop", pop_size); -@update(500) select_t.tournament_size = 4; @update(1000) exit(); From 245ffa8f69127e1f80055005bf8da6ab90b68567 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 23 Jun 2021 23:25:43 -0400 Subject: [PATCH 017/445] Seteup MABE controller to be able to print verbose information more easily for debug purposes. --- source/core/MABE.hpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 11ce365e..acdf1141 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -162,16 +162,6 @@ namespace mabe { void UpdateSignals(); - // -- Helper functions for debugging and extra output -- - - /// Output args if (and only if) we are in verbose mode. - template - void verbose_out(Ts &&... args) { - if (verbose) { - std::cout << emp::to_string(std::forward(args)...) << std::endl; - } - } - public: MABE(int argc, char* argv[]); ///< MABE command-line constructor. MABE(const MABE &) = delete; @@ -185,8 +175,17 @@ namespace mabe { // --- Basic accessors --- emp::Random & GetRandom() { return random; } size_t GetUpdate() const noexcept { return update; } + bool GetVerbose() const { return verbose; } mabe::ErrorManager & GetErrorManager() { return error_man; } + /// Output args if (and only if) we are in verbose mode. + template + void Verbose(Ts &&... args) { + if (verbose) { + std::cout << emp::to_string(std::forward(args)...) << std::endl; + } + } + // --- Tools to setup runs --- bool Setup(); @@ -298,6 +297,9 @@ namespace mabe { /// MABE controller will create instances of it.) Returns the position of the last /// organism placed. OrgPosition Inject(const std::string & type_name, Population & pop, size_t copy_count=1) { + Verbose("Injecting ", copy_count, " orgs of type '", type_name, + "' into population ", pop.GetID()); + auto & org_manager = GetModule(type_name); // Look up type of organism. OrgPosition pos; // Place to save injection position. for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. @@ -772,7 +774,7 @@ namespace mabe { /// As part of the main Setup(), load in all of the organism traits that modules need to /// read or write and make sure that there aren't any conflicts. void MABE::Setup_Traits() { - verbose_out("Analyzing configuration of ", trait_man.GetSize(), " traits."); + Verbose("Analyzing configuration of ", trait_man.GetSize(), " traits."); trait_man.Verify(verbose); // Make sure modules are accessing traits consistently trait_man.RegisterAll(org_data_map); // Load in all of the traits to the DataMap From edfc3ab439ff95cad9edf5982fbeaf18a9cbd0a7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 23 Jun 2021 23:26:11 -0400 Subject: [PATCH 018/445] Setup EvalMancala with more debug information. --- source/evaluate/games/EvalMancala.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 43fcdb08..3d87e640 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -181,7 +181,9 @@ namespace mabe { } - void OnUpdate(size_t /* update */) override { + void OnUpdate(size_t ud) override { + control.Verbose("UD ", ud, ": Running EvalMancala::OnUpdate()"); + emp_assert(control.GetNumPopulations() >= 1); // Determine the type of competitions to perform. @@ -189,6 +191,9 @@ namespace mabe { // Loop through the living organisms in the target collection to evaluate each. mabe::Collection alive_collect( target_collect.GetAlive() ); + + control.Verbose(" - ", alive_collect.GetSize(), " organisms found."); + for (Organism & org : alive_collect) { double & score = org.GetVar(score_trait); score = EvalGame(org, control.GetRandom()); // Start first. From 472e0e33589a2e4703b8cef5184caa46f2c447e4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:34:37 -0400 Subject: [PATCH 019/445] Added .gen and .mabe settings files for evolving AvidaGP Mancala players. --- build/settings/Mancala.gen | 19 ++++++++++++++ build/settings/Mancala.mabe | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 build/settings/Mancala.gen create mode 100644 build/settings/Mancala.mabe diff --git a/build/settings/Mancala.gen b/build/settings/Mancala.gen new file mode 100644 index 00000000..9a75e6ae --- /dev/null +++ b/build/settings/Mancala.gen @@ -0,0 +1,19 @@ +random_seed = 0; // Seed for random number generator; use 0 to base on time. +Population main_pop; // Collection of organisms +Population next_pop; // Collection of organisms + +Value pop_size = 200; // Population size to use (need to be hooked in) + +CommandLine cl; +FileOutput output; + +EvalMancala eval; +SelectTournament select_t; + +GrowthPlacement place_next; +MovePopulation sync_gen; +AvidaGPOrg gp_org; + +@start() print("random_seed = ", random_seed, "\n"); +@start() inject("gp_org", "main_pop", pop_size); +@update(1000) exit(); diff --git a/build/settings/Mancala.mabe b/build/settings/Mancala.mabe new file mode 100644 index 00000000..ed094f25 --- /dev/null +++ b/build/settings/Mancala.mabe @@ -0,0 +1,50 @@ +random_seed = 0; // Seed for random number generator; use 0 to base on time. +Population main_pop; // Collection of organisms +Population next_pop; // Collection of organisms +Value pop_size = 200; // Local value variable. +CommandLine cl { // Handle basic I/O on the command line. + target_pop = "main_pop"; // Which population should we print stats about? +} +FileOutput output { // Output collected data into a specified file. + filename = "output.csv"; // Name of file for output data. + format = "score:max,score:mean";// Column format to use in the file. + target = "main_pop"; // Which population(s) should we print from? + output_updates = "0:1"; // Which updates should we output data? +} +EvalMancala eval { // Evaluate organisms on their ability to play Mancala. + target = "main_pop"; // Which population(s) should we evaluate? + input_trait = "input"; // Into which trait should input values be placed? + output_trait = "output"; // Out of which trait should output values be read? + score_trait = "score"; // Which trait should we store success rating? + opponent_type = "random"; // Which type of opponent should organisms face? + // random: Always choose a random, legal move. + // ai: Human supplied (but not very good) AI + // random_org: Pick another random organism from collection. +} +SelectTournament select_t { // Select the top fitness organisms from random subgroups for replication. + select_pop = "main_pop"; // Which population should we select parents from? + birth_pop = "next_pop"; // Which population should births go into? + tournament_size = 7; // Number of orgs in each tournament + num_tournaments = pop_size; // Number of tournaments to run + fitness_trait = "score"; // Which trait provides the fitness value to use? +} +GrowthPlacement place_next { // Always appened births to the end of a population. + target = "main_pop,next_pop"; // Population(s) to manage. +} +MovePopulation sync_gen { // Move organisms from one populaiton to another. + from_pop = "next_pop"; // Population to move organisms from. + to_pop = "main_pop"; // Population to move organisms into. + reset_to = 1; // Should we erase organisms at the destination? +} +AvidaGPOrg avida_org { // Organism consisting of Avida instructions. + mut_prob = 0.01; // Probability of each instruction mutating on reproduction. + N = 50; // Initial number of instructions in genome + init_random = 1; // Should we randomize ancestor? (0 = "blank" default) + eval_time = 200; // How many CPU cycles should we give organisms to run? + input_name = "input"; // Where to find inputs + output_name = "output"; // Where to write outputs +} + +@start(0) print("random_seed = ", random_seed, "\n"); +@start(0) inject("avida_org", "main_pop", pop_size); +@update(1000) exit(); From 847040260f7636d6b60ea8b72ef785eb6aa0a3db Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:35:32 -0400 Subject: [PATCH 020/445] Added RoyalRoad eval module. --- source/evaluate/static/EvalRoyalRoad.hpp | 93 ++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 source/evaluate/static/EvalRoyalRoad.hpp diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp new file mode 100644 index 00000000..86d20ac3 --- /dev/null +++ b/source/evaluate/static/EvalRoyalRoad.hpp @@ -0,0 +1,93 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file EvalRoyalRoad.hpp + * @brief MABE Evaluation module for evaluating the royal road problem. + * + * In royal road, the number of 1s from the beginning of a bitstring are counted, but only + * in groups of B (brick size). + */ + +#ifndef MABE_EVAL_ROYAL_ROAD_H +#define MABE_EVAL_ROYAL_ROAD_H + +#include "../../core/MABE.hpp" +#include "../../core/Module.hpp" + +#include "emp/datastructs/reference_vector.hpp" + +namespace mabe { + + class EvalRoyalRoad : public Module { + private: + Collection target_collect; + + std::string bits_trait; + std::string fitness_trait; + + size_t brick_size = 8; + double extra_bit_cost = 0.5; + + public: + EvalRoyalRoad(mabe::MABE & control, + const std::string & name="EvalRoyalRoad", + const std::string & desc="Evaluate bitstrings by counting ones (or zeros).") + : Module(control, name, desc) + , target_collect(control.GetPopulation(0)) + , bits_trait("bits") + , fitness_trait("fitness") + { + SetEvaluateMod(true); + } + ~EvalRoyalRoad() { } + + void SetupConfig() override { + LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); + LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); + LinkVar(fitness_trait, "fitness_trait", "Which trait should we store Royal Road fitness in?"); + LinkVar(brick_size, "brick_size", "Number of ones to have a whole brick in the road."); + LinkVar(extra_bit_cost, "extra_bit_cost", "Penalty per-bit for extra-long roads."); + } + + void SetupModule() override { + AddRequiredTrait(bits_trait); + AddOwnedTrait(fitness_trait, "Royal Road fitness value", 0.0); + } + + void OnUpdate(size_t /* update */) override { + // Loop through the population and evaluate each organism. + double max_fitness = 0.0; + mabe::Collection alive_collect = target_collect.GetAlive(); + for (Organism & org : alive_collect) { + // Make sure this organism has its bit sequence ready for us to access. + org.GenerateOutput(); + + // Count the number of ones in the bit sequence. + const emp::BitVector & bits = org.GetVar(bits_trait); + int road_length = 0.0; + for (size_t i = 0; i < bits.size(); i++) { + if (bits[i] == 0) break; + road_length++; + } + + const int overage = road_length % brick_size; + + // Store the count on the organism in the fitness trait. + double fitness = road_length - overage * (extra_bit_cost + 1.0); + org.SetVar(fitness_trait, fitness); + + if (fitness > max_fitness) { + max_fitness = fitness; + } + } + + std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; + } + }; + + MABE_REGISTER_MODULE(EvalRoyalRoad, "Evaluate bitstrings by counting ones (or zeros)."); +} + +#endif From c0929fb3282a8bbfd1564f66d2252539e018be45 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:37:44 -0400 Subject: [PATCH 021/445] Cleanup on NK generator. --- build/settings/NK.gen | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/settings/NK.gen b/build/settings/NK.gen index 37c61e20..41c480e7 100644 --- a/build/settings/NK.gen +++ b/build/settings/NK.gen @@ -1,8 +1,9 @@ Population main_pop; Population next_pop; +Value pop_size = 200; + CommandLine cl; -Mutate mut; EvalNK eval_nk; FileOutput output; SelectElite select_elite; From cd3da86549c7076876d0bd85b332f00b42138350 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:39:50 -0400 Subject: [PATCH 022/445] Added RoyalRoad evaluator to mabe modules.hpp. --- source/modules.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/modules.hpp b/source/modules.hpp index 6befade1..c6dd3ed6 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -13,6 +13,7 @@ #include "evaluate/static/EvalDiagnostic.hpp" #include "evaluate/static/EvalMatchBits.hpp" #include "evaluate/static/EvalNK.hpp" +#include "evaluate/static/EvalRoyalRoad.hpp" // Interface Modules #include "interface/CommandLine.hpp" From 5a2cfeec372d8a3ec3b393a15a202ec605b97737 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:40:39 -0400 Subject: [PATCH 023/445] Fixed MABE maximum trait calculation to start from lowest double. --- source/core/data_collect.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 00e145b2..87dd87d6 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -88,7 +88,7 @@ namespace emp { return [get_fun](const CONTAIN_T & container) { DATA_T max{}; if constexpr (std::is_arithmetic_v) { - max = std::numeric_limits::min(); + max = std::numeric_limits::lowest(); } for (const auto & entry : container) { const DATA_T cur_val = get_fun(entry); From cf1cbb2dc86ace06a91c297a1920f0a941cb6ff5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 24 Jun 2021 23:41:47 -0400 Subject: [PATCH 024/445] Cleanup on AvidaGP org. --- source/orgs/AvidaGPOrg.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/orgs/AvidaGPOrg.hpp b/source/orgs/AvidaGPOrg.hpp index 58fa70d3..a8979ba6 100644 --- a/source/orgs/AvidaGPOrg.hpp +++ b/source/orgs/AvidaGPOrg.hpp @@ -15,6 +15,7 @@ #include "../core/Organism.hpp" #include "../core/OrganismManager.hpp" +#include "emp/datastructs/vector_utils.hpp" #include "emp/hardware/AvidaGP.hpp" #include "emp/math/Distribution.hpp" #include "emp/math/random_utils.hpp" @@ -82,18 +83,17 @@ namespace mabe { } /// Put the output values in the correct output position. - /// (Should be of type std::unordered_map) void GenerateOutput() override { hardware.ResetHardware(); - // @CAO Setup the input! - // org.SetInputs(game.AsInput(game.GetCurPlayer())); + // Setup the input. + hardware.SetInputs(GetVar>(SharedData().input_name)); // Run the code. hardware.Process(SharedData().eval_time); // Store the results. - SetVar>(SharedData().output_name, hardware.GetOutputs()); + SetVar>(SharedData().output_name, emp::ToVector(hardware.GetOutputs())); } /// Setup this organism type to be able to load from config. @@ -115,6 +115,9 @@ namespace mabe { /// Setup this organism type with the traits it need to track. void SetupModule() override { + // Setup the mutation distribution. + SharedData().mut_dist.Setup(SharedData().mut_prob, hardware.GetSize()); + // Setup the default vector to indicate mutation positions. SharedData().mut_sites.Resize(hardware.GetSize()); @@ -122,7 +125,7 @@ namespace mabe { GetManager().AddRequiredTrait>(SharedData().input_name); GetManager().AddSharedTrait(SharedData().output_name, "Value map output from organism.", - std::unordered_map()); + emp::vector()); } }; From 993e7228a8868fcae6fe380a3fd12a21c63815b0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 30 Jun 2021 21:53:39 -0400 Subject: [PATCH 025/445] Depricated Get/SetVar() function in Organism in favor of Get/SetTrait() --- source/core/Organism.hpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 7f3c6276..0a095059 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -58,21 +58,32 @@ namespace mabe { struct ManagerData { }; + [[deprecated("Use Organism::HasTrait() instead of Organism::HasVar()")]] bool HasVar(const std::string & name) const { return data_map.HasName(name); } - template T & GetVar(const std::string & name) { return data_map.Get(name); } - template const T & GetVar(const std::string & name) const { + template + [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] + T & GetVar(const std::string & name) { return data_map.Get(name); } + template + [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] + const T & GetVar(const std::string & name) const { return data_map.Get(name); } - template T & GetVar(size_t id) { return data_map.Get(id); } - template const T & GetVar(size_t id) const { return data_map.Get(id); } + template + [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] + T & GetVar(size_t id) { return data_map.Get(id); } + template + [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] + const T & GetVar(size_t id) const { return data_map.Get(id); } template + [[deprecated("Use Organism::SetTrait() instead of Organism::SetVar()")]] void SetVar(const std::string & name, const T & value) { if (data_map.HasName(name) == false) data_map.AddVar(name, value); else data_map.Set(name, value); } template + [[deprecated("Use Organism::SetTrait() instead of Organism::SetVar()")]] void SetVar(size_t id, const T & value) { emp_assert(data_map.HasID(id), id); data_map.Set(id, value); From 4695f5cfccc794f869ae147fc262a0150e4c538b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 1 Jul 2021 17:22:34 -0400 Subject: [PATCH 026/445] Moved settings/ out of build/ ; added RoyalRoad settings. --- {build/settings => settings}/Diagnostics.gen | 0 {build/settings => settings}/Diagnostics.mabe | 0 {build/settings => settings}/Mancala.gen | 0 {build/settings => settings}/Mancala.mabe | 0 {build/settings => settings}/NK.gen | 0 {build/settings => settings}/NK.mabe | 0 settings/RoyalRoad.gen | 19 +++++++ settings/RoyalRoad.mabe | 53 +++++++++++++++++++ .../settings => settings}/settings.proposed | 0 .../settings => settings}/settings.proposed2 | 0 10 files changed, 72 insertions(+) rename {build/settings => settings}/Diagnostics.gen (100%) rename {build/settings => settings}/Diagnostics.mabe (100%) rename {build/settings => settings}/Mancala.gen (100%) rename {build/settings => settings}/Mancala.mabe (100%) rename {build/settings => settings}/NK.gen (100%) rename {build/settings => settings}/NK.mabe (100%) create mode 100644 settings/RoyalRoad.gen create mode 100644 settings/RoyalRoad.mabe rename {build/settings => settings}/settings.proposed (100%) rename {build/settings => settings}/settings.proposed2 (100%) diff --git a/build/settings/Diagnostics.gen b/settings/Diagnostics.gen similarity index 100% rename from build/settings/Diagnostics.gen rename to settings/Diagnostics.gen diff --git a/build/settings/Diagnostics.mabe b/settings/Diagnostics.mabe similarity index 100% rename from build/settings/Diagnostics.mabe rename to settings/Diagnostics.mabe diff --git a/build/settings/Mancala.gen b/settings/Mancala.gen similarity index 100% rename from build/settings/Mancala.gen rename to settings/Mancala.gen diff --git a/build/settings/Mancala.mabe b/settings/Mancala.mabe similarity index 100% rename from build/settings/Mancala.mabe rename to settings/Mancala.mabe diff --git a/build/settings/NK.gen b/settings/NK.gen similarity index 100% rename from build/settings/NK.gen rename to settings/NK.gen diff --git a/build/settings/NK.mabe b/settings/NK.mabe similarity index 100% rename from build/settings/NK.mabe rename to settings/NK.mabe diff --git a/settings/RoyalRoad.gen b/settings/RoyalRoad.gen new file mode 100644 index 00000000..e057b811 --- /dev/null +++ b/settings/RoyalRoad.gen @@ -0,0 +1,19 @@ +Population main_pop; +Population next_pop; + +Value pop_size = 200; + +CommandLine cl; +Mutate mut; +EvalRoyalRoad eval_rr; +FileOutput output; +SelectElite select_elite; +SelectTournament select_tourny; +GrowthPlacement place_next; +MovePopulation sync_gen; + +BitsOrg bits_org; + +@start() print("random_seed = ", random_seed, "\n"); +@start() inject("bits_org", "main_pop", pop_size); +@update(1000) exit(); diff --git a/settings/RoyalRoad.mabe b/settings/RoyalRoad.mabe new file mode 100644 index 00000000..fa630fe9 --- /dev/null +++ b/settings/RoyalRoad.mabe @@ -0,0 +1,53 @@ +random_seed = 0; // Seed for random number generator; use 0 to base on time. +Population main_pop; // Collection of organisms +Population next_pop; // Collection of organisms + +Value pop_size = 200; // Local value variable. + +CommandLine cl { // Handle basic I/O on the command line. + target_pop = "main_pop"; // Which population should we print stats about? +} +EvalRoyalRoad eval_rr { // Evaluate bitstrings by counting ones (or zeros). + target = "main_pop"; // Which population(s) should we evaluate? + bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? + fitness_trait = "fitness"; // Which trait should we store Royal Road fitness in? + brick_size = 8; // Number of ones to have a whole brick in the road. +} +FileOutput output { // Output collected data into a specified file. + filename = "output.csv"; // Name of file for output data. + format = "fitness:max,fitness:mean,fitness:min,fitness:0,bits:0";// Column format to use in the file. + target = "main_pop"; // Which population(s) should we print from? + output_updates = "0:1"; // Which updates should we output data? +} +SelectElite select_elite { // Choose the top fitness organisms for replication. + select_pop = "main_pop"; // Which population should we select parents from? + birth_pop = "next_pop"; // Which population should births go into? + top_count = 1; // Number of top-fitness orgs to be replicated + copy_count = 1; // Number of copies to make of replicated organisms + fitness_trait = "fitness"; // Which trait provides the fitness value to use? +} +SelectTournament select_tourny { // Select the top fitness organisms from random subgroups for replication. + select_pop = "main_pop"; // Which population should we select parents from? + birth_pop = "next_pop"; // Which population should births go into? + tournament_size = 100; // Number of orgs in each tournament + num_tournaments = pop_size - 1; // Number of tournaments to run + fitness_trait = "fitness"; // Which trait provides the fitness value to use? +} +GrowthPlacement place_next { // Always appened births to the end of a population. + target = "main_pop,next_pop"; // Population(s) to manage. +} +MovePopulation sync_gen { // Move organisms from one populaiton to another. + from_pop = "next_pop"; // Population to move organisms from. + to_pop = "main_pop"; // Population to move organisms into. + reset_to = 1; // Should we erase organisms at the destination? +} +BitsOrg bits_org { // Organism consisting of a series of N bits. + N = 100; // Number of bits in organism + mut_prob = 0.01; // Probability of each bit mutating on reproduction. + output_name = "bits"; // Name of variable to contain bit sequence. + init_random = 0; // Should we randomize ancestor? (0 = all zeros) +} + +@start(0) print("random_seed = ", random_seed, "\n"); +@start(0) inject("bits_org", "main_pop", pop_size); +@update(1000) exit(); diff --git a/build/settings/settings.proposed b/settings/settings.proposed similarity index 100% rename from build/settings/settings.proposed rename to settings/settings.proposed diff --git a/build/settings/settings.proposed2 b/settings/settings.proposed2 similarity index 100% rename from build/settings/settings.proposed2 rename to settings/settings.proposed2 From 240ae98e7749e583cc41861b78ad98e399c46025 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 1 Jul 2021 17:26:18 -0400 Subject: [PATCH 027/445] Added lots of extras (for auto-generated and build/ files) to .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3473fb..8ec45d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Auto-generated files *~ +*.dSYM .vscode +.DS_Store # Prerequisites *.d @@ -36,3 +38,7 @@ *.app examples/NK +build/*.csv +build/*.gen +build/*.mabe +build/MABE From 5e87af12ffe56d2be713226d0269fb3e60ad5fc8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 1 Jul 2021 17:27:36 -0400 Subject: [PATCH 028/445] Updated all eval modules to work with Get/SetTrait() instead of Get/SetVar() --- source/evaluate/games/EvalMancala.hpp | 6 +++--- source/evaluate/static/EvalCountBits.hpp | 4 ++-- source/evaluate/static/EvalDiagnostic.hpp | 8 ++++---- source/evaluate/static/EvalMatchBits.hpp | 12 ++++++------ source/evaluate/static/EvalNK.hpp | 4 ++-- source/evaluate/static/EvalRoyalRoad.hpp | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 3d87e640..850f1e1c 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -74,12 +74,12 @@ namespace mabe { // Determine the next move of an organism. size_t EvalMove(emp::Mancala & game, Organism & org) { // Setup the hardware with proper inputs. - org.GetVar>(input_trait) = game.AsVectorInput(game.GetCurPlayer()); + org.GetTrait>(input_trait) = game.AsVectorInput(game.GetCurPlayer()); // Run the code. org.GenerateOutput(); - emp::vector results = org.GetVar>(output_trait); + emp::vector results = org.GetTrait>(output_trait); // Determine the chosen move. size_t best_move = 0; @@ -195,7 +195,7 @@ namespace mabe { control.Verbose(" - ", alive_collect.GetSize(), " organisms found."); for (Organism & org : alive_collect) { - double & score = org.GetVar(score_trait); + double & score = org.GetTrait(score_trait); score = EvalGame(org, control.GetRandom()); // Start first. score += EvalGame(org, control.GetRandom(), 1); // Start second. } diff --git a/source/evaluate/static/EvalCountBits.hpp b/source/evaluate/static/EvalCountBits.hpp index 7be417be..cf4f275f 100644 --- a/source/evaluate/static/EvalCountBits.hpp +++ b/source/evaluate/static/EvalCountBits.hpp @@ -66,14 +66,14 @@ namespace mabe { org.GenerateOutput(); // Count the number of ones in the bit sequence. - const emp::BitVector & bits = org.GetVar(bits_trait); + const emp::BitVector & bits = org.GetTrait(bits_trait); double fitness = (double) bits.CountOnes(); // If we were supposed to count zeros, subtract ones count from total number of bits. if (count_type == 0) fitness = bits.size() - fitness; // Store the count on the organism in the fitness trait. - org.SetVar(fitness_trait, fitness); + org.SetTrait(fitness_trait, fitness); if (fitness > max_fitness || !max_org) { max_fitness = fitness; diff --git a/source/evaluate/static/EvalDiagnostic.hpp b/source/evaluate/static/EvalDiagnostic.hpp index 1bd48504..67141855 100644 --- a/source/evaluate/static/EvalDiagnostic.hpp +++ b/source/evaluate/static/EvalDiagnostic.hpp @@ -38,7 +38,7 @@ namespace mabe { public: EvalDiagnostic(mabe::MABE & control, const std::string & name="EvalDiagnostic", - const std::string & desc="Evaluate bitstrings by counting ones (or zeros).", + const std::string & desc="Evaluate value sets using a specified diagnostic.", const std::string & _vtrait="vals", const std::string & _strait="scores", const std::string & _ttrait="total") @@ -86,9 +86,9 @@ namespace mabe { org.GenerateOutput(); // Get access to the data_map elements that we need. - const emp::vector & vals = org.GetVar>(vals_trait); - emp::vector & scores = org.GetVar>(scores_trait); - double & total_score = org.GetVar(total_trait); + const emp::vector & vals = org.GetTrait>(vals_trait); + emp::vector & scores = org.GetTrait>(scores_trait); + double & total_score = org.GetTrait(total_trait); // Initialize output values. scores.resize(vals.size()); diff --git a/source/evaluate/static/EvalMatchBits.hpp b/source/evaluate/static/EvalMatchBits.hpp index 59e908cd..949e97a1 100644 --- a/source/evaluate/static/EvalMatchBits.hpp +++ b/source/evaluate/static/EvalMatchBits.hpp @@ -66,7 +66,7 @@ namespace mabe { for (size_t pos = 0; pos < pop1.GetSize(); pos++) { // If the first population is empty, still check for organism in the second to score. if (pop1.IsEmpty(pos)) { - if (pop2.IsOccupied(pos)) pop2[pos].SetVar(fitness_trait, 0.0); + if (pop2.IsOccupied(pos)) pop2[pos].SetTrait(fitness_trait, 0.0); continue; // Skip over empty cell in first population. } @@ -83,8 +83,8 @@ namespace mabe { org2.GenerateOutput(); // Count the number of matches in the bit sequences. - const emp::BitVector & bits1 = org.GetVar(bits_trait); - const emp::BitVector & bits2 = org2.GetVar(bits_trait); + const emp::BitVector & bits1 = org.GetTrait(bits_trait); + const emp::BitVector & bits2 = org2.GetTrait(bits_trait); if (count_matches) { fitness = (double) (bits1 ^ bits2).CountZeros(); @@ -96,18 +96,18 @@ namespace mabe { if (fitness > best_match) best_match = fitness; // Store the count on the second organism in the fitness trait. - org2.SetVar(fitness_trait, fitness); + org2.SetTrait(fitness_trait, fitness); } // Store the count on the organism in the fitness trait. - org.SetVar(fitness_trait, fitness); + org.SetTrait(fitness_trait, fitness); } // If pop2 is bigger, make sure to mark any extra organisms as having a zero match fitness. for (size_t pos = pop1.GetSize(); pos < pop2.GetSize(); pos++) { - if (pop2.IsOccupied(pos)) pop2[pos].SetVar(fitness_trait, 0.0); + if (pop2.IsOccupied(pos)) pop2[pos].SetTrait(fitness_trait, 0.0); } } diff --git a/source/evaluate/static/EvalNK.hpp b/source/evaluate/static/EvalNK.hpp index 69b44860..ca6d609b 100644 --- a/source/evaluate/static/EvalNK.hpp +++ b/source/evaluate/static/EvalNK.hpp @@ -69,14 +69,14 @@ namespace mabe { mabe::Collection alive_collect( target_collect.GetAlive() ); for (Organism & org : alive_collect) { org.GenerateOutput(); - const auto & bits = org.GetVar(bits_trait); + const auto & bits = org.GetTrait(bits_trait); if (bits.size() != N) { AddError("Org returns ", bits.size(), " bits, but ", N, " bits needed for NK landscape.", "\nOrg: ", org.ToString()); } double fitness = landscape.GetFitness(bits); - org.SetVar(fitness_trait, fitness); + org.SetTrait(fitness_trait, fitness); if (fitness > max_fitness || !max_org) { max_fitness = fitness; diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp index 86d20ac3..69233b70 100644 --- a/source/evaluate/static/EvalRoyalRoad.hpp +++ b/source/evaluate/static/EvalRoyalRoad.hpp @@ -65,7 +65,7 @@ namespace mabe { org.GenerateOutput(); // Count the number of ones in the bit sequence. - const emp::BitVector & bits = org.GetVar(bits_trait); + const emp::BitVector & bits = org.GetTrait(bits_trait); int road_length = 0.0; for (size_t i = 0; i < bits.size(); i++) { if (bits[i] == 0) break; @@ -76,7 +76,7 @@ namespace mabe { // Store the count on the organism in the fitness trait. double fitness = road_length - overage * (extra_bit_cost + 1.0); - org.SetVar(fitness_trait, fitness); + org.SetTrait(fitness_trait, fitness); if (fitness > max_fitness) { max_fitness = fitness; From a2bb93deb5cd7c2afe9f79c1a4d032c35b7b5d31 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 1 Jul 2021 17:28:18 -0400 Subject: [PATCH 029/445] Setup organisms to use Get/SetTrait() --- source/orgs/AvidaGPOrg.hpp | 4 ++-- source/orgs/BitsOrg.hpp | 2 +- source/orgs/ValsOrg.hpp | 10 +++++----- source/select/SelectElite.hpp | 4 +--- source/select/SelectTournament.hpp | 12 +++++++++--- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/source/orgs/AvidaGPOrg.hpp b/source/orgs/AvidaGPOrg.hpp index a8979ba6..9c6c1b44 100644 --- a/source/orgs/AvidaGPOrg.hpp +++ b/source/orgs/AvidaGPOrg.hpp @@ -87,13 +87,13 @@ namespace mabe { hardware.ResetHardware(); // Setup the input. - hardware.SetInputs(GetVar>(SharedData().input_name)); + hardware.SetInputs(GetTrait>(SharedData().input_name)); // Run the code. hardware.Process(SharedData().eval_time); // Store the results. - SetVar>(SharedData().output_name, emp::ToVector(hardware.GetOutputs())); + SetTrait>(SharedData().output_name, emp::ToVector(hardware.GetOutputs())); } /// Setup this organism type to be able to load from config. diff --git a/source/orgs/BitsOrg.hpp b/source/orgs/BitsOrg.hpp index fa88e1b1..5325ccb5 100644 --- a/source/orgs/BitsOrg.hpp +++ b/source/orgs/BitsOrg.hpp @@ -80,7 +80,7 @@ namespace mabe { /// Put the bits in the correct output position. void GenerateOutput() override { - SetVar(SharedData().output_name, bits); + SetTrait(SharedData().output_name, bits); } /// Setup this organism type to be able to load from config. diff --git a/source/orgs/ValsOrg.hpp b/source/orgs/ValsOrg.hpp index 78272125..a9db4bfe 100644 --- a/source/orgs/ValsOrg.hpp +++ b/source/orgs/ValsOrg.hpp @@ -37,7 +37,7 @@ namespace mabe { void CalculateTotal() { for (double x : vals) total += x; - SetVar(SharedData().total_name, total); + SetTrait(SharedData().total_name, total); } public: @@ -95,7 +95,7 @@ namespace mabe { mut_pos = mut_sites.FindOne(mut_pos+1); // Move on to the next site to mutate. } - SetVar(SharedData().total_name, total); // Store total in data map. + SetTrait(SharedData().total_name, total); // Store total in data map. return num_muts; } @@ -105,7 +105,7 @@ namespace mabe { x = random.GetDouble(SharedData().min_value, SharedData().max_value); total += x; } - SetVar(SharedData().total_name, total); // Store total in data map. + SetTrait(SharedData().total_name, total); // Store total in data map. } void Initialize(emp::Random & random) override { @@ -116,8 +116,8 @@ namespace mabe { /// Put the values in the correct output positions. void GenerateOutput() override { - SetVar>(SharedData().output_name, vals); - SetVar(SharedData().total_name, total); + SetTrait>(SharedData().output_name, vals); + SetTrait(SharedData().total_name, total); } /// Setup this organism type to be able to load from config. diff --git a/source/select/SelectElite.hpp b/source/select/SelectElite.hpp index 5e3f454d..12853001 100644 --- a/source/select/SelectElite.hpp +++ b/source/select/SelectElite.hpp @@ -55,15 +55,13 @@ namespace mabe { emp::valsort_map id_fit_map; Collection select_col = control.GetAlivePopulation(select_pop_id); for (auto it = select_col.begin(); it != select_col.end(); it++) { - id_fit_map.Set(it.AsPosition(), it->GetVar(trait)); - //std::cout << "Measuring fit " << it->GetVar(trait) << std::endl; + id_fit_map.Set(it.AsPosition(), it->GetTrait(trait)); } // Loop through the IDs in fitness order (from highest), replicating each size_t num_reps = 0; Population & birth_pop = control.GetPopulation(birth_pop_id); for (auto it = id_fit_map.crvbegin(); it != id_fit_map.crvend() && num_reps++ < top_count; it++) { - //std::cout << "Replicating fit " << it->first->GetVar(trait) << std::endl; control.Replicate(it->first, birth_pop, copy_count); } } diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index d7170d6c..8043480e 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -49,7 +49,9 @@ namespace mabe { AddRequiredTrait(trait); ///< The fitness trait must be set by another module. } - void OnUpdate(size_t /* update */) override { + void OnUpdate(size_t ud) override { + control.Verbose("UD ", ud, ": Running SelectTournament::OnUpdate()"); + emp::Random & random = control.GetRandom(); Population & select_pop = control.GetPopulation(select_pop_id); Population & birth_pop = control.GetPopulation(birth_pop_id); @@ -67,13 +69,13 @@ namespace mabe { // Find a random organism in the population and call it "best" size_t best_id = random.GetUInt(N); while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); - double best_fit = select_pop[best_id].GetVar(trait); + double best_fit = select_pop[best_id].GetTrait(trait); // Loop through other organisms for the rest of the tournament size, and pick best. for (size_t test=1; test < tourny_size; test++) { size_t test_id = random.GetUInt(N); while (select_pop[test_id].IsEmpty()) test_id = random.GetUInt(N); - double test_fit = select_pop[test_id].GetVar(trait); + double test_fit = select_pop[test_id].GetTrait(trait); if (test_fit > best_fit) { best_id = test_id; best_fit = test_fit; @@ -83,6 +85,10 @@ namespace mabe { // Replicat the organism that did best in this tournament. control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); } + + control.Verbose(" - After ", num_tournies, " tournaments, select_pop has", + select_pop.GetNumOrgs(), "organisms and birth pop has", + birth_pop.GetNumOrgs(), "."); } }; From 8771ec669315acc5acebeed769962fea8be7b1f7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 1 Jul 2021 17:31:05 -0400 Subject: [PATCH 030/445] Setup remaining modules to use Get/SetTrait() --- source/interface/FileOutput.hpp | 1 + source/schema/MovePopulation.hpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/source/interface/FileOutput.hpp b/source/interface/FileOutput.hpp index ea00fa82..820a182d 100644 --- a/source/interface/FileOutput.hpp +++ b/source/interface/FileOutput.hpp @@ -107,6 +107,7 @@ namespace mabe { } void BeforeUpdate(size_t ud) override { + control.Verbose("UD ", ud, ": Running FileOutput::BeforeUpdate()"); DoOutput(ud); } diff --git a/source/schema/MovePopulation.hpp b/source/schema/MovePopulation.hpp index 7306d16b..a21950d2 100644 --- a/source/schema/MovePopulation.hpp +++ b/source/schema/MovePopulation.hpp @@ -37,7 +37,10 @@ namespace mabe { LinkVar(reset_to, "reset_to", "Should we erase organisms at the destination?"); } - void OnUpdate(size_t /* update */) override { + void OnUpdate(size_t ud) override { + control.Verbose("UD ", ud, ": Running MovePopulation::OnUpdate()"); + control.Verbose(" - from_pop ID=", from_id, "; to_pop ID=", to_id, "."); + Population & from_pop = control.GetPopulation(from_id); Population & to_pop = control.GetPopulation(to_id); @@ -61,8 +64,11 @@ namespace mabe { if (it_from.IsOccupied()) control.MoveOrg(it_from, it_to); } - // Clear out the next generation + // Clear out the next generation (which should just have EmptyOrganisms in it now.) control.EmptyPop(from_pop, 0); + + control.Verbose(" - Final pop ", from_id, " size is ", from_pop.GetNumOrgs(), "."); + control.Verbose(" - Final pop ", to_id, " size is ", to_pop.GetNumOrgs(), "."); } }; From de2c05f3bcd7763db5eb149074a8728be87c03d2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 2 Jul 2021 23:52:20 -0400 Subject: [PATCH 031/445] Setup tracing for Mancala games. --- source/evaluate/games/EvalMancala.hpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 850f1e1c..09fde582 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -19,11 +19,14 @@ namespace mabe { class EvalMancala : public Module { private: - Collection target_collect; // Which organisms should we evaluate? + Collection target_collect; ///< Which organisms should we evaluate? - std::string input_trait; // Name of trait to put input values. - std::string output_trait; // Name of trait to find output values. - std::string score_trait; // Trait to indicate game results. + std::string input_trait = "input"; ///< Name of trait to put input values. + std::string output_trait = "output"; ///< Name of trait to find output values. + std::string score_trait = "score"; ///< Trait to indicate game results. + std::string trace_trait = "mancala_moves"; ///< Where should game traces be stored? + + emp::vector game_trace; ///< Series of moves made in most recent game. /// What type of opponent should we use? enum Opponent { @@ -39,14 +42,8 @@ namespace mabe { EvalMancala(mabe::MABE & control, const std::string & name="EvalMancala", const std::string & desc="Evaluate organisms by having them play Mancala.", - const std::string & _itrait="input", - const std::string & _otrait="output", - const std::string & _strait="score") : Module(control, name, desc) , target_collect(control.GetPopulation(0)) - , input_trait(_itrait) - , output_trait(_otrait) - , score_trait(_strait) { SetEvaluateMod(true); } @@ -57,6 +54,7 @@ namespace mabe { LinkVar(input_trait, "input_trait", "Into which trait should input values be placed?"); LinkVar(output_trait, "output_trait", "Out of which trait should output values be read?"); LinkVar(score_trait, "score_trait", "Which trait should we store success rating?"); + LinkVar(trace_trait, "trace_trait", "Which trait should we track the game moves?"); LinkMenu(opponent_type, "opponent_type", "Which type of opponent should organisms face?", RANDOM_MOVES, "random", "Always choose a random, legal move.", AI, "ai", "Human supplied (but not very good) AI", @@ -68,6 +66,7 @@ namespace mabe { AddOwnedTrait>(input_trait, "Input values (curret board state)", emp::vector({0.0})); AddRequiredTrait>(output_trait); // Output values (move to make) AddOwnedTrait(score_trait, "Play success", 0.0); + AddOwnedTrait>(trace_trait, "Series of game moves", emp::vector()); } @@ -119,6 +118,7 @@ namespace mabe { bool cur_player=0, bool verbose=false) { emp::Mancala game(cur_player==0); size_t round = 0, errors = 0; + game_trace.resize(0); while (game.IsDone() == false) { // Determine the current player and their move. auto & play_fun = (cur_player == 0) ? player0 : player1; @@ -141,6 +141,8 @@ namespace mabe { if (++best_move > 5) best_move = 0; } + game_trace.push_back(best_move); // Record the move being done. + // Do the move and determine who goes next. bool go_again = game.DoMove(cur_player, best_move); if (!go_again) cur_player = !cur_player; @@ -197,6 +199,7 @@ namespace mabe { for (Organism & org : alive_collect) { double & score = org.GetTrait(score_trait); score = EvalGame(org, control.GetRandom()); // Start first. + org.SetTrait(trace_trait, game_trace); // Record the trace of the first game. score += EvalGame(org, control.GetRandom(), 1); // Start second. } } From 2936705f5a933f206d8e3691cd74eae18c02f3cb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 4 Jul 2021 11:01:27 -0400 Subject: [PATCH 032/445] updated MABE diagnostics to print every 10 generations. --- settings/Diagnostics.mabe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/Diagnostics.mabe b/settings/Diagnostics.mabe index 8d4d01eb..f7126641 100644 --- a/settings/Diagnostics.mabe +++ b/settings/Diagnostics.mabe @@ -56,7 +56,7 @@ FileOutput output { // Output collected data into a specified file. filename = "output.csv"; // Name of file for output data. format = "fitness:max,fitness:mean,fitness:min,fitness,vals,scores"; // Column format to use in the file. target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:1"; // Which updates should we output data? + output_updates = "0:10"; // Which updates should we output data? } GrowthPlacement place_next { // Always appened births to the end of a population. From 43000d4003eb7587e3cbe1ae712ba2b8b7a54475 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 7 Jul 2021 23:00:17 -0400 Subject: [PATCH 033/445] Rebuilt Genome prototype class. --- source/core/Genome.hpp | 243 ++++++++++++++++++++++++++++++----------- 1 file changed, 180 insertions(+), 63 deletions(-) diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp index b38c3602..11ecd4d0 100644 --- a/source/core/Genome.hpp +++ b/source/core/Genome.hpp @@ -10,94 +10,211 @@ #ifndef MABE_GENOME_HPP #define MABE_GENOME_HPP -#include "emp/bits/BitVector.hpp" +#include + +#include "emp/base/error.hpp" #include "emp/math/Random.hpp" #include "emp/meta/TypeID.hpp" namespace mabe { - // Base class for all genome types. - class Genome : public emp::BitVector { - /// Set all bits randomly, with a 50/50 probability. - virtual Genome & Randomize(emp::Random & random) = 0; + // Interface class for all genome types. + class Genome { + public: + // Note: No constructors given; Genomes are pure virtual and as such must always + // be constructed via a derived class. - /// Set all bits randomly, with a given probability of being a one. - virtual Genome & Randomize(emp::Random & random, const double p) = 0; + virtual emp::Ptr Clone() = 0; // Make an exact copy of this genome. + virtual emp::Ptr CloneProtocol() = 0; // Copy everything in this genome except sequence. - /// Identify the locus type used in this genome. - virtual emp::TypeID GetLocusType() const = 0; + virtual size_t GetSize() const = 0; // Return the size of this genome using underlying type. + virtual void Resize(size_t new_size) = 0; // Set new size using underlying type. + virtual void Resize(size_t new_size, double default_val) = 0; + virtual size_t GetNumBytes() const = 0; // Return number of bytes of data in this genome. + virtual void SetSizeRange(size_t min_size, size_t max_size) = 0; // Put limits on genome size. - /// Test to see if this genome has a specified locus type. - template - bool HasLocusType() const { return GetLocusType() == GetTypeID(); } + virtual bool IsValid(size_t pos) const { return pos < GetSize(); } + virtual size_t ValidatePosition(size_t pos) const { return pos; } // If circular, should mod, etc. - /// Get entry at a given indexof a specified type (in steps of that type size) - template - [[nodiscard]] LOCUS_T Get(const size_t index) { - return emp::BitVector::GetValueAtIndex(index) const; - } + virtual void Randomize(emp::Random &, size_t /*pos*/) = 0; // Randomize only at one locus - /// Set entry at a given indexof a specified type (in steps of that type size) - template - void Set(const size_t index, LOCUS_T value) { - emp::BitVector::SetValueAtIndex(index, value); - } - - /// Get entry at a given bit - template - [[nodiscard]] LOCUS_T GetAtBit(const size_t index) { - return emp::BitVector::GetValueAtBit(index) const; - } - - /// Set entry at a given bit - template - void SetAtBit(const size_t index, LOCUS_T value) { - emp::BitVector::SetValueAtBit(index, value); - } - - /// Get entry at a given byte - template - [[nodiscard]] LOCUS_T GetAtByte(const size_t index) { - return emp::BitVector::GetValueAtBit(index*8) const; - } - - /// Set entry at a given byte - template - void SetAtByte(const size_t index, LOCUS_T value) { - emp::BitVector::SetValueAtBit(index*8, value); + // Randomize whole genome + virtual void Randomize(emp::Random & random) { + for (size_t i = 0; i < GetSize(); i++) Randomize(random, i); } + // Test for mutations in whole genome; return number of mutations occurred. + virtual size_t Mutate(emp::Random & random) = 0; + + // Human-readable (if not easily understandable) shorthand representations. + virtual std::string ToString() const { return "[unknown]"; } + virtual void FromString(std::string & in) { emp_error("Cannot read genome from string."); } + + // Potentially more compressed or structured formats for saving/loading genomes. + // TODO: + // Archive & Serialize(Archive & archive) = 0; + + // Genome accessors for individual values… + virtual int ReadInt(size_t index) const = 0; + virtual double ReadDouble(size_t index) const = 0; + virtual std::byte ReadByte(size_t index) const = 0; + virtual bool ReadBit(size_t index) const = 0; + + // NEED MULTI-READ AND MULTI-WRITE. + // NEED READS AND WRITES THAT ARE RESTRICTED TO A RANGE + // EXAMPLE: + virtual int ReadInt(size_t index, int min, int max) const = 0; + + virtual void WriteInt(size_t index, int value) = 0; + virtual void WriteDouble(size_t index, double value) = 0; + virtual void WriteByte(size_t index, std::byte value) = 0; + virtual void WriteBit(size_t index, bool value) = 0; + + // Genome accessors for multiple values… + // TODO: + // emp::span ReadInts(size_t start_index, size_t end_index) = 0; + // emp::span ReadDoubles(size_t start_index, size_t end_index) = 0; + // emp::span ReadBytes(size_t start_index, size_t end_index) = 0; + // emp::BitVector ReadBits(size_t start_index, size_t end_index) = 0; + // + // + Multi-writes!! + + class Head { + protected: + emp::Ptr genome; // Attached genome. + size_t pos; // What position is this head located at? + int direction = 1; // Direction forward is forward. + + static const constexpr unsigned int NORMAL=0; + static const constexpr unsigned int END_OF_GENOME=1; + static const constexpr unsigned int END_OF_CHROMOSOME=2; + static const constexpr unsigned int INVALID=4; + + unsigned int state = NORMAL; + + public: + Head(Genome & in_genome, size_t in_pos=0, int in_dir=1) + : genome(&in_genome), pos(in_pos), direction(in_dir) { } + Head(const Head &) = default; + Head & operator=(const Head &) = default; + + // Check for Heads at the same position and same direction! + bool operator==(const Head & in) const { + return genome==in.genome && pos==in.pos && direction==in.direction; + } + bool operator!=(const Head & in) const { return !(*this == in); } + + bool IsValid() const { return genome->IsValid(pos); } + + // @CAO VALIDATIONS SHOULD HAPPEN BEFORE ACTIONS, NOT AFTER. + + Head & SetPosition(size_t in_pos) { + pos = genome->ValidatePosition(in_pos); + if (!IsValid()) state = INVALID; + return *this; + } + + Head & Advance(size_t factor=1) { return SetPosition(pos + direction * factor); } + + int ReadInt() { int out = IsValid() ? genome->ReadInt(pos) : 0; Advance(); return out; } + double ReadDouble() { double out = IsValid() ? genome->ReadDouble(pos) : 0; Advance(); return out; } + std::byte ReadByte() { std::byte out = IsValid() ? genome->ReadByte(pos) : std::byte(0); Advance(); return out; } + bool ReadBit() { bool out = IsValid() ? genome->ReadBit(pos) : 0; Advance(); return out; } + + Head & WriteInt(int value) { if (IsValid()) genome->WriteInt(pos, value); return Advance(); } + Head & WriteDouble(double value) { if (IsValid()) genome->WriteDouble(pos, value); return Advance(); } + Head & WriteByte(std::byte value) { if (IsValid()) genome->WriteByte(pos, value); return Advance(); } + Head & WriteBit(bool value) { if (IsValid()) genome->WriteBit(pos, value); return Advance(); } + + // NEED MULTI-READ AND MULTI-WRITE. + // NEED READS AND WRITES THAT ARE RESTRICTED TO A RANGE + // EXAMPLE OF RANGED-READ: + int ReadInt(int min, int max) { + int out = IsValid() ? genome->ReadInt(pos, min, max) : 0; Advance(); + return out; + } + + void Reset() { pos = 0; state = NORMAL; direction = 1; } + void ReverseDirection() { direction = -direction; } + + bool AtBegin() { return pos == 0; } + bool AtEnd() { return pos == genome->GetSize(); } + + void Randomize(emp::Random & random) { genome->Randomize(random, pos); } + }; + + Head GetHead(size_t position=0, int direction=1) { return Head(*this, position, direction); } + Head begin() { return GetHead(); } + Head end() { return GetHead(GetSize()); } + Head rbegin() { return GetHead(GetSize(), -1); } + Head rend() { return GetHead(0, -1); } }; + template class TypedGenome : public Genome { + protected: + using locus_t = LOCUS_T; using this_t = TypedGenome; - /// Set all bits randomly, with a 50/50 probability. - this_t & Randomize(emp::Random & random) override { - emp::BitVector::Randomize(random); - return *this; - } + emp::vector data; // Actual data in the genome. + double mut_p = 0.0; // Mutation probability (LOTS TO DO HERE!) + size_t min_size = 0; + size_t max_size = std::static_cast(-1); - /// Set all bits randomly, with a given probability of being a one. - this_t & Randomize(emp::Random & random, const double p) override { - emp::BitVector::Randomize(random, p); - return *this; - } + double alphabet_size = 4.0; - emp::TypeID GetLocusType() const override { return emp::GetTypeID(); } + public: + TypedGenome() { } + TypedGenome(this_t &) = default; - /// Get entry at a given index (in steps of that type size) - [[nodiscard]] virtual LOCUS_T Get(const size_t index) { - return emp::BitVector::GetValueAtIndex(index) const; + emp::Ptr Clone() override { return emp::NewPtr(*this); } + emp::Ptr CloneProtocol() override { + emp::Ptr out_ptr = NewPtr(); + out_ptr->mut_p = mut_p; + out_ptr->min_size = min_size; + out_ptr->max_size = max_size; + return out_ptr; } - /// Set entry at a given index (in steps of that type size) - virtual void Set(const size_t index, LOCUS_T value) { - emp::BitVector::SetValueAtIndex(index, value); + size_t GetSize() const override { return data.size(); } + void Resize(size_t new_size) override { data.resize(new_size); } + void Resize(size_t new_size, double default_val) override { + data.resize(new_size, static_cast(default_val)); } + size_t GetNumBytes() const override { return sizeof(locus_t) * GetSize(); } + void SetSizeRange(size_t _min, size_t _max) override { min_size = _min, max_size = _max; } + + void Randomize(emp::Random & random, size_t pos) override { + data[pos] = static_cast( random.GetDouble(alphabet_size) ); + } + + // Human-readable (if not easily understandable) shorthand representations. + // @CAO... needs to be done properly! + std::string ToString() const override { return "[unknown]"; } + void FromString(std::string & in) override { emp_error("Cannot read genome from string."); } + + // Potentially more compressed or structured formats for saving/loading genomes. + // TODO: + // Archive & Serialize(Archive & archive) = 0; + + // Genome accessors for individual values… + int ReadInt(size_t index) const override { return static_cast(data[index]); } + double ReadDouble(size_t index) const override { return static_cast(data[index]); } + std::byte ReadByte(size_t index) const override { return static_cast(data[index]); } + bool ReadBit(size_t index) const override { return static_cast(data[index]); } + + void WriteInt(size_t index, int value) { data[index] = static_cast(value); } + void WriteDouble(size_t index, double value) { data[index] = static_cast(value); } + void WriteByte(size_t index, std::byte value) { data[index] = static_cast(value); } + void WriteBit(size_t index, bool value) { data[index] = static_cast(value); } }; -} + template <> + class TypedGenome : public Genome { + // FILL ALL THIS OUT USING emp::BitVector instead!!! + } + +}; #endif From 4a5bc0da86039a24b525d5e1f8067c3f04a55b9e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 9 Jul 2021 00:15:42 -0400 Subject: [PATCH 034/445] Minor fixes to Genome so it can compile. --- source/core/Genome.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp index 11ecd4d0..54bc14ac 100644 --- a/source/core/Genome.hpp +++ b/source/core/Genome.hpp @@ -213,8 +213,8 @@ namespace mabe { template <> class TypedGenome : public Genome { // FILL ALL THIS OUT USING emp::BitVector instead!!! - } + }; -}; +} #endif From af3774f83dff92b51ecaf73d35bb4fbefec2ad01 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 9 Jul 2021 00:16:30 -0400 Subject: [PATCH 035/445] Removed Organism base class' reliance on manager calls. --- source/core/Organism.hpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 0a095059..a0033183 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -137,7 +137,7 @@ namespace mabe { } - /// Test if this organism represents an empy cell. + /// Test if this organism represents an empty cell. virtual bool IsEmpty() const noexcept { return false; } @@ -148,18 +148,20 @@ namespace mabe { /// Create an exact duplicate of this organism. /// @note We MUST be able to make a copy of organisms for MABE to function. If this function - /// is not overridden, try to the equivilent function in the organism manager. + /// is not overridden, the organism manager (which knows the derived type) will try to make a + /// clone using the copy constructor. [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneOrganism(*this); } /// Modify this organism based on configured mutation parameters. /// @note For evolution to function, we need to be able to mutate offspring. - virtual size_t Mutate(emp::Random & random) { return manager.Mutate(*this, random); } + virtual size_t Mutate(emp::Random & random) = 0; /// Merge this organism's genome with that of another organism to produce an offspring. /// @note Required for basic sexual recombination to work. [[nodiscard]] virtual emp::Ptr Recombine(emp::Ptr parent2, emp::Random & random) const { - return manager.Recombine(*this, parent2, random); + emp_assert(false, "Recombine() must be overridden for it to work."); + return nullptr; } /// Merge this organism's genome with that of a variable number of other organisms to produce @@ -168,7 +170,8 @@ namespace mabe { /// but also slower. [[nodiscard]] virtual emp::vector> Recombine(emp::vector> other_parents, emp::Random & random) const { - return manager.Recombine(*this, other_parents, random); + emp_assert(false, "Recombine() must be overridden for it to work."); + return emp::vector>(); } /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). @@ -199,13 +202,15 @@ namespace mabe { /// Convert this organism into a string of characters. /// @note Required if we are going to print organisms to screen or to file). If this function /// is not overridden, try to the equivilent function in the organism manager. - virtual std::string ToString() const { return manager.OrgToString(*this); } + virtual std::string ToString() const { return "__unknown__"; } /// Completely randomize a new organism (typically for initialization) - virtual void Randomize(emp::Random & random) { manager.Randomize(*this, random); } + virtual void Randomize(emp::Random & random) { + emp_assert(false, "Randomize() must be overridden before it can be called."); + } /// Setup a new organism from scratch; by default just randomize. - virtual void Initialize(emp::Random & random) { manager.Randomize(*this, random); } + virtual void Initialize(emp::Random & random) { Randomize(random); } /// Run the organism to generate an output in the pre-configured data_map entries. virtual void GenerateOutput() { ; } From 67f526b4c7c34dddc4dc7958ddbe283cdc3e8382 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 9 Jul 2021 00:21:36 -0400 Subject: [PATCH 036/445] Added a Print() function to Organism. --- source/core/Organism.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index a0033183..9cd8b345 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -204,6 +204,12 @@ namespace mabe { /// is not overridden, try to the equivilent function in the organism manager. virtual std::string ToString() const { return "__unknown__"; } + /// By default print an organism by triggering it's ToString() function. + std::ostream & Print(std::ostream & os) const { + os << ToString(); + return os; + } + /// Completely randomize a new organism (typically for initialization) virtual void Randomize(emp::Random & random) { emp_assert(false, "Randomize() must be overridden before it can be called."); From a2f538e6eab616771bf7a3fb658d270b402359ad Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 9 Jul 2021 00:22:20 -0400 Subject: [PATCH 037/445] Remove most virtual Organism helper functions from OrganismManager. --- source/core/OrganismManager.hpp | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index c78f1e85..d64821fb 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -45,23 +45,11 @@ namespace mabe { using org_t = ORG_T; /// Also get the TypeID for this organism for more run-time type management. - emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } - - /// Convert this organism to the correct type (after ensuring that it is!) - org_t & ConvertOrg(Organism & org) const { - emp_assert(&(org.GetManager()) == this); - return (org_t &) org; - } - - /// Convert this CONST organism to the correct type (after ensuring that it is!) - const org_t & ConvertOrg(const Organism & org) const { - emp_assert(&(org.GetManager()) == this); - return (const org_t &) org; - } + emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } /// Create a clone of the provided organism; default to using copy constructor. emp::Ptr CloneOrganism(const Organism & org) override { - return emp::NewPtr( ConvertOrg(org) ); + return emp::NewPtr( (const org_t &) org ); } /// Create a random organism from scratch. Default to using the org_prototype organism. @@ -78,16 +66,6 @@ namespace mabe { return org_ptr; } - /// Convert an organism to a string for printing; if not overridden, just prints - /// "__unknown__". - std::string OrgToString(const Organism &) const override { return "__unknown__"; }; - - /// By default print an organism by triggering it's ToString() function. - std::ostream & PrintOrganism(Organism & org, std::ostream & os) const override { - emp_assert(&(org.GetManager()) == this); - os << org.ToString(); - return os; - } void SetupModule() override { org_prototype->SetupModule(); From 1ec84db3f4737c9bbf0a4ae7d45cd30191ced911 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 9 Jul 2021 00:23:00 -0400 Subject: [PATCH 038/445] Removed virtual functions from ModuleBase that are no longer used in OrganismManager. --- source/core/ModuleBase.hpp | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index ee9d663d..deeb1984 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -291,32 +291,6 @@ namespace mabe { emp_assert(false, "MakeOrganism() must be overridden for either Organism or OrganismManager module."); return nullptr; } - virtual std::string OrgToString(const Organism &) const { - emp_assert(false, "OrgToString() must be overridden for either Organism or OrganismManager module."); - return ""; - } - virtual std::ostream & PrintOrganism(Organism &, std::ostream & is) const { - emp_assert(false, "Print() must be overridden for either Organism or OrganismManager module."); - return is; - } - virtual size_t Mutate(Organism &, emp::Random &) const { - emp_assert(false, "Mutate() must be overridden for either Organism or OrganismManager module."); - return 0; - } - virtual void Randomize(Organism &, emp::Random &) const { - emp_assert(false, "Randomize() must be overridden for either Organism or OrganismManager module."); - } - - virtual emp::Ptr Recombine(const Organism &, emp::Ptr, emp::Random &) const { - emp_assert(false, "Recombine() must be overridden for either Organism or OrganismManager module."); - return nullptr; - } - - virtual emp::vector> - Recombine(const Organism &, emp::vector>, emp::Random &) const { - emp_assert(false, "Recombine() must be overridden for either Organism or OrganismManager module."); - return emp::vector< emp::Ptr >(); - } virtual void SetupConfig() { } }; From 01857d9e099fae2c3a4c516092e2c04cd776e65d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 10 Jul 2021 11:27:03 -0400 Subject: [PATCH 039/445] Cleaned up organisms to remove unused parameters in base functions. --- source/core/Organism.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 9cd8b345..618a89b9 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -159,7 +159,7 @@ namespace mabe { /// Merge this organism's genome with that of another organism to produce an offspring. /// @note Required for basic sexual recombination to work. [[nodiscard]] virtual emp::Ptr - Recombine(emp::Ptr parent2, emp::Random & random) const { + Recombine(emp::Ptr /* parent2 */, emp::Random & /* random */) const { emp_assert(false, "Recombine() must be overridden for it to work."); return nullptr; } @@ -169,7 +169,7 @@ namespace mabe { /// @note More flexible version of recombine (allowing many parents and/or many offspring), /// but also slower. [[nodiscard]] virtual emp::vector> - Recombine(emp::vector> other_parents, emp::Random & random) const { + Recombine(emp::vector> /*other_parents*/, emp::Random & /*random*/) const { emp_assert(false, "Recombine() must be overridden for it to work."); return emp::vector>(); } @@ -211,7 +211,7 @@ namespace mabe { } /// Completely randomize a new organism (typically for initialization) - virtual void Randomize(emp::Random & random) { + virtual void Randomize(emp::Random & /*random*/) { emp_assert(false, "Randomize() must be overridden before it can be called."); } From 0e56b507cd9ea9c3efad3af3ee0ce634c01fa4eb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 11 Jul 2021 11:03:59 -0400 Subject: [PATCH 040/445] Removed unneeded functions from EmptyOrganism. --- source/core/EmptyOrganism.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/core/EmptyOrganism.hpp b/source/core/EmptyOrganism.hpp index d2f8035f..bb67eed4 100644 --- a/source/core/EmptyOrganism.hpp +++ b/source/core/EmptyOrganism.hpp @@ -35,11 +35,8 @@ namespace mabe { std::string GetTypeName() const override { return "EmptyOrganismManager"; } emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } - emp::Ptr CloneOrganism(const Organism &) override { emp_error("Cannot clone an EmptyOrganism."); return nullptr; } emp::Ptr MakeOrganism() override { return emp::NewPtr(*this); } emp::Ptr MakeOrganism(emp::Random &) override { emp_error("Cannot make a 'random' EmptyOrganism."); return nullptr; } - std::string OrgToString(const Organism &) const override { return "[empty]"; } - std::ostream & PrintOrganism(Organism &, std::ostream & os) const override { emp_error("Do not call functions on EmptyOrganism."); return os; } }; } From 474442c74c7a97c8f86d0c83c153050de8986689 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 11 Jul 2021 23:02:28 -0400 Subject: [PATCH 041/445] Make sure all OrganismManagers are marked as such. --- source/core/OrganismManager.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index d64821fb..8a21767b 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -37,6 +37,7 @@ namespace mabe { OrganismManager(MABE & in_control, const std::string & in_name, const std::string & in_desc="") : Module(in_control, in_name, in_desc) { + SetManageMod(); org_prototype = emp::NewPtr(*this); } virtual ~OrganismManager() { org_prototype.Delete(); } From 8623d02f09f668be28cae0647c1d3a920c94be3a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 12 Jul 2021 13:30:57 -0400 Subject: [PATCH 042/445] Refactored TraitManager::Verify() to modularize trait checks. --- source/core/TraitManager.hpp | 168 ++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 73 deletions(-) diff --git a/source/core/TraitManager.hpp b/source/core/TraitManager.hpp index b07b013d..c18039a0 100644 --- a/source/core/TraitManager.hpp +++ b/source/core/TraitManager.hpp @@ -40,6 +40,9 @@ namespace mabe { /// ErrorManager passed in from main program. mabe::ErrorManager & error_man; + /// Count the total number of errors encountered. + int error_count = 0; + public: TraitManager(mabe::ErrorManager & in_error_man) : error_man(in_error_man) { } ~TraitManager() { @@ -147,12 +150,100 @@ namespace mabe { cur_trait->SetAltTypes(intersect_types); } - // Add this modules access to the trait. + // Add this module's access to the trait. cur_trait->AddAccess(mod_name, mod_ptr, access); return *cur_trait; } + /// Verify that modules are handling private access of a trait correctly. + bool VerifyPrivacy(const std::string & trait_name, emp::Ptr trait_ptr) { + // Only one module can be involved for PRIVATE access. + if (trait_ptr->GetPrivateCount() > 1) { + std::stringstream error_msg; + error_msg << "Multiple modules declaring trait '" << trait_name + << "' as private: " << emp::to_english_list(trait_ptr->GetPrivateNames()) + << ".\n" + << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" + << " module-specific prefix. Otherwise modules need to be edited to not have\n" + << " trait private.]"; + error_man.AddError(error_msg.str()); + error_count++; + return false; + } + + if (trait_ptr->GetPrivateCount() && trait_ptr->GetModuleCount() > 1) { + error_man.AddError("Trait '", trait_name, "' is private in module '", trait_ptr->GetPrivateNames()[0], + "'; should not be used by other modules.\n", + "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", + " module-specific prefix. Otherwise module needs to be edited to not have\n", + " trait private.]"); + error_count++; + return false; + } + + return true; + } + + /// Verify that modules are allowing only a single owner of a trait. + bool VerifyOwnership(const std::string & trait_name, emp::Ptr trait_ptr) { + // A trait that is OWNED or GENERATED cannot have other modules writing to it. + const size_t claim_count = trait_ptr->GetOwnedCount() + trait_ptr->GetGeneratedCount(); + + if (claim_count > 1) { + auto mod_names = emp::Concat(trait_ptr->GetOwnedNames(), trait_ptr->GetGeneratedNames()); + std::stringstream error_msg; + error_msg << "Multiple modules declaring ownership of trait '" << trait_name << "': " + << emp::to_english_list(mod_names) << ".\n" + << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" + << " module-specific prefix. Otherwise modules should be edited to change trait\n" + << " to be SHARED (and all can modify) or have all but one shift to REQUIRED.]"; + error_man.AddError(error_msg.str()); + error_count++; + return false; + } + + if (claim_count && trait_ptr->IsShared()) { + auto mod_names = emp::Concat(trait_ptr->GetOwnedNames(), trait_ptr->GetGeneratedNames()); + error_man.AddError("Trait '", trait_name, + "' is fully OWNED by module '", mod_names[0], + "'; it cannot be SHARED (written to) by other modules:", + emp::to_english_list(trait_ptr->GetSharedNames()), + "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", + " module-specific prefix. Otherwise module needs to be edited to make trait\n", + " SHARED or have all but one shift to REQUIRED.]"); + error_count++; + return false; + } + + return true; + } + + /// Verify that modules use traits the ways other modules require. + bool VerifyRequirements(const std::string & trait_name, emp::Ptr trait_ptr) { + // A REQUIRED trait must have another module write to it (i.e. OWNED, GENERATED or SHARED). + if (trait_ptr->IsRequired() && + !trait_ptr->IsOwned() && !trait_ptr->IsShared() && !trait_ptr->IsGenerated()) { + error_man.AddError("Trait '", trait_name, "' marked REQUIRED by module(s) ", + emp::to_english_list(trait_ptr->GetRequiredNames()), + "'; must be written to by other modules.\n", + "[Suggestion: set another module to write to this trait (where it is either\n", + " SHARED or OWNED).]"); + error_count++; + return false; + } + + // A GENERATED trait requires another module to read (REQUIRE) it. + else if (trait_ptr->IsGenerated() && !trait_ptr->IsRequired()) { + error_man.AddError("Trait '", trait_name, "' marked GENERATED by module(s) ", + emp::to_english_list(trait_ptr->GetGeneratedNames()), + "'; must be read by other modules."); + error_count++; + return false; + } + + return true; + } /// Make sure modules are accessing traits correctly and consistently. void Verify(bool verbose) { @@ -160,8 +251,6 @@ namespace mabe { std::cout << "Analyzing configuration of " << trait_map.size() << " traits." << std::endl; } - int error_count = 0; - // Loop through all of the traits to ensure there are no conflicts. for (auto [trait_name, trait_ptr] : trait_map) { if (verbose) { @@ -184,76 +273,9 @@ namespace mabe { continue; } - // Only one module can be involved for PRIVATE access. - else if (trait_ptr->GetPrivateCount() > 1) { - std::stringstream error_msg; - error_msg << "Multiple modules declaring trait '" << trait_name - << "' as private: " << emp::to_english_list(trait_ptr->GetPrivateNames()) - << ".\n" - << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" - << " module-specific prefix. Otherwise modules need to be edited to not have\n" - << " trait private.]"; - error_man.AddError(error_msg.str()); - error_count++; - continue; - } - - else if (trait_ptr->GetPrivateCount() && trait_ptr->GetModuleCount() > 1) { - error_man.AddError("Trait '", trait_name, "' is private in module '", trait_ptr->GetPrivateNames()[0], - "'; should not be used by other modules.\n", - "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", - " module-specific prefix. Otherwise module needs to be edited to not have\n", - " trait private.]"); - error_count++; - continue; - } - - // A trait that is OWNED or GENERATED cannot have other modules writing to it. - else if (trait_ptr->GetOwnedCount() + trait_ptr->GetGeneratedCount() > 1) { - auto mod_names = emp::Concat(trait_ptr->GetOwnedNames(), trait_ptr->GetGeneratedNames()); - std::stringstream error_msg; - error_msg << "Multiple modules declaring ownership of trait '" << trait_name << "': " - << emp::to_english_list(mod_names) << ".\n" - << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" - << " module-specific prefix. Otherwise modules should be edited to change trait\n" - << " to be SHARED (and all can modify) or have all but one shift to REQUIRED.]"; - error_man.AddError(error_msg.str()); - error_count++; - continue; - } - - else if ((trait_ptr->IsOwned() || trait_ptr->IsGenerated()) && trait_ptr->IsShared()) { - error_man.AddError("Trait '", trait_name, - "' is fully OWNED by module '", trait_ptr->GetOwnedNames()[0], - "'; it cannot be SHARED (written to) by other modules:", - emp::to_english_list(trait_ptr->GetSharedNames()), - "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", - " module-specific prefix. Otherwise module needs to be edited to make trait\n", - " SHARED or have all but one shift to REQUIRED.]"); - error_count++; - continue; - } - - // A REQUIRED trait must have another module write to it (i.e. OWNED, GENERATED or SHARED). - else if (trait_ptr->IsRequired() && - !trait_ptr->IsOwned() && !trait_ptr->IsShared() && !trait_ptr->IsGenerated()) { - error_man.AddError("Trait '", trait_name, "' marked REQUIRED by module(s) ", - emp::to_english_list(trait_ptr->GetRequiredNames()), - "'; must be written to by other modules.\n", - "[Suggestion: set another module to write to this trait (where it is either\n", - " SHARED or OWNED).]"); - error_count++; - continue; - } - - // A GENERATED trait requires another module to read (REQUIRE) it. - else if (trait_ptr->IsGenerated() && !trait_ptr->IsRequired()) { - error_man.AddError("Trait '", trait_name, "' marked GENERATED by module(s) ", - emp::to_english_list(trait_ptr->GetGeneratedNames()), - "'; must be read by other modules."); - error_count++; - continue; - } + if (!VerifyPrivacy(trait_name, trait_ptr)) continue; + if (!VerifyOwnership(trait_name, trait_ptr)) continue; + if (!VerifyRequirements(trait_name, trait_ptr)) continue; } } From ff5cab44c7c0103cea683f203e767d3e0afcc63d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 12 Jul 2021 13:40:00 -0400 Subject: [PATCH 043/445] More cleanup on TraitManager::Verify(). --- source/core/TraitManager.hpp | 48 ++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/source/core/TraitManager.hpp b/source/core/TraitManager.hpp index c18039a0..5bd29e47 100644 --- a/source/core/TraitManager.hpp +++ b/source/core/TraitManager.hpp @@ -156,6 +156,22 @@ namespace mabe { return *cur_trait; } + ///////////////////////////////////////////////// + // --- Trait verification functions --- + + /// Make sure that there are no illegal states in this trait setup. + bool VerifyValid(const std::string & trait_name, emp::Ptr trait_ptr) { + // NO traits should be of UNKNOWN access. + if (trait_ptr->GetUnknownCount()) { + error_man.AddError("Unknown access mode for trait '", trait_name, + "' in module(s) ", emp::to_english_list(trait_ptr->GetUnknownNames()), + " (internal error!)"); + return false; + } + + return true; + } + /// Verify that modules are handling private access of a trait correctly. bool VerifyPrivacy(const std::string & trait_name, emp::Ptr trait_ptr) { // Only one module can be involved for PRIVATE access. @@ -168,17 +184,16 @@ namespace mabe { << " module-specific prefix. Otherwise modules need to be edited to not have\n" << " trait private.]"; error_man.AddError(error_msg.str()); - error_count++; return false; } if (trait_ptr->GetPrivateCount() && trait_ptr->GetModuleCount() > 1) { - error_man.AddError("Trait '", trait_name, "' is private in module '", trait_ptr->GetPrivateNames()[0], + error_man.AddError("Trait '", trait_name, "' is private in module '", + trait_ptr->GetPrivateNames()[0], "'; should not be used by other modules.\n", "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", " module-specific prefix. Otherwise module needs to be edited to not have\n", " trait private.]"); - error_count++; return false; } @@ -199,7 +214,6 @@ namespace mabe { << " module-specific prefix. Otherwise modules should be edited to change trait\n" << " to be SHARED (and all can modify) or have all but one shift to REQUIRED.]"; error_man.AddError(error_msg.str()); - error_count++; return false; } @@ -212,7 +226,6 @@ namespace mabe { "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", " module-specific prefix. Otherwise module needs to be edited to make trait\n", " SHARED or have all but one shift to REQUIRED.]"); - error_count++; return false; } @@ -229,7 +242,6 @@ namespace mabe { "'; must be written to by other modules.\n", "[Suggestion: set another module to write to this trait (where it is either\n", " SHARED or OWNED).]"); - error_count++; return false; } @@ -238,7 +250,6 @@ namespace mabe { error_man.AddError("Trait '", trait_name, "' marked GENERATED by module(s) ", emp::to_english_list(trait_ptr->GetGeneratedNames()), "'; must be read by other modules."); - error_count++; return false; } @@ -246,9 +257,9 @@ namespace mabe { } /// Make sure modules are accessing traits correctly and consistently. - void Verify(bool verbose) { + bool Verify(bool verbose) { if (verbose) { - std::cout << "Analyzing configuration of " << trait_map.size() << " traits." << std::endl; + std::cout << "Analyzing configuration of " << trait_map.size() << " traits.\n"; } // Loop through all of the traits to ensure there are no conflicts. @@ -264,23 +275,18 @@ namespace mabe { << std::endl; } - // NO traits should be of UNKNOWN access. - if (trait_ptr->GetUnknownCount()) { - error_man.AddError("Unknown access mode for trait '", trait_name, - "' in module(s) ", emp::to_english_list(trait_ptr->GetUnknownNames()), - " (internal error!)"); + if (!VerifyValid(trait_name, trait_ptr) || + !VerifyPrivacy(trait_name, trait_ptr) || + !VerifyOwnership(trait_name, trait_ptr) || + !VerifyRequirements(trait_name, trait_ptr)) { error_count++; - continue; - } - - if (!VerifyPrivacy(trait_name, trait_ptr)) continue; - if (!VerifyOwnership(trait_name, trait_ptr)) continue; - if (!VerifyRequirements(trait_name, trait_ptr)) continue; + } } + + return error_count; } }; - } #endif \ No newline at end of file From c28f3c99d235e78fd2cdb4196e57a3c1538ea721 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 14 Jul 2021 23:21:10 -0400 Subject: [PATCH 044/445] Setup TraitInfo to track manager access counts of different types. --- source/core/TraitInfo.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/core/TraitInfo.hpp b/source/core/TraitInfo.hpp index 9b0ba616..eefb762f 100644 --- a/source/core/TraitInfo.hpp +++ b/source/core/TraitInfo.hpp @@ -134,11 +134,13 @@ namespace mabe { std::string mod_name = ""; mod_ptr_t mod_ptr = nullptr; Access access = Access::UNKNOWN; + bool is_manager = false; }; emp::vector access_info; // Specific access categories - emp::array access_counts = { 0, 0, 0, 0, 0 }; + emp::array access_counts = { 0, 0, 0, 0, 0, 0, 0 }; + emp::array manager_access_counts = { 0, 0, 0, 0, 0, 0, 0 }; // Helper functions int GetInfoID(const std::string & mod_name) const { @@ -236,9 +238,10 @@ namespace mabe { TraitInfo & SetDesc(const std::string & in_desc) { desc = in_desc; return *this; } // Add a module that can access this trait. - TraitInfo & AddAccess(const std::string & in_name, mod_ptr_t in_mod, Access access) { - access_info.push_back(ModuleInfo{ in_name, in_mod, access }); + TraitInfo & AddAccess(const std::string & in_name, mod_ptr_t in_mod, Access access, bool is_manager) { + access_info.push_back(ModuleInfo{ in_name, in_mod, access, is_manager }); access_counts[access]++; + if (is_manager) manager_access_counts[access]++; return *this; } From 649c8e91bdf607addeb805fa340eacc4d97a7947 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 14 Jul 2021 23:29:00 -0400 Subject: [PATCH 045/445] Setup trait managers to handle organism managers. --- source/core/TraitManager.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/core/TraitManager.hpp b/source/core/TraitManager.hpp index 5bd29e47..e4f487ab 100644 --- a/source/core/TraitManager.hpp +++ b/source/core/TraitManager.hpp @@ -87,7 +87,8 @@ namespace mabe { { const std::string & mod_name = mod_ptr->GetName(); - // All configurations must be setup in SetupConfig(); afterward linking new traits is allowed. + // Traits must be added in the SetupModule() function for the given modules; + // afterward the trait manager is locked and additional new traits are not allowed. if (locked) { AddError("Module '", mod_name, "' adding trait '", trait_name, "' before config files have loaded; should be done in SetupModule()."); @@ -136,6 +137,8 @@ namespace mabe { trait_map[trait_name] = cur_trait; } + // @CAO Technically, we can shift to any of the intersect types. + // Otherwise we have incompatable types... else { AddError("Module ", mod_name, " is trying to use trait '", @@ -151,7 +154,8 @@ namespace mabe { } // Add this module's access to the trait. - cur_trait->AddAccess(mod_name, mod_ptr, access); + bool is_manager = mod_ptr->IsManageMod(); + cur_trait->AddAccess(mod_name, mod_ptr, access, is_manager); return *cur_trait; } @@ -178,8 +182,7 @@ namespace mabe { if (trait_ptr->GetPrivateCount() > 1) { std::stringstream error_msg; error_msg << "Multiple modules declaring trait '" << trait_name - << "' as private: " << emp::to_english_list(trait_ptr->GetPrivateNames()) - << ".\n" + << "' as private: " << emp::to_english_list(trait_ptr->GetPrivateNames()) << ".\n" << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" << " module-specific prefix. Otherwise modules need to be edited to not have\n" << " trait private.]"; From 5555e27ce693fcf4036b8557605a258440bd85bc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 24 Jul 2021 22:03:19 -0400 Subject: [PATCH 046/445] Started building an output event right in MABE core. --- source/core/MABE.hpp | 45 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index acdf1141..af3bf9ca 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -26,6 +26,7 @@ #include "emp/config/command_line.hpp" #include "emp/control/Signal.hpp" #include "emp/data/DataMap.hpp" +#include "emp/io/StreamManager.hpp" #include "emp/math/Random.hpp" #include "emp/datastructs/vector_utils.hpp" @@ -57,10 +58,15 @@ namespace mabe { // --- Variables to handle configuration, initialization, and error reporting --- - bool verbose = false; ///< Should we output extra information during setup? - bool show_help = false; ///< Should we show "help" before exiting? - bool exit_now = false; ///< Do we need to immediately clean up and exit the run? - ErrorManager error_man; ///< Object to manage warnings and errors. + bool verbose = false; ///< Should we output extra information during setup? + bool show_help = false; ///< Should we show "help" before exiting? + bool exit_now = false; ///< Do we need to immediately clean up and exit the run? + ErrorManager error_man; ///< Object to manage warnings and errors. + emp::StreamManager files; ///< Track all of the file streams used in MABE. + + // Setup a cache for functions used to collect data for files. + using trait_fun_t = std::function; + std::unordered_map> file_fun_cache; /// Populations used; generated in the configuration file. emp::vector< emp::Ptr > pops; @@ -491,7 +497,6 @@ namespace mabe { /// entopy : Return the Shannon entropy of this value. /// :trait : Return the mutual information with another provided trait. - using trait_fun_t = std::function; trait_fun_t BuildTraitFunction(const std::string & trait_name, std::string trait_filter) { // The trait input has two components: @@ -630,6 +635,36 @@ namespace mabe { }; config.AddFunction("print", print_fun, "Print out the provided variable."); + // 'output' will collect data and write it to a file. + files.SetIODefaultFile(); // String manager should use files. + std::function output_fun = + [this](const std::string & filename, const std::string & collection, const std::string & output) { + emp::vector funs; ///< Functions to call each update. + std::iostream & file = files.GetIOStream(filename); ///< File to write to. + auto fun_it = file_fun_cache.find(output); + + // If there functions don't exist yet, set them up! + if (fun_it == file_fun_cache.end()) { + // Identify the contents of each column. + std::string format = output; + emp::remove_whitespace(format); + emp::vector cols = emp::slice(format, ','); + + // Setup a function to collect data associated with each column. + funs.resize(cols.size()); + for (size_t i = 0; i < cols.size(); i++) { + std::string trait_filter = cols[i]; + std::string trait_name = emp::string_pop(trait_filter,':'); + funs[i] = BuildTraitFunction(trait_name, trait_filter); + } + + // Insert the new entry into the cache and update the iterator. + fun_it = file_fun_cache.insert({output, funs}).first; + } + return 0; + }; + config.AddFunction("output", output_fun, "Print out the provided variable."); + // Add in built-in event triggers; these are used to indicate when events should happen. config.AddEventType("start"); // Triggered at the beginning of a run. config.AddEventType("update"); // Tested every update. From d6e92a498bf1d2b20b4baca44e821babc16b30b9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 25 Jul 2021 23:23:08 -0400 Subject: [PATCH 047/445] Setup output files to print headers. --- source/core/MABE.hpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index af3bf9ca..e457240b 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -638,16 +638,26 @@ namespace mabe { // 'output' will collect data and write it to a file. files.SetIODefaultFile(); // String manager should use files. std::function output_fun = - [this](const std::string & filename, const std::string & collection, const std::string & output) { + [this](const std::string & filename, const std::string & collection, const std::string & format) { emp::vector funs; ///< Functions to call each update. + const bool file_exists = files.Has(filename); ///< Determine if file is already setup. std::iostream & file = files.GetIOStream(filename); ///< File to write to. - auto fun_it = file_fun_cache.find(output); + auto fun_it = file_fun_cache.find(format); + emp::remove_whitespace(format); + + // If we need headers, set them up! + if (!file_exists) { + // Print the headers into the file. + file << "#update"; + for (size_t i = 0; i < cols.size(); i++) { + file << ", " << cols[i]; + } + file << '\n'; + } // If there functions don't exist yet, set them up! if (fun_it == file_fun_cache.end()) { // Identify the contents of each column. - std::string format = output; - emp::remove_whitespace(format); emp::vector cols = emp::slice(format, ','); // Setup a function to collect data associated with each column. From c9da107ed5dfdba12577f843d332a5430818ca47 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 27 Jul 2021 13:11:35 -0400 Subject: [PATCH 048/445] Finished getting output events working. --- source/core/MABE.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index e457240b..6d6205f9 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -638,7 +638,7 @@ namespace mabe { // 'output' will collect data and write it to a file. files.SetIODefaultFile(); // String manager should use files. std::function output_fun = - [this](const std::string & filename, const std::string & collection, const std::string & format) { + [this](const std::string & filename, const std::string & collection, std::string format) { emp::vector funs; ///< Functions to call each update. const bool file_exists = files.Has(filename); ///< Determine if file is already setup. std::iostream & file = files.GetIOStream(filename); ///< File to write to. @@ -647,6 +647,9 @@ namespace mabe { // If we need headers, set them up! if (!file_exists) { + // Identify the contents of each column. + emp::vector cols = emp::slice(format, ','); + // Print the headers into the file. file << "#update"; for (size_t i = 0; i < cols.size(); i++) { @@ -669,8 +672,17 @@ namespace mabe { } // Insert the new entry into the cache and update the iterator. - fun_it = file_fun_cache.insert({output, funs}).first; + fun_it = file_fun_cache.insert({format, funs}).first; } + + // And, finally, print the data! + Collection target_collect = FromString(collection); + file << GetUpdate(); + for (auto & fun : funs) { + file << ", " << fun(target_collect); + } + file << std::endl; + return 0; }; config.AddFunction("output", output_fun, "Print out the provided variable."); From 0068b4698a639da8952b99b3d5d467d1d6e59b9c Mon Sep 17 00:00:00 2001 From: AriaKillebrewBruehl Date: Thu, 29 Jul 2021 11:18:20 -0700 Subject: [PATCH 049/445] Add example, box for GitHub link, and next steps --- docs/index.rst | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index a42dcd49..bcc3f50c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,8 +10,10 @@ Welcome to MABE2's documentation! ================================= .. image:: images/MABE.png :width: 600 - -`Visit MABE2 on github here `_. + +.. important:: + `Visit MABE2 on github here `_. + Modular Agent Based Evolver (MABE) is a software framework that allows users to easily build and customize software for evolutionary computation or artificial life. The resulting systems are useful for studying evolutionary dynamics, solving complex @@ -28,6 +30,35 @@ researchers. MABE’s primary goal is to **reduce the time between thinking up a new hypothesis and generating results**. + +Example MABE Project +******************** + +Imagine you are a computer scientist studying the performance of evolved neural networks on certain tasks: +perhaps you're interested in how these networks evolve on classification tasks vs. on predictive tasks. +Without MABE, your process might look something like this: + +1. Find an implementation of neural networks that suits your purposes, and is written in your favorite language, or write one from scratch +2. Write or find a selection scheme for your evolutionary process +3. Write or find a fitness function or fitness task for your evolutionary process +4. Make sure these elements incorporate together in order to generate an instance of your experiment +5. Write a script to randomly seed your experiment and generate multiple replicates of your results +6. Look at your data + +Each of these steps has, of course, multiple sub-steps, and could take a very long time. In MABE, however, +your experiment flow would look more like this: + +1. Edit a ``.gen`` file to choose which of MABE's pre-built neural nets, selection schemes, and/or tasks you want to use for your experiment. +2. Create the corresponding ``.mabe`` file. +3. Run ``./MABE -f settings/.mabe``. +4. View your data in the ``output.csv`` file! + + +If you are still not convinced MABE is useful for you, consider reading *add link* before making up your mind. + +Design +****** + MABE's design assumes that there are common elements (*e.g., fitness functions, selection schemes, populations, etc.*) in many evolutionary computation (EC) research projects. Leveraging these similarities allows for efficient reuse of common components while removing communication road blocks and simplifying the comparison, replication, and integration of @@ -39,7 +70,9 @@ reusable, and interoperable components, while maintaining the flexibility needed MABE also allows for the use of standardized interfaces for `non-common elements `_ so these elements can be used interchangeably. - +Next Steps +---------- +Ready to use MABE? Learn how to `install MABE `_, then `write and run your own experiments `_! .. toctree:: :hidden: From 1fd9e5e1c2d4f06123ad36ee388a64cbebdb12f1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 28 Jul 2021 08:55:21 -0400 Subject: [PATCH 050/445] Updated comments throughout Collection. --- source/core/Collection.hpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index f9df45a2..6fa63cfb 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -10,8 +10,13 @@ * to represent and manipulate groups of organisms (by their position). Organisms can be * added individully or as whole populations. * - * Internally, a Collection is represented by a map with keys of Population pointers and - * values of a BitVector indicating the positions in those populations that are included. + * Internally, a Collection is represented by a map; keys are pointers to the included Populations + * and values are a PopInfo class (a flag for "do we included the whole population" and a + * BitVector indicating the positions that are included if not the whole population). + * + * A CollectionIterator will track the current population being iterated through, and the position + * currently indicated. When an iterator reached the end, it's population pointer is set to + * nullptr. */ #ifndef MABE_COLLECTION_H @@ -169,6 +174,8 @@ namespace mabe { } }; + // Link each populaiton in the collection (by its pointer) to info about which organisms + // are included. using pos_map_t = std::map; pos_map_t pos_map; @@ -205,7 +212,7 @@ namespace mabe { using iterator_t = CollectionIterator; using const_iterator_t = ConstCollectionIterator; - /// Calculation the total number of positions represented in this collection. + /// Calculate the total number of positions represented in this collection. size_t GetSize() const noexcept override { size_t count = 0; for (auto [pop_ptr, pop_info] : pos_map) { @@ -474,7 +481,9 @@ namespace mabe { CollectionIterator_Interface ::CollectionIterator_Interface(emp::Ptr _col, size_t _pos) : base_t(_col->GetFirstPop(), _pos), collection_ptr(_col) - { + { + // Make sure that this iterator is actually valid. If not, move to next position. + if (base_t::IsValid() == false) IncPosition(); } /// Constructor where you can optionally supply population pointer and position. From 891c59a2eefc3186b89be8736cd4d60e6d052faf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 29 Jul 2021 23:00:50 -0400 Subject: [PATCH 051/445] Extended assert in OrgIterator to help track down crash. --- source/core/OrgIterator.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index b850aa85..092934ce 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -203,7 +203,7 @@ namespace mabe { /// Return a reference to the organism pointed to by this iterator; may advance iterator. ORG_T & operator*() { - emp_assert(IsValid()); // Make sure we're not outside of the vector. + emp_assert(IsValid(), pop_ptr, pos, PopSize()); // Make sure we're not outside of the vector. return *(OrgPtr()); } From 21da862d4cabe9536fe0362b931bc5839f569112 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 30 Jul 2021 00:09:59 -0400 Subject: [PATCH 052/445] Cleaned up FileOutput to collect data only from living organisms. --- source/interface/FileOutput.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/interface/FileOutput.hpp b/source/interface/FileOutput.hpp index 820a182d..00f3044c 100644 --- a/source/interface/FileOutput.hpp +++ b/source/interface/FileOutput.hpp @@ -78,8 +78,9 @@ namespace mabe { // If so, print! file << ud; + mabe::Collection cur_collect = target_collect.GetAlive(); for (auto & fun : funs) { - file << ", " << fun(target_collect); + file << ", " << fun(cur_collect); } file << std::endl; } From a208bae234002688d8a315ea8f9b4cd7c1145bd2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 31 Jul 2021 09:57:37 -0400 Subject: [PATCH 053/445] Fixed typo in EvalMancala so that it compiles properly. --- source/evaluate/games/EvalMancala.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 09fde582..3563dc74 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -40,8 +40,8 @@ namespace mabe { public: EvalMancala(mabe::MABE & control, - const std::string & name="EvalMancala", - const std::string & desc="Evaluate organisms by having them play Mancala.", + const std::string & name="EvalMancala", + const std::string & desc="Evaluate organisms by having them play Mancala.") : Module(control, name, desc) , target_collect(control.GetPopulation(0)) { From c7eaeaf478fcb6cfe800735cb283f9b88cf9f9c0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 1 Aug 2021 23:50:32 -0400 Subject: [PATCH 054/445] Added a MakeValid() member function on Collection iterators. --- source/core/Collection.hpp | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 6fa63cfb..793277d3 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -193,6 +193,41 @@ namespace mabe { return pos_map.find(pop_ptr.ConstCast()); } + // Take an iterator that may be in an illegal state and restore it to a legal state. + // Return whether it was originally valid. + template + bool MakeValid(T & it) { + const_pop_ptr_t cur_pop = it.PopPtr(); // Collect the current population pointer. + if (cur_pop == nullptr) return true; // This is an "end" iterator. + + auto info_it = GetInfoIT(cur_pop); // Look up this population's info. + + // If we have an invalid population, jump to end and signal that it was invalid. + if (info_it != pos_map.end()) { + it.Set(nullptr, 0); + return false; + } + + // We now know we have a valid population. Check if we are at a valid position. + if (info_it->second.Has(it.Pos())) return true; + + // Must move to a valid position, either in this population, another populaton, or end. + // Find the position of the next organism from this population + size_t next_pos = info_it->second.GetNextPos(it.Pos()); + + // If this position is good, set it! + if (next_pos < cur_pop->GetSize()) { it.SetPos(next_pos); return false; } + + // Otherwise advance to the first position in the next non-empty population, + ++info_it; + while (info_it != pos_map.end() && info_it->second.GetSize(it.PopPtr()) == 0) ++info_it; + + if (info_it == pos_map.end()) it.Set(nullptr, 0); // No more populations! + else it.Set(info_it->first, info_it->second.GetFirstPos()); // First position in next pop. + + return false; + } + public: Collection() = default; Collection(const Collection &) = default; @@ -299,7 +334,11 @@ namespace mabe { void IncPosition(T & it) const { const_pop_ptr_t cur_pop = it.PopPtr(); auto info_it = GetInfoIT(cur_pop); - emp_assert(info_it != pos_map.end()); + + // Make sure that the current population was found! This check will fail if either + // we are already at the end OR it is pointed to a populaiton not in the collection. + emp_assert(info_it != pos_map.end(), + "Invalid start position before collection iterator is incremented"); // Find the position of the next organism from this population size_t next_pos = info_it->second.GetNextPos(it.Pos()); @@ -310,10 +349,12 @@ namespace mabe { // Otherwise advance to the next population, else { ++info_it; + while (info_it != pos_map.end() && info_it->second.GetSize(it.PopPtr()) == 0) ++info_it; if (info_it == pos_map.end()) it.Set(nullptr, 0); // No more populations! else it.Set(info_it->first, info_it->second.GetFirstPos()); } } + template void DecPosition(T & /* it */) const { emp_error("DecPosition() not yet implemented for CollectionIterator."); From aaa30c71c6c4756e96a07c96a46a7d1664b6deeb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 2 Aug 2021 17:58:56 -0400 Subject: [PATCH 055/445] Setup a ConstPopPtr() function for OrgIterators. --- source/core/OrgIterator.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index 092934ce..39daaaec 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -96,6 +96,8 @@ namespace mabe { emp_assert(pop_ptr.template DynamicCast()); return pop_ptr.template Cast(); } + emp::Ptr ConstPopPtr() const noexcept { return PopPtr(); } + Population & Pop() noexcept { emp_assert(PopPtr() != nullptr); return *PopPtr(); From 973e6eb5dc5ca8adda806a4cc79b5d03159f4c42 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 3 Aug 2021 09:29:02 -0400 Subject: [PATCH 056/445] Properly fixed collections to skip over empty populations. --- source/core/Collection.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 793277d3..d34b6ee1 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -209,7 +209,7 @@ namespace mabe { } // We now know we have a valid population. Check if we are at a valid position. - if (info_it->second.Has(it.Pos())) return true; + if (info_it->second.pos_set.Has(it.Pos())) return true; // Must move to a valid position, either in this population, another populaton, or end. // Find the position of the next organism from this population @@ -218,9 +218,8 @@ namespace mabe { // If this position is good, set it! if (next_pos < cur_pop->GetSize()) { it.SetPos(next_pos); return false; } - // Otherwise advance to the first position in the next non-empty population, - ++info_it; - while (info_it != pos_map.end() && info_it->second.GetSize(it.PopPtr()) == 0) ++info_it; + // Otherwise advance to the first position in the next non-empty population OR end iterator. + while (++info_it != pos_map.end() && info_it->second.GetSize(info_it->first) == 0); if (info_it == pos_map.end()) it.Set(nullptr, 0); // No more populations! else it.Set(info_it->first, info_it->second.GetFirstPos()); // First position in next pop. @@ -348,8 +347,8 @@ namespace mabe { // Otherwise advance to the next population, else { - ++info_it; - while (info_it != pos_map.end() && info_it->second.GetSize(it.PopPtr()) == 0) ++info_it; + // Advance population pointer; If we are at a population and it is empty, keep advancing! + while (++info_it != pos_map.end() && info_it->second.GetSize(info_it->first) == 0); if (info_it == pos_map.end()) it.Set(nullptr, 0); // No more populations! else it.Set(info_it->first, info_it->second.GetFirstPos()); } From 9188eedf730856fdf4aa4ace6abbf9a9452b3647 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 4 Aug 2021 10:18:18 -0400 Subject: [PATCH 057/445] Setup output from a non-existant organism to always be 'Nan' --- source/core/data_collect.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 87dd87d6..0820fb72 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -27,6 +27,7 @@ namespace emp { template auto BuildCollectFun_Index(FUN_T get_fun, const size_t index) { return [get_fun,index](const CONTAIN_T & container) { + if (container.size() <= index) return "Nan"s; return emp::to_string( get_fun( container.At(index) ) ); }; } From 4beff6f8a5c67607b28c40abca487d3e081299a5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 5 Aug 2021 13:48:36 -0400 Subject: [PATCH 058/445] Added an initial version of a generic factory module. --- source/core/FactoryModule.hpp | 106 ++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 source/core/FactoryModule.hpp diff --git a/source/core/FactoryModule.hpp b/source/core/FactoryModule.hpp new file mode 100644 index 00000000..a3b171b9 --- /dev/null +++ b/source/core/FactoryModule.hpp @@ -0,0 +1,106 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file FactoryModule.hpp + * @brief Base module to manage a selection of objects that share a common configiguration. + */ + +#ifndef MABE_FACTORY_MODULE_H +#define MABE_FACTORY_MODULE_H + +#include "emp/meta/TypeID.hpp" + +#include "../config/Config.hpp" + +#include "MABE.hpp" +#include "Module.hpp" + +namespace mabe { + + class MABE; + + /// @param OBJ_T the object type being managed by the factory. + /// @param BASE_T the base object category being mnagaed by the factory. + template + class FactoryModule : public Module { + /// Allow factory products to access private shared data in their own manager only. + friend ProductTemplate; + + private: + /// Locate the specification for the data that we need to store in the factory module. + using data_t = typename OBJ_T::ModuleData; + + /// Shared data across all objects that use this factory. + data_t data; + + public: + FactoryModule(MABE & in_control, const std::string & in_name, const std::string & in_desc="") + : Module(in_control, in_name, in_desc) + { + SetManageMod(); // @CAO should specify what type of object is managed. + obj_prototype = emp::NewPtr(*this); + } + virtual ~FactoryModule() { obj_prototype.Delete(); } + + /// Save the object type that uses this manager. + using obj_t = OBJ_T; + + /// Also get the TypeID for this object for more run-time type management. + emp::TypeID GetObjType() const override { return emp::GetTypeID(); } + + /// Create a clone of the provided object; default to using copy constructor. + emp::Ptr Clone(const BASE_T & obj) override { + return emp::NewPtr( (const obj_t &) obj ); + } + + /// Create a random object from scratch. Default to using the obj_prototype object. + emp::Ptr Make() override { + auto obj_ptr = obj_prototype->Clone(); + return obj_ptr; + } + + /// Create a random object from scratch. Default to using the obj_prototype object + /// and then randomize if a random number generator is provided. + emp::Ptr Make(emp::Random & random) override { + auto obj_ptr = obj_prototype->Clone(); + obj_ptr->Initialize(random); + return obj_ptr; + } + + + void SetupModule() override { + obj_prototype->SetupModule(); + } + + void SetupDataMap(emp::DataMap & in_dm) override { + obj_prototype->SetDataMap(in_dm); + } + + void SetupConfig() override { + obj_prototype->SetupConfig(); + } + + }; + + /// Build a class that will automatically register modules when created (globally) + template + struct FactoryModuleRegistrar { + FactoryModuleRegistrar(const std::string & type_name, const std::string & desc) { + ModuleInfo new_info; + new_info.name = type_name; + new_info.desc = desc; + new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { + return control.AddModule(name, desc); + }; + GetModuleInfo().insert(new_info); + } + }; + +#define MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) \ + mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + +} + +#endif From 66fe466d86d5117eb98016001c0d6c9cf1b15913 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 6 Aug 2021 08:53:04 -0400 Subject: [PATCH 059/445] Added some backward compatability for OrganismManager in FactoryModule. --- source/core/FactoryModule.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/core/FactoryModule.hpp b/source/core/FactoryModule.hpp index a3b171b9..0236b2df 100644 --- a/source/core/FactoryModule.hpp +++ b/source/core/FactoryModule.hpp @@ -98,9 +98,16 @@ namespace mabe { } }; -#define MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) \ + /// MACRO for quickly adding new factory modules. + #define MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) \ mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + // Setup backward compatability with OrganismManager. + template + using OrganismManager = FactoryModule; + + #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) + } #endif From f1aa2b6fba8cdfa0127e7b59ac128ed50a15ff07 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 7 Aug 2021 11:35:54 -0400 Subject: [PATCH 060/445] Added more backward compatability in FactoryModule for OrganismTemplate base class --- source/core/FactoryModule.hpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/source/core/FactoryModule.hpp b/source/core/FactoryModule.hpp index 0236b2df..3fe25752 100644 --- a/source/core/FactoryModule.hpp +++ b/source/core/FactoryModule.hpp @@ -98,6 +98,29 @@ namespace mabe { } }; + /// Below is a base class for factory products that uses "curiously recursive templates" to fill + /// out default functionality for when you know the derived type. + template + class FactoryProductTemplate : public BASE_T { + public: + FactoryProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } + + using obj_t = OBJ_T; + using manager_t = FactoryModule; + + /// Get the manager for this type of organism. + manager_t & GetManager() { + return (manager_t &) BASE_T::GetManager(); + } + const manager_t & GetManager() const { + return (const manager_t &) BASE_T::GetManager(); + } + + auto & SharedData() { return GetManager().data; } + const auto & SharedData() const { return GetManager().data; } + }; + + /// MACRO for quickly adding new factory modules. #define MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) \ mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) @@ -105,6 +128,8 @@ namespace mabe { // Setup backward compatability with OrganismManager. template using OrganismManager = FactoryModule; + template + using OrganismTemplate = FactoryProductTemplate; #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) From c53f18f6144fa6ff5a65257cc75e7f6609df3538 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 08:39:55 -0400 Subject: [PATCH 061/445] Rename gen file --- .../first_steps/{000_write_gen_file.rst => 02_write_gen_file.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/first_steps/{000_write_gen_file.rst => 02_write_gen_file.rst} (100%) diff --git a/docs/first_steps/000_write_gen_file.rst b/docs/first_steps/02_write_gen_file.rst similarity index 100% rename from docs/first_steps/000_write_gen_file.rst rename to docs/first_steps/02_write_gen_file.rst From f54b7a8c01224627720c42fde8c2899d8545fbb1 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 08:44:07 -0400 Subject: [PATCH 062/445] Rename traitinfo --- docs/organisms/{traitinfo.rst => 00_traitinfo.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/organisms/{traitinfo.rst => 00_traitinfo.rst} (100%) diff --git a/docs/organisms/traitinfo.rst b/docs/organisms/00_traitinfo.rst similarity index 100% rename from docs/organisms/traitinfo.rst rename to docs/organisms/00_traitinfo.rst From 270b3c4f198486a1c32d3bfdc9b6068f798a87f2 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:04:25 -0400 Subject: [PATCH 063/445] Create organism and evaluator landing pages --- docs/evaluate/00_eval_overview.rst | 6 ++++++ docs/modules/00_module_overview.rst | 4 ++-- docs/organisms/00_organism_overview.rst | 0 docs/organisms/{00_traitinfo.rst => 01_traitinfo.rst} | 0 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 docs/evaluate/00_eval_overview.rst create mode 100644 docs/organisms/00_organism_overview.rst rename docs/organisms/{00_traitinfo.rst => 01_traitinfo.rst} (100%) diff --git a/docs/evaluate/00_eval_overview.rst b/docs/evaluate/00_eval_overview.rst new file mode 100644 index 00000000..c1bcacde --- /dev/null +++ b/docs/evaluate/00_eval_overview.rst @@ -0,0 +1,6 @@ + +====================== +What is an Evaluator? +====================== + +Landing page for evaluators; under construction. \ No newline at end of file diff --git a/docs/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index badfed74..c05054ec 100644 --- a/docs/modules/00_module_overview.rst +++ b/docs/modules/00_module_overview.rst @@ -6,7 +6,7 @@ MABE2 experiments are created by combining various types of modules. **Modules c cannot be held constant** (i.e., requiring unique internal implementation). MABE2 implements seven types of modules: -`organism modules <01_module_types.html>`_, `evaluation modules <01_module_types.html>`_, `selection modules <01_module_types.html>`_, +`organism modules <../organisms/00_organism_overview.html>`_, `evaluation modules <../evaluate/00_eval_overview>.html>`_, `selection modules <01_module_types.html>`_, `placement modules <01_module_types.html>`_, `schema modules <01_module_types.html>`_, `analysis modules <01_module_types.html>`_, and `interface modules <01_module_types.html>`_. Modules of a given type are interchangeable, so switching from one brain type to another is as simple as changing a brain type parameter. This feature allows @@ -25,7 +25,7 @@ agents had a single genome that was used to generate both the brain and sensor p genetic interactions), or the user could configure MABE so that the agent had two genomes (in which case the brain and sensor placement would be genetically independent). -The dynamics of evolution are defined by a combination of an `evaluation module <../evaluate/EvalNK.html>`_ , +The dynamics of evolution are defined by a combination of an `evaluation module <../evaluate/00_eval_overview.html>`_ , to determine how their phenotype will be assessed, and a **link to selection page** to determine how that phenotype will influence and organism's ability to move on to the next generation. diff --git a/docs/organisms/00_organism_overview.rst b/docs/organisms/00_organism_overview.rst new file mode 100644 index 00000000..e69de29b diff --git a/docs/organisms/00_traitinfo.rst b/docs/organisms/01_traitinfo.rst similarity index 100% rename from docs/organisms/00_traitinfo.rst rename to docs/organisms/01_traitinfo.rst From cadb462ee3ca34486837dec4431ed4fd7f58947a Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:05:59 -0400 Subject: [PATCH 064/445] Rearrange toctree --- docs/index.rst | 10 ++++------ docs/organisms/00_organism_overview.rst | 5 +++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 7ad75766..bcb4b6ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -90,18 +90,16 @@ Ready to use MABE? Learn how to `install MABE source/* modules/* - .. toctree:: :hidden: - :caption: Evaluators + :caption: Organisms :glob: - evaluate/* - + organisms/* .. toctree:: :hidden: - :caption: Organisms + :caption: Evaluators :glob: - organisms/* + evaluate/* diff --git a/docs/organisms/00_organism_overview.rst b/docs/organisms/00_organism_overview.rst index e69de29b..5a2df158 100644 --- a/docs/organisms/00_organism_overview.rst +++ b/docs/organisms/00_organism_overview.rst @@ -0,0 +1,5 @@ +====================== +What is an Organism? +====================== + +Landing page for organisms; under construction. \ No newline at end of file From f093eead3990f42d76a2b7572f13ea97b1f6e119 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:10:36 -0400 Subject: [PATCH 065/445] Internal sphinx link --- docs/modules/00_module_overview.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index c05054ec..4613f653 100644 --- a/docs/modules/00_module_overview.rst +++ b/docs/modules/00_module_overview.rst @@ -5,8 +5,7 @@ What is a Module? MABE2 experiments are created by combining various types of modules. **Modules contain the aspects of an experiment that cannot be held constant** (i.e., requiring unique internal implementation). -MABE2 implements seven types of modules: -`organism modules <../organisms/00_organism_overview.html>`_, `evaluation modules <../evaluate/00_eval_overview>.html>`_, `selection modules <01_module_types.html>`_, +MABE2 implements seven types of modules: :ref:`organism modules `, `evaluation modules <../evaluate/00_eval_overview>.html>`_, `selection modules <01_module_types.html>`_, `placement modules <01_module_types.html>`_, `schema modules <01_module_types.html>`_, `analysis modules <01_module_types.html>`_, and `interface modules <01_module_types.html>`_. Modules of a given type are interchangeable, so switching from one brain type to another is as simple as changing a brain type parameter. This feature allows From 4842880ec9c3f07d8bec0c0aaf8d3581922b073f Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:43:05 -0400 Subject: [PATCH 066/445] Referencing docs --- docs/conf.py | 5 ++++- docs/modules/00_module_overview.rst | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 19cb959d..44384776 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,6 @@ # ones. extensions = [ 'myst_parser', - 'sphinx.ext.autosectionlabel' ] # Add any paths that contain templates here, relative to this directory. @@ -52,3 +51,7 @@ # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] + +# -- Options for Autosection labeling --------------------------------------- + +autosectionlabel_prefix_document = True \ No newline at end of file diff --git a/docs/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index 4613f653..6f3ec985 100644 --- a/docs/modules/00_module_overview.rst +++ b/docs/modules/00_module_overview.rst @@ -5,7 +5,7 @@ What is a Module? MABE2 experiments are created by combining various types of modules. **Modules contain the aspects of an experiment that cannot be held constant** (i.e., requiring unique internal implementation). -MABE2 implements seven types of modules: :ref:`organism modules `, `evaluation modules <../evaluate/00_eval_overview>.html>`_, `selection modules <01_module_types.html>`_, +MABE2 implements seven types of modules: :ref:`organisms`, evaluators, `selection modules <01_module_types.html>`_, `placement modules <01_module_types.html>`_, `schema modules <01_module_types.html>`_, `analysis modules <01_module_types.html>`_, and `interface modules <01_module_types.html>`_. Modules of a given type are interchangeable, so switching from one brain type to another is as simple as changing a brain type parameter. This feature allows From cd834c0865e0b75b7c9bef328911e02688e301c3 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:50:43 -0400 Subject: [PATCH 067/445] Anonymize links --- docs/first_steps/00_installation.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index cbe653b6..b44da722 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -7,14 +7,14 @@ Installing Git Before attempting to install MABE2 **you must have Git installed so that you can use the MABE2 software on your local computer**. For information on installing -Git on any device, see `this guide `_. -For more information about GitHub, checkout the `GitHub Guides `_. +Git on any device, see `this guide `__. +For more information about GitHub, checkout the `GitHub Guides `__. Downloading MABE2 from GitHub ============================== The first step for installation on any machine is to visit MABE2 on -`GitHub `_. The simplest way to +`GitHub `__. The simplest way to download source code form GitHub is to download the entire repository. MABE2 can be downloaded :ref:`as a zip file`, or :ref:`via the command line`. @@ -43,7 +43,7 @@ clip board. :width: 600 *Note*: You can also use SHH keys to clone and download a GitHub repository. -For more information about SSH keys, checkout `this guide `_. +For more information about SSH keys, checkout `this guide `__. Once you have the URL copied to your clipboard, open your command line. @@ -70,7 +70,7 @@ in your terminal: .. -If you have issues cloning the repository, checkout `this guide `_. +If you have issues cloning the repository, checkout `this guide `__. Necessary compilers =================== @@ -117,15 +117,15 @@ Enter the following into your terminal to install gcc; .. -For MacOS, you will need `Apple's Command Line Tools for XCode `_. -To install a recent release of gcc, you can use `Homebrew `_ with -`this formula `_. +For MacOS, you will need `Apple's Command Line Tools for XCode `__. +To install a recent release of gcc, you can use `Homebrew `__ with +`this formula `__. Windows ------- The Windows Subsystem for Linux (WSL) makes it easy to run a GNU/Linux environment -directly on Windows. For information on installing WSL, checkout `this guide `_. +directly on Windows. For information on installing WSL, checkout `this guide `__. Once WSL is installed you can follow the same instructions as above. Next Steps From a202cfce1794c2122e3e19ff8146a5a65d8feefe Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:52:40 -0400 Subject: [PATCH 068/445] Remove cpp specification --- docs/first_steps/01_quickstart.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/first_steps/01_quickstart.rst b/docs/first_steps/01_quickstart.rst index e7132a4c..686125d9 100644 --- a/docs/first_steps/01_quickstart.rst +++ b/docs/first_steps/01_quickstart.rst @@ -12,7 +12,7 @@ have been set up in the different modules connected to the ``.mabe`` file. To generate your ``.mabe`` file, first you will want to make sure that you have run the ``make`` command since your last updates. To do so, navigate to the ``build`` directory and set up a clean run. From the ``MABE2`` directory, run the following commands: -.. code-block:: cpp +.. code-block:: cd build make clean ; make @@ -22,7 +22,7 @@ If there are any errors that pop up, now is the time to fix them! Next, you will navigate to the ``settings`` directory inside of ``build``, and check to see you have an appropriate ``.gen`` file. To do so, first make sure you are in the ``build`` directory, and then list all of the files in the settings to make sure the ``.gen`` file is there. -.. code-block:: cpp +.. code-block:: cd settings ls @@ -34,7 +34,7 @@ follow the directions to `write your .gen file <000_write_gen_file.html>`_. Now we're ready to create our ``.mabe`` file! To do so, we'll navigate back up to ``build`` and then create the ``.mabe`` file. You'll run the following commands to do so: -.. code-block:: cpp +.. code-block:: cd .. ./MABE -f settings/.gen -g settings/.mabe @@ -43,7 +43,7 @@ following commands to do so: This will generate a ``.mabe`` file named ``.mabe`` with the specifications from the ``.gen`` file. You can check to see that it exists by going into the ``settings`` directory and checking that it's there. To do so, run the following: -.. code-block:: cpp +.. code-block:: cd settings ls @@ -55,14 +55,14 @@ Summary Step 1: In the ``build`` directory, run the following: -.. code-block:: cpp +.. code-block:: make clean ; make Step 2: Then run these commands to make sure your ``.gen`` file exists. -.. code-block:: cpp +.. code-block:: cd settings ls @@ -72,7 +72,7 @@ to `write your .gen file <000_write_gen_file.html>`_. Step 3: Create your ``.mabe`` file and check to make sure it's created by running the following: -.. code-block:: cpp +.. code-block:: cd .. ./MABE -f settings/.gen -g settings/.mabe @@ -85,7 +85,7 @@ Running the ``.mabe`` File To run your ``.mabe`` file, navigate to the ``build`` directory and run your ``.mabe`` file. To do so, start in the ``MABE2`` folder and run the following commands: -.. code-block:: cpp +.. code-block:: cd build ./MABE -f settings/.mabe @@ -106,7 +106,8 @@ module, located in the `Modules Page <../modules/00_module_overview.html>`_ . To run your modified ``.mabe`` file, first make sure you have saved your file, then simply run the following command from the ``build`` directory: -.. code-block:: cpp +.. code-block:: + ./MABE -f settings/.mabe @@ -117,7 +118,7 @@ Viewing and Saving Your Data The data you have collected has been saved in a CSV file called ``output.csv``, which is located in the ``build`` directory. From the main ``MABE2`` folder, you can find this file by running the following commands: -.. code-block:: cpp +.. code-block:: cd build ls @@ -143,7 +144,7 @@ To do so, first open the ``.mabe`` file in question in your preferred text edito Within the ``.mabe`` file, there is a section called ``FileOutput``, which looks something like this: -.. code-block:: cpp +.. code-block:: FileOutput output { // Output collected data into a specified file. _active = 1; // Should we activate this module? (0=off, 1=on) @@ -159,7 +160,7 @@ You can modify this name to be something new, and when you run the ``.mabe`` fil with that name will appear in the same directory as the original ``output.csv`` file. Below is an example of a new CSV filename inserted called ``NEW_FILE_NAME``. -.. code-block:: cpp +.. code-block:: FileOutput output { // Output collected data into a specified file. _active = 1; // Should we activate this module? (0=off, 1=on) From b9e5e61597deaa06ae224d155d7052c49f6e3699 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:53:22 -0400 Subject: [PATCH 069/445] Remove cpp highlighting from install guide --- docs/first_steps/00_installation.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index b44da722..bcced94a 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -50,7 +50,7 @@ Once you have the URL copied to your clipboard, open your command line. Change your current working directory to the location where you want the cloned directory. Type `git clone`, then paste the URL into your command line. -.. code-block:: cpp +.. code-block:: $ git clone https://github.com/mercere99/MABE2.git @@ -59,7 +59,7 @@ Type `git clone`, then paste the URL into your command line. Then press enter to create your local clone! If all goes well, you will see the following in your terminal: -.. code-block:: cpp +.. code-block:: $ git clone https://github.com/mercere99/MABE2.git > Cloning into `MABE2`... @@ -87,7 +87,7 @@ Mac and Linux For Unix (e.g. Linux and MacOS) the most commonly used compilers are GCC and Clang. You can check if you have GCC or Clang installed by opening your terminal and entering: -.. code-block:: cpp +.. code-block:: $ which gcc $ which clang @@ -97,7 +97,7 @@ You can check if you have GCC or Clang installed by opening your terminal and en If a path is returned then you have gcc or clang, respectively. To check the version enter: -.. code-block:: cpp +.. code-block:: $ gcc --version $ clang --version @@ -111,7 +111,7 @@ If a path is not returned then you must install gcc. For Linux, your package manager (e.g. yum, apt, etc) will allow you to do this. Enter the following into your terminal to install gcc; -.. code-block:: cpp +.. code-block:: $ apt-get install gcc-8 From 0359170ce10edfe9a3f06205a01fbf7f4a92e8bc Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:54:41 -0400 Subject: [PATCH 070/445] Sphinx reference organisms --- docs/modules/00_module_overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index 6f3ec985..cab3dd78 100644 --- a/docs/modules/00_module_overview.rst +++ b/docs/modules/00_module_overview.rst @@ -9,7 +9,7 @@ MABE2 implements seven types of modules: :ref:`organisms`, evaluators, `selectio `placement modules <01_module_types.html>`_, `schema modules <01_module_types.html>`_, `analysis modules <01_module_types.html>`_, and `interface modules <01_module_types.html>`_. Modules of a given type are interchangeable, so switching from one brain type to another is as simple as changing a brain type parameter. This feature allows -users to focus their efforts specific aspects of their projects by only developing or modifying the modules of interest +users to focus their efforts specific aspects of their projecrts by only developing or modifying the modules of interest to them, by reusing existing modules when possible, and by not requiring detailed understanding of the entirety of MABE2. From 52dcdc6eef6e00e2ede11e4cf689fe2cce1b4372 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:55:06 -0400 Subject: [PATCH 071/445] Add reference label --- docs/organisms/00_organism_overview.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/organisms/00_organism_overview.rst b/docs/organisms/00_organism_overview.rst index 5a2df158..b9ecf125 100644 --- a/docs/organisms/00_organism_overview.rst +++ b/docs/organisms/00_organism_overview.rst @@ -1,3 +1,5 @@ +.. _organisms: + ====================== What is an Organism? ====================== From 9c4c95b49609cf9c7d260732b99dd8fdfc9eba42 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:56:06 -0400 Subject: [PATCH 072/445] Fix title overline --- docs/source/00_source_overview.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/00_source_overview.rst b/docs/source/00_source_overview.rst index c8a51023..b6aaa4a6 100644 --- a/docs/source/00_source_overview.rst +++ b/docs/source/00_source_overview.rst @@ -1,6 +1,6 @@ -========== +=================== Overview of Source -========== +=================== The files in the ``source`` directory an be divided into four different groups: Modules, Tools for Analysis, Core and Dependencies. Below, we'll get into what each category means, and how items are sorted into each individual category. From 59addc313ebcf71fd3d73c8371ca2517f241260c Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 09:57:02 -0400 Subject: [PATCH 073/445] Remove empty html static path --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 44384776..3ec69634 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +#html_static_path = ['_static'] # -- Options for Autosection labeling --------------------------------------- From 5fe270b091c762b28d2971122d4419ac7a021e6d Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 10:04:00 -0400 Subject: [PATCH 074/445] Add alt text to mabe logo --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index bcb4b6ac..f6d495d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Welcome to MABE2's documentation! ================================= .. image:: images/MABE.png + :alt: MABE logo :width: 600 .. important:: From 5aea63de75183e51268fe4c162b150b0b211f3a0 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 10:06:00 -0400 Subject: [PATCH 075/445] Add alt text to git examples --- docs/first_steps/00_installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index bcced94a..00652871 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -28,6 +28,7 @@ right hand corner. Click this button, then click *DownLoad Zip* to save the full zip of everything in MABE2's master branch to your computer. .. image:: ../images/GitHub_Zip.png + :alt: Github zip file download example :width: 600 .. _url: @@ -40,6 +41,7 @@ right hand corner. Click this button, then click the *clipboard icon* to copy th clip board. .. image:: ../images/GitHub_url.png + :alt: Github HTML file download example :width: 600 *Note*: You can also use SHH keys to clone and download a GitHub repository. From 3c3201db31a56f344b8a628833cd8c501837ee77 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 11 Aug 2021 10:06:16 -0400 Subject: [PATCH 076/445] Correct monospace text --- docs/first_steps/00_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index 00652871..63e1ca1c 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -50,7 +50,7 @@ For more information about SSH keys, checkout `this guide Date: Sun, 8 Aug 2021 16:40:15 -0400 Subject: [PATCH 077/445] Cleaned up FactoryModule so it can compile. --- source/core/FactoryModule.hpp | 66 +++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/source/core/FactoryModule.hpp b/source/core/FactoryModule.hpp index 3fe25752..dde15241 100644 --- a/source/core/FactoryModule.hpp +++ b/source/core/FactoryModule.hpp @@ -19,22 +19,50 @@ namespace mabe { + // Pre-declarations... class MABE; + template class FactoryModule; + + /// Base class for factory products that uses "curiously recursive templates" to fill + /// out default functionality for when you know the derived type. + template + class ProductTemplate : public BASE_T { + public: + ProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } + + using obj_t = OBJ_T; + using manager_t = FactoryModule; + + /// Get the manager for this type of organism. + manager_t & GetManager() { + return (manager_t &) BASE_T::GetManager(); + } + const manager_t & GetManager() const { + return (const manager_t &) BASE_T::GetManager(); + } + + auto & SharedData() { return GetManager().data; } + const auto & SharedData() const { return GetManager().data; } + }; + /// @param OBJ_T the object type being managed by the factory. /// @param BASE_T the base object category being mnagaed by the factory. template class FactoryModule : public Module { /// Allow factory products to access private shared data in their own manager only. - friend ProductTemplate; + friend class ProductTemplate; private: - /// Locate the specification for the data that we need to store in the factory module. - using data_t = typename OBJ_T::ModuleData; + /// Locate the specification for the data that we need for management in the factory module. + using data_t = typename OBJ_T::ManagerData; /// Shared data across all objects that use this factory. data_t data; + /// Maintain a prototype for the objects being created. + emp::Ptr obj_prototype; + public: FactoryModule(MABE & in_control, const std::string & in_name, const std::string & in_desc="") : Module(in_control, in_name, in_desc) @@ -51,7 +79,7 @@ namespace mabe { emp::TypeID GetObjType() const override { return emp::GetTypeID(); } /// Create a clone of the provided object; default to using copy constructor. - emp::Ptr Clone(const BASE_T & obj) override { + emp::Ptr CloneObject(const BASE_T & obj) override { return emp::NewPtr( (const obj_t &) obj ); } @@ -98,40 +126,18 @@ namespace mabe { } }; - /// Below is a base class for factory products that uses "curiously recursive templates" to fill - /// out default functionality for when you know the derived type. - template - class FactoryProductTemplate : public BASE_T { - public: - FactoryProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } - - using obj_t = OBJ_T; - using manager_t = FactoryModule; - - /// Get the manager for this type of organism. - manager_t & GetManager() { - return (manager_t &) BASE_T::GetManager(); - } - const manager_t & GetManager() const { - return (const manager_t &) BASE_T::GetManager(); - } - - auto & SharedData() { return GetManager().data; } - const auto & SharedData() const { return GetManager().data; } - }; - /// MACRO for quickly adding new factory modules. - #define MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) \ - mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + #define MABE_REGISTER_FACTORY_MODULE(TYPE, BASE_TYPE, DESC) \ + mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) // Setup backward compatability with OrganismManager. template using OrganismManager = FactoryModule; template - using OrganismTemplate = FactoryProductTemplate; + using OrganismTemplate = ProductTemplate; - #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, DESC) + #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, mabe::Organism, DESC) } From 2435d3dca9ce86e0368f80d8b43546e2de3657df Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 8 Aug 2021 23:41:37 -0400 Subject: [PATCH 078/445] Updated module base so Clone, Make, and other functions are not organism specific. --- source/core/ModuleBase.hpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index deeb1984..fe0af42e 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -120,9 +120,6 @@ namespace mabe { /// Other variables that we want to hook on to this Module externally. emp::DataMap data_map; - /// If this module is an organism manager, maintain a prototype of the organisms it handles. - emp::Ptr org_prototype; ///< Base organism to copy. - public: // Setup each signal with a unique ID number enum SignalID { @@ -271,24 +268,24 @@ namespace mabe { virtual bool DoFindNeighbor_IsTriggered() = 0; // ---=== Specialty Functions for Organism Managers ===--- - virtual emp::TypeID GetOrgType() const { - emp_assert(false, "GetOrgType() must be overridden for either Organism or OrganismManager module."); + virtual emp::TypeID GetObjType() const { + emp_assert(false, "GetObjType() must be overridden for FactoryModule."); return emp::TypeID(); } - virtual emp::Ptr CloneOrganism(const Organism &) { - emp_assert(false, "CloneOrganism() must be overridden for either Organism or OrganismManager module."); + virtual emp::Ptr CloneObject(const Organism &) { + emp_assert(false, "CloneObject() must be overridden for FactoryModule."); return nullptr; } - virtual emp::Ptr CloneOrganism(const Organism &, emp::Random &) { - emp_assert(false, "CloneOrganism() must be overridden for either Organism or OrganismManager module."); + virtual emp::Ptr CloneObject(const Organism &, emp::Random &) { + emp_assert(false, "CloneObject() must be overridden for FactoryModule."); return nullptr; } - virtual emp::Ptr MakeOrganism() { - emp_assert(false, "MakeOrganism() must be overridden for either Organism or OrganismManager module."); + virtual emp::Ptr Make() { + emp_assert(false, "Make() must be overridden for FactoryModule."); return nullptr; } - virtual emp::Ptr MakeOrganism(emp::Random &) { - emp_assert(false, "MakeOrganism() must be overridden for either Organism or OrganismManager module."); + virtual emp::Ptr Make(emp::Random &) { + emp_assert(false, "Make() must be overridden for FactoryModule."); return nullptr; } From 5d0c39ad7f6e5775184b75f0dfa3923c725c04a1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 9 Aug 2021 00:18:08 -0400 Subject: [PATCH 079/445] Redirected OrganismManager.hpp to FactoryModule.hpp --- source/core/OrganismManager.hpp | 168 ++++++++++++++++---------------- 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index 8a21767b..c2037c4e 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -10,95 +10,97 @@ #ifndef MABE_ORGANISM_MANAGER_H #define MABE_ORGANISM_MANAGER_H -#include "emp/meta/TypeID.hpp" +#include "FactoryModule.hpp" -#include "../config/Config.hpp" +// #include "emp/meta/TypeID.hpp" -#include "MABE.hpp" -#include "Module.hpp" +// #include "../config/Config.hpp" -namespace mabe { +// #include "MABE.hpp" +// #include "Module.hpp" - class Organism; - class MABE; +// namespace mabe { - template - class OrganismManager : public Module { - /// Allow organisms to access private shared data in their own manager only. - friend OrganismTemplate; +// class Organism; +// class MABE; - private: - using data_t = typename ORG_T::ManagerData; +// template +// class OrganismManager : public Module { +// /// Allow organisms to access private shared data in their own manager only. +// friend OrganismTemplate; + +// private: +// using data_t = typename ORG_T::ManagerData; - /// Shared data for organisms that use this manager. - data_t data; - - public: - OrganismManager(MABE & in_control, const std::string & in_name, const std::string & in_desc="") - : Module(in_control, in_name, in_desc) - { - SetManageMod(); - org_prototype = emp::NewPtr(*this); - } - virtual ~OrganismManager() { org_prototype.Delete(); } - - /// Save the organism type that uses this manager. - using org_t = ORG_T; - - /// Also get the TypeID for this organism for more run-time type management. - emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } - - /// Create a clone of the provided organism; default to using copy constructor. - emp::Ptr CloneOrganism(const Organism & org) override { - return emp::NewPtr( (const org_t &) org ); - } - - /// Create a random organism from scratch. Default to using the org_prototype organism. - emp::Ptr MakeOrganism() override { - auto org_ptr = org_prototype->Clone(); - return org_ptr; - } - - /// Create a random organism from scratch. Default to using the org_prototype organism - /// and then randomize if a random number generator is provided. - emp::Ptr MakeOrganism(emp::Random & random) override { - auto org_ptr = org_prototype->Clone(); - org_ptr->Initialize(random); - return org_ptr; - } - - - void SetupModule() override { - org_prototype->SetupModule(); - } - - void SetupDataMap(emp::DataMap & in_dm) override { - org_prototype->SetDataMap(in_dm); - } - - void SetupConfig() override { - org_prototype->SetupConfig(); - } - - }; - - /// Build a class that will automatically register modules when created (globally) - template - struct OrgManagerRegistrar { - OrgManagerRegistrar(const std::string & type_name, const std::string & desc) { - ModuleInfo new_info; - new_info.name = type_name; - new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { - return control.AddModule(name, desc); - }; - GetModuleInfo().insert(new_info); - } - }; - -#define MABE_REGISTER_ORG_TYPE(TYPE, DESC) \ - mabe::OrgManagerRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) - -} +// /// Shared data for organisms that use this manager. +// data_t data; + +// public: +// OrganismManager(MABE & in_control, const std::string & in_name, const std::string & in_desc="") +// : Module(in_control, in_name, in_desc) +// { +// SetManageMod(); +// org_prototype = emp::NewPtr(*this); +// } +// virtual ~OrganismManager() { org_prototype.Delete(); } + +// /// Save the organism type that uses this manager. +// using org_t = ORG_T; + +// /// Also get the TypeID for this organism for more run-time type management. +// emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } + +// /// Create a clone of the provided organism; default to using copy constructor. +// emp::Ptr CloneOrganism(const Organism & org) override { +// return emp::NewPtr( (const org_t &) org ); +// } + +// /// Create a random organism from scratch. Default to using the org_prototype organism. +// emp::Ptr MakeOrganism() override { +// auto org_ptr = org_prototype->Clone(); +// return org_ptr; +// } + +// /// Create a random organism from scratch. Default to using the org_prototype organism +// /// and then randomize if a random number generator is provided. +// emp::Ptr MakeOrganism(emp::Random & random) override { +// auto org_ptr = org_prototype->Clone(); +// org_ptr->Initialize(random); +// return org_ptr; +// } + + +// void SetupModule() override { +// org_prototype->SetupModule(); +// } + +// void SetupDataMap(emp::DataMap & in_dm) override { +// org_prototype->SetDataMap(in_dm); +// } + +// void SetupConfig() override { +// org_prototype->SetupConfig(); +// } + +// }; + +// /// Build a class that will automatically register modules when created (globally) +// template +// struct OrgManagerRegistrar { +// OrgManagerRegistrar(const std::string & type_name, const std::string & desc) { +// ModuleInfo new_info; +// new_info.name = type_name; +// new_info.desc = desc; +// new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { +// return control.AddModule(name, desc); +// }; +// GetModuleInfo().insert(new_info); +// } +// }; + +// #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) \ +// mabe::OrgManagerRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + +// } #endif From 0369603000dca40f7a773693e387f977a05168ea Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 10 Aug 2021 10:40:04 -0400 Subject: [PATCH 080/445] Cleaned up Organism to work with the new FactoryModule setup. --- source/core/Organism.hpp | 44 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 618a89b9..69216e78 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -150,7 +150,7 @@ namespace mabe { /// @note We MUST be able to make a copy of organisms for MABE to function. If this function /// is not overridden, the organism manager (which knows the derived type) will try to make a /// clone using the copy constructor. - [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneOrganism(*this); } + [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneObject(*this); } /// Modify this organism based on configured mutation parameters. /// @note For evolution to function, we need to be able to mutate offspring. @@ -241,32 +241,32 @@ namespace mabe { }; - // Pre-declare OrganismManager to allow for conversions. - template class OrganismManager; + // // Pre-declare OrganismManager to allow for conversions. + // template class OrganismManager; - /// Below is a specialty Organism type that uses "curiously recursive templates" to fill out - /// more default functionality for when you know the derived organism type. Specifically, - /// it should be used as the base class for any derived organism types. - template - class OrganismTemplate : public Organism { - public: - OrganismTemplate(ModuleBase & _man) : Organism(_man) { ; } + // /// Below is a specialty Organism type that uses "curiously recursive templates" to fill out + // /// more default functionality for when you know the derived organism type. Specifically, + // /// it should be used as the base class for any derived organism types. + // template + // class OrganismTemplate : public Organism { + // public: + // OrganismTemplate(ModuleBase & _man) : Organism(_man) { ; } - using org_t = ORG_T; - using manager_t = OrganismManager; + // using org_t = ORG_T; + // using manager_t = OrganismManager; - /// Get the manager for this type of organism. - manager_t & GetManager() { - return (manager_t &) Organism::GetManager(); - } - const manager_t & GetManager() const { - return (const manager_t &) Organism::GetManager(); - } + // /// Get the manager for this type of organism. + // manager_t & GetManager() { + // return (manager_t &) Organism::GetManager(); + // } + // const manager_t & GetManager() const { + // return (const manager_t &) Organism::GetManager(); + // } - auto & SharedData() { return GetManager().data; } - const auto & SharedData() const { return GetManager().data; } - }; + // auto & SharedData() { return GetManager().data; } + // const auto & SharedData() const { return GetManager().data; } + // }; } #endif From 97d9c36207d4703666f5bd3fc70a982abb09054b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 17:31:09 -0400 Subject: [PATCH 081/445] Fixed EmptyOrgansim to work with the new FactoryModule setup. --- source/core/EmptyOrganism.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/EmptyOrganism.hpp b/source/core/EmptyOrganism.hpp index bb67eed4..c4ea13b1 100644 --- a/source/core/EmptyOrganism.hpp +++ b/source/core/EmptyOrganism.hpp @@ -29,14 +29,14 @@ namespace mabe { class EmptyOrganismManager : public OrganismManager { public: EmptyOrganismManager(MABE & in_control, const std::string & in_name, const std::string & in_desc="") - : OrganismManager(in_control, in_name, in_desc) { ; } + : OrganismManager(in_control, in_name, in_desc) { ; } ~EmptyOrganismManager() { ; } std::string GetTypeName() const override { return "EmptyOrganismManager"; } - emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } + emp::TypeID GetObjType() const override { return emp::GetTypeID(); } - emp::Ptr MakeOrganism() override { return emp::NewPtr(*this); } - emp::Ptr MakeOrganism(emp::Random &) override { emp_error("Cannot make a 'random' EmptyOrganism."); return nullptr; } + emp::Ptr Make() override { return emp::NewPtr(*this); } + emp::Ptr Make(emp::Random &) override { emp_error("Cannot make a 'random' EmptyOrganism."); return nullptr; } }; } From 33a306061fa79d64f34e821cdfcdf4cb19cb3b27 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 17:33:56 -0400 Subject: [PATCH 082/445] Fixed MABE to call empty_manager.Make() rather than MakeOrganisms() --- source/core/MABE.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 6d6205f9..abfb28e0 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -203,7 +203,7 @@ namespace mabe { AddModule("EmptyOrg", "Manager for all 'empty' organisms in any population."); empty_manager.SetBuiltIn(); // Don't write the empty manager to config. - empty_org = empty_manager.MakeOrganism(); + empty_org = empty_manager.Make(); } /// Update MABE a single time step. @@ -306,13 +306,13 @@ namespace mabe { Verbose("Injecting ", copy_count, " orgs of type '", type_name, "' into population ", pop.GetID()); - auto & org_manager = GetModule(type_name); // Look up type of organism. - OrgPosition pos; // Place to save injection position. - for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. - auto org_ptr = org_manager.MakeOrganism(random); // ...Build an org of this type. - pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. + auto & org_manager = GetModule(type_name); // Look up type of organism. + OrgPosition pos; // Place to save injection position. + for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. + auto org_ptr = org_manager.Make(random); // ...Build an org of this type. + pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. } - return pos; // Return last position injected. + return pos; // Return last position injected. } /// Add an organism of a specified type and population (provide names of both and they From ed5d6935e157d0fdd2097e074faf108d0e14e9ab Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 18:03:24 -0400 Subject: [PATCH 083/445] Some cleanup on developer notes. --- source/core/DeveloperNotes.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 10b8f93f..0aefc475 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -3,13 +3,17 @@ changed for individual experients. # Layout -The core components of MABE are below. The first four files are tools with no internal dependancies. Each of the remaining files depend on all of those above it. +The core components of MABE are below. This first group are tools that have minimal internal dependancies (indicated by indentation below the requirement). data_collect.hpp - Tools to extract data from elements in a container. +ErrorManager.hpp - Track any run-time errors as they occur. SigListener.hpp - Tool to trigger a specified member function on other classes when triggered. TraitInfo.hpp - Specifications for module/trait interactions (on organisms, populations, etc.) + TraitManager.hpp - Manager for dealing with many requests for trait management. TraitSet.hpp - Collections of traits, all with the same type (or a vector of that type) +Specialty modules in MABE have a linear dependency; each module listed below is dependent on all of those above it. + ModuleBase.hpp - Core functionality for interfacing with all module types. Organism.hpp - Information about a single agent; ModuleBase is interface for OrganismManager. OrgIterator.hpp - Tools for identifying organism locations and stepping through sets of them. @@ -17,19 +21,22 @@ Population.hpp - Collection of Organisms (some of which could be EmptyOrgan Collection.hpp - A more flexible collection of organisms or whole populations for manipulation. MABE.hpp - Main contoller object; manipulates Populations and Organisms Module.hpp - Modify main MABE contoller functions. +FactoryModule.hpp - Framework to build specialty modules that manage other config objects. OrganismManager.hpp - Specialty Module type to manage organisms with shared configuration. EmptyOrganism.hpp - Specialty Organism type to represent empty cells. # Component Development -When Building new components for MABE, new organism types should be derived from the Organism class (with an optional specialized OrganismManager) and new modules should be derived from Module. +When Building new components for MABE, new organism types should be derived from the Organism class (with an optional specialized OrganismManager) and new modules should be derived from Module (for regular modules). If you want to add a new type in config that needs to be managed (as are organisms, brains, and genomes) this can be done with FactoryModule. -## Organisms +## Adding Organisms An organism can be devleoped as a class unto itself OR it can make use of a specialized OrganismManager that stores shared inforation. If an organism class doesn't have a specialized manager, it must override a few specialty "prototype" functions to specify the configuration parameters that it uses. -## Modules +## Adding Modules + +## Adding Managed Config Types # Core MABE Development @@ -56,7 +63,7 @@ MABE.h: # Next steps in Core MABE Development -* MABE::FindInjectPosition should use modules explicitly associated with populations, not scan all modules. This can be setup by modules declaring themselves as handling population structure needing to indicate WHICH populations (perhaps in SetupModule()?) +* MABE::FindInjectPosition should use modules explicitly associated with a specific population, not scan all modules. This can be setup by modules declaring themselves as handling population structure needing to indicate WHICH populations (perhaps in SetupModule()?) * Problem to resolve: trait access will often be limited to have only one module WRITE a trait. These seem important for organism managers, but that limitation should only be for THAT organism type. If, for example, we have a bits trait, ANY organism manager should be able to write to that trait. A simple solution would be to have access apply to ALL organism managers when any one needs to set it. From b8c758ccf95c712fcd604ec9ab16ab25e10f6107 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 18:12:00 -0400 Subject: [PATCH 084/445] Moved OrganismManager-specific functionality back into OrgansimManager.hpp. --- source/core/OrganismManager.hpp | 95 ++++----------------------------- 1 file changed, 9 insertions(+), 86 deletions(-) diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index c2037c4e..1701206c 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -12,95 +12,18 @@ #include "FactoryModule.hpp" -// #include "emp/meta/TypeID.hpp" +namespace mabe { -// #include "../config/Config.hpp" + // Setup an OrganismManager as a standard FactoryModule that builds different kinds of Organisms + template + using OrganismManager = FactoryModule; -// #include "MABE.hpp" -// #include "Module.hpp" + // Setup OrganismTemplate as a quick way to build new organism types. + template + using OrganismTemplate = ProductTemplate; -// namespace mabe { + #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, mabe::Organism, DESC) +} -// class Organism; -// class MABE; - -// template -// class OrganismManager : public Module { -// /// Allow organisms to access private shared data in their own manager only. -// friend OrganismTemplate; - -// private: -// using data_t = typename ORG_T::ManagerData; - -// /// Shared data for organisms that use this manager. -// data_t data; - -// public: -// OrganismManager(MABE & in_control, const std::string & in_name, const std::string & in_desc="") -// : Module(in_control, in_name, in_desc) -// { -// SetManageMod(); -// org_prototype = emp::NewPtr(*this); -// } -// virtual ~OrganismManager() { org_prototype.Delete(); } - -// /// Save the organism type that uses this manager. -// using org_t = ORG_T; - -// /// Also get the TypeID for this organism for more run-time type management. -// emp::TypeID GetOrgType() const override { return emp::GetTypeID(); } - -// /// Create a clone of the provided organism; default to using copy constructor. -// emp::Ptr CloneOrganism(const Organism & org) override { -// return emp::NewPtr( (const org_t &) org ); -// } - -// /// Create a random organism from scratch. Default to using the org_prototype organism. -// emp::Ptr MakeOrganism() override { -// auto org_ptr = org_prototype->Clone(); -// return org_ptr; -// } - -// /// Create a random organism from scratch. Default to using the org_prototype organism -// /// and then randomize if a random number generator is provided. -// emp::Ptr MakeOrganism(emp::Random & random) override { -// auto org_ptr = org_prototype->Clone(); -// org_ptr->Initialize(random); -// return org_ptr; -// } - - -// void SetupModule() override { -// org_prototype->SetupModule(); -// } - -// void SetupDataMap(emp::DataMap & in_dm) override { -// org_prototype->SetDataMap(in_dm); -// } - -// void SetupConfig() override { -// org_prototype->SetupConfig(); -// } - -// }; - -// /// Build a class that will automatically register modules when created (globally) -// template -// struct OrgManagerRegistrar { -// OrgManagerRegistrar(const std::string & type_name, const std::string & desc) { -// ModuleInfo new_info; -// new_info.name = type_name; -// new_info.desc = desc; -// new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { -// return control.AddModule(name, desc); -// }; -// GetModuleInfo().insert(new_info); -// } -// }; - -// #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) \ -// mabe::OrgManagerRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) - -// } #endif From be70fdcbfe5fed4709fc6b3a421be5d0fdf682c8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 18:12:42 -0400 Subject: [PATCH 085/445] Finished removal of old code after shift to FactoryModule. --- source/core/FactoryModule.hpp | 8 -------- source/core/Organism.hpp | 27 --------------------------- 2 files changed, 35 deletions(-) diff --git a/source/core/FactoryModule.hpp b/source/core/FactoryModule.hpp index dde15241..d476b695 100644 --- a/source/core/FactoryModule.hpp +++ b/source/core/FactoryModule.hpp @@ -131,14 +131,6 @@ namespace mabe { #define MABE_REGISTER_FACTORY_MODULE(TYPE, BASE_TYPE, DESC) \ mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) - // Setup backward compatability with OrganismManager. - template - using OrganismManager = FactoryModule; - template - using OrganismTemplate = ProductTemplate; - - #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, mabe::Organism, DESC) - } #endif diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 69216e78..cf3c91d6 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -241,32 +241,5 @@ namespace mabe { }; - // // Pre-declare OrganismManager to allow for conversions. - // template class OrganismManager; - - - // /// Below is a specialty Organism type that uses "curiously recursive templates" to fill out - // /// more default functionality for when you know the derived organism type. Specifically, - // /// it should be used as the base class for any derived organism types. - // template - // class OrganismTemplate : public Organism { - // public: - // OrganismTemplate(ModuleBase & _man) : Organism(_man) { ; } - - // using org_t = ORG_T; - // using manager_t = OrganismManager; - - // /// Get the manager for this type of organism. - // manager_t & GetManager() { - // return (manager_t &) Organism::GetManager(); - // } - // const manager_t & GetManager() const { - // return (const manager_t &) Organism::GetManager(); - // } - - // auto & SharedData() { return GetManager().data; } - // const auto & SharedData() const { return GetManager().data; } - // }; - } #endif From 3a9365a0616298f6043868fae729be720ba9b2b0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 18:30:19 -0400 Subject: [PATCH 086/445] Renamed FactoryModule to ManagerModule. --- .../{FactoryModule.hpp => ManagerModule.hpp} | 38 +++++++++---------- source/core/ModuleBase.hpp | 10 ++--- source/core/OrganismManager.hpp | 8 ++-- 3 files changed, 28 insertions(+), 28 deletions(-) rename source/core/{FactoryModule.hpp => ManagerModule.hpp} (77%) diff --git a/source/core/FactoryModule.hpp b/source/core/ManagerModule.hpp similarity index 77% rename from source/core/FactoryModule.hpp rename to source/core/ManagerModule.hpp index d476b695..a66270a7 100644 --- a/source/core/FactoryModule.hpp +++ b/source/core/ManagerModule.hpp @@ -3,12 +3,12 @@ * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file FactoryModule.hpp + * @file ManagerModule.hpp * @brief Base module to manage a selection of objects that share a common configiguration. */ -#ifndef MABE_FACTORY_MODULE_H -#define MABE_FACTORY_MODULE_H +#ifndef MABE_MANAGER_MODULE_H +#define MABE_MANAGER_MODULE_H #include "emp/meta/TypeID.hpp" @@ -21,9 +21,9 @@ namespace mabe { // Pre-declarations... class MABE; - template class FactoryModule; + template class ManagerModule; - /// Base class for factory products that uses "curiously recursive templates" to fill + /// Base class for managed products that uses "curiously recursive templates" to fill /// out default functionality for when you know the derived type. template class ProductTemplate : public BASE_T { @@ -31,7 +31,7 @@ namespace mabe { ProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } using obj_t = OBJ_T; - using manager_t = FactoryModule; + using manager_t = ManagerModule; /// Get the manager for this type of organism. manager_t & GetManager() { @@ -46,31 +46,31 @@ namespace mabe { }; - /// @param OBJ_T the object type being managed by the factory. - /// @param BASE_T the base object category being mnagaed by the factory. + /// @param OBJ_T the object type being managed. + /// @param BASE_T the base object category being mnagaed. template - class FactoryModule : public Module { - /// Allow factory products to access private shared data in their own manager only. + class ManagerModule : public Module { + /// Allow managed products to access private shared data in their own manager only. friend class ProductTemplate; private: - /// Locate the specification for the data that we need for management in the factory module. + /// Locate the specification for the data that we need for management in the manager module. using data_t = typename OBJ_T::ManagerData; - /// Shared data across all objects that use this factory. + /// Shared data across all objects that use this manager module. data_t data; /// Maintain a prototype for the objects being created. emp::Ptr obj_prototype; public: - FactoryModule(MABE & in_control, const std::string & in_name, const std::string & in_desc="") + ManagerModule(MABE & in_control, const std::string & in_name, const std::string & in_desc="") : Module(in_control, in_name, in_desc) { SetManageMod(); // @CAO should specify what type of object is managed. obj_prototype = emp::NewPtr(*this); } - virtual ~FactoryModule() { obj_prototype.Delete(); } + virtual ~ManagerModule() { obj_prototype.Delete(); } /// Save the object type that uses this manager. using obj_t = OBJ_T; @@ -114,8 +114,8 @@ namespace mabe { /// Build a class that will automatically register modules when created (globally) template - struct FactoryModuleRegistrar { - FactoryModuleRegistrar(const std::string & type_name, const std::string & desc) { + struct ManagerModuleRegistrar { + ManagerModuleRegistrar(const std::string & type_name, const std::string & desc) { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; @@ -127,9 +127,9 @@ namespace mabe { }; - /// MACRO for quickly adding new factory modules. - #define MABE_REGISTER_FACTORY_MODULE(TYPE, BASE_TYPE, DESC) \ - mabe::FactoryModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + /// MACRO for quickly adding new manager modules. + #define MABE_REGISTER_MANAGER_MODULE(TYPE, BASE_TYPE, DESC) \ + mabe::ManagerModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) } diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index fe0af42e..18cffe64 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -269,23 +269,23 @@ namespace mabe { // ---=== Specialty Functions for Organism Managers ===--- virtual emp::TypeID GetObjType() const { - emp_assert(false, "GetObjType() must be overridden for FactoryModule."); + emp_assert(false, "GetObjType() must be overridden for ManagerModule."); return emp::TypeID(); } virtual emp::Ptr CloneObject(const Organism &) { - emp_assert(false, "CloneObject() must be overridden for FactoryModule."); + emp_assert(false, "CloneObject() must be overridden for ManagerModule."); return nullptr; } virtual emp::Ptr CloneObject(const Organism &, emp::Random &) { - emp_assert(false, "CloneObject() must be overridden for FactoryModule."); + emp_assert(false, "CloneObject() must be overridden for ManagerModule."); return nullptr; } virtual emp::Ptr Make() { - emp_assert(false, "Make() must be overridden for FactoryModule."); + emp_assert(false, "Make() must be overridden for ManagerModule."); return nullptr; } virtual emp::Ptr Make(emp::Random &) { - emp_assert(false, "Make() must be overridden for FactoryModule."); + emp_assert(false, "Make() must be overridden for ManagerModule."); return nullptr; } diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index 1701206c..e3b4bec2 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -10,19 +10,19 @@ #ifndef MABE_ORGANISM_MANAGER_H #define MABE_ORGANISM_MANAGER_H -#include "FactoryModule.hpp" +#include "ManagerModule.hpp" namespace mabe { - // Setup an OrganismManager as a standard FactoryModule that builds different kinds of Organisms + // Setup an OrganismManager as a standard ManagerModule that builds different kinds of Organisms template - using OrganismManager = FactoryModule; + using OrganismManager = ManagerModule; // Setup OrganismTemplate as a quick way to build new organism types. template using OrganismTemplate = ProductTemplate; - #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_FACTORY_MODULE(TYPE, mabe::Organism, DESC) + #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_MANAGER_MODULE(TYPE, mabe::Organism, DESC) } From 8596648e2da8a017811986fa2205d34d68c1423a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 11 Aug 2021 20:36:25 -0400 Subject: [PATCH 087/445] Changed ManagerModule from handling 'produced objects' to 'managed type' --- source/core/ManagerModule.hpp | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index a66270a7..3f10c904 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -21,17 +21,17 @@ namespace mabe { // Pre-declarations... class MABE; - template class ManagerModule; + template class ManagerModule; /// Base class for managed products that uses "curiously recursive templates" to fill /// out default functionality for when you know the derived type. - template + template class ProductTemplate : public BASE_T { public: ProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } - using obj_t = OBJ_T; - using manager_t = ManagerModule; + using managed_t = MANAGED_T; + using manager_t = ManagerModule; /// Get the manager for this type of organism. manager_t & GetManager() { @@ -46,18 +46,18 @@ namespace mabe { }; - /// @param OBJ_T the object type being managed. - /// @param BASE_T the base object category being mnagaed. - template + /// @param MANAGED_T the type of object type being managed. + /// @param BASE_T the base type being mnagaed. + template class ManagerModule : public Module { /// Allow managed products to access private shared data in their own manager only. - friend class ProductTemplate; + friend class ProductTemplate; private: /// Locate the specification for the data that we need for management in the manager module. - using data_t = typename OBJ_T::ManagerData; + using data_t = typename MANAGED_T::ManagerData; - /// Shared data across all objects that use this manager module. + /// Shared data across all objects that use the same manager. data_t data; /// Maintain a prototype for the objects being created. @@ -68,19 +68,19 @@ namespace mabe { : Module(in_control, in_name, in_desc) { SetManageMod(); // @CAO should specify what type of object is managed. - obj_prototype = emp::NewPtr(*this); + obj_prototype = emp::NewPtr(*this); } virtual ~ManagerModule() { obj_prototype.Delete(); } - /// Save the object type that uses this manager. - using obj_t = OBJ_T; + /// Save the type that uses this manager. + using managed_t = MANAGED_T; - /// Also get the TypeID for this object for more run-time type management. - emp::TypeID GetObjType() const override { return emp::GetTypeID(); } + /// Also get the TypeID for more run-time type management. + emp::TypeID GetObjType() const override { return emp::GetTypeID(); } /// Create a clone of the provided object; default to using copy constructor. emp::Ptr CloneObject(const BASE_T & obj) override { - return emp::NewPtr( (const obj_t &) obj ); + return emp::NewPtr( (const managed_t &) obj ); } /// Create a random object from scratch. Default to using the obj_prototype object. @@ -113,14 +113,14 @@ namespace mabe { }; /// Build a class that will automatically register modules when created (globally) - template + template struct ManagerModuleRegistrar { ManagerModuleRegistrar(const std::string & type_name, const std::string & desc) { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { - return control.AddModule(name, desc); + return control.AddModule(name, desc); }; GetModuleInfo().insert(new_info); } From 65d9e11a339a254aa20454774f03845da70a17c9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 13 Aug 2021 13:30:13 -0400 Subject: [PATCH 088/445] Cleanup on DeveloperNotes. --- source/core/DeveloperNotes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 0aefc475..f7b3ef41 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -28,11 +28,11 @@ EmptyOrganism.hpp - Specialty Organism type to represent empty cells. # Component Development -When Building new components for MABE, new organism types should be derived from the Organism class (with an optional specialized OrganismManager) and new modules should be derived from Module (for regular modules). If you want to add a new type in config that needs to be managed (as are organisms, brains, and genomes) this can be done with FactoryModule. +When Building new components for MABE, new organism types should be derived from a mabe::OrganismTemplate and new modules should be derived from mabe::Module (for regular modules). If you want to add a new type in config that needs to be managed (as are organisms, brains, and genomes) this can be done with FactoryModule. ## Adding Organisms -An organism can be devleoped as a class unto itself OR it can make use of a specialized OrganismManager that stores shared inforation. If an organism class doesn't have a specialized manager, it must override a few specialty "prototype" functions to specify the configuration parameters that it uses. +A new specialty organism class must use OrganismsTemplate as a base class, where ORG_T is the type of the new organism. New organism classes must also override a few specialty functions to specify the configuration parameters that it uses. ## Adding Modules From e900cbfdeacfc55a3c4bc380d4356db2d806da62 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 14 Aug 2021 16:07:41 -0400 Subject: [PATCH 089/445] Removed extra semi-colon. --- source/core/data_collect.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 0820fb72..ea2b5518 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -269,6 +269,6 @@ namespace emp { return std::function(); } -}; +} #endif From 2f908e548868b85003beb642040313014a32674b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 14 Aug 2021 16:08:19 -0400 Subject: [PATCH 090/445] Setup verbose EvalMancala to print on evaluating each organism. --- source/evaluate/games/EvalMancala.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 3563dc74..e8383aa3 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -196,7 +196,9 @@ namespace mabe { control.Verbose(" - ", alive_collect.GetSize(), " organisms found."); + size_t org_count = 0; for (Organism & org : alive_collect) { + control.Verbose("...eval org #", org_count++); double & score = org.GetTrait(score_trait); score = EvalGame(org, control.GetRandom()); // Start first. org.SetTrait(trace_trait, game_trace); // Record the trace of the first game. From 7c183471bac10045cd6ef4732437b83af9b8dfde Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 14 Aug 2021 16:08:46 -0400 Subject: [PATCH 091/445] Overhauld CommandLine module to allow specification of what to print. --- source/interface/CommandLine.hpp | 60 ++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/source/interface/CommandLine.hpp b/source/interface/CommandLine.hpp index abdeb2cb..f9c957a9 100644 --- a/source/interface/CommandLine.hpp +++ b/source/interface/CommandLine.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file CommandLine.hpp * @brief Module to output errors and warnings to the command line. @@ -17,45 +17,77 @@ namespace mabe { class CommandLine : public Module { private: - int pop_id=0; // Which population should we print stats about? + std::string format; + Collection target_collect; + + // Calculated values from the inputs. + using trait_fun_t = std::function; + emp::vector cols; ///< Names of the columns to use. + emp::vector funs; ///< Functions to call each update. + bool init = false; + + void Initialize() { + // Identify the contents of each column. + emp::remove_whitespace(format); + emp::slice(format, cols, ','); + + // Setup a function to collect data associated with each column. + funs.resize(cols.size()); + for (size_t i = 0; i < cols.size(); i++) { + std::string trait_filter = cols[i]; + std::string trait_name = emp::string_pop(trait_filter,':'); + funs[i] = control.BuildTraitFunction(trait_name, trait_filter); + } + + init = true; + } public: CommandLine(mabe::MABE & control, const std::string & name="CommandLine", const std::string & desc="Module to handle basic I/O on the command line.") : Module(control, name, desc) + , format("fitness:max,fitness:mean") + , target_collect(control.GetPopulation(0)) { + SetInterfaceMod(); SetErrorHandleMod(); } ~CommandLine() { } void SetupConfig() override { - LinkPop(pop_id, "target_pop", "Which population should we print stats about?"); + LinkVar(format, "format", "Column format to use in the file."); + LinkCollection(target_collect, "target", "Which population(s) should we print from?"); } void SetupModule() override { - // For now, nothing here. } - void OnUpdate(size_t ud) override { + void BeforeUpdate(size_t ud) override { std::cout << "Update:" << ud; + + if (ud == 0) { // At the very beginning, no stats available. + std::cout << std::endl; + return; + } + + if (!init) Initialize(); + for (size_t pop_id = 0; pop_id < control.GetNumPopulations(); pop_id++) { const Population & pop = control.GetPopulation(pop_id); std::cout << " " << pop.GetName() << ":" << pop.GetNumOrgs(); } + + mabe::Collection cur_collect = target_collect.GetAlive(); + for (size_t i = 0; i < funs.size(); ++i) { + std::cout << ", " << cols[i] << "=" << funs[i](cur_collect); + } + std::cout << std::endl; } void BeforeExit() override { - auto & pop = control.GetPopulation(pop_id); - std::cout << "Exiting. Population " << pop.GetName() - << " has " << pop.GetNumOrgs() << " organisms."; - if (pop.GetNumOrgs()) { - size_t pos = 0; - while (pop[pos].IsEmpty()) pos++; - std::cout << " First org:\n" << pop[pos].ToString(); - } - std::cout << std::endl; + std::cout << "Exiting." << std::endl; } void OnError(const std::string & msg) override { From 71b1c6175225ce6804c4ccc16724055030c4d900 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 14 Aug 2021 16:09:47 -0400 Subject: [PATCH 092/445] Fixed population assert to warn if a pop ID is too high to be reasonable. --- source/core/Population.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 1c52b2e5..17553ede 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -204,7 +204,8 @@ namespace mabe { public: // ------ DEBUG FUNCTIONS ------ bool OK() const { - if (pop_id < 0) { + // We will usually have a handful of popoulations; assume error if we have more than a billion. + if (pop_id > 1000000000) { std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; return false; } From d21b2081a0bfd592eed54f4c0ed86e2c3f38bb30 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 15 Aug 2021 11:45:11 -0400 Subject: [PATCH 093/445] Restructure Organism to be made up of OrgType and AnnotatedType. --- source/core/Organism.hpp | 271 +++++++++++++++++++++++---------------- 1 file changed, 162 insertions(+), 109 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index cf3c91d6..52e0a0c1 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -1,14 +1,15 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * - * @file Organism.hpp + * @file OrgType.hpp * @brief A base class for all organisms in MABE. * @note Status: ALPHA * - * All organism types in MABE must have mabe::Organism as it ultimate base class. A helper - * template mabe::OrganismTeplate is derived from mabe::Organism and should be used as + * All organism types or organism component types (e.g., brains or genomes) that can be + * individually configured in MABE must have mabe::OrgType as its ultimate base class. A helper + * template mabe::OrganismTeplate is derived from mabe::OrgType and should be used as * the more immeidate base class for any user-defined organism types. Providing this template * with your new organism type as ORG_T will setup type-specific return values for ease of use. * @@ -16,10 +17,10 @@ * DataMap. The configuration files need to be setup to ensure that environments and organisms * agree on the input values, the output values, and use of any type adaptors. * - * If an environment wants to allow ACTIONS to occur during execution, it can provide - * callback functions to the organisms in the appropriate OrganismManager DataMap. If - * the environment wants to indicate EVENTS that occur during an organism's lifetime, - * it can find the appropriate function to call in the manager's DataMap. + * If an environment wants to allow ACTIONS to occur during execution, it can provide callback + * functions to the organisms in the appropriate OrganismManager DataMap. If the environment + * wants to indicate EVENTS that occur during an organism's lifetime, it can find the appropriate + * function to call in the manager's DataMap. * */ @@ -39,14 +40,14 @@ namespace mabe { class Module; - class Organism { - private: - emp::DataMap data_map; ///< Dynamic variables assigned to organism + // A class type managed by a ManagerModule. + class OrgType { + protected: ModuleBase & manager; ///< Manager for the specific organism type public: - Organism(ModuleBase & _man) : manager(_man) { ; } - virtual ~Organism() { ; } + OrgType(ModuleBase & _man) : manager(_man) { ; } + virtual ~OrgType() { ; } /// Get the manager for this type of organism. Module & GetManager() { return (Module&) manager; } @@ -58,88 +59,6 @@ namespace mabe { struct ManagerData { }; - [[deprecated("Use Organism::HasTrait() instead of Organism::HasVar()")]] - bool HasVar(const std::string & name) const { return data_map.HasName(name); } - template - [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] - T & GetVar(const std::string & name) { return data_map.Get(name); } - template - [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] - const T & GetVar(const std::string & name) const { - return data_map.Get(name); - } - template - [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] - T & GetVar(size_t id) { return data_map.Get(id); } - template - [[deprecated("Use Organism::GetTrait() instead of Organism::GetVar()")]] - const T & GetVar(size_t id) const { return data_map.Get(id); } - - template - [[deprecated("Use Organism::SetTrait() instead of Organism::SetVar()")]] - void SetVar(const std::string & name, const T & value) { - if (data_map.HasName(name) == false) data_map.AddVar(name, value); - else data_map.Set(name, value); - } - - template - [[deprecated("Use Organism::SetTrait() instead of Organism::SetVar()")]] - void SetVar(size_t id, const T & value) { - emp_assert(data_map.HasID(id), id); - data_map.Set(id, value); - } - - emp::DataMap & GetDataMap() { return data_map; } - const emp::DataMap & GetDataMap() const { return data_map; } - - void SetDataMap(emp::DataMap & in_dm) { data_map = in_dm; } - - bool HasTraitID(size_t id) const { return data_map.HasID(id); } - bool HasTrait(const std::string & name) const { return data_map.HasName(name); } - template - bool TestTraitType(size_t id) const { return data_map.IsType(id); } - template - bool TestTraitType(const std::string & name) const { return data_map.IsType(name); } - - size_t GetTraitID(const std::string & name) const { return data_map.GetID(name); } - - template - T & GetTrait(size_t id) { return data_map.Get(id); } - - template - const T & GetTrait(size_t id) const { return data_map.Get(id); } - - template - T & GetTrait(const std::string & name) { return data_map.Get(name); } - - template - const T & GetTrait(const std::string & name) const { return data_map.Get(name); } - - template - T & SetTrait(size_t id, const T & val) { return data_map.Set(id, val); } - - template - T & SetTrait(const std::string & name, const T & val) { return data_map.Set(name, val); } - - emp::TypeID GetTraitType(size_t id) const { return data_map.GetType(id); } - emp::TypeID GetTraitType(const std::string & name) const { return data_map.GetType(name); } - - double GetTraitAsDouble(size_t id) const { return data_map.GetAsDouble(id); } - - double GetTraitAsDouble(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsDouble(trait_id, type_id); - } - - std::string GetTraitAsString(size_t id) const { return data_map.GetAsString(id); } - - std::string GetTraitAsString(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsString(trait_id, type_id); - } - - - /// Test if this organism represents an empty cell. - virtual bool IsEmpty() const noexcept { return false; } - // ------------------------------------------ // ------ Functions for overriding ------ @@ -150,7 +69,7 @@ namespace mabe { /// @note We MUST be able to make a copy of organisms for MABE to function. If this function /// is not overridden, the organism manager (which knows the derived type) will try to make a /// clone using the copy constructor. - [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneObject(*this); } + [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneObject(*this); } /// Modify this organism based on configured mutation parameters. /// @note For evolution to function, we need to be able to mutate offspring. @@ -158,8 +77,8 @@ namespace mabe { /// Merge this organism's genome with that of another organism to produce an offspring. /// @note Required for basic sexual recombination to work. - [[nodiscard]] virtual emp::Ptr - Recombine(emp::Ptr /* parent2 */, emp::Random & /* random */) const { + [[nodiscard]] virtual emp::Ptr + Recombine(emp::Ptr /* parent2 */, emp::Random & /* random */) const { emp_assert(false, "Recombine() must be overridden for it to work."); return nullptr; } @@ -168,33 +87,33 @@ namespace mabe { /// a variable number of offspring. /// @note More flexible version of recombine (allowing many parents and/or many offspring), /// but also slower. - [[nodiscard]] virtual emp::vector> - Recombine(emp::vector> /*other_parents*/, emp::Random & /*random*/) const { + [[nodiscard]] virtual emp::vector> + Recombine(emp::vector> /*other_parents*/, emp::Random & /*random*/) const { emp_assert(false, "Recombine() must be overridden for it to work."); - return emp::vector>(); + return emp::vector>(); } /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). - [[nodiscard]] virtual emp::Ptr MakeOffspring(emp::Random & random) const { - emp::Ptr offspring = Clone(); + [[nodiscard]] virtual emp::Ptr MakeOffspring(emp::Random & random) const { + emp::Ptr offspring = Clone(); offspring->Mutate(random); return offspring; } /// Produce an sexual (two parent) offspring WITH MUTATIONS. By default, use Recombine() and /// then Mutate(). - [[nodiscard]] virtual emp::Ptr - MakeOffspring(emp::Ptr parent2, emp::Random & random) const { - emp::Ptr offspring = Recombine(parent2, random); + [[nodiscard]] virtual emp::Ptr + MakeOffspring(emp::Ptr parent2, emp::Random & random) const { + emp::Ptr offspring = Recombine(parent2, random); offspring->Mutate(random); return offspring; } /// Produce one or more offspring from multiple parents WITH MUTATIONS. By default, use /// Recombine() and then Mutate(). - [[nodiscard]] virtual emp::vector> - MakeOffspring(emp::vector> other_parents, emp::Random & random) const { - emp::vector> all_offspring = Recombine(other_parents, random); + [[nodiscard]] virtual emp::vector> + MakeOffspring(emp::vector> other_parents, emp::Random & random) const { + emp::vector> all_offspring = Recombine(other_parents, random); for (auto offspring : all_offspring) offspring->Mutate(random); return all_offspring; } @@ -237,9 +156,143 @@ namespace mabe { /// Setup organism-specific traits. virtual void SetupModule() { ; } + }; + + /// A generic base class implementing the use of dynamic traits via DataMaps. + class AnnotatedType { + protected: + emp::DataMap data_map; ///< Dynamic variables assigned to this class. + public: + emp::DataMap & GetDataMap() { return data_map; } + const emp::DataMap & GetDataMap() const { return data_map; } + + void SetDataMap(emp::DataMap & in_dm) { data_map = in_dm; } + + bool HasTraitID(size_t id) const { return data_map.HasID(id); } + bool HasTrait(const std::string & name) const { return data_map.HasName(name); } + template + bool TestTraitType(size_t id) const { return data_map.IsType(id); } + template + bool TestTraitType(const std::string & name) const { return data_map.IsType(name); } + + size_t GetTraitID(const std::string & name) const { return data_map.GetID(name); } + + template + T & GetTrait(size_t id) { return data_map.Get(id); } + + template + const T & GetTrait(size_t id) const { return data_map.Get(id); } + + template + T & GetTrait(const std::string & name) { return data_map.Get(name); } + + template + const T & GetTrait(const std::string & name) const { return data_map.Get(name); } + + template + T & SetTrait(size_t id, const T & val) { return data_map.Set(id, val); } + + template + T & SetTrait(const std::string & name, const T & val) { return data_map.Set(name, val); } + + emp::TypeID GetTraitType(size_t id) const { return data_map.GetType(id); } + emp::TypeID GetTraitType(const std::string & name) const { return data_map.GetType(name); } + + double GetTraitAsDouble(size_t id) const { return data_map.GetAsDouble(id); } + + double GetTraitAsDouble(size_t trait_id, emp::TypeID type_id) const { + return data_map.GetAsDouble(trait_id, type_id); + } + + std::string GetTraitAsString(size_t id) const { return data_map.GetAsString(id); } + + std::string GetTraitAsString(size_t trait_id, emp::TypeID type_id) const { + return data_map.GetAsString(trait_id, type_id); + } }; + class Organism : public OrgType, public AnnotatedType { + public: + Organism(ModuleBase & _man) : OrgType(_man) { ; } + virtual ~Organism() {} + + /// Test if this organism represents an empty cell. + virtual bool IsEmpty() const noexcept { return false; } + + /// Specialty version of Clone to return an Organism type. + [[nodiscard]] emp::Ptr CloneOrganism() const { + return OrgType::Clone().DynamicCast(); + } + + [[nodiscard]] emp::Ptr + RecombineOrganisms(emp::Ptr parent2, emp::Random & random) const { + return OrgType::Recombine(parent2, random).DynamicCast(); + } + + // @CAO: Need to clean this one up... + [[nodiscard]] emp::vector> + RecombineOrganisms(emp::vector> other_parents, emp::Random & random) const { + return OrgType::Recombine(other_parents, random); + } + + /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). + [[nodiscard]] emp::Ptr MakeOffspringOrganism(emp::Random & random) const { + return OrgType::MakeOffspring(random).DynamicCast(); + } + + /// Produce an sexual (two parent) offspring WITH MUTATIONS. By default, use Recombine() and + /// then Mutate(). + [[nodiscard]] emp::Ptr + MakeOffspringOrganism(emp::Ptr parent2, emp::Random & random) const { + return OrgType::MakeOffspring(parent2, random).DynamicCast(); + } + + // @CAO: Need to clean this one up to use Organism... + /// Produce one or more offspring from multiple parents WITH MUTATIONS. By default, use + /// Recombine() and then Mutate(). + [[nodiscard]] emp::vector> + MakeOffspringOrganisms(emp::vector> other_parents, emp::Random & random) const { + return OrgType::MakeOffspring(other_parents, random); + } + + + + + // -- Also deal with some depricated functionality... -- + + [[deprecated("Use OrgType::HasTrait() instead of OrgType::HasVar()")]] + bool HasVar(const std::string & name) const { return data_map.HasName(name); } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + T & GetVar(const std::string & name) { return data_map.Get(name); } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + const T & GetVar(const std::string & name) const { + return data_map.Get(name); + } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + T & GetVar(size_t id) { return data_map.Get(id); } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + const T & GetVar(size_t id) const { return data_map.Get(id); } + + template + [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] + void SetVar(const std::string & name, const T & value) { + if (data_map.HasName(name) == false) data_map.AddVar(name, value); + else data_map.Set(name, value); + } + + template + [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] + void SetVar(size_t id, const T & value) { + emp_assert(data_map.HasID(id), id); + data_map.Set(id, value); + } + + }; } #endif From 81712db7ca8b554549fd475104d8db6022fd57da Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 16 Aug 2021 23:37:17 -0400 Subject: [PATCH 094/445] Restructured manager support in ModuleBase to use *_impl function for overriding. --- source/core/ModuleBase.hpp | 44 +++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 18cffe64..b28330b5 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -87,6 +87,7 @@ namespace mabe { class MABE; + class OrgType; class Organism; class OrgPosition; class Population; @@ -160,6 +161,25 @@ namespace mabe { error_man->AddError(std::forward(args)...); } + + // Core implementation for ManagerModule functionality. + virtual emp::Ptr CloneObject_impl(const OrgType &) { + emp_assert(false, "CloneObject_impl() must be overridden for ManagerModule."); + return nullptr; + } + virtual emp::Ptr CloneObject_impl(const OrgType &, emp::Random &) { + emp_assert(false, "CloneObject_impl() must be overridden for ManagerModule."); + return nullptr; + } + virtual emp::Ptr Make_impl() { + emp_assert(false, "Make_impl() must be overridden for ManagerModule."); + return nullptr; + } + virtual emp::Ptr Make_impl(emp::Random &) { + emp_assert(false, "Make_impl() must be overridden for ManagerModule."); + return nullptr; + } + public: ModuleBase(MABE & in_control, const std::string & in_name, const std::string & in_desc="") : name(in_name), desc(in_desc), control(in_control) @@ -272,21 +292,21 @@ namespace mabe { emp_assert(false, "GetObjType() must be overridden for ManagerModule."); return emp::TypeID(); } - virtual emp::Ptr CloneObject(const Organism &) { - emp_assert(false, "CloneObject() must be overridden for ManagerModule."); - return nullptr; + template + emp::Ptr CloneObject(const OBJ_T & in_obj) { + return CloneObject_impl(in_obj).template DynamicCast(); } - virtual emp::Ptr CloneObject(const Organism &, emp::Random &) { - emp_assert(false, "CloneObject() must be overridden for ManagerModule."); - return nullptr; + template + emp::Ptr CloneObject(const OBJ_T & in_obj, emp::Random & random) { + return CloneObject_impl(in_obj, random).template DynamicCast(); } - virtual emp::Ptr Make() { - emp_assert(false, "Make() must be overridden for ManagerModule."); - return nullptr; + template + emp::Ptr Make() { + return Make_impl().template DynamicCast(); } - virtual emp::Ptr Make(emp::Random &) { - emp_assert(false, "Make() must be overridden for ManagerModule."); - return nullptr; + template + emp::Ptr Make(emp::Random & random) { + return Make_impl(random).template DynamicCast(); } virtual void SetupConfig() { } From 843bacbec9c8647da1fe110b12f53437c44e7d7a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 17 Aug 2021 00:08:51 -0400 Subject: [PATCH 095/445] Restructured ManagerModule to override new *_impl functions. --- source/core/ManagerModule.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index 3f10c904..8dcac8b4 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -79,19 +79,19 @@ namespace mabe { emp::TypeID GetObjType() const override { return emp::GetTypeID(); } /// Create a clone of the provided object; default to using copy constructor. - emp::Ptr CloneObject(const BASE_T & obj) override { + emp::Ptr CloneObject_impl(const OrgType & obj) override { return emp::NewPtr( (const managed_t &) obj ); } /// Create a random object from scratch. Default to using the obj_prototype object. - emp::Ptr Make() override { + emp::Ptr Make_impl() override { auto obj_ptr = obj_prototype->Clone(); return obj_ptr; } /// Create a random object from scratch. Default to using the obj_prototype object /// and then randomize if a random number generator is provided. - emp::Ptr Make(emp::Random & random) override { + emp::Ptr Make_impl(emp::Random & random) override { auto obj_ptr = obj_prototype->Clone(); obj_ptr->Initialize(random); return obj_ptr; From 185cf90473af5e970c325943cf1076e7e4963473 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 18 Aug 2021 22:00:14 -0400 Subject: [PATCH 096/445] Setup MABE controller to use new organism management functions --- source/core/MABE.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index abfb28e0..41c8d199 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -81,7 +81,6 @@ namespace mabe { emp::DataMap org_data_map; emp::Random random; ///< Master random number generator - int random_seed = 0; ///< Random number seed used for this run. size_t cur_pop_id = (size_t) -1; ///< Which population is currently active? size_t update = 0; ///< How many times has Update() been called? @@ -203,7 +202,7 @@ namespace mabe { AddModule("EmptyOrg", "Manager for all 'empty' organisms in any population."); empty_manager.SetBuiltIn(); // Don't write the empty manager to config. - empty_org = empty_manager.Make(); + empty_org = empty_manager.template Make(); } /// Update MABE a single time step. @@ -271,7 +270,7 @@ namespace mabe { emp_assert(org.GetDataMap().SameLayout(org_data_map)); OrgPosition pos; for (size_t i = 0; i < copy_count; i++) { - emp::Ptr inject_org = org.Clone(); + emp::Ptr inject_org = org.CloneOrganism(); on_inject_ready_sig.Trigger(*inject_org, pop); pos = FindInjectPosition(*inject_org, pop); if (pos.IsValid()) { @@ -309,7 +308,7 @@ namespace mabe { auto & org_manager = GetModule(type_name); // Look up type of organism. OrgPosition pos; // Place to save injection position. for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. - auto org_ptr = org_manager.Make(random); // ...Build an org of this type. + auto org_ptr = org_manager.Make(random); // ...Build an org of this type. pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. } return pos; // Return last position injected. @@ -332,7 +331,7 @@ namespace mabe { /// Inject a copy of the provided organism at a specified position. void InjectAt(const Organism & org, OrgPosition pos) { emp_assert(pos.IsValid()); - emp::Ptr inject_org = org.Clone(); + emp::Ptr inject_org = org.CloneOrganism(); on_inject_ready_sig.Trigger(*inject_org, GetPopulation(pos.PopID())); AddOrgAt( inject_org, pos); } @@ -350,7 +349,7 @@ namespace mabe { OrgPosition pos; // Position of each offspring placed. emp::Ptr new_org; for (size_t i = 0; i < birth_count; i++) { // Loop through offspring, adding each - new_org = do_mutations ? org.MakeOffspring(random) : org.Clone(); + new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); // Alert modules that offspring is ready, then find its birth position. on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); @@ -371,7 +370,7 @@ namespace mabe { emp_assert(target_pos.IsValid()); // Target positions must already be valid. before_repro_sig.Trigger(ppos); - emp::Ptr new_org = do_mutations ? org.MakeOffspring(random) : org.Clone(); + emp::Ptr new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); on_offspring_ready_sig.Trigger(*new_org, ppos, target_pos.Pop()); AddOrgAt(new_org, target_pos, ppos); @@ -870,9 +869,10 @@ namespace mabe { config.GetRootScope().GetName()); // Scope should start at root level. // Setup main MABE variables. - cur_scope->LinkVar("random_seed", - random_seed, - "Seed for random number generator; use 0 to base on time.").SetMin(0); + cur_scope->LinkFuns("random_seed", + [this](){ return random.GetSeed(); }, + [this](int seed){ random.ResetSeed(seed); }, + "Seed for random number generator; use 0 to base on time."); } From d0d7d8d1f450c28cb2b2e7dc3557330a2db77dd5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 19 Aug 2021 10:50:57 -0400 Subject: [PATCH 097/445] Fixed EmptyOrganism to override *_impl() functions. --- source/core/EmptyOrganism.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/core/EmptyOrganism.hpp b/source/core/EmptyOrganism.hpp index c4ea13b1..c998413f 100644 --- a/source/core/EmptyOrganism.hpp +++ b/source/core/EmptyOrganism.hpp @@ -19,7 +19,7 @@ namespace mabe { class EmptyOrganism : public Organism { public: EmptyOrganism(OrganismManager & _manager) : Organism(_manager) { ; } - emp::Ptr Clone() const override { emp_error("Do not clone EmptyOrganism"); return nullptr; } + emp::Ptr Clone() const override { emp_error("Do not clone EmptyOrganism"); return nullptr; } std::string ToString() const override { return "[empty]"; } size_t Mutate(emp::Random &) override { emp_error("EmptyOrganism cannot Mutate()"); return -1; } void Randomize(emp::Random &) override { emp_error("EmptyOrganism cannot Randomize()"); } @@ -35,8 +35,8 @@ namespace mabe { std::string GetTypeName() const override { return "EmptyOrganismManager"; } emp::TypeID GetObjType() const override { return emp::GetTypeID(); } - emp::Ptr Make() override { return emp::NewPtr(*this); } - emp::Ptr Make(emp::Random &) override { emp_error("Cannot make a 'random' EmptyOrganism."); return nullptr; } + emp::Ptr Make_impl() override { return emp::NewPtr(*this); } + emp::Ptr Make_impl(emp::Random &) override { emp_error("Cannot make a 'random' EmptyOrganism."); return nullptr; } }; } From 8d07c7bfc674cca4fe2a8eb2d9fc4b7c9ddc3560 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 19 Aug 2021 10:51:25 -0400 Subject: [PATCH 098/445] Made Population use CloneOrganism() instead of Clone() for correct typing. --- source/core/Population.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 17553ede..1a749de2 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -117,7 +117,7 @@ namespace mabe { emp_assert(!empty_org.IsNull(), "Empty organisms must be set before they can be used!"); orgs[i] = empty_org; } else { // Otherwise clone the organism. - orgs[i] = in_pop.orgs[i]->Clone(); + orgs[i] = in_pop.orgs[i]->CloneOrganism(); } } emp_assert(OK()); From 68320591e49e182cb7cc3ef27ac68f3c48a8c1a6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 20 Aug 2021 16:31:59 -0400 Subject: [PATCH 099/445] Moved OrgType into its own file. --- source/core/OrgType.hpp | 138 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 source/core/OrgType.hpp diff --git a/source/core/OrgType.hpp b/source/core/OrgType.hpp new file mode 100644 index 00000000..dda35b7c --- /dev/null +++ b/source/core/OrgType.hpp @@ -0,0 +1,138 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file OrgType.hpp + * @brief A base class for all organism components, with facilities for replication. + * @note Status: ALPHA + */ + +#ifndef MABE_ORG_TYPE_HPP +#define MABE_ORG_TYPE_HPP + +#include "ModuleBase.hpp" + +namespace mabe { + + class Module; + + // A class type managed by a ManagerModule. + class OrgType { + protected: + ModuleBase & manager; ///< Manager for the specific organism type + + public: + OrgType(ModuleBase & _man) : manager(_man) { ; } + virtual ~OrgType() { ; } + + /// Get the manager for this type of organism. + Module & GetManager() { return (Module&) manager; } + const Module & GetManager() const { return (Module&) manager; } + + /// The class below is a placeholder for storing any manager-specific data that the organims + /// should have access to. A derived organism class merely needs to shadow this one in order + /// to include specialized data. + struct ManagerData { + }; + + + // ------------------------------------------ + // ------ Functions for overriding ------ + // ------------------------------------------ + + + /// Create an exact duplicate of this organism. + /// @note We MUST be able to make a copy of organisms for MABE to function. If this function + /// is not overridden, the organism manager (which knows the derived type) will try to make a + /// clone using the copy constructor. + [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneObject(*this); } + + /// Modify this organism based on configured mutation parameters. + /// @note For evolution to function, we need to be able to mutate offspring. + virtual size_t Mutate(emp::Random & random) = 0; + + /// Merge this organism's genome with that of another organism to produce an offspring. + /// @note Required for basic sexual recombination to work. + [[nodiscard]] virtual emp::Ptr + Recombine(emp::Ptr /* parent2 */, emp::Random & /* random */) const { + emp_assert(false, "Recombine() must be overridden for it to work."); + return nullptr; + } + + /// Merge this organism's genome with that of a variable number of other organisms to produce + /// a variable number of offspring. + /// @note More flexible version of recombine (allowing many parents and/or many offspring), + /// but also slower. + [[nodiscard]] virtual emp::vector> + Recombine(emp::vector> /*other_parents*/, emp::Random & /*random*/) const { + emp_assert(false, "Recombine() must be overridden for it to work."); + return emp::vector>(); + } + + /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). + [[nodiscard]] virtual emp::Ptr MakeOffspring(emp::Random & random) const { + emp::Ptr offspring = Clone(); + offspring->Mutate(random); + return offspring; + } + + /// Produce an sexual (two parent) offspring WITH MUTATIONS. By default, use Recombine() and + /// then Mutate(). + [[nodiscard]] virtual emp::Ptr + MakeOffspring(emp::Ptr parent2, emp::Random & random) const { + emp::Ptr offspring = Recombine(parent2, random); + offspring->Mutate(random); + return offspring; + } + + /// Produce one or more offspring from multiple parents WITH MUTATIONS. By default, use + /// Recombine() and then Mutate(). + [[nodiscard]] virtual emp::vector> + MakeOffspring(emp::vector> other_parents, emp::Random & random) const { + emp::vector> all_offspring = Recombine(other_parents, random); + for (auto offspring : all_offspring) offspring->Mutate(random); + return all_offspring; + } + + /// Convert this organism into a string of characters. + /// @note Required if we are going to print organisms to screen or to file). If this function + /// is not overridden, try to the equivilent function in the organism manager. + virtual std::string ToString() const { return "__unknown__"; } + + /// By default print an organism by triggering it's ToString() function. + std::ostream & Print(std::ostream & os) const { + os << ToString(); + return os; + } + + /// Completely randomize a new organism (typically for initialization) + virtual void Randomize(emp::Random & /*random*/) { + emp_assert(false, "Randomize() must be overridden before it can be called."); + } + + /// Setup a new organism from scratch; by default just randomize. + virtual void Initialize(emp::Random & random) { Randomize(random); } + + /// Run the organism to generate an output in the pre-configured data_map entries. + virtual void GenerateOutput() { ; } + + /// Run the organisms a single time step; only implemented for continuous execution organisms. + virtual bool ProcessStep() { return false; } + + // virtual bool AddEvent(const std::string & event_name, int event_id) { return false; } + // virtual void TriggerEvent(int) { ; } + + + /// + /// --- Extra functions for when this is used as a PROTOTYPE ORGANISM only! --- + /// + + /// Setup organism-specific configuration options. + virtual void SetupConfig() { ; } + + /// Setup organism-specific traits. + virtual void SetupModule() { ; } + }; + +}; From 0fa02f91a62791f919ad0380125c94ade74d12ee Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 20 Aug 2021 16:41:00 -0400 Subject: [PATCH 100/445] Remove OrgType from Organism.hpp; made data_map private in AnnotatedType. --- source/core/Organism.hpp | 148 +++------------------------------------ 1 file changed, 10 insertions(+), 138 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 52e0a0c1..510384ea 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -3,7 +3,7 @@ * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file OrgType.hpp + * @file Organism.hpp * @brief A base class for all organisms in MABE. * @note Status: ALPHA * @@ -34,133 +34,13 @@ #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" -#include "ModuleBase.hpp" +#include "OrgType.hpp" namespace mabe { - class Module; - - // A class type managed by a ManagerModule. - class OrgType { - protected: - ModuleBase & manager; ///< Manager for the specific organism type - - public: - OrgType(ModuleBase & _man) : manager(_man) { ; } - virtual ~OrgType() { ; } - - /// Get the manager for this type of organism. - Module & GetManager() { return (Module&) manager; } - const Module & GetManager() const { return (Module&) manager; } - - /// The class below is a placeholder for storing any manager-specific data that the organims - /// should have access to. A derived organism class merely needs to shadow this one in order - /// to include specialized data. - struct ManagerData { - }; - - - // ------------------------------------------ - // ------ Functions for overriding ------ - // ------------------------------------------ - - - /// Create an exact duplicate of this organism. - /// @note We MUST be able to make a copy of organisms for MABE to function. If this function - /// is not overridden, the organism manager (which knows the derived type) will try to make a - /// clone using the copy constructor. - [[nodiscard]] virtual emp::Ptr Clone() const { return manager.CloneObject(*this); } - - /// Modify this organism based on configured mutation parameters. - /// @note For evolution to function, we need to be able to mutate offspring. - virtual size_t Mutate(emp::Random & random) = 0; - - /// Merge this organism's genome with that of another organism to produce an offspring. - /// @note Required for basic sexual recombination to work. - [[nodiscard]] virtual emp::Ptr - Recombine(emp::Ptr /* parent2 */, emp::Random & /* random */) const { - emp_assert(false, "Recombine() must be overridden for it to work."); - return nullptr; - } - - /// Merge this organism's genome with that of a variable number of other organisms to produce - /// a variable number of offspring. - /// @note More flexible version of recombine (allowing many parents and/or many offspring), - /// but also slower. - [[nodiscard]] virtual emp::vector> - Recombine(emp::vector> /*other_parents*/, emp::Random & /*random*/) const { - emp_assert(false, "Recombine() must be overridden for it to work."); - return emp::vector>(); - } - - /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). - [[nodiscard]] virtual emp::Ptr MakeOffspring(emp::Random & random) const { - emp::Ptr offspring = Clone(); - offspring->Mutate(random); - return offspring; - } - - /// Produce an sexual (two parent) offspring WITH MUTATIONS. By default, use Recombine() and - /// then Mutate(). - [[nodiscard]] virtual emp::Ptr - MakeOffspring(emp::Ptr parent2, emp::Random & random) const { - emp::Ptr offspring = Recombine(parent2, random); - offspring->Mutate(random); - return offspring; - } - - /// Produce one or more offspring from multiple parents WITH MUTATIONS. By default, use - /// Recombine() and then Mutate(). - [[nodiscard]] virtual emp::vector> - MakeOffspring(emp::vector> other_parents, emp::Random & random) const { - emp::vector> all_offspring = Recombine(other_parents, random); - for (auto offspring : all_offspring) offspring->Mutate(random); - return all_offspring; - } - - /// Convert this organism into a string of characters. - /// @note Required if we are going to print organisms to screen or to file). If this function - /// is not overridden, try to the equivilent function in the organism manager. - virtual std::string ToString() const { return "__unknown__"; } - - /// By default print an organism by triggering it's ToString() function. - std::ostream & Print(std::ostream & os) const { - os << ToString(); - return os; - } - - /// Completely randomize a new organism (typically for initialization) - virtual void Randomize(emp::Random & /*random*/) { - emp_assert(false, "Randomize() must be overridden before it can be called."); - } - - /// Setup a new organism from scratch; by default just randomize. - virtual void Initialize(emp::Random & random) { Randomize(random); } - - /// Run the organism to generate an output in the pre-configured data_map entries. - virtual void GenerateOutput() { ; } - - /// Run the organisms a single time step; only implemented for continuous execution organisms. - virtual bool ProcessStep() { return false; } - - // virtual bool AddEvent(const std::string & event_name, int event_id) { return false; } - // virtual void TriggerEvent(int) { ; } - - - /// - /// --- Extra functions for when this is used as a PROTOTYPE ORGANISM only! --- - /// - - /// Setup organism-specific configuration options. - virtual void SetupConfig() { ; } - - /// Setup organism-specific traits. - virtual void SetupModule() { ; } - }; - /// A generic base class implementing the use of dynamic traits via DataMaps. class AnnotatedType { - protected: + private: emp::DataMap data_map; ///< Dynamic variables assigned to this class. public: @@ -262,35 +142,27 @@ namespace mabe { // -- Also deal with some depricated functionality... -- [[deprecated("Use OrgType::HasTrait() instead of OrgType::HasVar()")]] - bool HasVar(const std::string & name) const { return data_map.HasName(name); } + bool HasVar(const std::string & name) const { return HasTrait(name); } template [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] - T & GetVar(const std::string & name) { return data_map.Get(name); } + T & GetVar(const std::string & name) { return GetTrait(name); } template [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] - const T & GetVar(const std::string & name) const { - return data_map.Get(name); - } + const T & GetVar(const std::string & name) const { return GetTrait(name); } template [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] - T & GetVar(size_t id) { return data_map.Get(id); } + T & GetVar(size_t id) { return GetTrait(id); } template [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] - const T & GetVar(size_t id) const { return data_map.Get(id); } + const T & GetVar(size_t id) const { return GetTrait(id); } template [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] - void SetVar(const std::string & name, const T & value) { - if (data_map.HasName(name) == false) data_map.AddVar(name, value); - else data_map.Set(name, value); - } + void SetVar(const std::string & name, const T & value) { SetTrait(name, value); } template [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] - void SetVar(size_t id, const T & value) { - emp_assert(data_map.HasID(id), id); - data_map.Set(id, value); - } + void SetVar(size_t id, const T & value) { SetTrait(id, value); } }; From f4a242bf2ca56e5e78f93afcca7a34c1efb76dd6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 21 Aug 2021 17:08:12 -0400 Subject: [PATCH 101/445] Moved AnnotatedType class into its own file. --- source/core/AnnotatedType.hpp | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 source/core/AnnotatedType.hpp diff --git a/source/core/AnnotatedType.hpp b/source/core/AnnotatedType.hpp new file mode 100644 index 00000000..d26c0365 --- /dev/null +++ b/source/core/AnnotatedType.hpp @@ -0,0 +1,79 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file AnnotatedType.hpp + * @brief A base class to provide a DataMap and accessors to another class. + * @note Status: ALPHA + * + */ + +#ifndef MABE_ANNOTATED_TYPE_H +#define MABE_ANNOTATED_TYPE_H + +#include "emp/base/assert.hpp" +#include "emp/data/DataMap.hpp" +#include "emp/meta/TypeID.hpp" +#include "emp/tools/string_utils.hpp" + +namespace mabe { + + /// A generic base class implementing the use of dynamic traits via DataMaps. + class AnnotatedType { + private: + emp::DataMap data_map; ///< Dynamic variables assigned to this class. + + public: + emp::DataMap & GetDataMap() { return data_map; } + const emp::DataMap & GetDataMap() const { return data_map; } + + void SetDataMap(emp::DataMap & in_dm) { data_map = in_dm; } + + bool HasTraitID(size_t id) const { return data_map.HasID(id); } + bool HasTrait(const std::string & name) const { return data_map.HasName(name); } + template + bool TestTraitType(size_t id) const { return data_map.IsType(id); } + template + bool TestTraitType(const std::string & name) const { return data_map.IsType(name); } + + size_t GetTraitID(const std::string & name) const { return data_map.GetID(name); } + + template + T & GetTrait(size_t id) { return data_map.Get(id); } + + template + const T & GetTrait(size_t id) const { return data_map.Get(id); } + + template + T & GetTrait(const std::string & name) { return data_map.Get(name); } + + template + const T & GetTrait(const std::string & name) const { return data_map.Get(name); } + + template + T & SetTrait(size_t id, const T & val) { return data_map.Set(id, val); } + + template + T & SetTrait(const std::string & name, const T & val) { return data_map.Set(name, val); } + + emp::TypeID GetTraitType(size_t id) const { return data_map.GetType(id); } + emp::TypeID GetTraitType(const std::string & name) const { return data_map.GetType(name); } + + double GetTraitAsDouble(size_t id) const { return data_map.GetAsDouble(id); } + + double GetTraitAsDouble(size_t trait_id, emp::TypeID type_id) const { + return data_map.GetAsDouble(trait_id, type_id); + } + + std::string GetTraitAsString(size_t id) const { return data_map.GetAsString(id); } + + std::string GetTraitAsString(size_t trait_id, emp::TypeID type_id) const { + return data_map.GetAsString(trait_id, type_id); + } + }; + + +} + +#endif \ No newline at end of file From b978ec972129d6389a4fb0c8be8e534f7f8f876b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 21 Aug 2021 17:08:35 -0400 Subject: [PATCH 102/445] Shifted Organism into using external AnnotatedType file. --- source/core/Organism.hpp | 58 +--------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 510384ea..9317825f 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -29,69 +29,13 @@ #include "emp/base/assert.hpp" #include "emp/base/vector.hpp" -#include "emp/bits/BitVector.hpp" -#include "emp/data/DataMap.hpp" -#include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" +#include "AnnotatedType.hpp" #include "OrgType.hpp" namespace mabe { - /// A generic base class implementing the use of dynamic traits via DataMaps. - class AnnotatedType { - private: - emp::DataMap data_map; ///< Dynamic variables assigned to this class. - - public: - emp::DataMap & GetDataMap() { return data_map; } - const emp::DataMap & GetDataMap() const { return data_map; } - - void SetDataMap(emp::DataMap & in_dm) { data_map = in_dm; } - - bool HasTraitID(size_t id) const { return data_map.HasID(id); } - bool HasTrait(const std::string & name) const { return data_map.HasName(name); } - template - bool TestTraitType(size_t id) const { return data_map.IsType(id); } - template - bool TestTraitType(const std::string & name) const { return data_map.IsType(name); } - - size_t GetTraitID(const std::string & name) const { return data_map.GetID(name); } - - template - T & GetTrait(size_t id) { return data_map.Get(id); } - - template - const T & GetTrait(size_t id) const { return data_map.Get(id); } - - template - T & GetTrait(const std::string & name) { return data_map.Get(name); } - - template - const T & GetTrait(const std::string & name) const { return data_map.Get(name); } - - template - T & SetTrait(size_t id, const T & val) { return data_map.Set(id, val); } - - template - T & SetTrait(const std::string & name, const T & val) { return data_map.Set(name, val); } - - emp::TypeID GetTraitType(size_t id) const { return data_map.GetType(id); } - emp::TypeID GetTraitType(const std::string & name) const { return data_map.GetType(name); } - - double GetTraitAsDouble(size_t id) const { return data_map.GetAsDouble(id); } - - double GetTraitAsDouble(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsDouble(trait_id, type_id); - } - - std::string GetTraitAsString(size_t id) const { return data_map.GetAsString(id); } - - std::string GetTraitAsString(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsString(trait_id, type_id); - } - }; - class Organism : public OrgType, public AnnotatedType { public: Organism(ModuleBase & _man) : OrgType(_man) { ; } From a2053104d9bbcdadaa94ccebf4364d6af8f4fe09 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 22 Aug 2021 11:29:54 -0400 Subject: [PATCH 103/445] Added argument descriptions for all MABE signals. --- source/core/Module.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index e4464a4b..db8a8465 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -176,6 +176,7 @@ namespace mabe { // Format: BeforeUpdate(size_t update_ending) // Trigger: Update is ending; new one is about to start + // Args: Update ID that is just finishing. void BeforeUpdate(size_t) override { has_signal[SIG_BeforeUpdate] = false; control.RescanSignals(); @@ -183,6 +184,7 @@ namespace mabe { // Format: OnUpdate(size_t new_update) // Trigger: New update has just started. + // Args: Update ID just starting. void OnUpdate(size_t) override { has_signal[SIG_OnUpdate] = false; control.RescanSignals(); @@ -190,6 +192,7 @@ namespace mabe { // Format: BeforeRepro(OrgPosition parent_pos) // Trigger: Parent is about to reproduce. + // Args: Position of organism about to reproduce. void BeforeRepro(OrgPosition) override { has_signal[SIG_BeforeRepro] = false; control.RescanSignals(); @@ -197,6 +200,7 @@ namespace mabe { // Format: OnOffspringReady(Organism & offspring, OrgPosition parent_pos, Population & target_pop) // Trigger: Offspring is ready to be placed. + // Args: Offspring to be born, position of parent, population to place offspring in. void OnOffspringReady(Organism &, OrgPosition, Population &) override { has_signal[SIG_OnOffspringReady] = false; control.RescanSignals(); @@ -204,6 +208,7 @@ namespace mabe { // Format: OnInjectReady(Organism & inject_org, Population & target_pop) // Trigger: Organism to be injected is ready to be placed. + // Args: Organism to be injected, population to inject into. void OnInjectReady(Organism &, Population &) override { has_signal[SIG_OnInjectReady] = false; control.RescanSignals(); @@ -227,6 +232,7 @@ namespace mabe { // Format: BeforeMutate(Organism & org) // Trigger: Mutate is about to run on an organism. + // Args: Organism about to mutate. void BeforeMutate(Organism &) override { has_signal[SIG_BeforeMutate] = false; control.RescanSignals(); @@ -234,6 +240,7 @@ namespace mabe { // Format: OnMutate(Organism & org) // Trigger: Organism has had its genome changed due to mutation. + // Args: Organism that just mutated. void OnMutate(Organism &) override { has_signal[SIG_OnMutate] = false; control.RescanSignals(); @@ -241,6 +248,7 @@ namespace mabe { // Format: BeforeDeath(OrgPosition remove_pos) // Trigger: Organism is about to die. + // Args: Position of organism about to die. void BeforeDeath(OrgPosition) override { has_signal[SIG_BeforeDeath] = false; control.RescanSignals(); @@ -248,6 +256,7 @@ namespace mabe { // Format: BeforeSwap(OrgPosition pos1, OrgPosition pos2) // Trigger: Two organisms' positions in the population are about to move. + // Args: Positions of organisms about to be swapped. void BeforeSwap(OrgPosition, OrgPosition) override { has_signal[SIG_BeforeSwap] = false; control.RescanSignals(); @@ -255,6 +264,7 @@ namespace mabe { // Format: OnSwap(OrgPosition pos1, OrgPosition pos2) // Trigger: Two organisms' positions in the population have just swapped. + // Args: Positions of organisms just swapped. void OnSwap(OrgPosition, OrgPosition) override { has_signal[SIG_OnSwap] = false; control.RescanSignals(); @@ -262,6 +272,7 @@ namespace mabe { // Format: BeforePopResize(Population & pop, size_t new_size) // Trigger: Full population is about to be resized. + // Args: Population about to be resized, the size it will become. void BeforePopResize(Population &, size_t) override { has_signal[SIG_BeforePopResize] = false; control.RescanSignals(); @@ -269,6 +280,7 @@ namespace mabe { // Format: OnPopResize(Population & pop, size_t old_size) // Trigger: Full population has just been resized. + // Args: Population just resized, previous size it was. void OnPopResize(Population &, size_t) override { has_signal[SIG_OnPopResize] = false; control.RescanSignals(); @@ -276,6 +288,7 @@ namespace mabe { // Format: OnError(const std::string & msg) // Trigger: An error has occurred and the user should be notified. + // Args: Message associated with this error. void OnError(const std::string &) override { has_signal[SIG_OnError] = false; control.RescanSignals(); @@ -283,6 +296,7 @@ namespace mabe { // Format: OnWarning(const std::string & msg) // Trigger: A atypical condition has occurred and the user should be notified. + // Args: Message associated with this warning. void OnWarning(const std::string &) override { has_signal[SIG_OnWarning] = false; control.RescanSignals(); From acba1b999e8124166182e2f85dd7f3cde729f7e3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 22 Aug 2021 13:03:14 -0400 Subject: [PATCH 104/445] Changed 'target_pop' to 'target' in CommandLine for Diagnostics.mabe. --- settings/Diagnostics.mabe | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/settings/Diagnostics.mabe b/settings/Diagnostics.mabe index f7126641..a352808f 100644 --- a/settings/Diagnostics.mabe +++ b/settings/Diagnostics.mabe @@ -6,7 +6,8 @@ Population main_pop; Population next_pop; CommandLine cl { // Handle basic I/O on the command line. - target_pop = "main_pop"; // Which population should we print stats about? + target = "main_pop"; // Which population should we print stats about? + format = "fitness:max,fitness:mean,fitness:min,fitness,vals,scores"; // Column format. } EvalDiagnostic eval { // Evaluate set of values with a specified diagnostic problem. @@ -93,6 +94,6 @@ ValsOrg vals_org { // Organism consisting of a series of N floating total_name = "total"; // Name of variable to contain total of all values. } -@start(0) print("random_seed = ", random_seed, "\n"); -@start(0) inject("vals_org", "main_pop", pop_size); +@start() print("random_seed = ", random_seed, "\n"); +@start() inject("vals_org", "main_pop", pop_size); @update(num_gens) exit(); From 38c6c9002c9b987726ef424aef7ccf029a8a7a41 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 23 Aug 2021 15:29:00 -0400 Subject: [PATCH 105/445] Fixed repeating events. --- source/config/ConfigEvents.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/config/ConfigEvents.hpp b/source/config/ConfigEvents.hpp index fadb37aa..030fafdd 100644 --- a/source/config/ConfigEvents.hpp +++ b/source/config/ConfigEvents.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file ConfigEvents.hpp * @brief Manages events for configurations. @@ -40,7 +40,7 @@ namespace mabe { double _next, double _repeat, double _max) : id(_id), ast_action(_node), next(_next), repeat(_repeat), max(_max), active(next <= max) { ; } - ~TimedEvent() { /* Do not delete ast_action; it will be handed in the main AST tree. */ } + ~TimedEvent() { /* Do not delete ast_action; it will be handled in the main AST tree. */ } // Trigger a single event as having occurred; return true/false base on whether this event // should continue to be considered active. @@ -49,8 +49,10 @@ namespace mabe { if (result_entry->IsTemporary()) result_entry.Delete(); next += repeat; + if (max != -1.0 && next > max) repeat = 0.0; + // Return "active" if we ARE repeating and the next time is stiil within range. - return (repeat != 0.0 && next <= max); + return (repeat != 0.0); } void Write(const std::string & command, std::ostream & os) const { From 5d5ba39070bbe620832f889de443242a36f79a11 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 23 Aug 2021 15:29:25 -0400 Subject: [PATCH 106/445] Updated DeveloperNotes for adding new signals. --- source/core/DeveloperNotes.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index f7b3ef41..c675e5f1 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -47,18 +47,23 @@ From a design perspective, we should keep the number of module signals to a mini decide that an additional signal is required, carefully follow the checklist below to add it. If any step is missed, the results can end up being hard to debug. -ModuleBase.h: +ModuleBase.hpp: * Add a description of the new signal in the comments at the top of the file. * Add a SIG_* id for the signal in the SignalID enum -* Declare a virtual member function to catch the signal in the SIGNALS section +* Declare a virtual member function to catch the signal in the SIGNALS section. +* Declare a virtal *_IsTriggered() to later test if we are currently reacting to signal. -Module.h: -* Overide the virtual member function for the signal (base method to catch the function) +Module.hpp: +* Override virtual function for signal (base method to mark function not used in module!) +* Override virtual function *_IsTriggered() to refer back to main MABE module. -MABE.h: -* Setup a new SigListener in the member variables section of MABEBase +MABEBase.hpp +* Setup a new SigListener in the member variables section * Initialize the new SigListener in the initializer section of the MABEBase constructor. -* Hook in a signal trigger in the appropriate place(s) + +MABE.hpp: +* Add a *_IsTriggered() function to test if the associated signal was triggered by a given module. +* Hook in a signal trigger in the appropriate place(s), as needed. # Next steps in Core MABE Development From 542b48ab4c798bc136c2c2ce3e05875c791cccaa Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 23 Aug 2021 15:30:22 -0400 Subject: [PATCH 107/445] Minor cleanups... --- source/core/OrgType.hpp | 4 +++- source/core/OrganismManager.hpp | 2 +- source/interface/CommandLine.hpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/core/OrgType.hpp b/source/core/OrgType.hpp index dda35b7c..23d66dc6 100644 --- a/source/core/OrgType.hpp +++ b/source/core/OrgType.hpp @@ -135,4 +135,6 @@ namespace mabe { virtual void SetupModule() { ; } }; -}; +} + +#endif diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index e3b4bec2..f9e376d3 100644 --- a/source/core/OrganismManager.hpp +++ b/source/core/OrganismManager.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file OrganismManager.hpp * @brief Track a category of organisms and maintain shared data within a category. diff --git a/source/interface/CommandLine.hpp b/source/interface/CommandLine.hpp index f9c957a9..6034c8a2 100644 --- a/source/interface/CommandLine.hpp +++ b/source/interface/CommandLine.hpp @@ -87,7 +87,7 @@ namespace mabe { } void BeforeExit() override { - std::cout << "Exiting." << std::endl; + std::cout << "==> Exiting." << std::endl; } void OnError(const std::string & msg) override { From c9c2d0264221e2e4ffcd9e8022a51426641b10f0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 24 Aug 2021 10:06:38 -0400 Subject: [PATCH 108/445] Added data collection for max or min ID. --- source/core/data_collect.hpp | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index ea2b5518..a51bac36 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -99,6 +99,45 @@ namespace emp { }; } + template + auto BuildCollectFun_MinID(FUN_T get_fun) { + return [get_fun](const CONTAIN_T & container) { + DATA_T min{}; + if constexpr (std::is_arithmetic_v) { + min = std::numeric_limits::max(); + } + else if constexpr (std::is_same_v) { + min = std::string('~',22); // '~' is ascii char 126 (last printable one.) + } + size_t id = 0; + size_t min_id = 0; + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val < min) { min = cur_val; min_id = id; } + ++id; + } + return emp::to_string(min_id); + }; + } + + template + auto BuildCollectFun_MaxID(FUN_T get_fun) { + return [get_fun](const CONTAIN_T & container) { + DATA_T max{}; + if constexpr (std::is_arithmetic_v) { + max = std::numeric_limits::lowest(); + } + size_t id = 0; + size_t max_id = 0; + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val > max) { max = cur_val; max_id = id; } + ++id; + } + return emp::to_string(max_id); + }; + } + template auto BuildCollectFun_Mean(FUN_T get_fun) { return [get_fun](const CONTAIN_T & container) { @@ -236,6 +275,16 @@ namespace emp { return emp::BuildCollectFun_Max(get_fun); } + // Return the lowest trait value. + else if (type == "min_id") { + return emp::BuildCollectFun_MinID(get_fun); + } + + // Return the highest trait value. + else if (type == "max_id") { + return emp::BuildCollectFun_MaxID(get_fun); + } + // Return the average trait value. else if (type == "ave" || type == "mean") { return emp::BuildCollectFun_Mean(get_fun); From 132afac11281c71fbeeb8f05a3e8c8e40b59f42e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 24 Aug 2021 10:07:32 -0400 Subject: [PATCH 109/445] Fixed 'output' function and cleaned up exiting. --- source/core/MABE.hpp | 46 ++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 41c8d199..fe0d718a 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -112,17 +112,6 @@ namespace mabe { // ----------- Helper Functions ----------- - /// Call when ready to end a run. - void Exit() { - // Let all modules know that exit is about to occur. - before_exit_sig.Trigger(); - - // @CAO: Other local cleanup in case destructor is not run due to early termination? - - // Exit as soon as possible. - exit_now = true; - } - /// Print information on how to run the software. void ShowHelp() { std::cout << "MABE v" << VERSION << "\n" @@ -135,7 +124,7 @@ namespace mabe { } on_help_sig.Trigger(); std::cout << "Note: Settings and files are applied in the order provided.\n"; - Exit(); + exit_now = true; } /// List all of the available modules included in the current compilation. @@ -149,7 +138,11 @@ namespace mabe { for (auto & info : GetModuleInfo()) { std::cout << " " << info.name << " : " << info.desc << "\n"; } - Exit(); + exit_now = true;; + } + + void TraceEval(Organism & org, std::ostream & os) { + trace_eval_sig.Trigger(org, os); } /// Process all of the arguments that were passed in on the command line. @@ -214,7 +207,7 @@ namespace mabe { for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { Update(); } - Exit(); + before_exit_sig.Trigger(); } // -- World Structure -- @@ -577,6 +570,7 @@ namespace mabe { bool OnError_IsTriggered(mod_ptr_t mod) { return on_error_sig.cur_mod == mod; }; bool OnWarning_IsTriggered(mod_ptr_t mod) { return on_warning_sig.cur_mod == mod; }; bool BeforeExit_IsTriggered(mod_ptr_t mod) { return before_exit_sig.cur_mod == mod; }; + bool TraceEval_IsTriggered(mod_ptr_t mod) { return trace_eval_sig.cur_mod == mod; }; bool OnHelp_IsTriggered(mod_ptr_t mod) { return on_help_sig.cur_mod == mod; }; bool DoPlaceBirth_IsTriggered(mod_ptr_t mod) { return do_place_birth_sig.cur_mod == mod; }; bool DoPlaceInject_IsTriggered(mod_ptr_t mod) { return do_place_inject_sig.cur_mod == mod; }; @@ -613,7 +607,7 @@ namespace mabe { // Add other built-in functions to the config file. // 'exit' should terminate a run. - std::function exit_fun = [this](){ Exit(); return 0; }; + std::function exit_fun = [this](){ exit_now = true; return 0; }; config.AddFunction("exit", exit_fun, "Exit from this MABE run."); @@ -635,14 +629,14 @@ namespace mabe { config.AddFunction("print", print_fun, "Print out the provided variable."); // 'output' will collect data and write it to a file. - files.SetIODefaultFile(); // String manager should use files. + files.SetOutputDefaultFile(); // Stream manager should default to files for output. std::function output_fun = [this](const std::string & filename, const std::string & collection, std::string format) { - emp::vector funs; ///< Functions to call each update. - const bool file_exists = files.Has(filename); ///< Determine if file is already setup. - std::iostream & file = files.GetIOStream(filename); ///< File to write to. - auto fun_it = file_fun_cache.find(format); + emp::vector funs; ///< Functions to call each update. + const bool file_exists = files.Has(filename); ///< Is file is already setup? + std::ostream & file = files.GetOutputStream(filename); ///< File to write to. emp::remove_whitespace(format); + auto fun_it = file_fun_cache.find(format); // If we need headers, set them up! if (!file_exists) { @@ -673,6 +667,7 @@ namespace mabe { // Insert the new entry into the cache and update the iterator. fun_it = file_fun_cache.insert({format, funs}).first; } + else funs = fun_it->second; // And, finally, print the data! Collection target_collect = FromString(collection); @@ -684,7 +679,8 @@ namespace mabe { return 0; }; - config.AddFunction("output", output_fun, "Print out the provided variable."); + config.AddFunction("output", output_fun, + "Print out the provided trait-based data; args: filename, collection, format."); // Add in built-in event triggers; these are used to indicate when events should happen. config.AddEventType("start"); // Triggered at the beginning of a run. @@ -713,7 +709,7 @@ namespace mabe { if (gen_filename != "") { std::cout << "Generating file '" << gen_filename << "'." << std::endl; config.Write(gen_filename); - Exit(); + exit_now = true; } // If any of the inital flags triggered an 'exit_now', do so. @@ -761,13 +757,13 @@ namespace mabe { [this](const emp::vector & in) { if (in.size() != 1) { std::cout << "'--generate' must be followed by a single filename.\n"; - Exit(); + exit_now = true; } else { // MABE Config files should be generated FROM a *.gen file, typically creating a *.mabe // file. If output file is *.gen assume an error. (for now; override should be allowed) if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { error_man.AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); - Exit(); + exit_now = true; } else gen_filename = in[0]; } @@ -784,7 +780,7 @@ namespace mabe { arg_set.emplace_back("--version", "-v", " ", "Version ID of MABE", [this](const emp::vector &){ std::cout << "MABE v" << VERSION << "\n"; - Exit(); + exit_now = true; }); arg_set.emplace_back("--verbose", "-+", " ", "Output extra setup info", [this](const emp::vector &){ verbose = true; } ); From b41e78ac9192c1959332e3f314a31544c7bfedc2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 25 Aug 2021 11:31:56 -0400 Subject: [PATCH 110/445] Added a format to CommandLine output in Mancala config. --- settings/Mancala.mabe | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/settings/Mancala.mabe b/settings/Mancala.mabe index ed094f25..79c62f90 100644 --- a/settings/Mancala.mabe +++ b/settings/Mancala.mabe @@ -1,9 +1,10 @@ -random_seed = 0; // Seed for random number generator; use 0 to base on time. +random_seed = 1; // Seed for random number generator; use 0 to base on time. Population main_pop; // Collection of organisms Population next_pop; // Collection of organisms Value pop_size = 200; // Local value variable. CommandLine cl { // Handle basic I/O on the command line. - target_pop = "main_pop"; // Which population should we print stats about? + target = "main_pop"; // Which population should we print stats about? + format = "score:max,score:mean";// Column format to use in the file. } FileOutput output { // Output collected data into a specified file. filename = "output.csv"; // Name of file for output data. @@ -28,6 +29,7 @@ SelectTournament select_t { // Select the top fitness organisms from random num_tournaments = pop_size; // Number of tournaments to run fitness_trait = "score"; // Which trait provides the fitness value to use? } + GrowthPlacement place_next { // Always appened births to the end of a population. target = "main_pop,next_pop"; // Population(s) to manage. } From da2e32bed28868b6a550ac40d840537e09bb7c73 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 27 Aug 2021 16:52:40 -0400 Subject: [PATCH 111/445] Added a size() function to the scripting language. --- source/core/MABE.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index fe0d718a..702971aa 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -513,6 +513,26 @@ namespace mabe { trait_filter.erase(0,1); // Erase the '=' and we are left with the string to match. } + // // If the filter begins with a $, convert the rest to an ID and use it. + // else if (trait_filter[0] == '$') { + // // Make sure proper parentheses are used after $. + // if (trait_filter[1] != '(' || trait_filter.back() != ')') { + // error_man.AddError("$ specifier must be followed by parens; '", trait_filter, "' invalid."); + // } + + // // Determine the function to be converted. + // std::string new_filter = emp::string_get_range(trait_filter, 2, trait_filter.size()-1); + // std::string new_name = emp::string_pop(trait_filter,':'); + + // // Build the function that will give us the ID we need. + // auto in_fun = BuildTraitFunction(new_name, new_filter); + + // return [get_fun,index](const CONTAIN_T & container) { + // if (container.size() <= index) return "Nan"s; + // return emp::to_string( get_fun( container.At(index) ) ); + // }; + // } + // Otherwise pass along to the BuildCollectFun with the correct type... auto result = is_numeric ? emp::BuildCollectFun(trait_filter, get_double_fun) @@ -682,6 +702,13 @@ namespace mabe { config.AddFunction("output", output_fun, "Print out the provided trait-based data; args: filename, collection, format."); + // @CAO Should have this work with a Poplation variable, not by name. + std::function pop_size_fun = + [this](const std::string & pop_name) { + return GetPopulation(GetPopID(pop_name)).GetSize(); + }; + config.AddFunction("size", pop_size_fun, "Return the size of the target population."); + // Add in built-in event triggers; these are used to indicate when events should happen. config.AddEventType("start"); // Triggered at the beginning of a run. config.AddEventType("update"); // Tested every update. From 7f640fed5d97b6520c738fe46b5a8cc926d7559a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 28 Aug 2021 17:16:59 -0400 Subject: [PATCH 112/445] Adjusted BuildCollectFun_Index() to just CollectFun_Index()... must convert others... --- source/core/data_collect.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index a51bac36..872f4d55 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -23,13 +23,11 @@ namespace emp { - // Count up the number of distinct values. - template - auto BuildCollectFun_Index(FUN_T get_fun, const size_t index) { - return [get_fun,index](const CONTAIN_T & container) { - if (container.size() <= index) return "Nan"s; - return emp::to_string( get_fun( container.At(index) ) ); - }; + // Return the value at a specified index. + template + std::string CollectFun_Index(const CONTAIN_T & container, FUN_T get_fun, const size_t index) { + if (container.size() <= index) return "Nan"s; + return emp::to_string( get_fun( container.At(index) ) ); } @@ -252,7 +250,9 @@ namespace emp { // Return the index if a simple number was provided. if (emp::is_digits(type)) { size_t index = emp::from_string(type); - return emp::BuildCollectFun_Index(get_fun, index); + return [get_fun,index](const CONTAIN_T & container) { + return emp::CollectFun_Index(container, get_fun, index); + }; } // Return the number of distinct values found in this trait. From 9bcd4303befacc129a2442e51c00dfa71907aa5f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 28 Aug 2021 18:14:09 -0400 Subject: [PATCH 113/445] Adjusted remaining BuildCollectFun_*() functions. --- source/core/data_collect.hpp | 354 +++++++++++++++++------------------ 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 872f4d55..cbc50ca6 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -33,211 +33,187 @@ namespace emp { // Count up the number of distinct values. template - auto BuildCollectFun_Unique(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - std::unordered_set vals; - for (const auto & entry : container) { - vals.insert( get_fun(entry) ); - } - return emp::to_string(vals.size()); - }; + auto CollectFun_Unique(const CONTAIN_T & container, FUN_T get_fun) { + std::unordered_set vals; + for (const auto & entry : container) { + vals.insert( get_fun(entry) ); + } + return emp::to_string(vals.size()); } template - auto BuildCollectFun_Mode(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - std::map vals; - for (const auto & entry : container) { - vals[ get_fun(entry) ]++; - } - DATA_T mode_val; - size_t mode_count = 0; - - for (auto [cur_val, cur_count] : vals) { - if (cur_count > mode_count) { - mode_count = cur_count; - mode_val = cur_val; - } - } - return emp::to_string(mode_val); - }; - } + auto CollectFun_Mode(const CONTAIN_T & container, FUN_T get_fun) { + std::map vals; + for (const auto & entry : container) { + vals[ get_fun(entry) ]++; + } + DATA_T mode_val; + size_t mode_count = 0; - template - auto BuildCollectFun_Min(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - DATA_T min{}; - if constexpr (std::is_arithmetic_v) { - min = std::numeric_limits::max(); + for (auto [cur_val, cur_count] : vals) { + if (cur_count > mode_count) { + mode_count = cur_count; + mode_val = cur_val; } - else if constexpr (std::is_same_v) { - min = std::string('~',22); // '~' is ascii char 126 (last printable one.) - } - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val < min) min = cur_val; - } - return emp::to_string(min); - }; + } + return emp::to_string(mode_val); } template - auto BuildCollectFun_Max(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - DATA_T max{}; - if constexpr (std::is_arithmetic_v) { - max = std::numeric_limits::lowest(); - } - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val > max) max = cur_val; - } - return emp::to_string(max); - }; + auto CollectFun_Min(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T min{}; + if constexpr (std::is_arithmetic_v) { + min = std::numeric_limits::max(); + } + else if constexpr (std::is_same_v) { + min = std::string('~',22); // '~' is ascii char 126 (last printable one.) + } + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val < min) min = cur_val; + } + return emp::to_string(min); } template - auto BuildCollectFun_MinID(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - DATA_T min{}; - if constexpr (std::is_arithmetic_v) { - min = std::numeric_limits::max(); - } - else if constexpr (std::is_same_v) { - min = std::string('~',22); // '~' is ascii char 126 (last printable one.) - } - size_t id = 0; - size_t min_id = 0; - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val < min) { min = cur_val; min_id = id; } - ++id; - } - return emp::to_string(min_id); - }; + auto CollectFun_Max(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T max{}; + if constexpr (std::is_arithmetic_v) { + max = std::numeric_limits::lowest(); + } + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val > max) max = cur_val; + } + return emp::to_string(max); } template - auto BuildCollectFun_MaxID(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - DATA_T max{}; - if constexpr (std::is_arithmetic_v) { - max = std::numeric_limits::lowest(); - } - size_t id = 0; - size_t max_id = 0; - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val > max) { max = cur_val; max_id = id; } - ++id; - } - return emp::to_string(max_id); - }; + auto CollectFun_MinID(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T min{}; + if constexpr (std::is_arithmetic_v) { + min = std::numeric_limits::max(); + } + else if constexpr (std::is_same_v) { + min = std::string('~',22); // '~' is ascii char 126 (last printable one.) + } + size_t id = 0; + size_t min_id = 0; + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val < min) { min = cur_val; min_id = id; } + ++id; + } + return emp::to_string(min_id); } template - auto BuildCollectFun_Mean(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - size_t count = 0; - for (const auto & entry : container) { - total += (double) get_fun(entry); - count++; - } - return emp::to_string( total / count ); - } - return std::string{"nan"}; - }; + auto CollectFun_MaxID(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T max{}; + if constexpr (std::is_arithmetic_v) { + max = std::numeric_limits::lowest(); + } + size_t id = 0; + size_t max_id = 0; + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val > max) { max = cur_val; max_id = id; } + ++id; + } + return emp::to_string(max_id); } template - auto BuildCollectFun_Median(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - emp::vector values(container.size()); + auto CollectFun_Mean(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; size_t count = 0; for (const auto & entry : container) { - values[count++] = get_fun(entry); + total += (double) get_fun(entry); + count++; } - emp::Sort(values); - return emp::to_string( values[count/2] ); - }; + return emp::to_string( total / count ); + } + return std::string{"nan"}; } template - auto BuildCollectFun_Variance(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - const double N = (double) container.size(); - for (const auto & entry : container) { - total += (double) get_fun(entry); - } - double mean = total / N; - double var_total = 0.0; - for (const auto & entry : container) { - double cur_val = mean - (double) get_fun(entry); - var_total += cur_val * cur_val; - } - - return emp::to_string( var_total / (N-1) ); - } - return std::string{"nan"}; - }; + auto CollectFun_Median(const CONTAIN_T & container, FUN_T get_fun) { + emp::vector values(container.size()); + size_t count = 0; + for (const auto & entry : container) { + values[count++] = get_fun(entry); + } + emp::Sort(values); + return emp::to_string( values[count/2] ); } template - auto BuildCollectFun_StandardDeviation(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - const double N = (double) container.size(); - for (const auto & entry : container) { - total += (double) get_fun(entry); - } - double mean = total / N; - double var_total = 0.0; - for (const auto & entry : container) { - double cur_val = mean - (double) get_fun(entry); - var_total += cur_val * cur_val; - } - - return emp::to_string( sqrt(var_total / (N-1)) ); + auto CollectFun_Variance(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + const double N = (double) container.size(); + for (const auto & entry : container) { + total += (double) get_fun(entry); } - return std::string{"nan"}; - }; + double mean = total / N; + double var_total = 0.0; + for (const auto & entry : container) { + double cur_val = mean - (double) get_fun(entry); + var_total += cur_val * cur_val; + } + + return emp::to_string( var_total / (N-1) ); + } + return std::string{"nan"}; } template - auto BuildCollectFun_Sum(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - for (const auto & entry : container) { - total += (double) get_fun(entry); - } - return emp::to_string( total ); + auto CollectFun_StandardDeviation(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + const double N = (double) container.size(); + for (const auto & entry : container) { + total += (double) get_fun(entry); + } + double mean = total / N; + double var_total = 0.0; + for (const auto & entry : container) { + double cur_val = mean - (double) get_fun(entry); + var_total += cur_val * cur_val; } - return std::string{"nan"}; - }; + + return emp::to_string( sqrt(var_total / (N-1)) ); + } + return std::string{"nan"}; } template - auto BuildCollectFun_Entropy(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { - std::map vals; + auto CollectFun_Sum(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; for (const auto & entry : container) { - vals[ get_fun(entry) ]++; - } - const size_t N = container.size(); - double entropy = 0.0; - for (auto [entry, count] : vals) { - double p = ((double) count) / (double) N; - entropy -= p * log2(p); + total += (double) get_fun(entry); } - return emp::to_string(entropy); - }; + return emp::to_string( total ); + } + return std::string{"nan"}; + } + + template + auto CollectFun_Entropy(const CONTAIN_T & container, FUN_T get_fun) { + std::map vals; + for (const auto & entry : container) { + vals[ get_fun(entry) ]++; + } + const size_t N = container.size(); + double entropy = 0.0; + for (auto [entry, count] : vals) { + double p = ((double) count) / (double) N; + entropy -= p * log2(p); + } + return emp::to_string(entropy); } template @@ -257,62 +233,86 @@ namespace emp { // Return the number of distinct values found in this trait. else if (type == "unique" || type == "richness") { - return emp::BuildCollectFun_Unique(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Unique(container, get_fun); + }; } // Return the most common value found for this trait. else if (type == "mode" || type == "dom" || type == "dominant") { - return emp::BuildCollectFun_Mode(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Mode(container, get_fun); + }; } // Return the lowest trait value. else if (type == "min") { - return emp::BuildCollectFun_Min(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Min(container, get_fun); + }; } // Return the highest trait value. else if (type == "max") { - return emp::BuildCollectFun_Max(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Max(container, get_fun); + }; } // Return the lowest trait value. else if (type == "min_id") { - return emp::BuildCollectFun_MinID(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_MinID(container, get_fun); + }; } // Return the highest trait value. else if (type == "max_id") { - return emp::BuildCollectFun_MaxID(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_MaxID(container, get_fun); + }; } // Return the average trait value. else if (type == "ave" || type == "mean") { - return emp::BuildCollectFun_Mean(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Mean(container, get_fun); + }; } // Return the middle-most trait value. else if (type == "median") { - return emp::BuildCollectFun_Median(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Median(container, get_fun); + }; } // Return the standard deviation of all trait values. else if (type == "variance") { - return emp::BuildCollectFun_Variance(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Variance(container, get_fun); + }; } // Return the standard deviation of all trait values. else if (type == "stddev") { - return emp::BuildCollectFun_StandardDeviation(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_StandardDeviation(container, get_fun); + }; } // Return the total of all trait values. else if (type == "sum" || type=="total") { - return emp::BuildCollectFun_Sum(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Sum(container, get_fun); + }; } // Return the entropy of values for this trait. else if (type == "entropy") { - return emp::BuildCollectFun_Entropy(get_fun); + return [get_fun](const CONTAIN_T & container) { + return emp::CollectFun_Entropy(container, get_fun); + }; } return std::function(); From 1b43ededb7b49b7fbf495d328f4b340b09ffb6e4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 29 Aug 2021 10:12:38 -0400 Subject: [PATCH 114/445] Adusted data_collect.hpp to use the DataCollect namespace for collection functions. --- source/core/data_collect.hpp | 350 ++++++++++++++++++----------------- 1 file changed, 176 insertions(+), 174 deletions(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index cbc50ca6..372cde58 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -22,199 +22,201 @@ #include "emp/tools/string_utils.hpp" namespace emp { + namespace DataCollect { - // Return the value at a specified index. - template - std::string CollectFun_Index(const CONTAIN_T & container, FUN_T get_fun, const size_t index) { - if (container.size() <= index) return "Nan"s; - return emp::to_string( get_fun( container.At(index) ) ); - } - - - // Count up the number of distinct values. - template - auto CollectFun_Unique(const CONTAIN_T & container, FUN_T get_fun) { - std::unordered_set vals; - for (const auto & entry : container) { - vals.insert( get_fun(entry) ); + // Return the value at a specified index. + template + std::string Index(const CONTAIN_T & container, FUN_T get_fun, const size_t index) { + if (container.size() <= index) return "Nan"s; + return emp::to_string( get_fun( container.At(index) ) ); } - return emp::to_string(vals.size()); - } - - template - auto CollectFun_Mode(const CONTAIN_T & container, FUN_T get_fun) { - std::map vals; - for (const auto & entry : container) { - vals[ get_fun(entry) ]++; - } - DATA_T mode_val; - size_t mode_count = 0; - for (auto [cur_val, cur_count] : vals) { - if (cur_count > mode_count) { - mode_count = cur_count; - mode_val = cur_val; + // Count up the number of distinct values. + template + auto Unique(const CONTAIN_T & container, FUN_T get_fun) { + std::unordered_set vals; + for (const auto & entry : container) { + vals.insert( get_fun(entry) ); } + return emp::to_string(vals.size()); } - return emp::to_string(mode_val); - } - - template - auto CollectFun_Min(const CONTAIN_T & container, FUN_T get_fun) { - DATA_T min{}; - if constexpr (std::is_arithmetic_v) { - min = std::numeric_limits::max(); - } - else if constexpr (std::is_same_v) { - min = std::string('~',22); // '~' is ascii char 126 (last printable one.) - } - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val < min) min = cur_val; - } - return emp::to_string(min); - } - - template - auto CollectFun_Max(const CONTAIN_T & container, FUN_T get_fun) { - DATA_T max{}; - if constexpr (std::is_arithmetic_v) { - max = std::numeric_limits::lowest(); - } - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val > max) max = cur_val; - } - return emp::to_string(max); - } - template - auto CollectFun_MinID(const CONTAIN_T & container, FUN_T get_fun) { - DATA_T min{}; - if constexpr (std::is_arithmetic_v) { - min = std::numeric_limits::max(); - } - else if constexpr (std::is_same_v) { - min = std::string('~',22); // '~' is ascii char 126 (last printable one.) - } - size_t id = 0; - size_t min_id = 0; - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val < min) { min = cur_val; min_id = id; } - ++id; - } - return emp::to_string(min_id); - } - template - auto CollectFun_MaxID(const CONTAIN_T & container, FUN_T get_fun) { - DATA_T max{}; - if constexpr (std::is_arithmetic_v) { - max = std::numeric_limits::lowest(); - } - size_t id = 0; - size_t max_id = 0; - for (const auto & entry : container) { - const DATA_T cur_val = get_fun(entry); - if (cur_val > max) { max = cur_val; max_id = id; } - ++id; + template + auto Mode(const CONTAIN_T & container, FUN_T get_fun) { + std::map vals; + for (const auto & entry : container) { + vals[ get_fun(entry) ]++; + } + DATA_T mode_val; + size_t mode_count = 0; + + for (auto [cur_val, cur_count] : vals) { + if (cur_count > mode_count) { + mode_count = cur_count; + mode_val = cur_val; + } + } + return emp::to_string(mode_val); } - return emp::to_string(max_id); - } - template - auto CollectFun_Mean(const CONTAIN_T & container, FUN_T get_fun) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - size_t count = 0; + template + auto Min(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T min{}; + if constexpr (std::is_arithmetic_v) { + min = std::numeric_limits::max(); + } + else if constexpr (std::is_same_v) { + min = std::string('~',22); // '~' is ascii char 126 (last printable one.) + } for (const auto & entry : container) { - total += (double) get_fun(entry); - count++; + const DATA_T cur_val = get_fun(entry); + if (cur_val < min) min = cur_val; } - return emp::to_string( total / count ); + return emp::to_string(min); } - return std::string{"nan"}; - } - template - auto CollectFun_Median(const CONTAIN_T & container, FUN_T get_fun) { - emp::vector values(container.size()); - size_t count = 0; - for (const auto & entry : container) { - values[count++] = get_fun(entry); + template + auto Max(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T max{}; + if constexpr (std::is_arithmetic_v) { + max = std::numeric_limits::lowest(); + } + for (const auto & entry : container) { + const DATA_T cur_val = get_fun(entry); + if (cur_val > max) max = cur_val; + } + return emp::to_string(max); } - emp::Sort(values); - return emp::to_string( values[count/2] ); - } - template - auto CollectFun_Variance(const CONTAIN_T & container, FUN_T get_fun) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - const double N = (double) container.size(); - for (const auto & entry : container) { - total += (double) get_fun(entry); + template + auto MinID(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T min{}; + if constexpr (std::is_arithmetic_v) { + min = std::numeric_limits::max(); } - double mean = total / N; - double var_total = 0.0; + else if constexpr (std::is_same_v) { + min = std::string('~',22); // '~' is ascii char 126 (last printable one.) + } + size_t id = 0; + size_t min_id = 0; for (const auto & entry : container) { - double cur_val = mean - (double) get_fun(entry); - var_total += cur_val * cur_val; + const DATA_T cur_val = get_fun(entry); + if (cur_val < min) { min = cur_val; min_id = id; } + ++id; } - - return emp::to_string( var_total / (N-1) ); + return emp::to_string(min_id); } - return std::string{"nan"}; - } - template - auto CollectFun_StandardDeviation(const CONTAIN_T & container, FUN_T get_fun) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; - const double N = (double) container.size(); - for (const auto & entry : container) { - total += (double) get_fun(entry); + template + auto MaxID(const CONTAIN_T & container, FUN_T get_fun) { + DATA_T max{}; + if constexpr (std::is_arithmetic_v) { + max = std::numeric_limits::lowest(); } - double mean = total / N; - double var_total = 0.0; + size_t id = 0; + size_t max_id = 0; for (const auto & entry : container) { - double cur_val = mean - (double) get_fun(entry); - var_total += cur_val * cur_val; + const DATA_T cur_val = get_fun(entry); + if (cur_val > max) { max = cur_val; max_id = id; } + ++id; } - - return emp::to_string( sqrt(var_total / (N-1)) ); + return emp::to_string(max_id); + } + + template + auto Mean(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + size_t count = 0; + for (const auto & entry : container) { + total += (double) get_fun(entry); + count++; + } + return emp::to_string( total / count ); + } + return std::string{"nan"}; } - return std::string{"nan"}; - } - template - auto CollectFun_Sum(const CONTAIN_T & container, FUN_T get_fun) { - if constexpr (std::is_arithmetic_v) { - double total = 0.0; + template + auto Median(const CONTAIN_T & container, FUN_T get_fun) { + emp::vector values(container.size()); + size_t count = 0; for (const auto & entry : container) { - total += (double) get_fun(entry); + values[count++] = get_fun(entry); + } + emp::Sort(values); + return emp::to_string( values[count/2] ); + } + + template + auto Variance(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + const double N = (double) container.size(); + for (const auto & entry : container) { + total += (double) get_fun(entry); + } + double mean = total / N; + double var_total = 0.0; + for (const auto & entry : container) { + double cur_val = mean - (double) get_fun(entry); + var_total += cur_val * cur_val; + } + + return emp::to_string( var_total / (N-1) ); + } + return std::string{"nan"}; + } + + template + auto StandardDeviation(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + const double N = (double) container.size(); + for (const auto & entry : container) { + total += (double) get_fun(entry); + } + double mean = total / N; + double var_total = 0.0; + for (const auto & entry : container) { + double cur_val = mean - (double) get_fun(entry); + var_total += cur_val * cur_val; + } + + return emp::to_string( sqrt(var_total / (N-1)) ); } - return emp::to_string( total ); + return std::string{"nan"}; } - return std::string{"nan"}; - } - template - auto CollectFun_Entropy(const CONTAIN_T & container, FUN_T get_fun) { - std::map vals; - for (const auto & entry : container) { - vals[ get_fun(entry) ]++; + template + auto Sum(const CONTAIN_T & container, FUN_T get_fun) { + if constexpr (std::is_arithmetic_v) { + double total = 0.0; + for (const auto & entry : container) { + total += (double) get_fun(entry); + } + return emp::to_string( total ); + } + return std::string{"nan"}; } - const size_t N = container.size(); - double entropy = 0.0; - for (auto [entry, count] : vals) { - double p = ((double) count) / (double) N; - entropy -= p * log2(p); + + template + auto Entropy(const CONTAIN_T & container, FUN_T get_fun) { + std::map vals; + for (const auto & entry : container) { + vals[ get_fun(entry) ]++; + } + const size_t N = container.size(); + double entropy = 0.0; + for (auto [entry, count] : vals) { + double p = ((double) count) / (double) N; + entropy -= p * log2(p); + } + return emp::to_string(entropy); } - return emp::to_string(entropy); - } + } // End namespace DataCollect template std::function @@ -227,91 +229,91 @@ namespace emp { if (emp::is_digits(type)) { size_t index = emp::from_string(type); return [get_fun,index](const CONTAIN_T & container) { - return emp::CollectFun_Index(container, get_fun, index); + return DataCollect::Index(container, get_fun, index); }; } // Return the number of distinct values found in this trait. else if (type == "unique" || type == "richness") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Unique(container, get_fun); + return DataCollect::Unique(container, get_fun); }; } // Return the most common value found for this trait. else if (type == "mode" || type == "dom" || type == "dominant") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Mode(container, get_fun); + return DataCollect::Mode(container, get_fun); }; } // Return the lowest trait value. else if (type == "min") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Min(container, get_fun); + return DataCollect::Min(container, get_fun); }; } // Return the highest trait value. else if (type == "max") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Max(container, get_fun); + return DataCollect::Max(container, get_fun); }; } // Return the lowest trait value. else if (type == "min_id") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_MinID(container, get_fun); + return DataCollect::MinID(container, get_fun); }; } // Return the highest trait value. else if (type == "max_id") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_MaxID(container, get_fun); + return DataCollect::MaxID(container, get_fun); }; } // Return the average trait value. else if (type == "ave" || type == "mean") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Mean(container, get_fun); + return DataCollect::Mean(container, get_fun); }; } // Return the middle-most trait value. else if (type == "median") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Median(container, get_fun); + return DataCollect::Median(container, get_fun); }; } // Return the standard deviation of all trait values. else if (type == "variance") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Variance(container, get_fun); + return DataCollect::Variance(container, get_fun); }; } // Return the standard deviation of all trait values. else if (type == "stddev") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_StandardDeviation(container, get_fun); + return DataCollect::StandardDeviation(container, get_fun); }; } // Return the total of all trait values. else if (type == "sum" || type=="total") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Sum(container, get_fun); + return DataCollect::Sum(container, get_fun); }; } // Return the entropy of values for this trait. else if (type == "entropy") { return [get_fun](const CONTAIN_T & container) { - return emp::CollectFun_Entropy(container, get_fun); + return DataCollect::Entropy(container, get_fun); }; } From 37565307fbf10d19b8557454cd22bc17a1d6456f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 31 Aug 2021 14:57:30 -0400 Subject: [PATCH 115/445] Redirected organism to be derived from AnnotatedType in Empirical. --- source/core/Organism.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 9317825f..e7d2fdb0 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -29,14 +29,14 @@ #include "emp/base/assert.hpp" #include "emp/base/vector.hpp" +#include "emp/data/AnnotatedType.hpp" #include "emp/tools/string_utils.hpp" -#include "AnnotatedType.hpp" #include "OrgType.hpp" namespace mabe { - class Organism : public OrgType, public AnnotatedType { + class Organism : public OrgType, public emp::AnnotatedType { public: Organism(ModuleBase & _man) : OrgType(_man) { ; } virtual ~Organism() {} From 83f0dafd939ee7067e9f1e4c3364bd464ee2fc68 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 31 Aug 2021 14:57:54 -0400 Subject: [PATCH 116/445] Removed AnnotatedType from mabe (we are now using Empirical's) --- source/core/AnnotatedType.hpp | 79 ----------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 source/core/AnnotatedType.hpp diff --git a/source/core/AnnotatedType.hpp b/source/core/AnnotatedType.hpp deleted file mode 100644 index d26c0365..00000000 --- a/source/core/AnnotatedType.hpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. - * - * @file AnnotatedType.hpp - * @brief A base class to provide a DataMap and accessors to another class. - * @note Status: ALPHA - * - */ - -#ifndef MABE_ANNOTATED_TYPE_H -#define MABE_ANNOTATED_TYPE_H - -#include "emp/base/assert.hpp" -#include "emp/data/DataMap.hpp" -#include "emp/meta/TypeID.hpp" -#include "emp/tools/string_utils.hpp" - -namespace mabe { - - /// A generic base class implementing the use of dynamic traits via DataMaps. - class AnnotatedType { - private: - emp::DataMap data_map; ///< Dynamic variables assigned to this class. - - public: - emp::DataMap & GetDataMap() { return data_map; } - const emp::DataMap & GetDataMap() const { return data_map; } - - void SetDataMap(emp::DataMap & in_dm) { data_map = in_dm; } - - bool HasTraitID(size_t id) const { return data_map.HasID(id); } - bool HasTrait(const std::string & name) const { return data_map.HasName(name); } - template - bool TestTraitType(size_t id) const { return data_map.IsType(id); } - template - bool TestTraitType(const std::string & name) const { return data_map.IsType(name); } - - size_t GetTraitID(const std::string & name) const { return data_map.GetID(name); } - - template - T & GetTrait(size_t id) { return data_map.Get(id); } - - template - const T & GetTrait(size_t id) const { return data_map.Get(id); } - - template - T & GetTrait(const std::string & name) { return data_map.Get(name); } - - template - const T & GetTrait(const std::string & name) const { return data_map.Get(name); } - - template - T & SetTrait(size_t id, const T & val) { return data_map.Set(id, val); } - - template - T & SetTrait(const std::string & name, const T & val) { return data_map.Set(name, val); } - - emp::TypeID GetTraitType(size_t id) const { return data_map.GetType(id); } - emp::TypeID GetTraitType(const std::string & name) const { return data_map.GetType(name); } - - double GetTraitAsDouble(size_t id) const { return data_map.GetAsDouble(id); } - - double GetTraitAsDouble(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsDouble(trait_id, type_id); - } - - std::string GetTraitAsString(size_t id) const { return data_map.GetAsString(id); } - - std::string GetTraitAsString(size_t trait_id, emp::TypeID type_id) const { - return data_map.GetAsString(trait_id, type_id); - } - }; - - -} - -#endif \ No newline at end of file From 17892e818e6578f37cf56f43a91dcb30829ccf7a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 31 Aug 2021 17:04:07 -0400 Subject: [PATCH 117/445] Added a TraceEval signal to ModuleBase.hpp --- source/core/ModuleBase.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index b28330b5..a1cc3572 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -56,6 +56,8 @@ * : Run immediately before MABE is about to exit. * OnHelp() * : Run when the --help option is called at startup. + * TraceEval(Organism & org, ostream & out_stream) + * : Print a trace of the evaluation of an organism. * ... * * - Various Do* functions run in modules until one of them returns a valid answer. @@ -142,6 +144,7 @@ namespace mabe { SIG_OnWarning, SIG_BeforeExit, SIG_OnHelp, + SIG_TraceEval, SIG_DoPlaceBirth, SIG_DoPlaceInject, SIG_DoFindNeighbor, @@ -256,6 +259,7 @@ namespace mabe { virtual void OnWarning(const std::string &) = 0; virtual void BeforeExit() = 0; virtual void OnHelp() = 0; + virtual void TraceEval(Organism &, std::ostream &) = 0; virtual OrgPosition DoPlaceBirth(Organism &, OrgPosition, Population &) = 0; virtual OrgPosition DoPlaceInject(Organism &, Population &) = 0; @@ -282,6 +286,7 @@ namespace mabe { virtual bool OnWarning_IsTriggered() = 0; virtual bool BeforeExit_IsTriggered() = 0; virtual bool OnHelp_IsTriggered() = 0; + virtual bool TraceEval_IsTriggered() = 0; virtual bool DoPlaceBirth_IsTriggered() = 0; virtual bool DoPlaceInject_IsTriggered() = 0; From d8bdb7e85c262a6c7624d46c61367de00fcde2b4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 31 Aug 2021 17:04:40 -0400 Subject: [PATCH 118/445] Added a TraceEval() default function to Module.hpp --- source/core/Module.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index db8a8465..eebd29aa 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -316,6 +316,14 @@ namespace mabe { control.RescanSignals(); } + // Format: TraceEval(Organism & trace_org, std::ostream & out_stream) + // Trigger: Request to print a trace of the evaluation of an organism. + // Args: Organism to be traces, stream to print trace to. + void TraceEval(Organism &, std::ostream &) override { + has_signal[SIG_TraceEval] = false; + control.RescanSignals(); + } + // Functions to be called based on actions that need to happen. Each of these returns a // viable result or an invalid object if need to pass on to the next module. Modules will @@ -379,6 +387,7 @@ namespace mabe { bool OnWarning_IsTriggered() override { return control.OnWarning_IsTriggered(this); }; bool BeforeExit_IsTriggered() override { return control.BeforeExit_IsTriggered(this); }; bool OnHelp_IsTriggered() override { return control.OnHelp_IsTriggered(this); }; + bool TraceEval_IsTriggered() override { return control.TraceEval_IsTriggered(this); }; bool DoPlaceBirth_IsTriggered() override { return control.DoPlaceBirth_IsTriggered(this); }; bool DoPlaceInject_IsTriggered() override { return control.DoPlaceInject_IsTriggered(this); }; bool DoFindNeighbor_IsTriggered() override { return control.DoFindNeighbor_IsTriggered(this); }; From 05a13a9d578d5b4d662463f06dde511b94caff07 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 31 Aug 2021 17:05:17 -0400 Subject: [PATCH 119/445] Setup the TraceEval signal object in MABEBase.hpp. --- source/core/MABEBase.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 0ff0d922..e70eb87b 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -63,9 +63,9 @@ namespace mabe { // OnSwap(OrgPosition pos1, OrgPosition pos2) SigListener on_swap_sig; // BeforePopResize(Population & pop, size_t new_size) - SigListener before_pop_resize_sig; + SigListener before_pop_resize_sig; // OnPopResize(Population & pop, size_t old_size) - SigListener on_pop_resize_sig; + SigListener on_pop_resize_sig; // OnError(const std::string & msg) SigListener on_error_sig; // OnWarning(const std::string & msg) @@ -74,6 +74,8 @@ namespace mabe { SigListener before_exit_sig; // OnHelp() SigListener on_help_sig; + // TraceEval() + SigListener trace_eval_sig; // OrgPosition DoPlaceBirth(Organism & offspring, OrgPosition parent_position, Population & target_pop); SigListener do_place_birth_sig; @@ -106,6 +108,7 @@ namespace mabe { , on_warning_sig("on_warning", ModuleBase::SIG_OnWarning, &ModuleBase::OnWarning, sig_ptrs) , before_exit_sig("before_exit", ModuleBase::SIG_BeforeExit, &ModuleBase::BeforeExit, sig_ptrs) , on_help_sig("on_help", ModuleBase::SIG_OnHelp, &ModuleBase::OnHelp, sig_ptrs) + , trace_eval_sig("trace_eval", ModuleBase::SIG_TraceEval, &ModuleBase::TraceEval, sig_ptrs) , do_place_birth_sig("do_place_birth", ModuleBase::SIG_DoPlaceBirth, &ModuleBase::DoPlaceBirth, sig_ptrs) , do_place_inject_sig("do_place_inject", ModuleBase::SIG_DoPlaceInject, &ModuleBase::DoPlaceInject, sig_ptrs) , do_find_neighbor_sig("do_find_neighbor", ModuleBase::SIG_DoFindNeighbor, &ModuleBase::DoFindNeighbor, sig_ptrs) From 3e1b59aeb8b4658a8f5094d301d985b9a287521c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 4 Sep 2021 14:44:54 -0400 Subject: [PATCH 120/445] Shift from using a vector of Tokens to a TokenStream (and iterators for positions). --- source/config/Config.hpp | 137 ++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index a279e953..ec4464d7 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -90,14 +90,16 @@ namespace mabe { std::function init_fun; }; + using pos_t = emp::TokenStream::Iterator; + protected: - std::string filename; ///< Source for for code to generate. - ConfigLexer lexer; ///< Lexer to process input code. - emp::vector tokens; ///< Tokenized version of input file. - ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = false; ///< Should we print full debug information? + std::string filename; ///< Source for for code to generate. + ConfigLexer lexer; ///< Lexer to process input code. + emp::TokenStream tokens; ///< Tokenized version of input file. + ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. + bool debug = false; ///< Should we print full debug information? - ConfigScope root_scope; ///< All variables from the root level. + ConfigScope root_scope; ///< All variables from the root level. /// A map of names to event groups. std::map events_map; @@ -109,38 +111,38 @@ namespace mabe { std::unordered_map precedence_map; // -- Helper functions -- - bool HasToken(int pos) const { return (pos >= 0) && (pos < (int) tokens.size()); } - bool IsID(int pos) const { return HasToken(pos) && lexer.IsID(tokens[pos]); } - bool IsNumber(int pos) const { return HasToken(pos) && lexer.IsNumber(tokens[pos]); } - bool IsChar(int pos) const { return HasToken(pos) && lexer.IsChar(tokens[pos]); } - bool IsString(int pos) const { return HasToken(pos) && lexer.IsString(tokens[pos]); } - bool IsDots(int pos) const { return HasToken(pos) && lexer.IsDots(tokens[pos]); } + bool IsID(pos_t pos) const { return pos.IsValid() && lexer.IsID(*pos); } + bool IsNumber(pos_t pos) const { return pos.IsValid() && lexer.IsNumber(*pos); } + bool IsChar(pos_t pos) const { return pos.IsValid() && lexer.IsChar(*pos); } + bool IsString(pos_t pos) const { return pos.IsValid() && lexer.IsString(*pos); } + bool IsDots(pos_t pos) const { return pos.IsValid() && lexer.IsDots(*pos); } - bool IsType(int pos) const { return HasToken(pos) && emp::Has(type_map, tokens[pos].lexeme); } + bool IsType(pos_t pos) const { return pos.IsValid() && emp::Has(type_map, pos->lexeme); } - char AsChar(int pos) const { - return (HasToken(pos) && lexer.IsSymbol(tokens[pos])) ? tokens[pos].lexeme[0] : 0; + char AsChar(pos_t pos) const { + return (pos.IsValid() && lexer.IsSymbol(*pos)) ? pos->lexeme[0] : 0; } - const std::string & AsLexeme(int pos) const { - return HasToken(pos) ? tokens[pos].lexeme : emp::empty_string(); + const std::string & AsLexeme(pos_t pos) const { + return pos.IsValid() ? pos->lexeme : emp::empty_string(); } - size_t GetSize(int pos) const { return HasToken(pos) ? tokens[pos].lexeme.size() : 0; } + size_t GetSize(pos_t pos) const { return pos.IsValid() ? pos->lexeme.size() : 0; } - std::string ConcatLexemes(size_t start_pos, size_t end_pos) const { + std::string ConcatLexemes(pos_t start_pos, pos_t end_pos) const { emp_assert(start_pos <= end_pos); - emp_assert(end_pos <= tokens.size()); - std::stringstream ss; - for (size_t i = start_pos; i < end_pos; i++) { - if (i > start_pos) ss << " "; // No space with labels. - ss << tokens[i].lexeme; - if (tokens[i].lexeme == ";") ss << " "; // Extra space after semi-colons for now... + emp_assert(start_pos.IsValid() && end_pos.IsValid()); + std::stringstream ss; + while (start_pos < end_pos) { + ss << start_pos->lexeme; + if (start_pos != end_pos) ss << " "; // spaces between tokens. + if (start_pos->lexeme == ";") ss << " "; // extra space after semi-colons for now... + ++start_pos; } return ss.str(); } template - void Error(int pos, Ts... args) const { - std::cout << "Error (line " << tokens[pos].line_id << "): " << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; + void Error(pos_t pos, Ts... args) const { + std::cout << "Error (line " << pos->line_id << "): " << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; exit(1); } @@ -150,40 +152,40 @@ namespace mabe { } template - void Require(bool result, int pos, Ts... args) const { + void Require(bool result, pos_t pos, Ts... args) const { if (!result) { Error(pos, std::forward(args)...); } } template - void RequireID(int pos, Ts... args) const { + void RequireID(pos_t pos, Ts... args) const { if (!IsID(pos)) { Error(pos, std::forward(args)...); } } template - void RequireNumber(int pos, Ts... args) const { + void RequireNumber(pos_t pos, Ts... args) const { if (!IsNumber(pos)) { Error(pos, std::forward(args)...); } } template - void RequireString(int pos, Ts... args) const { + void RequireString(pos_t pos, Ts... args) const { if (!IsString(pos)) { Error(pos, std::forward(args)...); } } template - void RequireChar(char req_char, int pos, Ts... args) const { + void RequireChar(char req_char, pos_t pos, Ts... args) const { if (AsChar(pos) != req_char) { Error(pos, std::forward(args)...); } } template - void RequireLexeme(const std::string & req_str, int pos, Ts... args) const { + void RequireLexeme(const std::string & req_str, pos_t pos, Ts... args) const { if (AsLexeme(pos) != req_str) { Error(pos, std::forward(args)...); } } /// Load a variable name from the provided scope. /// If create_ok is true, create any variables that we don't find. Otherwise continue the /// search for them in successively outer (lower) scopes. - [[nodiscard]] emp::Ptr ParseVar(size_t & pos, - ConfigScope & cur_scope, - bool create_ok=false, - bool scan_scopes=true); + [[nodiscard]] emp::Ptr ParseVar(pos_t & pos, + ConfigScope & cur_scope, + bool create_ok=false, + bool scan_scopes=true); /// Load a value from the provided scope, which can come from a variable or a literal. - [[nodiscard]] emp::Ptr ParseValue(size_t & pos, ConfigScope & cur_scope); + [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, ConfigScope & cur_scope); /// Calculate the result of the provided operation on two computed entries. [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, @@ -191,23 +193,23 @@ namespace mabe { emp::Ptr value2); /// Calculate a full expression found in a token sequence, using the provided scope. - [[nodiscard]] emp::Ptr ParseExpression(size_t & pos, ConfigScope & cur_scope, size_t prec_limit=1000); + [[nodiscard]] emp::Ptr ParseExpression(pos_t & pos, ConfigScope & cur_scope, size_t prec_limit=1000); /// Parse the declaration of a variable and return the newly created ConfigEntry - ConfigEntry & ParseDeclaration(size_t & pos, ConfigScope & scope); + ConfigEntry & ParseDeclaration(pos_t & pos, ConfigScope & scope); /// Parse an event description. - emp::Ptr ParseEvent(size_t & pos, ConfigScope & scope); + emp::Ptr ParseEvent(pos_t & pos, ConfigScope & scope); /// Parse the next input in the specified Struct. A statement can be a variable declaration, /// an expression, or an event. - [[nodiscard]] emp::Ptr ParseStatement(size_t & pos, ConfigScope & scope); + [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, ConfigScope & scope); /// Keep parsing statments until there aren't any more or we leave this scope. - [[nodiscard]] emp::Ptr ParseStatementList(size_t & pos, ConfigScope & scope) { - Debug("Running ParseStatementList(", pos, ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); + [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigScope & scope) { + Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); auto cur_block = emp::NewPtr(); - while (pos < tokens.size() && AsChar(pos) != '}') { + while (pos.IsValid() && AsChar(pos) != '}') { // Parse each statement in the file. emp::Ptr statement_node = ParseStatement(pos, scope); @@ -326,7 +328,7 @@ namespace mabe { std::ifstream file(filename); // Load the provided file. tokens = lexer.Tokenize(file); // Convert to more-usable tokens. file.close(); // Close the file (now that it's converted) - size_t pos = 0; // Start at the beginning of the file. + pos_t pos = tokens.begin(); // Start at the beginning of the file. // Parse and run the program, starting from the outer scope. auto cur_block = ParseStatementList(pos, root_scope); @@ -345,7 +347,7 @@ namespace mabe { void LoadStatements(const emp::vector & statements) { Debug("Running LoadStatements()"); tokens = lexer.Tokenize(statements); // Convert to more-usable tokens. - size_t pos = 0; // Start at the beginning of the file. + pos_t pos = tokens.begin(); // Start at the beginning of the file. // Parse and run the program, starting from the outer scope. auto cur_block = ParseStatementList(pos, root_scope); @@ -355,6 +357,11 @@ namespace mabe { ast_root.AddChild(cur_block); } + // Load the provided statement, run it, convert the result to a string, and return just that string. + std::string Eval(const std::string & statement) { + return statement + "@CAO IMPLEMENT!!!!"; + } + Config & Write(std::ostream & os=std::cout) { root_scope.WriteContents(os); @@ -378,11 +385,11 @@ namespace mabe { // Load a variable name from the provided scope. - emp::Ptr Config::ParseVar(size_t & pos, - ConfigScope & cur_scope, - bool create_ok, bool scan_scopes) + emp::Ptr Config::ParseVar(pos_t & pos, + ConfigScope & cur_scope, + bool create_ok, bool scan_scopes) { - Debug("Running ParseVar(", pos, ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); + Debug("Running ParseVar(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); // First, check for leading dots. if (IsDots(pos)) { @@ -393,7 +400,7 @@ namespace mabe { scope_ptr = scope_ptr->GetScope(); if (scope_ptr.IsNull()) Error(pos, "Too many dots; goes beyond global scope."); } - pos++; + ++pos; // Recursively call in the found scope if needed; given leading dot, do not scan scopes. if (scope_ptr.Raw() != &cur_scope) return ParseVar(pos, *scope_ptr, create_ok, false); @@ -433,8 +440,8 @@ namespace mabe { } // Load a value from the provided scope, which can come from a variable or a literal. - emp::Ptr Config::ParseValue(size_t & pos, ConfigScope & cur_scope) { - Debug("Running ParseValue(", pos, ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ")"); + emp::Ptr Config::ParseValue(pos_t & pos, ConfigScope & cur_scope) { + Debug("Running ParseValue(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ")"); // Anything that begins with an identifier or dots must represent a variable. Refer! if (IsID(pos) || IsDots(pos)) return ParseVar(pos, cur_scope, false, true); @@ -475,8 +482,8 @@ namespace mabe { // Process a single provided operation on two ConfigEntry objects. emp::Ptr Config::ProcessOperation(const std::string & symbol, - emp::Ptr in_node1, - emp::Ptr in_node2) + emp::Ptr in_node1, + emp::Ptr in_node2) { emp_assert(!in_node1.IsNull()); emp_assert(!in_node2.IsNull()); @@ -518,8 +525,8 @@ namespace mabe { // Calculate an expression in the provided scope. - emp::Ptr Config::ParseExpression(size_t & pos, ConfigScope & scope, size_t prec_limit) { - Debug("Running ParseExpression(", pos, ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); + emp::Ptr Config::ParseExpression(pos_t & pos, ConfigScope & scope, size_t prec_limit) { + Debug("Running ParseExpression(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); // @CAO Should test for unary operators at the beginning of an expression. @@ -527,7 +534,7 @@ namespace mabe { emp::Ptr cur_node = ParseValue(pos, scope); std::string symbol = AsLexeme(pos); while ( emp::Has(precedence_map, symbol) && precedence_map[symbol] < prec_limit ) { - pos++; + ++pos; // Do we have a function call? if (symbol == "(") { // Collect arguments. @@ -536,7 +543,7 @@ namespace mabe { emp::Ptr next_arg = ParseExpression(pos, scope); args.push_back(next_arg); // Save this argument. if (AsChar(pos) != ',') break; // If we don't have a comma, no more args! - pos++; // Move on to the next argument. + ++pos; // Move on to the next argument. } RequireChar(')', pos++, "Expected a ')' to end function call."); cur_node = emp::NewPtr(cur_node, args); @@ -557,7 +564,7 @@ namespace mabe { } // Parse an the declaration of a variable. - ConfigEntry & Config::ParseDeclaration(size_t & pos, ConfigScope & scope) { + ConfigEntry & Config::ParseDeclaration(pos_t & pos, ConfigScope & scope) { std::string type_name = AsLexeme(pos++); RequireID(pos, "Type name '", type_name, "' must be followed by variable to declare."); std::string var_name = AsLexeme(pos++); @@ -585,7 +592,7 @@ namespace mabe { } // Parse an event description. - emp::Ptr Config::ParseEvent(size_t & pos, ConfigScope & scope) { + emp::Ptr Config::ParseEvent(pos_t & pos, ConfigScope & scope) { RequireChar('@', pos++, "All event declarations must being with an '@'."); RequireID(pos, "Events must start by specifying event name."); const std::string & event_name = AsLexeme(pos++); @@ -614,8 +621,8 @@ namespace mabe { } // Process the next input in the specified Struct. - emp::Ptr Config::ParseStatement(size_t & pos, ConfigScope & scope) { - Debug("Running ParseStatement(", pos, ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); + emp::Ptr Config::ParseStatement(pos_t & pos, ConfigScope & scope) { + Debug("Running ParseStatement(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); // Allow a statement with an empty line. if (AsChar(pos) == ';') { pos++; return nullptr; } @@ -653,7 +660,7 @@ namespace mabe { } // Otherwise rewind so that variable can be used to start an expression. - pos--; + --pos; } From b63b6bd46ae08916bcad0b62b8cd37a98951530c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Sep 2021 14:31:38 -0400 Subject: [PATCH 121/445] Shifted TokenStreams to be used only locally when needed. --- source/config/Config.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index ec4464d7..54ff1ca4 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -95,7 +95,6 @@ namespace mabe { protected: std::string filename; ///< Source for for code to generate. ConfigLexer lexer; ///< Lexer to process input code. - emp::TokenStream tokens; ///< Tokenized version of input file. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. bool debug = false; ///< Should we print full debug information? @@ -326,7 +325,7 @@ namespace mabe { void Load(const std::string & filename) { Debug("Running Load(", filename, ")"); std::ifstream file(filename); // Load the provided file. - tokens = lexer.Tokenize(file); // Convert to more-usable tokens. + emp::TokenStream tokens = lexer.Tokenize(file); // Convert to more-usable tokens. file.close(); // Close the file (now that it's converted) pos_t pos = tokens.begin(); // Start at the beginning of the file. @@ -346,8 +345,8 @@ namespace mabe { // Load a single, specified configuration file. void LoadStatements(const emp::vector & statements) { Debug("Running LoadStatements()"); - tokens = lexer.Tokenize(statements); // Convert to more-usable tokens. - pos_t pos = tokens.begin(); // Start at the beginning of the file. + emp::TokenStream tokens = lexer.Tokenize(statements); // Convert to tokens. + pos_t pos = tokens.begin(); // Parse and run the program, starting from the outer scope. auto cur_block = ParseStatementList(pos, root_scope); From 50cda36b27955e5f0f69e843cf06c292a69b06cc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Sep 2021 15:28:33 -0400 Subject: [PATCH 122/445] Setup ASTNode_Block to track the scope for that block. --- source/config/ConfigAST.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index 4b06e281..8cbafe46 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -16,6 +16,7 @@ #include "emp/base/vector.hpp" #include "ConfigEntry.hpp" +#include "ConfigScope.hpp" namespace mabe { @@ -115,7 +116,12 @@ namespace mabe { }; class ASTNode_Block : public ASTNode_Internal { + protected: + emp::Ptr scope_ptr; + public: + ASTNode_Block(ConfigScope & in_scope) : scope_ptr(&in_scope) { } + entry_ptr_t Process() override { for (auto node : children) { entry_ptr_t out = node->Process(); From 688fe7502018eac029a9339b10064ac5453b1370 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Sep 2021 15:29:14 -0400 Subject: [PATCH 123/445] Provide scope info to AST blocks; started draft of Eval function. --- source/config/Config.hpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 54ff1ca4..3b32b5cb 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -95,10 +95,10 @@ namespace mabe { protected: std::string filename; ///< Source for for code to generate. ConfigLexer lexer; ///< Lexer to process input code. + ConfigScope root_scope; ///< All variables from the root level. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. bool debug = false; ///< Should we print full debug information? - ConfigScope root_scope; ///< All variables from the root level. /// A map of names to event groups. std::map events_map; @@ -207,7 +207,7 @@ namespace mabe { /// Keep parsing statments until there aren't any more or we leave this scope. [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigScope & scope) { Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); - auto cur_block = emp::NewPtr(); + auto cur_block = emp::NewPtr(scope); while (pos.IsValid() && AsChar(pos) != '}') { // Parse each statement in the file. emp::Ptr statement_node = ParseStatement(pos, scope); @@ -222,6 +222,7 @@ namespace mabe { Config(std::string in_filename="") : filename(in_filename) , root_scope("MABE", "Outer-most, global scope.", nullptr) + , ast_root(root_scope) { if (filename != "") Load(filename); @@ -357,8 +358,16 @@ namespace mabe { } // Load the provided statement, run it, convert the result to a string, and return just that string. - std::string Eval(const std::string & statement) { - return statement + "@CAO IMPLEMENT!!!!"; + std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { + Debug("Running Eval()"); + if (!scope) scope = &root_scope; // Default scope to root level. + emp::TokenStream tokens = lexer.Tokenize(statement); // Convert to tokens. + pos_t pos = tokens.begin(); // Start are beginning of stream. + auto cur_block = ParseStatementList(pos, root_scope); // Convert tokens to AST + auto result_ptr = cur_block->Process(); // Process AST to get result entry. + std::string result = result_ptr->AsString(); // Convert result to output string. + result_ptr.Delete(); // Delete the result entry. + return result; // Return the result string. } From 07f31fb949270161ba7a31e3db6231ab6b33476b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Sep 2021 13:35:47 -0400 Subject: [PATCH 124/445] Setup AST Nodes to be able to track parents and scope. --- source/config/ConfigAST.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index 8cbafe46..3c45e720 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -29,6 +29,8 @@ namespace mabe { using node_ptr_t = emp::Ptr; using node_vector_t = emp::vector; + node_ptr_t parent = nullptr; + // Helper functions. emp::Ptr MakeTempDouble(double val) { auto out_ptr = emp::NewPtr("temp", val, "Temporary double", nullptr); @@ -52,6 +54,9 @@ namespace mabe { virtual size_t GetNumChildren() const { return 0; } virtual node_ptr_t GetChild(size_t /* id */) { emp_assert(false); return nullptr; } + node_ptr_t GetParent() { return parent; } + void SetParent(node_ptr_t in_parent) { parent = in_parent; } + virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } virtual entry_ptr_t Process() = 0; @@ -122,6 +127,8 @@ namespace mabe { public: ASTNode_Block(ConfigScope & in_scope) : scope_ptr(&in_scope) { } + emp::Ptr GetScope() override { return scope_ptr; } + entry_ptr_t Process() override { for (auto node : children) { entry_ptr_t out = node->Process(); From bc1121ef8763a651afc5030f10fe8d1c2556f4c7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 9 Sep 2021 17:39:27 -0400 Subject: [PATCH 125/445] Added trait_mean as a config function. --- source/core/MABE.hpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 702971aa..67192a29 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -520,14 +520,14 @@ namespace mabe { // error_man.AddError("$ specifier must be followed by parens; '", trait_filter, "' invalid."); // } - // // Determine the function to be converted. + // // Determine the variable to use. // std::string new_filter = emp::string_get_range(trait_filter, 2, trait_filter.size()-1); // std::string new_name = emp::string_pop(trait_filter,':'); // // Build the function that will give us the ID we need. // auto in_fun = BuildTraitFunction(new_name, new_filter); - // return [get_fun,index](const CONTAIN_T & container) { + // return [get_fun,index](const CONTAINER_T & container) { // if (container.size() <= index) return "Nan"s; // return emp::to_string( get_fun( container.At(index) ) ); // }; @@ -702,13 +702,29 @@ namespace mabe { config.AddFunction("output", output_fun, "Print out the provided trait-based data; args: filename, collection, format."); - // @CAO Should have this work with a Poplation variable, not by name. + // @CAO Should have this work with a Population or Collection variable, not by name. std::function pop_size_fun = - [this](const std::string & pop_name) { - return GetPopulation(GetPopID(pop_name)).GetSize(); + [this](const std::string & target) { + return FromString(target).GetSize(); }; config.AddFunction("size", pop_size_fun, "Return the size of the target population."); + // std::function trait_mean_fun = + // [this](const std::string & target, const std::string & trait) { + // if constexpr (std::is_arithmetic_v) { + // double total = 0.0; + // size_t count = 0; + // for (const auto & entry : container) { + // total += (double) get_fun(entry); + // count++; + // } + // return emp::to_string( total / count ); + // } + // return 0.0; // @CAO: or Nan? + // }; + // config.AddFunction("trait_mean", trait_mean_fun, "Return the size of the target population."); + + // Add in built-in event triggers; these are used to indicate when events should happen. config.AddEventType("start"); // Triggered at the beginning of a run. config.AddEventType("update"); // Tested every update. From b32415dc69e3d5694d3424e4902516def80f37e0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 13 Sep 2021 17:25:01 -0400 Subject: [PATCH 126/445] Moved MABE 'output' to its own function; continued to build 'eval'. --- source/core/MABE.hpp | 120 +++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 67192a29..b290e1f6 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -547,6 +547,55 @@ namespace mabe { return result; } + // Handler for printing trait data + void OutputTraitData(std::ostream & os, + Collection target_collect, + std::string format, + bool print_headers=false) + { + emp::vector funs; ///< Functions to call each update. + emp::remove_whitespace(format); + auto fun_it = file_fun_cache.find(format); + + // If we need headers, set them up! + if (print_headers) { + // Identify the contents of each column. + emp::vector cols = emp::slice(format, ','); + + // Print the headers into the file. + os << "#update"; + for (size_t i = 0; i < cols.size(); i++) { + os << ", " << cols[i]; + } + os << '\n'; + } + + // If the functions don't exist yet, set them up! + if (fun_it == file_fun_cache.end()) { + // Identify the contents of each column. + emp::vector cols = emp::slice(format, ','); + + // Setup a function to collect data associated with each column. + funs.resize(cols.size()); + for (size_t i = 0; i < cols.size(); i++) { + std::string trait_filter = cols[i]; + std::string trait_name = emp::string_pop(trait_filter,':'); + funs[i] = BuildTraitFunction(trait_name, trait_filter); + } + + // Insert the new entry into the cache and update the iterator. + fun_it = file_fun_cache.insert({format, funs}).first; + } + else funs = fun_it->second; + + // And, finally, print the data! + os << GetUpdate(); + for (auto & fun : funs) { + os << ", " << fun(target_collect); + } + os << std::endl; + } + // --- Manage configuration scope --- /// Access to the current configuration scope. @@ -626,6 +675,15 @@ namespace mabe { // Add other built-in functions to the config file. + // 'eval' dynamically evaluates the contents of a string. + std::function eval_fun = + [this](const std::string & expression) { + config.Eval(expression); + return 0; + }; + config.AddFunction("eval", eval_fun, "Dynamically evaluate the string passed in."); + + // 'exit' should terminate a run. std::function exit_fun = [this](){ exit_now = true; return 0; }; config.AddFunction("exit", exit_fun, "Exit from this MABE run."); @@ -640,68 +698,29 @@ namespace mabe { config.AddFunction("inject", inject_fun, "Inject organisms into a population (args: org_name, pop_name, org_count)."); - // 'print' is a simple debugging command to output the value of a variable. - std::function> &)> print_fun = - [](const emp::vector> & args) { - for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); - return 0; - }; - config.AddFunction("print", print_fun, "Print out the provided variable."); // 'output' will collect data and write it to a file. files.SetOutputDefaultFile(); // Stream manager should default to files for output. std::function output_fun = [this](const std::string & filename, const std::string & collection, std::string format) { - emp::vector funs; ///< Functions to call each update. const bool file_exists = files.Has(filename); ///< Is file is already setup? std::ostream & file = files.GetOutputStream(filename); ///< File to write to. - emp::remove_whitespace(format); - auto fun_it = file_fun_cache.find(format); - - // If we need headers, set them up! - if (!file_exists) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Print the headers into the file. - file << "#update"; - for (size_t i = 0; i < cols.size(); i++) { - file << ", " << cols[i]; - } - file << '\n'; - } - - // If there functions don't exist yet, set them up! - if (fun_it == file_fun_cache.end()) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Setup a function to collect data associated with each column. - funs.resize(cols.size()); - for (size_t i = 0; i < cols.size(); i++) { - std::string trait_filter = cols[i]; - std::string trait_name = emp::string_pop(trait_filter,':'); - funs[i] = BuildTraitFunction(trait_name, trait_filter); - } - - // Insert the new entry into the cache and update the iterator. - fun_it = file_fun_cache.insert({format, funs}).first; - } - else funs = fun_it->second; - - // And, finally, print the data! - Collection target_collect = FromString(collection); - file << GetUpdate(); - for (auto & fun : funs) { - file << ", " << fun(target_collect); - } - file << std::endl; - + OutputTraitData(file, FromString(collection), format, !file_exists); return 0; }; config.AddFunction("output", output_fun, "Print out the provided trait-based data; args: filename, collection, format."); + + // 'print' is a simple debugging command to output the value of a variable. + std::function> &)> print_fun = + [](const emp::vector> & args) { + for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); + return 0; + }; + config.AddFunction("print", print_fun, "Print out the provided variable."); + + // @CAO Should have this work with a Population or Collection variable, not by name. std::function pop_size_fun = [this](const std::string & target) { @@ -709,6 +728,7 @@ namespace mabe { }; config.AddFunction("size", pop_size_fun, "Return the size of the target population."); + // std::function trait_mean_fun = // [this](const std::string & target, const std::string & trait) { // if constexpr (std::is_arithmetic_v) { From 47d885d5ee2d50eb0efad43062a9d4181e136483 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 13 Sep 2021 17:25:47 -0400 Subject: [PATCH 127/445] Removed direct settings from constructor. --- source/schema/MovePopulation.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/schema/MovePopulation.hpp b/source/schema/MovePopulation.hpp index a21950d2..fd10e5b3 100644 --- a/source/schema/MovePopulation.hpp +++ b/source/schema/MovePopulation.hpp @@ -24,9 +24,8 @@ namespace mabe { public: MovePopulation(mabe::MABE & control, const std::string & name="MovePopulation", - const std::string & desc="Module to move organisms to a new population", - int _from_id=0, int _to_id=1, bool _reset_to=true) - : Module(control, name, desc), from_id(_from_id), to_id(_to_id), reset_to(_reset_to) + const std::string & desc="Module to move organisms to a new population") + : Module(control, name, desc) { SetManageMod(true); ///< Mark this module as a population module. } From 9a660411433b7df8e3e2aa500840086e22c11d8c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 15 Sep 2021 18:10:08 -0400 Subject: [PATCH 128/445] Fixed eval; now works --- source/config/Config.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 3b32b5cb..33dbf104 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -357,17 +357,20 @@ namespace mabe { ast_root.AddChild(cur_block); } - // Load the provided statement, run it, convert the result to a string, and return just that string. - std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { + // Load the provided statement and run it. + void Eval(const std::string & statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); + std::cout << "EVAL on: " << statement << std::endl; if (!scope) scope = &root_scope; // Default scope to root level. emp::TokenStream tokens = lexer.Tokenize(statement); // Convert to tokens. pos_t pos = tokens.begin(); // Start are beginning of stream. auto cur_block = ParseStatementList(pos, root_scope); // Convert tokens to AST - auto result_ptr = cur_block->Process(); // Process AST to get result entry. - std::string result = result_ptr->AsString(); // Convert result to output string. - result_ptr.Delete(); // Delete the result entry. - return result; // Return the result string. +// auto result_ptr = cur_block->Process(); // Process AST to get result entry. +// std::string result = result_ptr->AsString(); // Convert result to output string. +// result_ptr.Delete(); // Delete the result entry. +// return result; // Return the result string. + cur_block->Process(); // Process AST to get result entry. + cur_block.Delete(); // Delete the AST. } @@ -424,8 +427,7 @@ namespace mabe { // If we can't find this variable, either build it or throw an error. if (cur_entry.IsNull()) { - Error(pos, "'", var_name, - "' does not exist as a parameter, variable, or type."); + Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type."); } // If this variable just provided a scope, keep going. From 801c036a08e7ddf2672a6a1bf4b28fe5050b017f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 15 Sep 2021 18:10:35 -0400 Subject: [PATCH 129/445] Added asserts to ConfigScope to prevent duplicate identifiers. --- source/config/ConfigScope.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/config/ConfigScope.hpp b/source/config/ConfigScope.hpp index 8c414c24..d4965478 100644 --- a/source/config/ConfigScope.hpp +++ b/source/config/ConfigScope.hpp @@ -6,6 +6,10 @@ * @file ConfigScope.hpp * @brief Manages a full scope with many conig entries (or sub-scopes). * @note Status: ALPHA + * + * DEVELOPER NOTES: + * - Need to fix Add() function to give a user-level error, rather than an assert on duplication. + * */ #ifndef MABE_CONFIG_SCOPE_H @@ -33,6 +37,8 @@ namespace mabe { T & Add(const std::string & name, ARGS &&... args) { auto new_ptr = emp::NewPtr(name, std::forward(args)...); entry_list.push_back(new_ptr); + emp_assert(!emp::Has(entry_map, name), "Do not redeclare functions or variables!", + name); entry_map[name] = new_ptr; return *new_ptr; } @@ -41,6 +47,8 @@ namespace mabe { T & AddBuiltin(const std::string & name, ARGS &&... args) { auto new_ptr = emp::NewPtr(name, std::forward(args)...); builtin_list.push_back(new_ptr); + emp_assert(!emp::Has(entry_map, name), "Do not redeclare built-in functions or variables!", + name); entry_map[name] = new_ptr; return *new_ptr; } From 294484d88a0d2df76442140c268d8d00a425ebd3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 13:59:25 -0400 Subject: [PATCH 130/445] Cleaned up Eval to (hopefully) return its result for expressions. --- source/config/Config.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 33dbf104..5f723e0d 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -358,19 +358,20 @@ namespace mabe { } // Load the provided statement and run it. - void Eval(const std::string & statement, emp::Ptr scope=nullptr) { + std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); - std::cout << "EVAL on: " << statement << std::endl; if (!scope) scope = &root_scope; // Default scope to root level. emp::TokenStream tokens = lexer.Tokenize(statement); // Convert to tokens. pos_t pos = tokens.begin(); // Start are beginning of stream. auto cur_block = ParseStatementList(pos, root_scope); // Convert tokens to AST -// auto result_ptr = cur_block->Process(); // Process AST to get result entry. -// std::string result = result_ptr->AsString(); // Convert result to output string. -// result_ptr.Delete(); // Delete the result entry. -// return result; // Return the result string. - cur_block->Process(); // Process AST to get result entry. + auto result_ptr = cur_block->Process(); // Process AST to get result entry. + std::string result = ""; // Default result to an empty string. + if (result_ptr) { + result = result_ptr->AsString(); // Convert result to output string. + result_ptr.Delete(); // Delete the result entry. + } cur_block.Delete(); // Delete the AST. + return result; // Return the result string. } From f4466dac454577d30a9af5142e0102179a224085 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 17:04:10 -0400 Subject: [PATCH 131/445] Setup config parsing errors to print name of input source. --- source/config/Config.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 5f723e0d..6c5d7452 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -141,7 +141,9 @@ namespace mabe { template void Error(pos_t pos, Ts... args) const { - std::cout << "Error (line " << pos->line_id << "): " << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; + std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); + std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " + << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; exit(1); } @@ -326,7 +328,7 @@ namespace mabe { void Load(const std::string & filename) { Debug("Running Load(", filename, ")"); std::ifstream file(filename); // Load the provided file. - emp::TokenStream tokens = lexer.Tokenize(file); // Convert to more-usable tokens. + emp::TokenStream tokens = lexer.Tokenize(file, filename); // Convert to more-usable tokens. file.close(); // Close the file (now that it's converted) pos_t pos = tokens.begin(); // Start at the beginning of the file. @@ -344,9 +346,11 @@ namespace mabe { } // Load a single, specified configuration file. - void LoadStatements(const emp::vector & statements) { + // @param statements List is statements to be parsed. + // @param name Name of statement group (for error messages) + void LoadStatements(const emp::vector & statements, const std::string & name) { Debug("Running LoadStatements()"); - emp::TokenStream tokens = lexer.Tokenize(statements); // Convert to tokens. + emp::TokenStream tokens = lexer.Tokenize(statements, name); // Convert to tokens. pos_t pos = tokens.begin(); // Parse and run the program, starting from the outer scope. @@ -361,7 +365,7 @@ namespace mabe { std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); if (!scope) scope = &root_scope; // Default scope to root level. - emp::TokenStream tokens = lexer.Tokenize(statement); // Convert to tokens. + emp::TokenStream tokens = lexer.Tokenize(statement, "eval command"); // Convert to tokens. pos_t pos = tokens.begin(); // Start are beginning of stream. auto cur_block = ParseStatementList(pos, root_scope); // Convert tokens to AST auto result_ptr = cur_block->Process(); // Process AST to get result entry. From 1b646f663f8e34ca0560dd6ebcde8bd33afeb9dc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 17:04:55 -0400 Subject: [PATCH 132/445] Config cleanup; provide input source info. --- source/core/MABE.hpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index b290e1f6..b6f74870 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -676,11 +676,11 @@ namespace mabe { // Add other built-in functions to the config file. // 'eval' dynamically evaluates the contents of a string. - std::function eval_fun = - [this](const std::string & expression) { - config.Eval(expression); - return 0; - }; + // std::function eval_fun = + // [this](const std::string & expression) { config.Eval(expression); return 0; }; + // config.AddFunction("eval", eval_fun, "Dynamically evaluate the string passed in."); + std::function eval_fun = + [this](const std::string & expression) { return config.Eval(expression); }; config.AddFunction("eval", eval_fun, "Dynamically evaluate the string passed in."); @@ -718,17 +718,14 @@ namespace mabe { for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); return 0; }; - config.AddFunction("print", print_fun, "Print out the provided variable."); + config.AddFunction("print", print_fun, "Print out the provided variables."); - // @CAO Should have this work with a Population or Collection variable, not by name. + // @CAO Should be a method on a Population or Collection, not called by name. std::function pop_size_fun = - [this](const std::string & target) { - return FromString(target).GetSize(); - }; + [this](const std::string & target) { return FromString(target).GetSize(); }; config.AddFunction("size", pop_size_fun, "Return the size of the target population."); - // std::function trait_mean_fun = // [this](const std::string & target, const std::string & trait) { // if constexpr (std::is_arithmetic_v) { @@ -765,7 +762,7 @@ namespace mabe { if (config_settings.size()) { std::cout << "Loading command-line settings." << std::endl; - config.LoadStatements(config_settings); + config.LoadStatements(config_settings, "command-line settings"); } // If we are writing a file, do so and then exit. From 93e7cda5399a174f68853e01e20452a768579df3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 23:39:48 -0400 Subject: [PATCH 133/445] Added virtual HasNumericReturn() and HasStringReturn() to ConfigEntry base class. --- source/config/ConfigEntry.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index bfb4f99d..596c7934 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -9,6 +9,8 @@ * * * Development Notes: + * - Currently we are not using Format; this would be useful if we want to type-check inputs more + * carefully. * - When a ConfigEntry is used for a temporary value, it doesn't acutally need name or desc; * we can probably remove these pretty easily to save on memory if needed. */ @@ -94,10 +96,13 @@ namespace mabe { virtual bool IsDouble() const { return false; } ///< Is entry a floting point value? virtual bool IsString() const { return false; } ///< Is entry a string? - virtual bool IsLocal() const { return false; } ///< Was this entry defined in config file? - virtual bool IsFunction() const { return false; } ///< Is this entry a function? - virtual bool IsScope() const { return false; } ///< Is this entry a full scope? - virtual bool IsError() const { return false; } ///< Does this entry flag an error? + virtual bool IsLocal() const { return false; } ///< Was entry defined in config file? + virtual bool IsFunction() const { return false; } ///< Is entry a function? + virtual bool IsScope() const { return false; } ///< Is entry a full scope? + virtual bool IsError() const { return false; } ///< Does entry flag an error? + + virtual bool HasNumericReturn() const { return false; } ///< Is entry a function that returns a number? + virtual bool HasStringReturn() const { return false; } ///< Is entry a function that returns a string? ConfigEntry & SetName(const std::string & in) { name = in; return *this; } ConfigEntry & SetDesc(const std::string & in) { desc = in; return *this; } @@ -214,7 +219,7 @@ namespace mabe { bool CopyValue(const ConfigEntry & in) override { var = in.AsDouble(); return true; } }; - /// Specializatin for ConfigEntry linked to a string variable. + /// Specialization for ConfigEntry linked to a string variable. template <> class ConfigEntry_Linked : public ConfigEntry { private: From b336399b3a9c5ad633ea1ba9f8ed38561a9de7ac Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 23:40:47 -0400 Subject: [PATCH 134/445] Setup ConfigFunction to correctly report back on HasNumericReturn() and HasStringReturn(). --- source/config/ConfigFunction.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/config/ConfigFunction.hpp b/source/config/ConfigFunction.hpp index 64d6098e..91892d4d 100644 --- a/source/config/ConfigFunction.hpp +++ b/source/config/ConfigFunction.hpp @@ -29,6 +29,8 @@ namespace mabe { using entry_vector_t = emp::vector; using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; fun_t fun; + bool numeric_return = false; + bool string_return = false; // size_t arg_count; public: @@ -49,10 +51,15 @@ namespace mabe { emp::Ptr Clone() const override { return emp::NewPtr(*this); } bool IsFunction() const override { return true; } + bool HasNumericReturn() const override { return numeric_return; } + bool HasStringReturn() const override { return string_return; } /// Setup a function that takes NO arguments. template void SetFunction( std::function in_fun ) { + numeric_return = std::is_scalar_v; + string_return = std::is_same(); + // Convert the function call to using entry pointers. fun = [in_fun, name=name, desc=desc](const emp::vector & args) -> emp::Ptr { // If arguments are passed in, we need to raise an error. From be0a2a7093127f81bfffc3bd94f53574ea2d2da1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 16 Sep 2021 23:41:29 -0400 Subject: [PATCH 135/445] Setup type information on all AST nodes. --- source/config/ConfigAST.hpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index 3c45e720..5abb1878 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -49,6 +49,12 @@ namespace mabe { virtual const std::string & GetName() const = 0; + virtual bool IsNumeric() const { return false; } // Can node be reprsented as a number? + virtual bool IsString() const { return false; } // Can node be reprsented as a string? + virtual bool HasValue() const { return false; } // Does node have any value (vs internal block) + virtual bool HasNumericReturn() const { return false; } // Is node function with numeric return? + virtual bool HasStringReturn() const { return false; } // Is node function with string return? + virtual bool IsLeaf() const { return false; } virtual bool IsInternal() const { return false; } @@ -101,6 +107,12 @@ namespace mabe { const std::string & GetName() const override { return entry_ptr->GetName(); } ConfigEntry & GetEntry() { return *entry_ptr; } + bool IsNumeric() const override { return entry_ptr->IsNumeric(); } + bool IsString() const override { return entry_ptr->IsString(); } + bool HasValue() const override { return true; } + bool HasNumericReturn() const override { return entry_ptr->HasNumericReturn(); } + bool HasStringReturn() const override { return entry_ptr->HasStringReturn(); } + bool IsLeaf() const override { return true; } entry_ptr_t Process() override { return entry_ptr; }; @@ -153,6 +165,9 @@ namespace mabe { public: ASTNode_Math1(const std::string & name) : ASTNode_Internal(name) { } + bool IsNumeric() const override { return true; } + bool HasValue() const override { return true; } + void SetFun(std::function< double(double) > _fun) { fun = _fun; } entry_ptr_t Process() override { @@ -177,6 +192,9 @@ namespace mabe { public: ASTNode_Math2(const std::string & name) : ASTNode_Internal(name) { } + bool IsNumeric() const override { return true; } + bool HasValue() const override { return true; } + void SetFun(std::function< double(double, double) > _fun) { fun = _fun; } entry_ptr_t Process() override { @@ -203,6 +221,12 @@ namespace mabe { AddChild(rhs); } + bool IsNumeric() const override { return children[0]->IsNumeric(); } + bool IsString() const override { return children[0]->IsString(); } + bool HasValue() const override { return true; } + bool HasNumericReturn() const override { return children[0]->HasNumericReturn(); } + bool HasStringReturn() const override { return children[0]->HasStringReturn(); } + entry_ptr_t Process() override { emp_assert(children.size() == 2); entry_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. @@ -227,6 +251,12 @@ namespace mabe { for (auto arg : args) AddChild(arg); } + bool IsNumeric() const override { return children[0]->HasNumericReturn(); } + bool IsString() const override { return children[0]->HasStringReturn(); } + bool HasValue() const override { return true; } + // @CAO Technically, one function can return another, so we should check + // HasNumericReturn() and HasStringReturn() on return values... but hard to implement. + entry_ptr_t Process() override { emp_assert(children.size() >= 1); entry_ptr_t fun = children[0]->Process(); From 1f8a28ed29392a921534fa81a57136883ceb5957 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 17 Sep 2021 09:16:01 -0400 Subject: [PATCH 136/445] Changed the Math2 AST node into a more general Op2 node. --- source/config/ConfigAST.hpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index 5abb1878..b0fcbfad 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -184,27 +184,32 @@ namespace mabe { } }; - /// Binary mathematical operations. - class ASTNode_Math2 : public ASTNode_Internal { + /// Binary operations. + template + class ASTNode_Op2 : public ASTNode_Internal { protected: - // A binary operator takes in two doubles and returns a third. - std::function< double(double, double) > fun; + std::function< RETURN_T(ARG1_T, ARG2_T) > fun; public: - ASTNode_Math2(const std::string & name) : ASTNode_Internal(name) { } + ASTNode_Op2(const std::string & name) : ASTNode_Internal(name) { } - bool IsNumeric() const override { return true; } + bool IsNumeric() const override { return std::is_same(); } + bool IsString() const override { return std::is_same(); } bool HasValue() const override { return true; } - void SetFun(std::function< double(double, double) > _fun) { fun = _fun; } + void SetFun(std::function< RETURN_T(ARG1_T, ARG2_T) > _fun) { fun = _fun; } entry_ptr_t Process() override { emp_assert(children.size() == 2); entry_ptr_t in1 = children[0]->Process(); // Process 1st child to input entry entry_ptr_t in2 = children[1]->Process(); // Process 2nd child to input entry - double out_val = fun(in1->AsDouble(), in2->AsDouble()); // Run function; get ouput + auto out_val = fun(in1->As(), in2->As()); // Run function; get ouput if (in1->IsTemporary()) in1.Delete(); // If we are done with in1; delete! if (in2->IsTemporary()) in2.Delete(); // If we are done with in2; delete! - return MakeTempDouble(out_val); + if constexpr (std::is_same()) { + return MakeTempDouble(out_val); + } else { + return MakeTempString(out_val); + } } void Write(std::ostream & os, const std::string & offset) const override { @@ -214,6 +219,9 @@ namespace mabe { } }; + using ASTNode_Math2 = ASTNode_Op2; + + class ASTNode_Assign : public ASTNode_Internal { public: ASTNode_Assign(node_ptr_t lhs, node_ptr_t rhs) { From f731bbe74fe2bee66690fe002b1da8449c5e84e7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 17 Sep 2021 09:20:16 -0400 Subject: [PATCH 137/445] Added a generic As() template to ConfigEntrys to specify the conversion type. --- source/config/ConfigEntry.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 596c7934..e5be1c09 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -111,6 +111,12 @@ namespace mabe { virtual double AsDouble() const { emp_assert(false); return 0.0; } virtual std::string AsString() const { emp_assert(false); return ""; } + template + T As() const { + if constexpr (std::is_same()) return AsDouble(); + else return AsString(); + } + virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual ConfigEntry & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } From 8df2678670e212038ba483a263e9c21fc8fa27fa Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 17 Sep 2021 09:20:44 -0400 Subject: [PATCH 138/445] Added a range of string operators to the config language. --- source/config/Config.hpp | 105 ++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 6c5d7452..3adef89f 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -505,37 +505,82 @@ namespace mabe { // If this operation is assignment, do so! if (symbol == "=") return emp::NewPtr(in_node1, in_node2); + + // If the first argument is numeric, assume we are using a math operator. + if (in_node1->IsNumeric()) { + + // Determine the output value and put it in a temporary node. + std::function fun; + if (symbol == "+") fun = [](double val1, double val2){ return val1 + val2; }; + else if (symbol == "-") fun = [](double val1, double val2){ return val1 - val2; }; + else if (symbol == "*") fun = [](double val1, double val2){ return val1 * val2; }; + else if (symbol == "/") fun = [](double val1, double val2){ return val1 / val2; }; + else if (symbol == "%") fun = [](double val1, double val2){ return ((size_t) val1) % ((size_t) val2); }; + else if (symbol == "==") fun = [](double val1, double val2){ return val1 == val2; }; + else if (symbol == "!=") fun = [](double val1, double val2){ return val1 != val2; }; + else if (symbol == "<") fun = [](double val1, double val2){ return val1 < val2; }; + else if (symbol == "<=") fun = [](double val1, double val2){ return val1 <= val2; }; + else if (symbol == ">") fun = [](double val1, double val2){ return val1 > val2; }; + else if (symbol == ">=") fun = [](double val1, double val2){ return val1 >= val2; }; + + // @CAO: Need to still handle these last two differently for short-circuiting. + else if (symbol == "&&") fun = [](double val1, double val2){ return val1 && val2; }; + else if (symbol == "||") fun = [](double val1, double val2){ return val1 || val2; }; + + emp::Ptr out_value = emp::NewPtr(symbol); + out_value->SetFun(fun); + out_value->AddChild(in_node1); + out_value->AddChild(in_node2); + + return out_value; + } + + // Otherwise assume that we are dealing with strings. + if (symbol == "+") { + std::function fun; + fun = [](std::string val1, std::string val2){ return val1 + val2; }; + + auto out_value = emp::NewPtr>(symbol); + out_value->SetFun(fun); + out_value->AddChild(in_node1); + out_value->AddChild(in_node2); + + return out_value; + } + else if (symbol == "*") { + std::function fun; + fun = [](std::string val1, double val2) { + std::string out_string; + out_string.reserve(val1.size() * (size_t) val2); + for (size_t i = 0; i < (size_t) val2; i++) out_string += val1; + return out_string; + }; + + auto out_value = emp::NewPtr>(symbol); + out_value->SetFun(fun); + out_value->AddChild(in_node1); + out_value->AddChild(in_node2); + + return out_value; + } + else { + std::function fun; + if (symbol == "==") fun = [](std::string val1, std::string val2){ return val1 == val2; }; + else if (symbol == "!=") fun = [](std::string val1, std::string val2){ return val1 != val2; }; + else if (symbol == "<") fun = [](std::string val1, std::string val2){ return val1 < val2; }; + else if (symbol == "<=") fun = [](std::string val1, std::string val2){ return val1 <= val2; }; + else if (symbol == ">") fun = [](std::string val1, std::string val2){ return val1 > val2; }; + else if (symbol == ">=") fun = [](std::string val1, std::string val2){ return val1 >= val2; }; + + auto out_value = emp::NewPtr>(symbol); + out_value->SetFun(fun); + out_value->AddChild(in_node1); + out_value->AddChild(in_node2); + + return out_value; + } - // If both values are numeric, act on the math operator. -// if (in_node1->IsNumeric() && in_node2->IsNumeric()) { -// double val1 = in_node1->AsDouble(); -// double val2 = in_node2->AsDouble(); -// } - - // Determine the output value and put it in a temporary node. - std::function fun; - if (symbol == "+") fun = [](double val1, double val2){ return val1 + val2; }; - else if (symbol == "-") fun = [](double val1, double val2){ return val1 - val2; }; - else if (symbol == "*") fun = [](double val1, double val2){ return val1 * val2; }; - else if (symbol == "/") fun = [](double val1, double val2){ return val1 / val2; }; - else if (symbol == "%") fun = [](double val1, double val2){ return ((size_t) val1) % ((size_t) val2); }; - else if (symbol == "==") fun = [](double val1, double val2){ return val1 == val2; }; - else if (symbol == "!=") fun = [](double val1, double val2){ return val1 != val2; }; - else if (symbol == "<") fun = [](double val1, double val2){ return val1 < val2; }; - else if (symbol == "<=") fun = [](double val1, double val2){ return val1 <= val2; }; - else if (symbol == ">") fun = [](double val1, double val2){ return val1 > val2; }; - else if (symbol == ">=") fun = [](double val1, double val2){ return val1 >= val2; }; - - // @CAO: Need to still handle these last two differently for short-circuiting. - else if (symbol == "&&") fun = [](double val1, double val2){ return val1 && val2; }; - else if (symbol == "||") fun = [](double val1, double val2){ return val1 || val2; }; - - emp::Ptr out_value = emp::NewPtr(symbol); - out_value->SetFun(fun); - out_value->AddChild(in_node1); - out_value->AddChild(in_node2); - - return out_value; + return nullptr; } From 1fa95e18069bebbf745a5e7de0b062c19e7df370 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 17 Sep 2021 13:47:22 -0400 Subject: [PATCH 139/445] Fixed Config::Eval() to properly clean up after itself. --- source/config/Config.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 3adef89f..881e7895 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -365,14 +365,15 @@ namespace mabe { std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); if (!scope) scope = &root_scope; // Default scope to root level. - emp::TokenStream tokens = lexer.Tokenize(statement, "eval command"); // Convert to tokens. + auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. + tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. - auto cur_block = ParseStatementList(pos, root_scope); // Convert tokens to AST + auto cur_block = ParseStatement(pos, root_scope); // Convert tokens to AST auto result_ptr = cur_block->Process(); // Process AST to get result entry. std::string result = ""; // Default result to an empty string. if (result_ptr) { result = result_ptr->AsString(); // Convert result to output string. - result_ptr.Delete(); // Delete the result entry. + if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete the result entry if done. } cur_block.Delete(); // Delete the AST. return result; // Return the result string. From 7db0e065a366a8d57911ef90fd80571a4dfb753a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 18 Sep 2021 14:24:17 -0400 Subject: [PATCH 140/445] Reorganized MABE.hpp to have all longer function defined out-of-class. --- source/core/MABE.hpp | 807 +++++++++++++++++++++++-------------------- 1 file changed, 436 insertions(+), 371 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index b6f74870..ee4d6011 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -26,6 +26,7 @@ #include "emp/config/command_line.hpp" #include "emp/control/Signal.hpp" #include "emp/data/DataMap.hpp" +#include "emp/data/DataMapParser.hpp" #include "emp/io/StreamManager.hpp" #include "emp/math/Random.hpp" #include "emp/datastructs/vector_utils.hpp" @@ -113,33 +114,10 @@ namespace mabe { // ----------- Helper Functions ----------- /// Print information on how to run the software. - void ShowHelp() { - std::cout << "MABE v" << VERSION << "\n" - << "Usage: " << args[0] << " [options]\n" - << "Options:\n"; - for (const auto & cur_arg : arg_set) { - std::cout << " " << cur_arg.flag << " " << cur_arg.args - << " : " << cur_arg.desc << " (or " << cur_arg.name << ")" - << std::endl; - } - on_help_sig.Trigger(); - std::cout << "Note: Settings and files are applied in the order provided.\n"; - exit_now = true; - } + void ShowHelp(); /// List all of the available modules included in the current compilation. - void ShowModules() { - std::cout << "MABE v" << VERSION << "\n" - << "Active modules:\n"; - // for (auto mod_ptr : modules) { - // std::cout << " " << mod_ptr->GetName() << " : " << mod_ptr->GetDesc() << "\n"; - // } - std::cout << "Available modules:\n"; - for (auto & info : GetModuleInfo()) { - std::cout << " " << info.name << " : " << info.desc << "\n"; - } - exit_now = true;; - } + void ShowModules(); void TraceEval(Organism & org, std::ostream & os) { trace_eval_sig.Trigger(org, os); @@ -177,38 +155,21 @@ namespace mabe { mabe::ErrorManager & GetErrorManager() { return error_man; } /// Output args if (and only if) we are in verbose mode. - template - void Verbose(Ts &&... args) { - if (verbose) { - std::cout << emp::to_string(std::forward(args)...) << std::endl; - } + template void Verbose(Ts &&... args) { + if (verbose) std::cout << emp::to_string(std::forward(args)...) << std::endl; } // --- Tools to setup runs --- bool Setup(); /// Setup an organism as a placeholder for all "empty" positions in the population. - template - void SetupEmpty() { - if (empty_org) empty_org.Delete(); // If we already have an empty organism, replace it. - auto & empty_manager = - AddModule("EmptyOrg", "Manager for all 'empty' organisms in any population."); - empty_manager.SetBuiltIn(); // Don't write the empty manager to config. - - empty_org = empty_manager.template Make(); - } + template void SetupEmpty(); /// Update MABE a single time step. void Update(); /// Update MABE a specified number of time steps. - void DoRun(size_t num_updates) { - config.TriggerEvents("start"); - for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { - Update(); - } - before_exit_sig.Trigger(); - } + void DoRun(size_t num_updates); // -- World Structure -- @@ -233,22 +194,10 @@ namespace mabe { Population & GetPopulation(size_t id) { return *pops[id]; } /// New populaitons must be given a name and an optional size. - Population & AddPopulation(const std::string & name, size_t pop_size=0) { - cur_pop_id = (int) pops.size(); // Set new pop to "current" - emp::Ptr new_pop = - emp::NewPtr(name, cur_pop_id, pop_size, empty_org); // Create new population. - pops.push_back(new_pop); // Record new population. - return *new_pop; // Return new population. - } + Population & AddPopulation(const std::string & name, size_t pop_size=0); /// If GetPopulation() is called without an ID, return the current population or create one. - Population & GetPopulation() { - if (pops.size() == 0) { // If we don't have a population, add one! - emp_assert(cur_pop_id == (size_t) -1); // Current population should now be default; - AddPopulation("main"); // Default population is named main. - } - return *pops[cur_pop_id]; - } + Population & GetPopulation(); /// Move an organism from one position to another; kill anything that previously occupied /// the target position. @@ -259,67 +208,23 @@ namespace mabe { /// Inject a copy of the provided organism and return the position it was placed in; /// if more than one is added, return the position of the final injection. - OrgPosition Inject(const Organism & org, Population & pop, size_t copy_count=1) { - emp_assert(org.GetDataMap().SameLayout(org_data_map)); - OrgPosition pos; - for (size_t i = 0; i < copy_count; i++) { - emp::Ptr inject_org = org.CloneOrganism(); - on_inject_ready_sig.Trigger(*inject_org, pop); - pos = FindInjectPosition(*inject_org, pop); - if (pos.IsValid()) { - AddOrgAt( inject_org, pos); - } else { - inject_org.Delete(); - error_man.AddError("Invalid position; failed to inject organism ", i, "!"); - } - } - return pos; - } + OrgPosition Inject(const Organism & org, Population & pop, size_t copy_count=1); /// Inject this specific instance of an organism and turn over the pointer to be managed /// by MABE. Teturn the position the organism was placed in. - OrgPosition InjectInstance(emp::Ptr org_ptr, Population & pop) { - emp_assert(org_ptr->GetDataMap().SameLayout(org_data_map)); - on_inject_ready_sig.Trigger(*org_ptr, pop); - OrgPosition pos = FindInjectPosition(*org_ptr, pop); - if (pos.IsValid()) AddOrgAt( org_ptr, pos); - else { - org_ptr.Delete(); - error_man.AddError("Invalid position; failed to inject organism!"); - } - return pos; - } + OrgPosition InjectInstance(emp::Ptr org_ptr, Population & pop); /// Add an organsim of a specified type to the world (provide the type name and the /// MABE controller will create instances of it.) Returns the position of the last /// organism placed. - OrgPosition Inject(const std::string & type_name, Population & pop, size_t copy_count=1) { - Verbose("Injecting ", copy_count, " orgs of type '", type_name, - "' into population ", pop.GetID()); - - auto & org_manager = GetModule(type_name); // Look up type of organism. - OrgPosition pos; // Place to save injection position. - for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. - auto org_ptr = org_manager.Make(random); // ...Build an org of this type. - pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. - } - return pos; // Return last position injected. - } + OrgPosition Inject(const std::string & type_name, Population & pop, size_t copy_count=1); /// Add an organism of a specified type and population (provide names of both and they /// will be properly setup.) OrgPosition Inject(const std::string & type_name, const std::string & pop_name, - size_t copy_count=1) { - int pop_id = GetPopID(pop_name); - if (pop_id == -1) { - error_man.AddError("Invalid population name used in inject '", pop_name, "'."); - } - Population & pop = GetPopulation(pop_id); - OrgPosition pos = Inject(type_name, pop, copy_count); // Inject a copy of the organism. - return pos; // Return last position injected. - } + size_t copy_count=1); /// Inject a copy of the provided organism at a specified position. void InjectAt(const Organism & org, OrgPosition pos) { @@ -336,40 +241,12 @@ namespace mabe { OrgPosition ppos, Population & target_pop, size_t birth_count=1, - bool do_mutations=true) { - emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. - before_repro_sig.Trigger(ppos); - OrgPosition pos; // Position of each offspring placed. - emp::Ptr new_org; - for (size_t i = 0; i < birth_count; i++) { // Loop through offspring, adding each - new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); - - // Alert modules that offspring is ready, then find its birth position. - on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); - pos = FindBirthPosition(*new_org, ppos, target_pop); - - // If this placement is valid, do so. Otherwise delete the organism. - if (pos.IsValid()) AddOrgAt(new_org, pos, ppos); - else new_org.Delete(); - } - return pos; - } + bool do_mutations=true); OrgPosition DoBirth(const Organism & org, OrgPosition ppos, OrgPosition target_pos, - bool do_mutations=true) { - emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. - emp_assert(target_pos.IsValid()); // Target positions must already be valid. - - before_repro_sig.Trigger(ppos); - emp::Ptr new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); - on_offspring_ready_sig.Trigger(*new_org, ppos, target_pos.Pop()); - - AddOrgAt(new_org, target_pos, ppos); - - return target_pos; - } + bool do_mutations=true); /// A shortcut to DoBirth where only the parent position needs to be supplied. @@ -379,14 +256,7 @@ namespace mabe { } /// Resize a population while clearing all of the organisms in it. - void EmptyPop(Population & pop, size_t new_size) { - // Clean up any organisms in the population. - for (PopIterator pos = pop.begin(); pos != pop.end(); ++pos) { - ClearOrgAt(pos); - } - - MABEBase::ResizePop(pop, new_size); - } + void EmptyPop(Population & pop, size_t new_size); /// Return a ramdom position from a desginated population. OrgPosition GetRandomPos(Population & pop) { @@ -398,13 +268,7 @@ namespace mabe { OrgPosition GetRandomPos(size_t pop_id) { return GetRandomPos(GetPopulation(pop_id)); } /// Return a ramdom position from a desginated population with a living organism in it. - OrgPosition GetRandomOrgPos(Population & pop) { - emp_assert(pop.GetNumOrgs() > 0, "GetRandomOrgPos cannot be called if there are no orgs."); - // @CAO: Something better to do in a sparse population? - OrgPosition pos = GetRandomPos(pop); - while (pos.IsEmpty()) pos = GetRandomPos(pop); - return pos; - } + OrgPosition GetRandomOrgPos(Population & pop); /// Return a ramdom position of a living organism from the population with the specified id. OrgPosition GetRandomOrgPos(size_t pop_id) { return GetRandomOrgPos(GetPopulation(pop_id)); } @@ -416,16 +280,7 @@ namespace mabe { return collect.ToString(); } - Collection FromString(const std::string & load_str) { - Collection out; - auto slices = emp::view_slices(load_str, ','); - for (auto name : slices) { - int pop_id = GetPopID(name); - if (pop_id == -1) error_man.AddError("Unknown population: ", name); - else out.Insert(GetPopulation(pop_id)); - } - return out; - } + Collection FromString(const std::string & load_str); Collection GetAlivePopulation(size_t id) { Collection col(GetPopulation(id)); @@ -467,134 +322,15 @@ namespace mabe { /// Build a function to scan a collection of organisms, reading the value for the given /// trait_name from each, aggregating those values based on the trait_filter and returning /// the result as a string. - /// - /// trait_filter option are: - /// : Default to the value of the trait for the first organism in the collection. - /// [ID] : Value of this trait for the organism at the given index of the collection. - /// [OP][VALUE] : Count how often this value has the [OP] relationship with [VALUE]. - /// [OP] can be ==, !=, <, >, <=, or >= - /// [VALUE] can be any numeric value - /// [OP][TRAIT] : Count how often this trait has the [OP] relationship with [TRAIT] - /// [OP] can be ==, !=, <, >, <=, or >= - /// [TRAIT] can be any other trait name - /// unique : Return the number of distinct value for this trait (alias="richness"). - /// mode : Return the most common value in this colection (aliases="dom","dominant"). - /// min : Return the smallest value of this trait present. - /// max : Return the largest value of this trait present. - /// ave : Return the average value of this trait (alias="mean"). - /// median : Return the median value of this trait. - /// variance : Return the variance of this trait. - /// stddev : Return the standard deviation of this trait. - /// sum : Return the summation of all values of this trait (alias="total") - /// entopy : Return the Shannon entropy of this value. - /// :trait : Return the mutual information with another provided trait. trait_fun_t BuildTraitFunction(const std::string & trait_name, - std::string trait_filter) { - // The trait input has two components: - // (1) the trait NAME and - // (2) (optionally) how to calculate the trait SUMMARY, such as min, max, ave, etc. - - // Everything before the first colon is the trait name. - size_t trait_id = org_data_map.GetID(trait_name); - emp::TypeID trait_type = org_data_map.GetType(trait_id); - const bool is_numeric = trait_type.IsArithmetic(); - - auto get_double_fun = [trait_id, trait_type](const Organism & org) { - return org.GetTraitAsDouble(trait_id, trait_type); - }; - auto get_string_fun = [trait_id, trait_type](const Organism & org) { - return emp::to_literal( org.GetTraitAsString(trait_id, trait_type) ); - }; - - // Return the number of times a specific value was found. - if (trait_filter[0] == '=') { - // @CAO: DO THIS! - trait_filter.erase(0,1); // Erase the '=' and we are left with the string to match. - } - - // // If the filter begins with a $, convert the rest to an ID and use it. - // else if (trait_filter[0] == '$') { - // // Make sure proper parentheses are used after $. - // if (trait_filter[1] != '(' || trait_filter.back() != ')') { - // error_man.AddError("$ specifier must be followed by parens; '", trait_filter, "' invalid."); - // } - - // // Determine the variable to use. - // std::string new_filter = emp::string_get_range(trait_filter, 2, trait_filter.size()-1); - // std::string new_name = emp::string_pop(trait_filter,':'); - - // // Build the function that will give us the ID we need. - // auto in_fun = BuildTraitFunction(new_name, new_filter); - - // return [get_fun,index](const CONTAINER_T & container) { - // if (container.size() <= index) return "Nan"s; - // return emp::to_string( get_fun( container.At(index) ) ); - // }; - // } - - // Otherwise pass along to the BuildCollectFun with the correct type... - auto result = is_numeric - ? emp::BuildCollectFun(trait_filter, get_double_fun) - : emp::BuildCollectFun(trait_filter, get_string_fun); - - // If we made it past the 'if' statements, we don't know this aggregation type. - if (!result) { - error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_name, "'."); - return [](const Collection &){ return std::string("Error! Unknown trait function"); }; - } - - return result; - } + std::string trait_filter); // Handler for printing trait data void OutputTraitData(std::ostream & os, Collection target_collect, std::string format, - bool print_headers=false) - { - emp::vector funs; ///< Functions to call each update. - emp::remove_whitespace(format); - auto fun_it = file_fun_cache.find(format); - - // If we need headers, set them up! - if (print_headers) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Print the headers into the file. - os << "#update"; - for (size_t i = 0; i < cols.size(); i++) { - os << ", " << cols[i]; - } - os << '\n'; - } - - // If the functions don't exist yet, set them up! - if (fun_it == file_fun_cache.end()) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Setup a function to collect data associated with each column. - funs.resize(cols.size()); - for (size_t i = 0; i < cols.size(); i++) { - std::string trait_filter = cols[i]; - std::string trait_name = emp::string_pop(trait_filter,':'); - funs[i] = BuildTraitFunction(trait_name, trait_filter); - } - - // Insert the new entry into the cache and update the iterator. - fun_it = file_fun_cache.insert({format, funs}).first; - } - else funs = fun_it->second; - - // And, finally, print the data! - os << GetUpdate(); - for (auto & fun : funs) { - os << ", " << fun(target_collect); - } - os << std::endl; - } + bool print_headers=false); // --- Manage configuration scope --- @@ -649,6 +385,149 @@ namespace mabe { // ========================== OUT-OF-CLASS DEFINITIONS! ========================== + // ---------------- PRIVATE MEMBER FUNCTIONS ----------------- + + /// Print information on how to run the software. + void MABE::ShowHelp() { + std::cout << "MABE v" << VERSION << "\n" + << "Usage: " << args[0] << " [options]\n" + << "Options:\n"; + for (const auto & cur_arg : arg_set) { + std::cout << " " << cur_arg.flag << " " << cur_arg.args + << " : " << cur_arg.desc << " (or " << cur_arg.name << ")" + << std::endl; + } + on_help_sig.Trigger(); + std::cout << "Note: Settings and files are applied in the order provided.\n"; + exit_now = true; + } + + /// List all of the available modules included in the current compilation. + void MABE::ShowModules() { + std::cout << "MABE v" << VERSION << "\n" + << "Active modules:\n"; + // for (auto mod_ptr : modules) { + // std::cout << " " << mod_ptr->GetName() << " : " << mod_ptr->GetDesc() << "\n"; + // } + std::cout << "Available modules:\n"; + for (auto & info : GetModuleInfo()) { + std::cout << " " << info.name << " : " << info.desc << "\n"; + } + exit_now = true;; + } + + void MABE::ProcessArgs() { + arg_set.emplace_back("--filename", "-f", "[filename...] ", "Filenames of configuration settings", + [this](const emp::vector & in){ config_filenames = in; } ); + arg_set.emplace_back("--generate", "-g", "[filename] ", "Generate a new output file", + [this](const emp::vector & in) { + if (in.size() != 1) { + std::cout << "'--generate' must be followed by a single filename.\n"; + exit_now = true; + } else { + // MABE Config files should be generated FROM a *.gen file, typically creating a *.mabe + // file. If output file is *.gen assume an error. (for now; override should be allowed) + if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { + error_man.AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); + exit_now = true; + } + else gen_filename = in[0]; + } + }); + arg_set.emplace_back("--help", "-h", " ", "Help; print command-line options for MABE", + [this](const emp::vector &){ show_help = true; } ); + arg_set.emplace_back("--modules", "-m", " ", "Module list", + [this](const emp::vector &){ ShowModules(); } ); + arg_set.emplace_back("--set", "-s", "[param=value] ", "Set specified parameter", + [this](const emp::vector & in){ + emp::Append(config_settings, in); + config_settings.push_back(";"); // Extra semi-colon so not needed on command line. + }); + arg_set.emplace_back("--version", "-v", " ", "Version ID of MABE", + [this](const emp::vector &){ + std::cout << "MABE v" << VERSION << "\n"; + exit_now = true; + }); + arg_set.emplace_back("--verbose", "-+", " ", "Output extra setup info", + [this](const emp::vector &){ verbose = true; } ); + + // Scan through all input argument positions. + for (size_t pos = 1; pos < args.size(); pos++) { + // Match the input argument to the function to call. + bool found = false; + for (auto & cur_arg : arg_set) { + // If we have a match... + if (args[pos] == cur_arg.name || args[pos] == cur_arg.flag) { + // ...collect all of the options associated with this match. + emp::vector option_args; + // We want args until we run out or hit another option. + while (pos+1 < args.size() && args[pos+1][0] != '-') { + option_args.push_back(args[++pos]); + } + + // And call the function! + cur_arg.action(option_args); + found = true; + break; + } + } + if (found == false) { + std::cout << "Error: unknown command line argument '" << args[pos] << "'." << std::endl; + show_help = true; + break; + } + } + + if (show_help) ShowHelp(); + } + + /// As part of the main Setup(), run SetupModule() method on each module we've loaded. + void MABE::Setup_Modules() { + // Allow the user-defined module SetupModule() member functions run. These are + // typically used for any internal setup needed by modules are the configuration is + // complete. + for (emp::Ptr mod_ptr : modules) mod_ptr->SetupModule(); + } + + /// As part of the main Setup(), load in all of the organism traits that modules need to + /// read or write and make sure that there aren't any conflicts. + void MABE::Setup_Traits() { + Verbose("Analyzing configuration of ", trait_man.GetSize(), " traits."); + + trait_man.Verify(verbose); // Make sure modules are accessing traits consistently + trait_man.RegisterAll(org_data_map); // Load in all of the traits to the DataMap + org_data_map.LockLayout(); // Freeze the data map into its current state + + // Alert modules (especially org managers) to the final set of traits. + for (emp::Ptr mod_ptr : modules) { + mod_ptr->SetupDataMap(org_data_map); + } + } + + /// Link signals to the modules that implment responses to those signals. + void MABE::UpdateSignals() { + // Clear all module vectors. + for (auto modv : sig_ptrs) modv->resize(0); + + // Loop through each module to update its signals. + for (emp::Ptr mod_ptr : modules) { + // If a module is deactivated, don't use it's signals. + if (mod_ptr->_active == false) continue; + + // For the current module, loop through all of the signals. + for (size_t sig_id = 0; sig_id < sig_ptrs.size(); sig_id++) { + if (mod_ptr->has_signal[sig_id]) sig_ptrs[sig_id]->push_back(mod_ptr); + } + } + + // Now that we have scanned the signals, we can turn off the rescan flag. + rescan_signals = false; + } + + + // ---------------- PUBLIC MEMBER FUNCTIONS ----------------- + + MABE::MABE(int argc, char* argv[]) : error_man( [this](const std::string & msg){ on_error_sig.Trigger(msg); }, [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) @@ -810,112 +689,297 @@ namespace mabe { config.UpdateEventValue("update", update); } - void MABE::ProcessArgs() { - arg_set.emplace_back("--filename", "-f", "[filename...] ", "Filenames of configuration settings", - [this](const emp::vector & in){ config_filenames = in; } ); - arg_set.emplace_back("--generate", "-g", "[filename] ", "Generate a new output file", - [this](const emp::vector & in) { - if (in.size() != 1) { - std::cout << "'--generate' must be followed by a single filename.\n"; - exit_now = true; - } else { - // MABE Config files should be generated FROM a *.gen file, typically creating a *.mabe - // file. If output file is *.gen assume an error. (for now; override should be allowed) - if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { - error_man.AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); - exit_now = true; - } - else gen_filename = in[0]; - } - }); - arg_set.emplace_back("--help", "-h", " ", "Help; print command-line options for MABE", - [this](const emp::vector &){ show_help = true; } ); - arg_set.emplace_back("--modules", "-m", " ", "Module list", - [this](const emp::vector &){ ShowModules(); } ); - arg_set.emplace_back("--set", "-s", "[param=value] ", "Set specified parameter", - [this](const emp::vector & in){ - emp::Append(config_settings, in); - config_settings.push_back(";"); // Extra semi-colon so not needed on command line. - }); - arg_set.emplace_back("--version", "-v", " ", "Version ID of MABE", - [this](const emp::vector &){ - std::cout << "MABE v" << VERSION << "\n"; - exit_now = true; - }); - arg_set.emplace_back("--verbose", "-+", " ", "Output extra setup info", - [this](const emp::vector &){ verbose = true; } ); - // Scan through all input argument positions. - for (size_t pos = 1; pos < args.size(); pos++) { - // Match the input argument to the function to call. - bool found = false; - for (auto & cur_arg : arg_set) { - // If we have a match... - if (args[pos] == cur_arg.name || args[pos] == cur_arg.flag) { - // ...collect all of the options associated with this match. - emp::vector option_args; - // We want args until we run out or hit another option. - while (pos+1 < args.size() && args[pos+1][0] != '-') { - option_args.push_back(args[++pos]); - } + /// Setup an organism as a placeholder for all "empty" positions in the population. + template + void MABE::SetupEmpty() { + if (empty_org) empty_org.Delete(); // If we already have an empty organism, replace it. + auto & empty_manager = + AddModule("EmptyOrg", "Manager for all 'empty' organisms in any population."); + empty_manager.SetBuiltIn(); // Don't write the empty manager to config. - // And call the function! - cur_arg.action(option_args); - found = true; - break; - } - } - if (found == false) { - std::cout << "Error: unknown command line argument '" << args[pos] << "'." << std::endl; - show_help = true; - break; + empty_org = empty_manager.template Make(); + } + + /// Update MABE a specified number of time steps. + void MABE::DoRun(size_t num_updates) { + config.TriggerEvents("start"); + for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { + Update(); + } + before_exit_sig.Trigger(); + } + + /// New populaitons must be given a name and an optional size. + Population & MABE::AddPopulation(const std::string & name, size_t pop_size) { + cur_pop_id = (int) pops.size(); // Set new pop to "current" + emp::Ptr new_pop = + emp::NewPtr(name, cur_pop_id, pop_size, empty_org); // Create new population. + pops.push_back(new_pop); // Record new population. + return *new_pop; // Return new population. + } + + /// If GetPopulation() is called without an ID, return the current population or create one. + Population & MABE::GetPopulation() { + if (pops.size() == 0) { // If we don't have a population, add one! + emp_assert(cur_pop_id == (size_t) -1); // Current population should now be default; + AddPopulation("main"); // Default population is named main. + } + return *pops[cur_pop_id]; + } + + /// Inject a copy of the provided organism and return the position it was placed in; + /// if more than one is added, return the position of the final injection. + OrgPosition MABE::Inject(const Organism & org, Population & pop, size_t copy_count) { + emp_assert(org.GetDataMap().SameLayout(org_data_map)); + OrgPosition pos; + for (size_t i = 0; i < copy_count; i++) { + emp::Ptr inject_org = org.CloneOrganism(); + on_inject_ready_sig.Trigger(*inject_org, pop); + pos = FindInjectPosition(*inject_org, pop); + if (pos.IsValid()) { + AddOrgAt( inject_org, pos); + } else { + inject_org.Delete(); + error_man.AddError("Invalid position; failed to inject organism ", i, "!"); } } + return pos; + } - if (show_help) ShowHelp(); + /// Inject this specific instance of an organism and turn over the pointer to be managed + /// by MABE. Teturn the position the organism was placed in. + OrgPosition MABE::InjectInstance(emp::Ptr org_ptr, Population & pop) { + emp_assert(org_ptr->GetDataMap().SameLayout(org_data_map)); + on_inject_ready_sig.Trigger(*org_ptr, pop); + OrgPosition pos = FindInjectPosition(*org_ptr, pop); + if (pos.IsValid()) AddOrgAt( org_ptr, pos); + else { + org_ptr.Delete(); + error_man.AddError("Invalid position; failed to inject organism!"); + } + return pos; + } + + + /// Add an organsim of a specified type to the world (provide the type name and the + /// MABE controller will create instances of it.) Returns the position of the last + /// organism placed. + OrgPosition MABE::Inject(const std::string & type_name, Population & pop, size_t copy_count) { + Verbose("Injecting ", copy_count, " orgs of type '", type_name, + "' into population ", pop.GetID()); + + auto & org_manager = GetModule(type_name); // Look up type of organism. + OrgPosition pos; // Place to save injection position. + for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. + auto org_ptr = org_manager.Make(random); // ...Build an org of this type. + pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. + } + return pos; // Return last position injected. } - /// As part of the main Setup(), run SetupModule() method on each module we've loaded. - void MABE::Setup_Modules() { - // Allow the user-defined module SetupModule() member functions run. These are - // typically used for any internal setup needed by modules are the configuration is - // complete. - for (emp::Ptr mod_ptr : modules) mod_ptr->SetupModule(); + /// Add an organism of a specified type and population (provide names of both and they + /// will be properly setup.) + OrgPosition MABE::Inject(const std::string & type_name, + const std::string & pop_name, + size_t copy_count) { + int pop_id = GetPopID(pop_name); + if (pop_id == -1) { + error_man.AddError("Invalid population name used in inject '", pop_name, "'."); + } + Population & pop = GetPopulation(pop_id); + OrgPosition pos = Inject(type_name, pop, copy_count); // Inject a copy of the organism. + return pos; // Return last position injected. } - /// As part of the main Setup(), load in all of the organism traits that modules need to - /// read or write and make sure that there aren't any conflicts. - void MABE::Setup_Traits() { - Verbose("Analyzing configuration of ", trait_man.GetSize(), " traits."); + /// Give birth to one or more offspring; return position of last placed. + /// Triggers 'before repro' signal on parent (once) and 'offspring ready' on each offspring. + /// Regular signal triggers occur in AddOrgAt. + OrgPosition MABE::DoBirth(const Organism & org, + OrgPosition ppos, + Population & target_pop, + size_t birth_count, + bool do_mutations) { + emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. + before_repro_sig.Trigger(ppos); + OrgPosition pos; // Position of each offspring placed. + emp::Ptr new_org; + for (size_t i = 0; i < birth_count; i++) { // Loop through offspring, adding each + new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); + + // Alert modules that offspring is ready, then find its birth position. + on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); + pos = FindBirthPosition(*new_org, ppos, target_pop); + + // If this placement is valid, do so. Otherwise delete the organism. + if (pos.IsValid()) AddOrgAt(new_org, pos, ppos); + else new_org.Delete(); + } + return pos; + } - trait_man.Verify(verbose); // Make sure modules are accessing traits consistently - trait_man.RegisterAll(org_data_map); // Load in all of the traits to the DataMap - org_data_map.LockLayout(); // Freeze the data map into its current state + OrgPosition MABE::DoBirth(const Organism & org, + OrgPosition ppos, + OrgPosition target_pos, + bool do_mutations) { + emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. + emp_assert(target_pos.IsValid()); // Target positions must already be valid. - // Alert modules (especially org managers) to the final set of traits. - for (emp::Ptr mod_ptr : modules) { - mod_ptr->SetupDataMap(org_data_map); + before_repro_sig.Trigger(ppos); + emp::Ptr new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); + on_offspring_ready_sig.Trigger(*new_org, ppos, target_pos.Pop()); + + AddOrgAt(new_org, target_pos, ppos); + + return target_pos; + } + + + /// Resize a population while clearing all of the organisms in it. + void MABE::EmptyPop(Population & pop, size_t new_size) { + // Clean up any organisms in the population. + for (PopIterator pos = pop.begin(); pos != pop.end(); ++pos) { + ClearOrgAt(pos); } + + MABEBase::ResizePop(pop, new_size); } - /// Link signals to the modules that implment responses to those signals. - void MABE::UpdateSignals() { - // Clear all module vectors. - for (auto modv : sig_ptrs) modv->resize(0); - // Loop through each module to update its signals. - for (emp::Ptr mod_ptr : modules) { - // If a module is deactivated, don't use it's signals. - if (mod_ptr->_active == false) continue; + /// Return a ramdom position from a desginated population with a living organism in it. + OrgPosition MABE::GetRandomOrgPos(Population & pop) { + emp_assert(pop.GetNumOrgs() > 0, "GetRandomOrgPos cannot be called if there are no orgs."); + // @CAO: Something better to do in a sparse population? + OrgPosition pos = GetRandomPos(pop); + while (pos.IsEmpty()) pos = GetRandomPos(pop); + return pos; + } - // For the current module, loop through all of the signals. - for (size_t sig_id = 0; sig_id < sig_ptrs.size(); sig_id++) { - if (mod_ptr->has_signal[sig_id]) sig_ptrs[sig_id]->push_back(mod_ptr); + + // --- Collection Management --- + + Collection MABE::FromString(const std::string & load_str) { + Collection out; + auto slices = emp::view_slices(load_str, ','); + for (auto name : slices) { + int pop_id = GetPopID(name); + if (pop_id == -1) error_man.AddError("Unknown population: ", name); + else out.Insert(GetPopulation(pop_id)); + } + return out; + } + + + /// Build a function to scan a collection of organisms, reading the value for the given + /// trait_name from each, aggregating those values based on the trait_filter and returning + /// the result as a string. + /// + /// trait_filter option are: + /// : Default to the value of the trait for the first organism in the collection. + /// [ID] : Value of this trait for the organism at the given index of the collection. + /// [OP][VALUE] : Count how often this value has the [OP] relationship with [VALUE]. + /// [OP] can be ==, !=, <, >, <=, or >= + /// [VALUE] can be any numeric value + /// [OP][TRAIT] : Count how often this trait has the [OP] relationship with [TRAIT] + /// [OP] can be ==, !=, <, >, <=, or >= + /// [TRAIT] can be any other trait name + /// unique : Return the number of distinct value for this trait (alias="richness"). + /// mode : Return the most common value in this colection (aliases="dom","dominant"). + /// min : Return the smallest value of this trait present. + /// max : Return the largest value of this trait present. + /// ave : Return the average value of this trait (alias="mean"). + /// median : Return the median value of this trait. + /// variance : Return the variance of this trait. + /// stddev : Return the standard deviation of this trait. + /// sum : Return the summation of all values of this trait (alias="total") + /// entopy : Return the Shannon entropy of this value. + /// :trait : Return the mutual information with another provided trait. + + MABE::trait_fun_t MABE::BuildTraitFunction(const std::string & trait_name, + std::string trait_filter) { + // The trait input has two components: + // (1) the trait NAME and + // (2) (optionally) how to calculate the trait SUMMARY, such as min, max, ave, etc. + + // Everything before the first colon is the trait name. + size_t trait_id = org_data_map.GetID(trait_name); + emp::TypeID trait_type = org_data_map.GetType(trait_id); + const bool is_numeric = trait_type.IsArithmetic(); + + auto get_double_fun = [trait_id, trait_type](const Organism & org) { + return org.GetTraitAsDouble(trait_id, trait_type); + }; + auto get_string_fun = [trait_id, trait_type](const Organism & org) { + return emp::to_literal( org.GetTraitAsString(trait_id, trait_type) ); + }; + + // Return the number of times a specific value was found. + if (trait_filter[0] == '=') { + // @CAO: DO THIS! + trait_filter.erase(0,1); // Erase the '=' and we are left with the string to match. + } + + // Otherwise pass along to the BuildCollectFun with the correct type... + auto result = is_numeric + ? emp::BuildCollectFun(trait_filter, get_double_fun) + : emp::BuildCollectFun(trait_filter, get_string_fun); + + // If we made it past the 'if' statements, we don't know this aggregation type. + if (!result) { + error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_name, "'."); + return [](const Collection &){ return std::string("Error! Unknown trait function"); }; + } + + return result; + } + + // Handler for printing trait data + void MABE::OutputTraitData(std::ostream & os, + Collection target_collect, + std::string format, + bool print_headers) + { + emp::vector funs; ///< Functions to call each update. + emp::remove_whitespace(format); + auto fun_it = file_fun_cache.find(format); + + // If we need headers, set them up! + if (print_headers) { + // Identify the contents of each column. + emp::vector cols = emp::slice(format, ','); + + // Print the headers into the file. + os << "#update"; + for (size_t i = 0; i < cols.size(); i++) { + os << ", " << cols[i]; } + os << '\n'; } - // Now that we have scanned the signals, we can turn off the rescan flag. - rescan_signals = false; + // If the functions don't exist yet, set them up! + if (fun_it == file_fun_cache.end()) { + // Identify the contents of each column. + emp::vector cols = emp::slice(format, ','); + + // Setup a function to collect data associated with each column. + funs.resize(cols.size()); + for (size_t i = 0; i < cols.size(); i++) { + std::string trait_filter = cols[i]; + std::string trait_name = emp::string_pop(trait_filter,':'); + funs[i] = BuildTraitFunction(trait_name, trait_filter); + } + + // Insert the new entry into the cache and update the iterator. + fun_it = file_fun_cache.insert({format, funs}).first; + } + else funs = fun_it->second; + + // And, finally, print the data! + os << GetUpdate(); + for (auto & fun : funs) { + os << ", " << fun(target_collect); + } + os << std::endl; } void MABE::SetupConfig() { @@ -945,6 +1009,7 @@ namespace mabe { return result; } + } #endif From 701bf440386a42213ca9ba583363fd4356298625 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 19 Sep 2021 14:03:04 -0400 Subject: [PATCH 141/445] Renamed FromString() to ToCollection() for acuracy and specificity. --- source/core/MABE.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index ee4d6011..8f962187 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -280,7 +280,7 @@ namespace mabe { return collect.ToString(); } - Collection FromString(const std::string & load_str); + Collection ToCollection(const std::string & load_str); Collection GetAlivePopulation(size_t id) { Collection col(GetPopulation(id)); @@ -584,7 +584,7 @@ namespace mabe { [this](const std::string & filename, const std::string & collection, std::string format) { const bool file_exists = files.Has(filename); ///< Is file is already setup? std::ostream & file = files.GetOutputStream(filename); ///< File to write to. - OutputTraitData(file, FromString(collection), format, !file_exists); + OutputTraitData(file, ToCollection(collection), format, !file_exists); return 0; }; config.AddFunction("output", output_fun, @@ -602,7 +602,7 @@ namespace mabe { // @CAO Should be a method on a Population or Collection, not called by name. std::function pop_size_fun = - [this](const std::string & target) { return FromString(target).GetSize(); }; + [this](const std::string & target) { return ToCollection(target).GetSize(); }; config.AddFunction("size", pop_size_fun, "Return the size of the target population."); // std::function trait_mean_fun = @@ -858,7 +858,7 @@ namespace mabe { // --- Collection Management --- - Collection MABE::FromString(const std::string & load_str) { + Collection MABE::ToCollection(const std::string & load_str) { Collection out; auto slices = emp::view_slices(load_str, ','); for (auto name : slices) { From bdbe5085e8740535ae063c040fc1ae7fda28df57 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 19 Sep 2021 14:03:36 -0400 Subject: [PATCH 142/445] Fixed use of FromString(); now ToCollection(). --- source/core/Module.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index eebd29aa..6039f699 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -75,7 +75,7 @@ namespace mabe { std::function set_fun = [this,&var](const std::string & load_str){ - var = control.FromString(load_str); + var = control.ToCollection(load_str); }; return GetScope().LinkFuns(name, get_fun, set_fun, desc); From f016341341f6f247204927d2b845d1d697096714 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 19 Sep 2021 15:01:36 -0400 Subject: [PATCH 143/445] Shifted Eval() to use a string_view. --- source/config/Config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 881e7895..5325cb16 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -362,7 +362,7 @@ namespace mabe { } // Load the provided statement and run it. - std::string Eval(const std::string & statement, emp::Ptr scope=nullptr) { + std::string Eval(std::string_view statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); if (!scope) scope = &root_scope; // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. From 471b1d5064ed973abd2a9ecf41863e7aa2c0fe5f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 19 Sep 2021 15:02:16 -0400 Subject: [PATCH 144/445] Added a MABE::Preprocess() helper function. --- source/core/MABE.hpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 8f962187..a72309dd 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -137,6 +137,8 @@ namespace mabe { /// Link signals to the modules that implment responses to those signals. void UpdateSignals(); + /// Find any instances of ${X} and eval the X. + std::string Preprocess(const std::string & in_string); public: MABE(int argc, char* argv[]); ///< MABE command-line constructor. @@ -524,6 +526,32 @@ namespace mabe { rescan_signals = false; } + /// Find any instances of ${X} and eval the X. + std::string MABE::Preprocess(const std::string & in_string) { + std::string out_string = in_string; + + // Seek out instances of "${" to indicate the start of pre-processing. + for (size_t i = 0; i < out_string.size(); ++i) { + if (out_string[i] != '$') continue; // Replacement tag must start with a '$'. + if (out_string.size() <= i+2) break; // Not enough room for a replacement tag. + if (out_string[i+1] == '$') { // Compress two $$ into on $ + out_string.erase(i,1); + continue; + } + if (out_string[i+1] != '{') continue; // Eval must be surrounded by braces. + + // If we made it this far, we have a starting match! + size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); + if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? + const std::string replacement_text = + config.Eval(emp::view_string_range(out_string, i+2, end_pos-1)); + out_string.replace(i, end_pos-i, replacement_text); + + i += replacement_text.size(); // Continue from the end point... + } + + return out_string; + } // ---------------- PUBLIC MEMBER FUNCTIONS ----------------- From 8c356647cd53b29b3da010d4fa07db5d012dc8b0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 20 Sep 2021 12:53:04 -0400 Subject: [PATCH 145/445] Fixed MABE::Preprocess() and added PP function to config language. --- source/core/MABE.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index a72309dd..be181aee 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -544,8 +544,8 @@ namespace mabe { size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? const std::string replacement_text = - config.Eval(emp::view_string_range(out_string, i+2, end_pos-1)); - out_string.replace(i, end_pos-i, replacement_text); + config.Eval(emp::view_string_range(out_string, i+2, end_pos)); + out_string.replace(i, end_pos-i+1, replacement_text); i += replacement_text.size(); // Continue from the end point... } @@ -627,6 +627,10 @@ namespace mabe { }; config.AddFunction("print", print_fun, "Print out the provided variables."); + std::function preprocess_fun = + [this](const std::string & str) { return Preprocess(str); }; + config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); + // @CAO Should be a method on a Population or Collection, not called by name. std::function pop_size_fun = From 8b7092d5de6cf7029c7903795fdc35547025c1a1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 20 Sep 2021 22:55:59 -0400 Subject: [PATCH 146/445] Setup output to be pre-processed; added config functions trait_value() and trait_string() --- source/core/MABE.hpp | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index be181aee..29126393 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -27,9 +27,10 @@ #include "emp/control/Signal.hpp" #include "emp/data/DataMap.hpp" #include "emp/data/DataMapParser.hpp" +#include "emp/datastructs/vector_utils.hpp" #include "emp/io/StreamManager.hpp" #include "emp/math/Random.hpp" -#include "emp/datastructs/vector_utils.hpp" +#include "emp/tools/string_utils.hpp" #include "../config/Config.hpp" @@ -637,7 +638,26 @@ namespace mabe { [this](const std::string & target) { return ToCollection(target).GetSize(); }; config.AddFunction("size", pop_size_fun, "Return the size of the target population."); - // std::function trait_mean_fun = + + // --- TRAIT-BASED FUNCTIONS --- + + std::function trait_string_fun = + [this](const std::string & target, std::string trait_filter) { + std::string trait_name = emp::string_pop(trait_filter,':'); + auto fun = BuildTraitFunction(trait_name, trait_filter); + return fun( ToCollection(target) ); + }; + config.AddFunction("trait_string", trait_string_fun, "Collect information about a specified trait."); + + std::function trait_value_fun = + [this](const std::string & target, std::string trait_filter) { + std::string trait_name = emp::string_pop(trait_filter,':'); + auto fun = BuildTraitFunction(trait_name, trait_filter); + return emp::from_string(fun( ToCollection(target) )); + }; + config.AddFunction("trait_value", trait_value_fun, "Collect information about a specified trait."); + + // std::function trait_mean_fun = // [this](const std::string & target, const std::string & trait) { // if constexpr (std::is_arithmetic_v) { // double total = 0.0; @@ -928,7 +948,7 @@ namespace mabe { /// :trait : Return the mutual information with another provided trait. MABE::trait_fun_t MABE::BuildTraitFunction(const std::string & trait_name, - std::string trait_filter) { + std::string trait_filter) { // The trait input has two components: // (1) the trait NAME and // (2) (optionally) how to calculate the trait SUMMARY, such as min, max, ave, etc. @@ -973,7 +993,6 @@ namespace mabe { { emp::vector funs; ///< Functions to call each update. emp::remove_whitespace(format); - auto fun_it = file_fun_cache.find(format); // If we need headers, set them up! if (print_headers) { @@ -988,7 +1007,11 @@ namespace mabe { os << '\n'; } - // If the functions don't exist yet, set them up! + // Pre-process the format to deal with config variables that need translating. + format = Preprocess(format); + + // Check the cache for the functions to run; if they don't exist yet, set them up! + auto fun_it = file_fun_cache.find(format); if (fun_it == file_fun_cache.end()) { // Identify the contents of each column. emp::vector cols = emp::slice(format, ','); From 0f24b63b80708cb5dfb3063a9f365ad667bf482e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 20 Sep 2021 23:17:17 -0400 Subject: [PATCH 147/445] Fixed event triggering error on deleting result entry. --- source/config/ConfigEvents.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/ConfigEvents.hpp b/source/config/ConfigEvents.hpp index 030fafdd..b99c3ed2 100644 --- a/source/config/ConfigEvents.hpp +++ b/source/config/ConfigEvents.hpp @@ -46,7 +46,7 @@ namespace mabe { // should continue to be considered active. bool Trigger() { auto result_entry = ast_action->Process(); - if (result_entry->IsTemporary()) result_entry.Delete(); + if (result_entry && result_entry->IsTemporary()) result_entry.Delete(); next += repeat; if (max != -1.0 && next > max) repeat = 0.0; From a49e0d967b0066a71b57f63657004887c821b952 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 21 Sep 2021 12:24:01 -0400 Subject: [PATCH 148/445] Changed config functions to be ALL_CAPS and deprecated the old versions. --- source/core/MABE.hpp | 70 +++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 29126393..2bf9f692 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -141,6 +141,9 @@ namespace mabe { /// Find any instances of ${X} and eval the X. std::string Preprocess(const std::string & in_string); + /// Setup a function as deprecated so we can phase it out. + void Deprecate(const std::string & old_name, const std::string & new_name); + public: MABE(int argc, char* argv[]); ///< MABE command-line constructor. MABE(const MABE &) = delete; @@ -554,6 +557,17 @@ namespace mabe { return out_string; } + void MABE::Deprecate(const std::string & old_name, const std::string & new_name) { + std::function> &)> dep_fun = + [this,old_name,new_name](const emp::vector> &){ + std::cerr << "Function '" << old_name << "' deprecated; use '" << new_name << "'\n"; + exit_now = true; + return 0; + }; + + config.AddFunction(old_name, dep_fun, std::string("Deprecated. Use: ") + new_name); + } + // ---------------- PUBLIC MEMBER FUNCTIONS ----------------- @@ -581,52 +595,41 @@ namespace mabe { } + // ------ DEPRECATED FUNCTION NAMES ------ + Deprecate("exit", "EXIT"); + Deprecate("inject", "INJECT"); + Deprecate("print", "PRINT"); + // Add other built-in functions to the config file. - // 'eval' dynamically evaluates the contents of a string. - // std::function eval_fun = - // [this](const std::string & expression) { config.Eval(expression); return 0; }; - // config.AddFunction("eval", eval_fun, "Dynamically evaluate the string passed in."); + // 'EVAL' dynamically evaluates the contents of a string. std::function eval_fun = [this](const std::string & expression) { return config.Eval(expression); }; - config.AddFunction("eval", eval_fun, "Dynamically evaluate the string passed in."); + config.AddFunction("EVAL", eval_fun, "Dynamically evaluate the string passed in."); - // 'exit' should terminate a run. + // 'EXIT' terminates a run gracefully. std::function exit_fun = [this](){ exit_now = true; return 0; }; - config.AddFunction("exit", exit_fun, "Exit from this MABE run."); + config.AddFunction("EXIT", exit_fun, "Exit from this MABE run."); - // 'inject' allows a user to add an organism to a population. + // 'INJECT' allows a user to add an organism to a population. std::function inject_fun = [this](const std::string & org_type_name, const std::string & pop_name, size_t count) { Inject(org_type_name, pop_name, count); return 0; }; - config.AddFunction("inject", inject_fun, + config.AddFunction("INJECT", inject_fun, "Inject organisms into a population (args: org_name, pop_name, org_count)."); - // 'output' will collect data and write it to a file. - files.SetOutputDefaultFile(); // Stream manager should default to files for output. - std::function output_fun = - [this](const std::string & filename, const std::string & collection, std::string format) { - const bool file_exists = files.Has(filename); ///< Is file is already setup? - std::ostream & file = files.GetOutputStream(filename); ///< File to write to. - OutputTraitData(file, ToCollection(collection), format, !file_exists); - return 0; - }; - config.AddFunction("output", output_fun, - "Print out the provided trait-based data; args: filename, collection, format."); - - - // 'print' is a simple debugging command to output the value of a variable. + // 'PRINT' is a simple debugging command to output the value of a variable. std::function> &)> print_fun = [](const emp::vector> & args) { for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); return 0; }; - config.AddFunction("print", print_fun, "Print out the provided variables."); + config.AddFunction("PRINT", print_fun, "Print out the provided variables."); std::function preprocess_fun = [this](const std::string & str) { return Preprocess(str); }; @@ -636,7 +639,20 @@ namespace mabe { // @CAO Should be a method on a Population or Collection, not called by name. std::function pop_size_fun = [this](const std::string & target) { return ToCollection(target).GetSize(); }; - config.AddFunction("size", pop_size_fun, "Return the size of the target population."); + config.AddFunction("SIZE", pop_size_fun, "Return the size of the target population."); + + + // 'WRITE' will collect data and write it to a file. + files.SetOutputDefaultFile(); // Stream manager should default to files for output. + std::function write_fun = + [this](const std::string & filename, const std::string & collection, std::string format) { + const bool file_exists = files.Has(filename); ///< Is file is already setup? + std::ostream & file = files.GetOutputStream(filename); ///< File to write to. + OutputTraitData(file, ToCollection(collection), format, !file_exists); + return 0; + }; + config.AddFunction("WRITE", write_fun, + "Write the provided trait-based data to file; args: filename, collection, format."); // --- TRAIT-BASED FUNCTIONS --- @@ -647,7 +663,7 @@ namespace mabe { auto fun = BuildTraitFunction(trait_name, trait_filter); return fun( ToCollection(target) ); }; - config.AddFunction("trait_string", trait_string_fun, "Collect information about a specified trait."); + config.AddFunction("TRAIT_STRING", trait_string_fun, "Collect information about a specified trait."); std::function trait_value_fun = [this](const std::string & target, std::string trait_filter) { @@ -655,7 +671,7 @@ namespace mabe { auto fun = BuildTraitFunction(trait_name, trait_filter); return emp::from_string(fun( ToCollection(target) )); }; - config.AddFunction("trait_value", trait_value_fun, "Collect information about a specified trait."); + config.AddFunction("TRAIT_VALUE", trait_value_fun, "Collect information about a specified trait."); // std::function trait_mean_fun = // [this](const std::string & target, const std::string & trait) { From 2a0b5d0fdddcd988dbb1fdb11f18656e2b07a3d1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 21 Sep 2021 13:29:27 -0400 Subject: [PATCH 149/445] Addred a range of default functions to Config. --- source/config/Config.hpp | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 5325cb16..47f89040 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -245,6 +245,96 @@ namespace mabe { precedence_map["&&"] = cur_prec++; precedence_map["||"] = cur_prec++; precedence_map["="] = cur_prec++; + + // Setup default functions. + + // 'EVAL' dynamically evaluates the contents of a string. + std::function eval_fun = + [this](const std::string & expression) { return Eval(expression); }; + AddFunction("EVAL", eval_fun, "Dynamically evaluate the string passed in."); + + // 'PRINT' is a simple debugging command to output the value of a variable. + std::function> &)> print_fun = + [](const emp::vector> & args) { + for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); + return 0; + }; + AddFunction("PRINT", print_fun, "Print out the provided variables."); + + // Default 1-input math functions + std::function math1_fun = [](double x){ return std::abs(x); }; + AddFunction("ABS", math1_fun, "Absolute Value" ); + math1_fun = [](double x){ return emp::Pow(emp::E, x); }; + AddFunction("EXP", math1_fun, "Exponentiation" ); + math1_fun = [](double x){ return std::log(x); }; + AddFunction("LOG2", math1_fun, "Log base-2" ); + math1_fun = [](double x){ return std::log10(x); }; + AddFunction("LOG10", math1_fun, "Log base-10" ); + + math1_fun = [](double x){ return std::sqrt(x); }; + AddFunction("SQRT", math1_fun, "Square Root" ); + math1_fun = [](double x){ return std::cbrt(x); }; + AddFunction("CBRT", math1_fun, "Cube Root" ); + + math1_fun = [](double x){ return std::sin(x); }; + AddFunction("SIN", math1_fun, "Sine" ); + math1_fun = [](double x){ return std::cos(x); }; + AddFunction("COS", math1_fun, "Cosine" ); + math1_fun = [](double x){ return std::tan(x); }; + AddFunction("TAN", math1_fun, "Tangent" ); + math1_fun = [](double x){ return std::asin(x); }; + AddFunction("ASIN", math1_fun, "Arc Sine" ); + math1_fun = [](double x){ return std::acos(x); }; + AddFunction("ACOS", math1_fun, "Arc Cosine" ); + math1_fun = [](double x){ return std::atan(x); }; + AddFunction("ATAN", math1_fun, "Arc Tangent" ); + math1_fun = [](double x){ return std::sinh(x); }; + AddFunction("SINH", math1_fun, "Hyperbolic Sine" ); + math1_fun = [](double x){ return std::cosh(x); }; + AddFunction("COSH", math1_fun, "Hyperbolic Cosine" ); + math1_fun = [](double x){ return std::tanh(x); }; + AddFunction("TANH", math1_fun, "Hyperbolic Tangent" ); + math1_fun = [](double x){ return std::asinh(x); }; + AddFunction("ASINH", math1_fun, "Hyperbolic Arc Sine" ); + math1_fun = [](double x){ return std::acosh(x); }; + AddFunction("ACOSH", math1_fun, "Hyperbolic Arc Cosine" ); + math1_fun = [](double x){ return std::atanh(x); }; + AddFunction("ATANH", math1_fun, "Hyperbolic Arc Tangent" ); + + math1_fun = [](double x){ return std::ceil(x); }; + AddFunction("CEIL", math1_fun, "Round UP" ); + math1_fun = [](double x){ return std::floor(x); }; + AddFunction("FLOOR", math1_fun, "Round DOWN" ); + math1_fun = [](double x){ return std::round(x); }; + AddFunction("ROUND", math1_fun, "Round to nearest" ); + + math1_fun = [](double x){ return std::isinf(x); }; + AddFunction("ISINF", math1_fun, "Test if Infinite" ); + math1_fun = [](double x){ return std::isnan(x); }; + AddFunction("ISNAN", math1_fun, "Test if Not-a-number" ); + + // Default 2-input math functions + std::function math2_fun = [](double x, double y){ return std::hypot(x,y); }; + AddFunction("HYPOT", math2_fun, "Given sides, find hypotenuse" ); + math2_fun = [](double x, double y){ return emp::Pow(x,y); }; + AddFunction("LOG", math2_fun, "Take log of arg1 with base arg2" ); + math2_fun = [](double x, double y){ return (xy) ? x : y; }; + AddFunction("MAX", math2_fun, "Return greater value" ); + math2_fun = [](double x, double y){ return emp::Pow(x,y); }; + AddFunction("POW", math2_fun, "Take arg1 to the arg2 power" ); + + // Default 3-input math functions + std::function math3_fun = + [](double x, double y, double z){ return (x!=0.0) ? y : z; }; + AddFunction("IF", math3_fun, "If arg1 is true, return arg2, else arg3" ); + math3_fun = [](double x, double y, double z){ return (xz) ? z : x; }; + AddFunction("CLAMP", math3_fun, "Return arg1, forced into range [arg2,arg3]" ); + math3_fun = [](double x, double y, double z){ return (z-y)*x+y; }; + AddFunction("TO_SCALE", math3_fun, "Scale arg1 to arg2-arg3 as unit distance" ); + math3_fun = [](double x, double y, double z){ return (x-y) / (z-y); }; + AddFunction("FROM_SCALE", math3_fun, "Scale arg1 from arg2-arg3 as unit distance" ); } // Prevent copy or move since we are using lambdas that capture 'this' From 923ba6575835bb0a64832094e2458fffec67c792 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 21 Sep 2021 13:38:44 -0400 Subject: [PATCH 150/445] Removed functions EVAL and PRINT from MABE setup of Config since they're not built-in. --- source/core/MABE.hpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 2bf9f692..e25c2d8b 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -602,12 +602,6 @@ namespace mabe { // Add other built-in functions to the config file. - // 'EVAL' dynamically evaluates the contents of a string. - std::function eval_fun = - [this](const std::string & expression) { return config.Eval(expression); }; - config.AddFunction("EVAL", eval_fun, "Dynamically evaluate the string passed in."); - - // 'EXIT' terminates a run gracefully. std::function exit_fun = [this](){ exit_now = true; return 0; }; config.AddFunction("EXIT", exit_fun, "Exit from this MABE run."); @@ -623,14 +617,6 @@ namespace mabe { "Inject organisms into a population (args: org_name, pop_name, org_count)."); - // 'PRINT' is a simple debugging command to output the value of a variable. - std::function> &)> print_fun = - [](const emp::vector> & args) { - for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); - return 0; - }; - config.AddFunction("PRINT", print_fun, "Print out the provided variables."); - std::function preprocess_fun = [this](const std::string & str) { return Preprocess(str); }; config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); From 797a113758dc330dda9f28a1d604c06acbb074ee Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 21 Sep 2021 13:39:18 -0400 Subject: [PATCH 151/445] Added ** operator to ConfigLexer. --- source/config/ConfigLexer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/ConfigLexer.hpp b/source/config/ConfigLexer.hpp index 3512c8bf..8489704b 100644 --- a/source/config/ConfigLexer.hpp +++ b/source/config/ConfigLexer.hpp @@ -39,7 +39,7 @@ namespace mabe { /// Symbol tokens should have least priority. They include any solitary character not listed /// above, or pre-specified multi-character groups. - token_symbol = AddToken("Symbol", ".|\"::\"|\"==\"|\"!=\"|\"<=\"|\">=\"|\"->\"|\"&&\"|\"||\"|\"<<\"|\">>\"|\"++\"|\"--\""); + token_symbol = AddToken("Symbol", ".|\"::\"|\"==\"|\"!=\"|\"<=\"|\">=\"|\"->\"|\"&&\"|\"||\"|\"<<\"|\">>\"|\"++\"|\"--\"|\"**\""); } bool IsID(const emp::Token token) const noexcept { return token.token_id == token_identifier; } From af7cd2d64f8a2eceb1525893b53d4cfa87a1dedd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 21 Sep 2021 13:39:39 -0400 Subject: [PATCH 152/445] Setup operator ** to work for exponentiation --- source/config/Config.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 47f89040..159da809 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -238,6 +238,7 @@ namespace mabe { // Setup operator precedence. size_t cur_prec = 0; precedence_map["("] = cur_prec++; + precedence_map["**"] = cur_prec++; precedence_map["*"] = precedence_map["/"] = precedence_map["%"] = cur_prec++; precedence_map["+"] = precedence_map["-"] = cur_prec++; precedence_map["<"] = precedence_map["<="] = precedence_map[">"] = precedence_map[">="] = cur_prec++; @@ -604,9 +605,10 @@ namespace mabe { std::function fun; if (symbol == "+") fun = [](double val1, double val2){ return val1 + val2; }; else if (symbol == "-") fun = [](double val1, double val2){ return val1 - val2; }; + else if (symbol == "**") fun = [](double val1, double val2){ return emp::Pow(val1, val2); }; else if (symbol == "*") fun = [](double val1, double val2){ return val1 * val2; }; else if (symbol == "/") fun = [](double val1, double val2){ return val1 / val2; }; - else if (symbol == "%") fun = [](double val1, double val2){ return ((size_t) val1) % ((size_t) val2); }; + else if (symbol == "%") fun = [](double val1, double val2){ return emp::Mod(val1, val2); }; else if (symbol == "==") fun = [](double val1, double val2){ return val1 == val2; }; else if (symbol == "!=") fun = [](double val1, double val2){ return val1 != val2; }; else if (symbol == "<") fun = [](double val1, double val2){ return val1 < val2; }; From bc12db177ce183e1c6f566c15eafff2fe36bf068 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 22 Sep 2021 23:51:32 -0400 Subject: [PATCH 153/445] Added a trace_eval function to config. --- source/core/MABE.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index e25c2d8b..ad45d2dc 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -632,8 +632,8 @@ namespace mabe { files.SetOutputDefaultFile(); // Stream manager should default to files for output. std::function write_fun = [this](const std::string & filename, const std::string & collection, std::string format) { - const bool file_exists = files.Has(filename); ///< Is file is already setup? - std::ostream & file = files.GetOutputStream(filename); ///< File to write to. + const bool file_exists = files.Has(filename); // Is file is already setup? + std::ostream & file = files.GetOutputStream(filename); // File to write to. OutputTraitData(file, ToCollection(collection), format, !file_exists); return 0; }; @@ -641,6 +641,18 @@ namespace mabe { "Write the provided trait-based data to file; args: filename, collection, format."); + // --- ORGANISM-BASED FUNCTIONS --- + + std::function trace_eval_fun = + [this](const std:string & filename, const std::string & target, double id) { + Collection c = ToCollection(target); // Collection with organisms + Organism & org = c.At((size_t) id); // Specific organism to analyze. + ostream & file = files.GetOutputStream(filename); // File to write to. + TraceEval(org, file); + return 0; + }; + config.AddFunction("TRACE_EVAL", trace_eval_fun, "Return the size of the target population."); + // --- TRAIT-BASED FUNCTIONS --- std::function trait_string_fun = From bbf61bd7d76907f73b7a2277e118d777dbdcd8dc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 23 Sep 2021 23:24:49 -0400 Subject: [PATCH 154/445] Cleanup on data collection functions. --- source/core/data_collect.hpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 372cde58..0dc11c83 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -220,98 +220,98 @@ namespace emp { template std::function - BuildCollectFun(std::string type, FUN_T get_fun) { + BuildCollectFun(std::string action, FUN_T get_fun) { // ### DEFAULT // If no trait function is specified, assume that we should use the first index. - if (type == "") type = "0"; + if (action == "") action = "0"; // Return the index if a simple number was provided. - if (emp::is_digits(type)) { - size_t index = emp::from_string(type); + if (emp::is_digits(action)) { + size_t index = emp::from_string(action); return [get_fun,index](const CONTAIN_T & container) { return DataCollect::Index(container, get_fun, index); }; } // Return the number of distinct values found in this trait. - else if (type == "unique" || type == "richness") { + else if (action == "unique" || action == "richness") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Unique(container, get_fun); }; } // Return the most common value found for this trait. - else if (type == "mode" || type == "dom" || type == "dominant") { + else if (action == "mode" || action == "dom" || action == "dominant") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Mode(container, get_fun); }; } // Return the lowest trait value. - else if (type == "min") { + else if (action == "min") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Min(container, get_fun); }; } // Return the highest trait value. - else if (type == "max") { + else if (action == "max") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Max(container, get_fun); }; } // Return the lowest trait value. - else if (type == "min_id") { + else if (action == "min_id") { return [get_fun](const CONTAIN_T & container) { return DataCollect::MinID(container, get_fun); }; } // Return the highest trait value. - else if (type == "max_id") { + else if (action == "max_id") { return [get_fun](const CONTAIN_T & container) { return DataCollect::MaxID(container, get_fun); }; } // Return the average trait value. - else if (type == "ave" || type == "mean") { + else if (action == "ave" || action == "mean") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Mean(container, get_fun); }; } // Return the middle-most trait value. - else if (type == "median") { + else if (action == "median") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Median(container, get_fun); }; } // Return the standard deviation of all trait values. - else if (type == "variance") { + else if (action == "variance") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Variance(container, get_fun); }; } // Return the standard deviation of all trait values. - else if (type == "stddev") { + else if (action == "stddev") { return [get_fun](const CONTAIN_T & container) { return DataCollect::StandardDeviation(container, get_fun); }; } // Return the total of all trait values. - else if (type == "sum" || type=="total") { + else if (action == "sum" || action == "total") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Sum(container, get_fun); }; } // Return the entropy of values for this trait. - else if (type == "entropy") { + else if (action == "entropy") { return [get_fun](const CONTAIN_T & container) { return DataCollect::Entropy(container, get_fun); }; From 1dbcd61b7a96989b82a7bfcd5cff21fb859e679d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 24 Sep 2021 16:43:13 -0400 Subject: [PATCH 155/445] Trace eval functionality now works. --- source/core/MABE.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index ad45d2dc..e7dbb2b5 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -120,9 +120,8 @@ namespace mabe { /// List all of the available modules included in the current compilation. void ShowModules(); - void TraceEval(Organism & org, std::ostream & os) { - trace_eval_sig.Trigger(org, os); - } + /// Ask evaluation modules to trace the execution of the provided organism. + void TraceEval(Organism & org, std::ostream & os) { trace_eval_sig.Trigger(org, os); } /// Process all of the arguments that were passed in on the command line. void ProcessArgs(); @@ -644,10 +643,10 @@ namespace mabe { // --- ORGANISM-BASED FUNCTIONS --- std::function trace_eval_fun = - [this](const std:string & filename, const std::string & target, double id) { - Collection c = ToCollection(target); // Collection with organisms - Organism & org = c.At((size_t) id); // Specific organism to analyze. - ostream & file = files.GetOutputStream(filename); // File to write to. + [this](const std::string & filename, const std::string & target, double id) { + Collection c = ToCollection(target); // Collection with organisms + Organism & org = c.At((size_t) id); // Specific organism to analyze. + std::ostream & file = files.GetOutputStream(filename); // File to write to. TraceEval(org, file); return 0; }; From 49365ca32de2c84ff5ad51047d507a4df691c890 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 24 Sep 2021 16:45:05 -0400 Subject: [PATCH 156/445] Updated EvalMancala,hpp to have a TraceEval function; also updated doc comments. --- source/evaluate/games/EvalMancala.hpp | 67 +++++++++++++++++++-------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index e8383aa3..905b6c0d 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -110,12 +110,18 @@ namespace mabe { return (size_t) (move - 'A'); } - + /// A uniform function specification that takes a game state and returns a move to make. using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; - // Setup the fitness function for a whole game. - double EvalGame(const mancala_ai_t & player0, const mancala_ai_t & player1, - bool cur_player=0, bool verbose=false) { + /// Evaluate a game between two functions that each take the game state as input and return + /// their next move as output. + /// @param player0 The function to be evaluated + /// @param player1 The function to test against + /// @param cur_player Which player should make the first move? (default=0, the organism) + /// @param verbose Should we print out extra output? (default=false) + /// @param os Output stream for any extra ouput. (default=cout) + double EvalGame(const mancala_ai_t & player0, const mancala_ai_t & player1, bool cur_player=0, + bool verbose=false, std::ostream & os=std::cout) { emp::Mancala game(cur_player==0); size_t round = 0, errors = 0; game_trace.resize(0); @@ -125,14 +131,14 @@ namespace mabe { size_t best_move = play_fun(game); if (verbose) { - std::cout << "round = " << round++ << " errors = " << errors << std::endl; - game.Print(); + os << "round = " << round++ << " errors = " << errors << std::endl; + game.Print(os); char move_sym = (char) ('A' + best_move); - std::cout << "Move = " << move_sym; + os << "Move = " << move_sym; if (game.GetCurSide()[best_move] == 0) { - std::cout << " (illegal!)"; + os << " (illegal!)"; } - std::cout << std::endl << std::endl; + os << std::endl << std::endl; } // If the chosen move is illegal, shift through other options. @@ -149,7 +155,7 @@ namespace mabe { } if (verbose) { - std::cout << "Final scores -- A: " << game.ScoreA() + os << "Final scores -- A: " << game.ScoreA() << " B: " << game.ScoreB() << std::endl; } @@ -157,31 +163,52 @@ namespace mabe { return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); } + /// Convert an organism into a uniform function that can be plugged into Mancala. mancala_ai_t ToOrgFun(mabe::Organism & org) { return [this,&org](emp::Mancala & game){ return EvalMove(game, org); }; } - // Wrapper for two Organisms competing - double EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool cur_player=0, bool verbose=false) { - return EvalGame(ToOrgFun(org0), ToOrgFun(org1), cur_player, verbose); + /// Evaluate a game: Organism vs. Organism. + /// @param org0 The organism to be evaluated + /// @param org1 The organism to test against + /// @param start_player Which player should make the first move? (default=0, the organism) + /// @param verbose Should we print out extra output? (default=false) + /// @param os Output stream for any extra ouput. (default=cout) + double EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool start_player=0, + bool verbose=false, std::ostream & os=std::cout) { + return EvalGame(ToOrgFun(org0), ToOrgFun(org1), start_player, verbose, os); } - // Wrapper for organism vs. random opponent. - double EvalGame(mabe::Organism & org, emp::Random & random, bool cur_player=0, bool verbose=false) { + /// Evaluate a game: Organism vs. random opponent. + /// @param org The organism to be evaluated + /// @param random The random number generator to use for opponent moves. + /// @param start_player Which player should make the first move? (default=0, the organism) + /// @param verbose Should we print out extra output? (default=false) + /// @param os Output stream for any extra ouput. (default=cout) + double EvalGame(mabe::Organism & org, emp::Random & random, bool start_player=0, + bool verbose=false, std::ostream & os=std::cout) { mancala_ai_t rand_fun = [&random](emp::Mancala & game) { size_t move_id = random.GetUInt(6); while (!game.IsMoveValid(move_id)) move_id = random.GetUInt(6); return move_id; }; - return EvalGame(ToOrgFun(org), rand_fun, cur_player, verbose); + return EvalGame(ToOrgFun(org), rand_fun, start_player, verbose, os); } - // Wrapper for organism vs. human - double EvalGame(mabe::Organism & org, bool cur_player=0) { - mancala_ai_t human_fun = [this](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; - return EvalGame(ToOrgFun(org), human_fun, cur_player, true); + /// Evaluate a game: Organism vs. human opponent. + /// @param org The organism to be evaluated + /// @param start_player Which player should make the first move? (default=0, the organism) + double EvalGame(mabe::Organism & org, bool start_player=0) { + mancala_ai_t human_fun = [this](emp::Mancala & game){ + return EvalMove(game, std::cout, std::cin); + }; + return EvalGame(ToOrgFun(org), human_fun, start_player, true); } + /// Trace the evaluation of an organism, sending output to a specified stream. + void TraceEval(Organism & org, std::ostream & os) override { + double score = EvalGame(org, control.GetRandom(), 0, true, os); + } void OnUpdate(size_t ud) override { control.Verbose("UD ", ud, ": Running EvalMancala::OnUpdate()"); From 86c92ab7c496850b439dfa62e667223a652c4a12 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 25 Sep 2021 10:29:50 -0400 Subject: [PATCH 157/445] Added a RANDOM option for which trait should be inhereted. --- source/core/TraitInfo.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/core/TraitInfo.hpp b/source/core/TraitInfo.hpp index eefb762f..9601f942 100644 --- a/source/core/TraitInfo.hpp +++ b/source/core/TraitInfo.hpp @@ -83,7 +83,7 @@ namespace mabe { NUM_ACCESS ///< How many access methods are there? }; - /// How should this trait be initialized in a newly-born organism? + /// How should this trait be initialized (via inheretence) in a newly-born organism? /// * Injected organisms always use the default value. /// * Modules can moitor signals to make other changes at any time. enum class Init { @@ -91,7 +91,8 @@ namespace mabe { FIRST, ///< Trait is inhereted (from first parent if more than one) AVERAGE, ///< Trait becomes average of all parents on birth. MINIMUM, ///< Trait becomes lowest of all parents on birth. - MAXIMUM ///< Trait becomes highest of all parents on birth. + MAXIMUM, ///< Trait becomes highest of all parents on birth. + RANDOM ///< Choose a random parent and use its value. }; /// Which information should we store in the trait as we go? From 94b70bf57632d89c390bc20ba42526af6834c950 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 26 Sep 2021 22:08:40 -0400 Subject: [PATCH 158/445] Split Mancala evaluation into individual scores and error count. --- source/evaluate/games/EvalMancala.hpp | 71 ++++++++++++++++++--------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 905b6c0d..3ae5c62f 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -21,12 +21,12 @@ namespace mabe { private: Collection target_collect; ///< Which organisms should we evaluate? - std::string input_trait = "input"; ///< Name of trait to put input values. - std::string output_trait = "output"; ///< Name of trait to find output values. - std::string score_trait = "score"; ///< Trait to indicate game results. - std::string trace_trait = "mancala_moves"; ///< Where should game traces be stored? - - emp::vector game_trace; ///< Series of moves made in most recent game. + std::string input_trait = "input"; ///< Trait to put input values. + std::string output_trait = "output"; ///< Trait to find output values. + std::string scoreA_trait = "scoreA"; ///< Trait for this player's game results. + std::string scoreB_trait = "scoreB"; ///< Trait for other player's game results. + std::string error_trait = "num_errors"; ///< Trait counting illegal moves attempted. + std::string fitness_trait = "fitness"; ///< Trait for combined fitness. /// What type of opponent should we use? enum Opponent { @@ -53,8 +53,10 @@ namespace mabe { LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(input_trait, "input_trait", "Into which trait should input values be placed?"); LinkVar(output_trait, "output_trait", "Out of which trait should output values be read?"); - LinkVar(score_trait, "score_trait", "Which trait should we store success rating?"); - LinkVar(trace_trait, "trace_trait", "Which trait should we track the game moves?"); + LinkVar(scoreA_trait, "scoreA_trait", "Trait to save score for this player."); + LinkVar(scoreB_trait, "scoreB_trait", "Trait to save score for opponent."); + LinkVar(error_trait, "error_trait", "Trait to count number of illegal moves attempted."); + LinkVar(fitness_trait, "fitness_trait", "Trait with combined success rating."); LinkMenu(opponent_type, "opponent_type", "Which type of opponent should organisms face?", RANDOM_MOVES, "random", "Always choose a random, legal move.", AI, "ai", "Human supplied (but not very good) AI", @@ -65,8 +67,10 @@ namespace mabe { void SetupModule() override { AddOwnedTrait>(input_trait, "Input values (curret board state)", emp::vector({0.0})); AddRequiredTrait>(output_trait); // Output values (move to make) - AddOwnedTrait(score_trait, "Play success", 0.0); - AddOwnedTrait>(trace_trait, "Series of game moves", emp::vector()); + AddOwnedTrait(scoreA_trait, "Score for this player", 0.0); + AddOwnedTrait(scoreB_trait, "Score for opponent", 0.0); + AddOwnedTrait(error_trait, "Number of illegal moves attempted", 0.0); + AddOwnedTrait(fitness_trait, "Combined success rating", 0.0); } @@ -113,6 +117,17 @@ namespace mabe { /// A uniform function specification that takes a game state and returns a move to make. using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; + /// Information about the results of a match. + struct Results { + size_t scoreA = 0; + size_t scoreB = 0; + size_t num_errors = 0; + + double CalcFitness() const { + return ((double) scoreA) - ((double) scoreB) - ((double) num_errors * 10.0); + } + }; + /// Evaluate a game between two functions that each take the game state as input and return /// their next move as output. /// @param player0 The function to be evaluated @@ -120,11 +135,10 @@ namespace mabe { /// @param cur_player Which player should make the first move? (default=0, the organism) /// @param verbose Should we print out extra output? (default=false) /// @param os Output stream for any extra ouput. (default=cout) - double EvalGame(const mancala_ai_t & player0, const mancala_ai_t & player1, bool cur_player=0, + Results EvalGame(const mancala_ai_t & player0, const mancala_ai_t & player1, bool cur_player=0, bool verbose=false, std::ostream & os=std::cout) { emp::Mancala game(cur_player==0); size_t round = 0, errors = 0; - game_trace.resize(0); while (game.IsDone() == false) { // Determine the current player and their move. auto & play_fun = (cur_player == 0) ? player0 : player1; @@ -147,8 +161,6 @@ namespace mabe { if (++best_move > 5) best_move = 0; } - game_trace.push_back(best_move); // Record the move being done. - // Do the move and determine who goes next. bool go_again = game.DoMove(cur_player, best_move); if (!go_again) cur_player = !cur_player; @@ -160,7 +172,7 @@ namespace mabe { << std::endl; } - return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); + return Results{ game.ScoreA(), game.ScoreB(), errors }; } /// Convert an organism into a uniform function that can be plugged into Mancala. @@ -174,7 +186,7 @@ namespace mabe { /// @param start_player Which player should make the first move? (default=0, the organism) /// @param verbose Should we print out extra output? (default=false) /// @param os Output stream for any extra ouput. (default=cout) - double EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool start_player=0, + Results EvalGame(mabe::Organism & org0, mabe::Organism & org1, bool start_player=0, bool verbose=false, std::ostream & os=std::cout) { return EvalGame(ToOrgFun(org0), ToOrgFun(org1), start_player, verbose, os); } @@ -185,7 +197,7 @@ namespace mabe { /// @param start_player Which player should make the first move? (default=0, the organism) /// @param verbose Should we print out extra output? (default=false) /// @param os Output stream for any extra ouput. (default=cout) - double EvalGame(mabe::Organism & org, emp::Random & random, bool start_player=0, + Results EvalGame(mabe::Organism & org, emp::Random & random, bool start_player=0, bool verbose=false, std::ostream & os=std::cout) { mancala_ai_t rand_fun = [&random](emp::Mancala & game) { size_t move_id = random.GetUInt(6); @@ -198,7 +210,7 @@ namespace mabe { /// Evaluate a game: Organism vs. human opponent. /// @param org The organism to be evaluated /// @param start_player Which player should make the first move? (default=0, the organism) - double EvalGame(mabe::Organism & org, bool start_player=0) { + Results EvalGame(mabe::Organism & org, bool start_player=0) { mancala_ai_t human_fun = [this](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; @@ -207,7 +219,7 @@ namespace mabe { /// Trace the evaluation of an organism, sending output to a specified stream. void TraceEval(Organism & org, std::ostream & os) override { - double score = EvalGame(org, control.GetRandom(), 0, true, os); + EvalGame(org, control.GetRandom(), 0, true, os); } void OnUpdate(size_t ud) override { @@ -216,7 +228,7 @@ namespace mabe { emp_assert(control.GetNumPopulations() >= 1); // Determine the type of competitions to perform. - // @CAO: For the moment, just doing a random opponent!! + // ==> @CAO: For the moment, just doing a random opponent!! // Loop through the living organisms in the target collection to evaluate each. mabe::Collection alive_collect( target_collect.GetAlive() ); @@ -226,10 +238,21 @@ namespace mabe { size_t org_count = 0; for (Organism & org : alive_collect) { control.Verbose("...eval org #", org_count++); - double & score = org.GetTrait(score_trait); - score = EvalGame(org, control.GetRandom()); // Start first. - org.SetTrait(trace_trait, game_trace); // Record the trace of the first game. - score += EvalGame(org, control.GetRandom(), 1); // Start second. + double & scoreA = org.GetTrait(scoreA_trait); + double & scoreB = org.GetTrait(scoreB_trait); + double & num_errors = org.GetTrait(error_trait); + double & fitness = org.GetTrait(fitness_trait); + Results results = EvalGame(org, control.GetRandom()); // Start first. + scoreA = results.scoreA; + scoreB = results.scoreB; + num_errors = results.num_errors; + fitness = results.CalcFitness(); + + results = EvalGame(org, control.GetRandom(), 1); // Start second. + scoreA += results.scoreA; + scoreB += results.scoreB; + num_errors += results.num_errors; + fitness += results.CalcFitness(); } } }; From bd44404c9ff56ad17196a9d95047bee8ed9a06e3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 27 Sep 2021 09:11:20 -0400 Subject: [PATCH 159/445] Updated Mancala.mabe to work with new traits and print out sample games. --- settings/Mancala.mabe | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/settings/Mancala.mabe b/settings/Mancala.mabe index 79c62f90..482b3c8a 100644 --- a/settings/Mancala.mabe +++ b/settings/Mancala.mabe @@ -4,11 +4,11 @@ Population next_pop; // Collection of organisms Value pop_size = 200; // Local value variable. CommandLine cl { // Handle basic I/O on the command line. target = "main_pop"; // Which population should we print stats about? - format = "score:max,score:mean";// Column format to use in the file. + format = "fitness:max,fitness:mean"; // Column format to use in the file. } FileOutput output { // Output collected data into a specified file. filename = "output.csv"; // Name of file for output data. - format = "score:max,score:mean";// Column format to use in the file. + format = "fitness:max,fitness:mean"; // Column format to use in the file. target = "main_pop"; // Which population(s) should we print from? output_updates = "0:1"; // Which updates should we output data? } @@ -16,7 +16,10 @@ EvalMancala eval { // Evaluate organisms on their ability to play M target = "main_pop"; // Which population(s) should we evaluate? input_trait = "input"; // Into which trait should input values be placed? output_trait = "output"; // Out of which trait should output values be read? - score_trait = "score"; // Which trait should we store success rating? + scoreA_trait = "scoreA"; // Trait to save score for this player. + scoreB_trait = "scoreB"; // Trait to save score for opponent. + error_trait = "num_errors"; // Trait to count number of illegal moves attempted. + fitness_trait = "fitness"; // Trait with combined success rating. opponent_type = "random"; // Which type of opponent should organisms face? // random: Always choose a random, legal move. // ai: Human supplied (but not very good) AI @@ -27,13 +30,13 @@ SelectTournament select_t { // Select the top fitness organisms from random birth_pop = "next_pop"; // Which population should births go into? tournament_size = 7; // Number of orgs in each tournament num_tournaments = pop_size; // Number of tournaments to run - fitness_trait = "score"; // Which trait provides the fitness value to use? + fitness_trait = "fitness"; // Which trait provides the fitness value to use? } -GrowthPlacement place_next { // Always appened births to the end of a population. +GrowthPlacement place_next { // Always append births to the end of a population. target = "main_pop,next_pop"; // Population(s) to manage. } -MovePopulation sync_gen { // Move organisms from one populaiton to another. +MovePopulation sync_gen { // Move organisms from one population to another. from_pop = "next_pop"; // Population to move organisms from. to_pop = "main_pop"; // Population to move organisms into. reset_to = 1; // Should we erase organisms at the destination? @@ -47,6 +50,7 @@ AvidaGPOrg avida_org { // Organism consisting of Avida instructions. output_name = "output"; // Where to write outputs } -@start(0) print("random_seed = ", random_seed, "\n"); -@start(0) inject("avida_org", "main_pop", pop_size); -@update(1000) exit(); +@start(0) PRINT("random_seed = ", random_seed, "\n"); +@start(0) INJECT("avida_org", "main_pop", pop_size); +@update(1000) EXIT(); +@update(10,10) TRACE_EVAL("output.dat", "main_pop", 0); From f5c290689e8375635025f3899675ee09cca57685 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 28 Sep 2021 23:45:20 -0400 Subject: [PATCH 160/445] Cleanup on comments in SelectTournament and changed 'triat' to 'fitness_fun' --- source/select/SelectTournament.hpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index 8043480e..c142f133 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -18,35 +18,35 @@ namespace mabe { /// Add elite selection with the current population. class SelectTournament : public Module { private: - std::string trait; ///< Which trait should we select on? - size_t tourny_size; ///< How big should each tournament be? - size_t num_tournies; ///< How many tournaments should we run? - int select_pop_id = 0; ///< Which population are we selecting from? - int birth_pop_id = 1; ///< Which population should births go into? + std::string fit_fun; ///< Trait function that we should select on + size_t tourny_size; ///< Number of organisms in each tournament + size_t num_tournies; ///< Number of tournaments to run + int select_pop_id = 0; ///< Population that we are selecting from + int birth_pop_id = 1; ///< Population that births should go into public: SelectTournament(mabe::MABE & control, const std::string & name="SelectTournament", const std::string & desc="Module to select the top fitness organisms from random subgroups for replication.", - const std::string & in_trait="fitness", + const std::string & in_fun="fitness", size_t t_size=7, size_t num_t=1) : Module(control, name, desc) - , trait(in_trait), tourny_size(t_size), num_tournies(num_t) + , fit_fun(in_fun), tourny_size(t_size), num_tournies(num_t) { SetSelectMod(true); ///< Mark this module as a selection module. } ~SelectTournament() { } void SetupConfig() override { - LinkPop(select_pop_id, "select_pop", "Which population should we select parents from?"); - LinkPop(birth_pop_id, "birth_pop", "Which population should births go into?"); + LinkPop(select_pop_id, "select_pop", "Population from which to select parents"); + LinkPop(birth_pop_id, "birth_pop", "Population into which offspring should be placed"); LinkVar(tourny_size, "tournament_size", "Number of orgs in each tournament"); LinkVar(num_tournies, "num_tournaments", "Number of tournaments to run"); - LinkVar(trait, "fitness_trait", "Which trait provides the fitness value to use?"); + LinkVar(fit_fun, "fitness_fun", "Trait equation that produces fitness value to use"); } void SetupModule() override { - AddRequiredTrait(trait); ///< The fitness trait must be set by another module. + AddRequiredTrait(fit_fun); ///< The fitness traits must be set by another module. } void OnUpdate(size_t ud) override { @@ -69,13 +69,13 @@ namespace mabe { // Find a random organism in the population and call it "best" size_t best_id = random.GetUInt(N); while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); - double best_fit = select_pop[best_id].GetTrait(trait); + double best_fit = select_pop[best_id].GetTrait(fit_fun); // Loop through other organisms for the rest of the tournament size, and pick best. for (size_t test=1; test < tourny_size; test++) { size_t test_id = random.GetUInt(N); while (select_pop[test_id].IsEmpty()) test_id = random.GetUInt(N); - double test_fit = select_pop[test_id].GetTrait(trait); + double test_fit = select_pop[test_id].GetTrait(fit_fun); if (test_fit > best_fit) { best_id = test_id; best_fit = test_fit; From 3376c5f0c5aca7d2285b9eef938a175934dc5e3f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 2 Oct 2021 13:01:01 -0400 Subject: [PATCH 161/445] Updated trait functions to trait SUMMARY functions; Fixed spelling in MABE core --- source/core/MABE.hpp | 46 +++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index e7dbb2b5..f6448281 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -67,8 +67,8 @@ namespace mabe { emp::StreamManager files; ///< Track all of the file streams used in MABE. // Setup a cache for functions used to collect data for files. - using trait_fun_t = std::function; - std::unordered_map> file_fun_cache; + using trait_summary_t = std::function; + std::unordered_map> file_fun_cache; /// Populations used; generated in the configuration file. emp::vector< emp::Ptr > pops; @@ -108,7 +108,7 @@ namespace mabe { emp::vector config_filenames; ///< Names of configuration files to load. emp::vector config_settings; ///< Additional config commands to run. std::string gen_filename; ///< Name of output file to generate. - Config config; ///< Configutation information for this run. + Config config; ///< Configuration information for this run. emp::Ptr cur_scope; ///< Which config scope are we currently using? @@ -134,7 +134,7 @@ namespace mabe { /// Load organism traits that modules need to read or write and test for conflicts. void Setup_Traits(); - /// Link signals to the modules that implment responses to those signals. + /// Link signals to the modules that implement responses to those signals. void UpdateSignals(); /// Find any instances of ${X} and eval the X. @@ -198,7 +198,7 @@ namespace mabe { const Population & GetPopulation(size_t id) const { return *pops[id]; } Population & GetPopulation(size_t id) { return *pops[id]; } - /// New populaitons must be given a name and an optional size. + /// New populations must be given a name and an optional size. Population & AddPopulation(const std::string & name, size_t pop_size=0); /// If GetPopulation() is called without an ID, return the current population or create one. @@ -328,8 +328,8 @@ namespace mabe { /// trait_name from each, aggregating those values based on the trait_filter and returning /// the result as a string. - trait_fun_t BuildTraitFunction(const std::string & trait_name, - std::string trait_filter); + trait_summary_t BuildTraitSummaryFunction(const std::string & trait_name, + std::string trait_filter); // Handler for printing trait data void OutputTraitData(std::ostream & os, @@ -509,7 +509,7 @@ namespace mabe { } } - /// Link signals to the modules that implment responses to those signals. + /// Link signals to the modules that implement responses to those signals. void MABE::UpdateSignals() { // Clear all module vectors. for (auto modv : sig_ptrs) modv->resize(0); @@ -657,7 +657,7 @@ namespace mabe { std::function trait_string_fun = [this](const std::string & target, std::string trait_filter) { std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitFunction(trait_name, trait_filter); + auto fun = BuildTraitSummaryFunction(trait_name, trait_filter); return fun( ToCollection(target) ); }; config.AddFunction("TRAIT_STRING", trait_string_fun, "Collect information about a specified trait."); @@ -665,7 +665,7 @@ namespace mabe { std::function trait_value_fun = [this](const std::string & target, std::string trait_filter) { std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitFunction(trait_name, trait_filter); + auto fun = BuildTraitSummaryFunction(trait_name, trait_filter); return emp::from_string(fun( ToCollection(target) )); }; config.AddFunction("TRAIT_VALUE", trait_value_fun, "Collect information about a specified trait."); @@ -775,7 +775,7 @@ namespace mabe { before_exit_sig.Trigger(); } - /// New populaitons must be given a name and an optional size. + /// New populations must be given a name and an optional size. Population & MABE::AddPopulation(const std::string & name, size_t pop_size) { cur_pop_id = (int) pops.size(); // Set new pop to "current" emp::Ptr new_pop = @@ -838,7 +838,7 @@ namespace mabe { OrgPosition pos; // Place to save injection position. for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. auto org_ptr = org_manager.Make(random); // ...Build an org of this type. - pos = InjectInstance(org_ptr, pop); // ...Inject it into the popultation. + pos = InjectInstance(org_ptr, pop); // ...Inject it into the population. } return pos; // Return last position injected. } @@ -949,7 +949,7 @@ namespace mabe { /// [OP] can be ==, !=, <, >, <=, or >= /// [TRAIT] can be any other trait name /// unique : Return the number of distinct value for this trait (alias="richness"). - /// mode : Return the most common value in this colection (aliases="dom","dominant"). + /// mode : Return the most common value in this collection (aliases="dom","dominant"). /// min : Return the smallest value of this trait present. /// max : Return the largest value of this trait present. /// ave : Return the average value of this trait (alias="mean"). @@ -957,11 +957,13 @@ namespace mabe { /// variance : Return the variance of this trait. /// stddev : Return the standard deviation of this trait. /// sum : Return the summation of all values of this trait (alias="total") - /// entopy : Return the Shannon entropy of this value. + /// entropy : Return the Shannon entropy of this value. /// :trait : Return the mutual information with another provided trait. - MABE::trait_fun_t MABE::BuildTraitFunction(const std::string & trait_name, - std::string trait_filter) { + MABE::trait_summary_t MABE::BuildTraitSummaryFunction( + const std::string & trait_name, + std::string trait_filter + ) { // The trait input has two components: // (1) the trait NAME and // (2) (optionally) how to calculate the trait SUMMARY, such as min, max, ave, etc. @@ -1004,7 +1006,7 @@ namespace mabe { std::string format, bool print_headers) { - emp::vector funs; ///< Functions to call each update. + emp::vector trait_functions; ///< Summary functions to call each update. emp::remove_whitespace(format); // If we need headers, set them up! @@ -1030,21 +1032,21 @@ namespace mabe { emp::vector cols = emp::slice(format, ','); // Setup a function to collect data associated with each column. - funs.resize(cols.size()); + trait_functions.resize(cols.size()); for (size_t i = 0; i < cols.size(); i++) { std::string trait_filter = cols[i]; std::string trait_name = emp::string_pop(trait_filter,':'); - funs[i] = BuildTraitFunction(trait_name, trait_filter); + trait_functions[i] = BuildTraitSummaryFunction(trait_name, trait_filter); } // Insert the new entry into the cache and update the iterator. - fun_it = file_fun_cache.insert({format, funs}).first; + fun_it = file_fun_cache.insert({format, trait_functions}).first; } - else funs = fun_it->second; + else trait_functions = fun_it->second; // And, finally, print the data! os << GetUpdate(); - for (auto & fun : funs) { + for (auto & fun : trait_functions) { os << ", " << fun(target_collect); } os << std::endl; From 5fa8bbdbe9722b5afa77b26fc958472a450b5db1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 2 Oct 2021 16:25:27 -0400 Subject: [PATCH 162/445] Changed BuildTraitSummaryFunction() to just BuildTraitSummary(); added BuildTraitEquation(). --- source/core/MABE.hpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index f6448281..cd54f70e 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -66,8 +66,11 @@ namespace mabe { ErrorManager error_man; ///< Object to manage warnings and errors. emp::StreamManager files; ///< Track all of the file streams used in MABE. - // Setup a cache for functions used to collect data for files. + // Setup helper types. + using trait_equation_t = std::function; using trait_summary_t = std::function; + + // Setup a cache for functions used to collect data for files. std::unordered_map> file_fun_cache; /// Populations used; generated in the configuration file. @@ -82,6 +85,8 @@ namespace mabe { /// value of all traits that modules associate with organisms. emp::DataMap org_data_map; + emp::DataMapParser dm_parser; ///< Parser to process functions on a data map. + emp::Random random; ///< Master random number generator size_t cur_pop_id = (size_t) -1; ///< Which population is currently active? size_t update = 0; ///< How many times has Update() been called? @@ -324,12 +329,25 @@ namespace mabe { // --- Deal with Organism TRAITS --- TraitManager & GetTraitManager() { return trait_man; } + /// Build a function to scan a single data map and run the provided equation on the + /// enties in there, returning the result. + + trait_equation_t BuildTraitEquation(const std::string & equation) { + return dm_parser.BuildMathFunction(org_data_map, equation); + } + + /// Scan a provided equation and return the names of all traits used in that equation. + + const std::set & GetEquationTraits(const std::string & equation) { + dm_parser.BuildMathFunction(org_data_map, equation); + return dm_parser.GetNamesUsed(); + } + /// Build a function to scan a collection of organisms, reading the value for the given /// trait_name from each, aggregating those values based on the trait_filter and returning /// the result as a string. - trait_summary_t BuildTraitSummaryFunction(const std::string & trait_name, - std::string trait_filter); + trait_summary_t BuildTraitSummary(const std::string & trait_name, std::string trait_filter); // Handler for printing trait data void OutputTraitData(std::ostream & os, @@ -657,7 +675,7 @@ namespace mabe { std::function trait_string_fun = [this](const std::string & target, std::string trait_filter) { std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitSummaryFunction(trait_name, trait_filter); + auto fun = BuildTraitSummary(trait_name, trait_filter); return fun( ToCollection(target) ); }; config.AddFunction("TRAIT_STRING", trait_string_fun, "Collect information about a specified trait."); @@ -665,7 +683,7 @@ namespace mabe { std::function trait_value_fun = [this](const std::string & target, std::string trait_filter) { std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitSummaryFunction(trait_name, trait_filter); + auto fun = BuildTraitSummary(trait_name, trait_filter); return emp::from_string(fun( ToCollection(target) )); }; config.AddFunction("TRAIT_VALUE", trait_value_fun, "Collect information about a specified trait."); @@ -960,7 +978,7 @@ namespace mabe { /// entropy : Return the Shannon entropy of this value. /// :trait : Return the mutual information with another provided trait. - MABE::trait_summary_t MABE::BuildTraitSummaryFunction( + MABE::trait_summary_t MABE::BuildTraitSummary( const std::string & trait_name, std::string trait_filter ) { @@ -1036,7 +1054,7 @@ namespace mabe { for (size_t i = 0; i < cols.size(); i++) { std::string trait_filter = cols[i]; std::string trait_name = emp::string_pop(trait_filter,':'); - trait_functions[i] = BuildTraitSummaryFunction(trait_name, trait_filter); + trait_functions[i] = BuildTraitSummary(trait_name, trait_filter); } // Insert the new entry into the cache and update the iterator. From ac3526741f15f15ff72d1972cd50581bb4a76106 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 2 Oct 2021 16:26:10 -0400 Subject: [PATCH 163/445] Added types value_fun_t and string_fun_t to ModuleBase. --- source/core/ModuleBase.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index a1cc3572..e85b3d96 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -123,6 +123,9 @@ namespace mabe { /// Other variables that we want to hook on to this Module externally. emp::DataMap data_map; + using value_fun_t = std::function; + using string_fun_t = std::function; + public: // Setup each signal with a unique ID number enum SignalID { From 396fe9d10ee5c114f68d1b5a0a1fc1cb6e6d4484 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 2 Oct 2021 16:26:55 -0400 Subject: [PATCH 164/445] Added Module::AddRequiredEquation to require all traits used in an equation. --- source/core/Module.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 6039f699..ba0fda31 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -167,10 +167,18 @@ namespace mabe { return AddTrait(TraitInfo::Access::REQUIRED, name); } + /// Add all of the traits that that this module needs to be able to READ, in order to + /// computer the provided equation. Another module must WRITE these traits and provide the + /// descriptions. + void AddRequiredEquation(const std::string & equation) { + const std::set & traits = control.GetEquationTraits(equation); + for (const std::string & name : traits) AddRequiredTrait(name); + } + // ---== Signal Handling ==--- - // Functions to be called based on signals. Note that the existance of an overridden version + // Functions to be called based on signals. Note that the existence of an overridden version // of each function is tracked by an associated bool value that we default to true until the // base version of the function is called indicating that it has NOT been overridden. From d39fd1212c2bfeb2fa64c8062d7afb0199b9d694 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 3 Oct 2021 15:18:11 -0400 Subject: [PATCH 165/445] Switched MABE to using new version of GetNamesUsed() that takes equation as an argument. --- source/core/MABE.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index cd54f70e..8c23ca88 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -339,8 +339,7 @@ namespace mabe { /// Scan a provided equation and return the names of all traits used in that equation. const std::set & GetEquationTraits(const std::string & equation) { - dm_parser.BuildMathFunction(org_data_map, equation); - return dm_parser.GetNamesUsed(); + return dm_parser.GetNamesUsed(equation); } /// Build a function to scan a collection of organisms, reading the value for the given From 018acf2dc9ce16d31d2acbe75056bf1ddb38a9ec Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 4 Oct 2021 16:10:57 -0400 Subject: [PATCH 166/445] SelectTournament can now take a full equation for its fitness function. --- source/select/SelectTournament.hpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index c142f133..3c55130d 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -18,7 +18,7 @@ namespace mabe { /// Add elite selection with the current population. class SelectTournament : public Module { private: - std::string fit_fun; ///< Trait function that we should select on + std::string fit_equation; ///< Trait function that we should select on size_t tourny_size; ///< Number of organisms in each tournament size_t num_tournies; ///< Number of tournaments to run int select_pop_id = 0; ///< Population that we are selecting from @@ -28,10 +28,10 @@ namespace mabe { SelectTournament(mabe::MABE & control, const std::string & name="SelectTournament", const std::string & desc="Module to select the top fitness organisms from random subgroups for replication.", - const std::string & in_fun="fitness", + const std::string & in_fit="fitness", size_t t_size=7, size_t num_t=1) : Module(control, name, desc) - , fit_fun(in_fun), tourny_size(t_size), num_tournies(num_t) + , fit_equation(in_fit), tourny_size(t_size), num_tournies(num_t) { SetSelectMod(true); ///< Mark this module as a selection module. } @@ -42,11 +42,11 @@ namespace mabe { LinkPop(birth_pop_id, "birth_pop", "Population into which offspring should be placed"); LinkVar(tourny_size, "tournament_size", "Number of orgs in each tournament"); LinkVar(num_tournies, "num_tournaments", "Number of tournaments to run"); - LinkVar(fit_fun, "fitness_fun", "Trait equation that produces fitness value to use"); + LinkVar(fit_equation, "fitness_fun", "Trait equation that produces fitness value to use"); } void SetupModule() override { - AddRequiredTrait(fit_fun); ///< The fitness traits must be set by another module. + AddRequiredEquation(fit_equation); ///< The fitness traits must be set by another module. } void OnUpdate(size_t ud) override { @@ -62,6 +62,9 @@ namespace mabe { return; } + // Setup the fitness function + auto fit_fun = control.BuildTraitEquation(fit_equation); + // @CAO if we have a sparse Population, we probably want to take that into account. // Loop through each round of tournament selection. @@ -69,20 +72,20 @@ namespace mabe { // Find a random organism in the population and call it "best" size_t best_id = random.GetUInt(N); while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); - double best_fit = select_pop[best_id].GetTrait(fit_fun); + double best_fit = fit_fun(select_pop[best_id].GetDataMap()); // Loop through other organisms for the rest of the tournament size, and pick best. for (size_t test=1; test < tourny_size; test++) { size_t test_id = random.GetUInt(N); while (select_pop[test_id].IsEmpty()) test_id = random.GetUInt(N); - double test_fit = select_pop[test_id].GetTrait(fit_fun); + double test_fit = fit_fun(select_pop[test_id].GetDataMap()); if (test_fit > best_fit) { best_id = test_id; best_fit = test_fit; } } - // Replicat the organism that did best in this tournament. + // Replicate the organism that did best in this tournament. control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); } From 21642bcdcc03fedad866acff2ad4556d50b111a4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 12:29:41 -0400 Subject: [PATCH 167/445] Updated CommandLine module to use BuiltTraitSummary() --- source/interface/CommandLine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interface/CommandLine.hpp b/source/interface/CommandLine.hpp index 6034c8a2..d99fbdc4 100644 --- a/source/interface/CommandLine.hpp +++ b/source/interface/CommandLine.hpp @@ -36,7 +36,7 @@ namespace mabe { for (size_t i = 0; i < cols.size(); i++) { std::string trait_filter = cols[i]; std::string trait_name = emp::string_pop(trait_filter,':'); - funs[i] = control.BuildTraitFunction(trait_name, trait_filter); + funs[i] = control.BuildTraitSummary(trait_name, trait_filter); } init = true; From 2230dfa215527c2685e00cf95cbebe71394f22fc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 12:30:11 -0400 Subject: [PATCH 168/445] Updated FileOutput module to use BuiltTraitSummary() --- source/interface/FileOutput.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interface/FileOutput.hpp b/source/interface/FileOutput.hpp index 00f3044c..b5169231 100644 --- a/source/interface/FileOutput.hpp +++ b/source/interface/FileOutput.hpp @@ -55,7 +55,7 @@ namespace mabe { for (size_t i = 0; i < cols.size(); i++) { std::string trait_filter = cols[i]; std::string trait_name = emp::string_pop(trait_filter,':'); - funs[i] = control.BuildTraitFunction(trait_name, trait_filter); + funs[i] = control.BuildTraitSummary(trait_name, trait_filter); } // Print the headers into the file. From fa67c53f4e237063737a86d72e1613bd3d011d21 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 12:42:55 -0400 Subject: [PATCH 169/445] Updated comments throughout Config.hpp --- source/config/Config.hpp | 47 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 159da809..9b47f4a5 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -8,25 +8,26 @@ * @note Status: ALPHA * * Example usage: - * a = 7; // a is a variable with the value 7 - * b = "balloons"; // b is a variable equal to the literal string "balloons" - * c = a + 10; // '+' will add values; c is a variable equal to 17. - * d = "99 " + b; // '+' will append strings; d is a variable equal to "99 balloons" - * // e = "abc" + 123; // ERROR - cannot add strings and values! - * f = { // f is a structure/scope/dictionary - * g = 1; - * h = "two"; - * i = { - * j = 3; + * Value a = 7; // a is a variable with the value 7 + * String b = "balloons"; // b is a variable equal to the literal string "balloons" + * Value c = a + 10; // '+' will add values; c is a variable equal to 17. + * String d = "99 " + b; // '+' will append strings; d is a variable equal to "99 balloons" + * // String e = "abc" + 12; // ERROR - cannot add strings and values! + * String = "01" * a; // e is now "01010101010101" + * Struct f = { // f is a structure/scope/dictionary + * Value g = 1.7; // Values are floating point. + * String h = "two"; + * Struct i = { // Structure-within-structures are allowed. + * Value j = 3; * } - * a = "shadow!"; // A variable can be redeclared in other scopes, shadowing the original. - * // Note: the LHS assumes current scope; on RHS will search outer scopes. - * j = "spooky!"; // A NEW variable since we are out of the namespace of the other j. - * j = .a; // Change j to "shadow"; an initial . indicates current namespace. - * b = i.j; // Namespaces can be stepped through with dots. - * c = ..a; // A variable name beginning with a ".." indicates parent namespace. - * c = @f.i.j; // A variable name beginning with an @ must have its full path specified. - * } // f has been initialized with seven variables in its scope. + * String a = "shadow!"; // Variables can be redeclared in other scopes; shadows original. + * String j = "spooky!"; // A NEW variable since we are now out of Struct i. + * j = .a; // Change j to "shadow!"; initial . indicates current namespace. + * b = i.j; // Namespaces can be stepped through with dots. + * c = ..a; // A variable name beginning with a ".." indicates parent namespace. + * } // f has been initialized with six variables in its scope. + * + * --- The functionality below does not yet work and may change when implemented --- * f["new"] = 22; // You can always add new fields to structures. * // d["bad"] = 4; // ERROR - You cannot add fields to non-structures. * k = [ 1 , 2 , 3]; // k is a vector of values (vectors must have all types the same!) @@ -44,7 +45,7 @@ * // :type (returns a string indicating type!) * * - * In practice: + * In practice, most settings will be pre-defined in typed scopes: * MarkovBrain Sheep = { * outputs = 10; * node_weights = 0.75; @@ -206,7 +207,7 @@ namespace mabe { /// an expression, or an event. [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, ConfigScope & scope); - /// Keep parsing statments until there aren't any more or we leave this scope. + /// Keep parsing statements until there aren't any more or we leave this scope. [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigScope & scope) { Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); auto cur_block = emp::NewPtr(scope); @@ -395,7 +396,7 @@ namespace mabe { return type_id; } - /// Retrieve a uniqe type ID by providing the type name. + /// Retrieve a unique type ID by providing the type name. size_t GetTypeID(const std::string & type_name) { emp_assert(emp::Has(type_map, type_name)); return type_map[type_name].type_id; @@ -483,8 +484,8 @@ namespace mabe { if (filename == "" || filename == "_") return Write(); // Otherwise generate an output file. - std::ofstream ofile(filename); - return Write(ofile); + std::ofstream out_file(filename); + return Write(out_file); } }; From 8607f822716dbd30f47d1483410a956429ed3f73 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 15:31:15 -0400 Subject: [PATCH 170/445] Cleaned up spelling in DeveloperNotes. --- source/core/DeveloperNotes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index c675e5f1..7e8f7129 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -1,9 +1,9 @@ This directory contains all of the core functionality for MABE. This code should never be -changed for individual experients. +changed for individual experiments. # Layout -The core components of MABE are below. This first group are tools that have minimal internal dependancies (indicated by indentation below the requirement). +The core components of MABE are below. This first group are tools that have minimal internal dependencies (indicated by indentation below the requirement). data_collect.hpp - Tools to extract data from elements in a container. ErrorManager.hpp - Track any run-time errors as they occur. @@ -19,8 +19,8 @@ Organism.hpp - Information about a single agent; ModuleBase is interface OrgIterator.hpp - Tools for identifying organism locations and stepping through sets of them. Population.hpp - Collection of Organisms (some of which could be EmptyOrganisms) Collection.hpp - A more flexible collection of organisms or whole populations for manipulation. -MABE.hpp - Main contoller object; manipulates Populations and Organisms -Module.hpp - Modify main MABE contoller functions. +MABE.hpp - Main controller object; manipulates Populations and Organisms +Module.hpp - Modify main MABE controller functions. FactoryModule.hpp - Framework to build specialty modules that manage other config objects. OrganismManager.hpp - Specialty Module type to manage organisms with shared configuration. EmptyOrganism.hpp - Specialty Organism type to represent empty cells. @@ -51,7 +51,7 @@ ModuleBase.hpp: * Add a description of the new signal in the comments at the top of the file. * Add a SIG_* id for the signal in the SignalID enum * Declare a virtual member function to catch the signal in the SIGNALS section. -* Declare a virtal *_IsTriggered() to later test if we are currently reacting to signal. +* Declare a virtual *_IsTriggered() to later test if we are currently reacting to signal. Module.hpp: * Override virtual function for signal (base method to mark function not used in module!) From ded182367e9ca0703949ca616d67128b62c530bd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 15:32:13 -0400 Subject: [PATCH 171/445] Cleaned up spelling throughout MABE core. --- source/core/Collection.hpp | 12 ++++++------ source/core/MABEBase.hpp | 2 +- source/core/ManagerModule.hpp | 4 ++-- source/core/Module.hpp | 8 +++++--- source/core/ModuleBase.hpp | 2 +- source/core/OrgIterator.hpp | 2 +- source/core/OrgType.hpp | 4 ++-- source/core/Organism.hpp | 4 ++-- source/core/Population.hpp | 6 +++--- source/core/TraitInfo.hpp | 14 +++++++------- source/core/TraitSet.hpp | 8 ++++---- source/core/data_collect.hpp | 2 +- 12 files changed, 35 insertions(+), 33 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index d34b6ee1..77e1721a 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -8,7 +8,7 @@ * * While organisms must be managed by Population objects, collections are an easy way * to represent and manipulate groups of organisms (by their position). Organisms can be - * added individully or as whole populations. + * added individually or as whole populations. * * Internally, a Collection is represented by a map; keys are pointers to the included Populations * and values are a PopInfo class (a flag for "do we included the whole population" and a @@ -174,7 +174,7 @@ namespace mabe { } }; - // Link each populaiton in the collection (by its pointer) to info about which organisms + // Link each population in the collection (by its pointer) to info about which organisms // are included. using pos_map_t = std::map; pos_map_t pos_map; @@ -211,7 +211,7 @@ namespace mabe { // We now know we have a valid population. Check if we are at a valid position. if (info_it->second.pos_set.Has(it.Pos())) return true; - // Must move to a valid position, either in this population, another populaton, or end. + // Must move to a valid position, either in this population, another population, or end. // Find the position of the next organism from this population size_t next_pos = info_it->second.GetNextPos(it.Pos()); @@ -335,7 +335,7 @@ namespace mabe { auto info_it = GetInfoIT(cur_pop); // Make sure that the current population was found! This check will fail if either - // we are already at the end OR it is pointed to a populaiton not in the collection. + // we are already at the end OR it is pointed to a population not in the collection. emp_assert(info_it != pos_map.end(), "Invalid start position before collection iterator is incremented"); @@ -454,7 +454,7 @@ namespace mabe { return Insert(collection2); } - /// Reduce to the intersection with another colleciton. + /// Reduce to the intersection with another collection. Collection & operator &= (const Collection & in_collection) { auto cur_it = pos_map.begin(); auto in_it = in_collection.pos_map.begin(); @@ -487,7 +487,7 @@ namespace mabe { }; // ------------------------------------------------------- - // Implementations of CollectionItertor member functions + // Implementations of CollectionIterator member functions // ------------------------------------------------------- template diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index e70eb87b..3df1c3cf 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -132,7 +132,7 @@ namespace mabe { } /// All permanent deletion of organisms from a population should come through here. - /// If the relavant position is already empty, nothing happens. + /// If the relevant position is already empty, nothing happens. /// @param[in] pos is the position to perform the deletion. void ClearOrgAt(OrgPosition pos) { emp_assert(pos.IsValid()); diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index 8dcac8b4..b0c70547 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -4,7 +4,7 @@ * @date 2021. * * @file ManagerModule.hpp - * @brief Base module to manage a selection of objects that share a common configiguration. + * @brief Base module to manage a selection of objects that share a common configuration. */ #ifndef MABE_MANAGER_MODULE_H @@ -47,7 +47,7 @@ namespace mabe { /// @param MANAGED_T the type of object type being managed. - /// @param BASE_T the base type being mnagaed. + /// @param BASE_T the base type being managed. template class ManagerModule : public Module { /// Allow managed products to access private shared data in their own manager only. diff --git a/source/core/Module.hpp b/source/core/Module.hpp index ba0fda31..d67588ff 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -129,7 +129,9 @@ namespace mabe { TraitInfo & AddTrait(TraitInfo::Access access, const std::string & name, const std::string & desc="", - const T & default_val=T()) { + const T & default_val=T() + ) { + emp_assert(name != "", name); return control.GetTraitManager().AddTrait(this, access, name, desc, default_val); } @@ -231,7 +233,7 @@ namespace mabe { } // Format: OnPlacement(OrgPosition placement_pos) - // Trigger: New organism has been placed in the poulation. + // Trigger: New organism has been placed in the population. // Args: Position new organism was placed. void OnPlacement(OrgPosition) override { has_signal[SIG_OnPlacement] = false; @@ -335,7 +337,7 @@ namespace mabe { // Functions to be called based on actions that need to happen. Each of these returns a // viable result or an invalid object if need to pass on to the next module. Modules will - // be querried in order until one of them returns a valid result. + // be queried in order until one of them returns a valid result. // Function: Place a new organism about to be born. // Args: Organism that will be placed, position of parent, population to place. diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index e85b3d96..c17fd010 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -33,7 +33,7 @@ * BeforePlacement(Organism & org, OrgPosition target_pos, OrgPosition parent_pos) * : Placement location has been identified (For birth or inject) * OnPlacement(OrgPosition placement_pos) - * : New organism has been placed in the poulation. + * : New organism has been placed in the population. * BeforeMutate(Organism & org) * : Mutate is about to run on an organism. * OnMutate(Organism & org) diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index 39daaaec..9a242e2c 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -9,7 +9,7 @@ * Organisms in MABE are stored in indexed collections (typically Population objects). * This class allows you to refer to the position of an organism and step through sets of organisms. * - * An OrgIterator_Interface sets up all of the virutal functions in all iterators. + * An OrgIterator_Interface sets up all of the virtual functions in all iterators. * * @todo Add a reverse iterator. * @todo Fix operator-- which can go off of the beginning of the world. diff --git a/source/core/OrgType.hpp b/source/core/OrgType.hpp index 23d66dc6..59349e09 100644 --- a/source/core/OrgType.hpp +++ b/source/core/OrgType.hpp @@ -30,7 +30,7 @@ namespace mabe { Module & GetManager() { return (Module&) manager; } const Module & GetManager() const { return (Module&) manager; } - /// The class below is a placeholder for storing any manager-specific data that the organims + /// The class below is a placeholder for storing any manager-specific data that the organisms /// should have access to. A derived organism class merely needs to shadow this one in order /// to include specialized data. struct ManagerData { @@ -97,7 +97,7 @@ namespace mabe { /// Convert this organism into a string of characters. /// @note Required if we are going to print organisms to screen or to file). If this function - /// is not overridden, try to the equivilent function in the organism manager. + /// is not overridden, try to the equivalent function in the organism manager. virtual std::string ToString() const { return "__unknown__"; } /// By default print an organism by triggering it's ToString() function. diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index e7d2fdb0..b466ab20 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -9,7 +9,7 @@ * * All organism types or organism component types (e.g., brains or genomes) that can be * individually configured in MABE must have mabe::OrgType as its ultimate base class. A helper - * template mabe::OrganismTeplate is derived from mabe::OrgType and should be used as + * template mabe::OrganismTemplate is derived from mabe::OrgType and should be used as * the more immeidate base class for any user-defined organism types. Providing this template * with your new organism type as ORG_T will setup type-specific return values for ease of use. * @@ -83,7 +83,7 @@ namespace mabe { - // -- Also deal with some depricated functionality... -- + // -- Also deal with some deprecated functionality... -- [[deprecated("Use OrgType::HasTrait() instead of OrgType::HasVar()")]] bool HasVar(const std::string & name) const { return HasTrait(name); } diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 1a749de2..f489abe5 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -153,7 +153,7 @@ namespace mabe { iterator_t IteratorAt(size_t pos) { return iterator_t(this, pos); } const_iterator_t ConstIteratorAt(size_t pos) const { return const_iterator_t(this, pos); } - /// Required SetupConfig function; for now population don't have any config optons. + /// Required SetupConfig function; for now population don't have any config options. void SetupConfig() override { } private: // ---== To be used by friend class MABEBase only! ==--- @@ -204,7 +204,7 @@ namespace mabe { public: // ------ DEBUG FUNCTIONS ------ bool OK() const { - // We will usually have a handful of popoulations; assume error if we have more than a billion. + // We will usually have a handful of populations; assume error if we have more than a billion. if (pop_id > 1000000000) { std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; return false; @@ -229,7 +229,7 @@ namespace mabe { if (!orgs[pos]->IsEmpty()) org_count++; } - // Make sure we counted the correct number of organims in the population. + // Make sure we counted the correct number of organisms in the population. if (num_orgs != org_count) { std::cout << "ERROR: Population " << pop_id << " has num_orgs = " << num_orgs << ", but audit counts " << org_count << " orgs." << std::endl; diff --git a/source/core/TraitInfo.hpp b/source/core/TraitInfo.hpp index 9601f942..1752f6c0 100644 --- a/source/core/TraitInfo.hpp +++ b/source/core/TraitInfo.hpp @@ -11,7 +11,7 @@ * * The TARGET indicates what type of object the trait should be applied to. * [ORGANISM] - Every organism in MABE must have this trait. - * [POPULATUON] - Collections of organsims must have this trait. + * [POPULATION] - Collections of organsims must have this trait. * [MODULE] - Every module attached to MABE must have this trait. * [MANAGER] - Every OrganismManager must have this trait. * @@ -27,16 +27,16 @@ * (note that injected organisms always get the DEFAULT value.) * [DEFAULT] - Always initialize this trait to its default value. * [FIRST] - Initialize trait to the first parent's value (only parent for asexual runs) - * [AVERAGE] - Initiialize trait to the average value of all parents. - * [MINIMUM] - Initiialize trait to the minimum value of all parents. - * [MAXIMUM] - Initiialize trait to the maximum value of all parents. + * [AVERAGE] - Initialize trait to the average value of all parents. + * [MINIMUM] - Initialize trait to the minimum value of all parents. + * [MAXIMUM] - Initialize trait to the maximum value of all parents. * * The ARCHIVE method determines how many older values should be saved with each organism. * [NONE] - Only the most recent value should be tracked, no archived values. * [AT_BIRTH] - Store value of this trait was born with in "birth_(name)". * [LAST_REPRO] - Store value of trait at the last reproduction in "last_(name)". * [ALL_REPROS] - Store all value of trait at each reproduction event in "archive_(name)". - * [ALL_VALUES] - Store every value chane of trait at any time in "sequence_(name)". + * [ALL_VALUES] - Store every value change of trait at any time in "sequence_(name)". * * The SUMMARY method determines how a trait should be summarized over a collection of organisms. * [[[ needs refinement... ]]] @@ -83,7 +83,7 @@ namespace mabe { NUM_ACCESS ///< How many access methods are there? }; - /// How should this trait be initialized (via inheretence) in a newly-born organism? + /// How should this trait be initialized (via inheritance) in a newly-born organism? /// * Injected organisms always use the default value. /// * Modules can moitor signals to make other changes at any time. enum class Init { @@ -115,7 +115,7 @@ namespace mabe { /// How should these data be summarized in groups such as whole population or phyla types /// (such as Genotype, Species, etc.) Some traits shouldn't be summarized at all (IGNORE) - /// Otherwise the summary values can be takens as: + /// Otherwise the summary values can be taken as: enum class Summary { IGNORE=0, ///< Don't include this trait in phyla records. AVERAGE, ///< Average of current value of all organisms (or final value at death). diff --git a/source/core/TraitSet.hpp b/source/core/TraitSet.hpp index 949d439f..91ed8173 100644 --- a/source/core/TraitSet.hpp +++ b/source/core/TraitSet.hpp @@ -164,7 +164,7 @@ namespace mabe { // Otherwise it must be from a vector. else { - size_t vpos = id - base_IDs.size(); // Adjust id to be in range. + size_t vector_pos = id - base_IDs.size(); // Adjust id to be in range. // Step through the vectors to find the one with this index. size_t vid = 0; @@ -172,12 +172,12 @@ namespace mabe { while (vid < vector_IDs.size()) { const size_t trait_id = vector_IDs[vid]; const emp::vector & cur_vec = dmap.Get>(trait_id); - if (vpos < cur_vec.size()) { - out[id] = cur_vec[vpos]; + if (vector_pos < cur_vec.size()) { + out[id] = cur_vec[vector_pos]; found = true; break; } - vpos -= cur_vec.size(); + vector_pos -= cur_vec.size(); vid++; } emp_assert(found, "PROBLEM! TraitSet ran out of vectors without finding trait id."); diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 0dc11c83..09b7b6dd 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -6,7 +6,7 @@ * @file data_collect.hpp * @brief Functions to collect data from containers. * - * A collection of mechanisms to agregate data from arbitrary objects in arbitrary containers. + * A collection of mechanisms to aggregate data from arbitrary objects in arbitrary containers. * * Each build function must know the data type it is working with (DATA_T), the type of container * it should expect (CONTAIN_T), and be provided a function that will take a container element and From f01ab89fcf1e5a88c9d060cb855111cb60e8da9d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 17:48:31 -0400 Subject: [PATCH 172/445] Clean up comments in Population::OK() --- source/core/Population.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index f489abe5..91f633f4 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -204,28 +204,30 @@ namespace mabe { public: // ------ DEBUG FUNCTIONS ------ bool OK() const { - // We will usually have a handful of populations; assume error if we have more than a billion. + // We may have a handful of populations, but assume error if we have more than a billion. if (pop_id > 1000000000) { std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; return false; } + // We should never have more living organisms than slots in the population. if (num_orgs > orgs.size()) { std::cout << "ERROR: Population " << pop_id << " size is " << orgs.size() << " but num_orgs = " << num_orgs << std::endl; return false; } + // Scan through the population and make sure every position is valid. size_t org_count = 0; for (size_t pos = 0; pos < orgs.size(); pos++) { - // No vector positions should be NULL (though they may have an empty organism) + // No vector positions should be NULL (use EmptyOrganism instead) if (orgs[pos].IsNull()) { std::cout << "ERROR: Population " << pop_id << " as position " << pos << " has null pointer instead of an organism." << std::endl; return false; } - // Double check the organism count. + // Count the number of living (non-empty) organisms as we go. if (!orgs[pos]->IsEmpty()) org_count++; } @@ -236,7 +238,7 @@ namespace mabe { return false; } - // @CAO: Check if num_orgs > max_orgs? + // @CAO: If we have a cap on the population size, make sure we haven't crossed it? return true; } From 7af358a7e0f9a6e84017a8564e96575791a82e9f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 5 Oct 2021 17:49:26 -0400 Subject: [PATCH 173/445] Shifted Config::type_map to use a pointer to TypeInfo, allowing for derived classes. --- source/config/Config.hpp | 50 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 9b47f4a5..dde3d20a 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -86,9 +86,14 @@ namespace mabe { class Config { public: struct TypeInfo { - size_t type_id; + size_t index; std::string desc; - std::function init_fun; + + using init_fun_t = std::function; + init_fun_t init_fun; + + TypeInfo(size_t in_id, const std::string & in_desc, init_fun_t in_init) + : index(in_id), desc(in_desc), init_fun(in_init) { } }; using pos_t = emp::TokenStream::Iterator; @@ -105,7 +110,7 @@ namespace mabe { std::map events_map; /// A map of all types available in the script. - std::unordered_map type_map; + std::unordered_map> type_map; /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -230,11 +235,11 @@ namespace mabe { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = TypeInfo{ (size_t) BaseType::INVALID, "Error, Invalid type!", nullptr }; - type_map["Void"] = TypeInfo{ (size_t) BaseType::VOID, "Non-type variable; no value", nullptr }; - type_map["Value"] = TypeInfo{ (size_t) BaseType::VALUE, "Numeric variable", nullptr }; - type_map["String"] = TypeInfo{ (size_t) BaseType::STRING, "String variable", nullptr }; - type_map["Struct"] = TypeInfo{ (size_t) BaseType::STRUCT, "User-made structure", nullptr }; + type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!", nullptr ); + type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value", nullptr ); + type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable", nullptr ); + type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable", nullptr ); + type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure", nullptr ); // Setup operator precedence. size_t cur_prec = 0; @@ -345,7 +350,10 @@ namespace mabe { Config & operator=(const Config &) = delete; Config & operator=(Config &&) = delete; - ~Config() { } + ~Config() { + // Clean up type information. + for (auto [name, ptr] : type_map) ptr.Delete(); + } /// Create a new type of event that can be used in the scripting language. ConfigEvents & AddEventType(const std::string & name) { @@ -385,21 +393,21 @@ namespace mabe { /// To add a type, provide the type name (that can be referred to in a script) and a function /// that should be called (with the variable name) when an instance of that type is created. /// The function must return a reference to the newly created instance. - size_t AddType(const std::string & type_name, const std::string & desc, - std::function init_fun) - { + size_t AddType( + const std::string & type_name, + const std::string & desc, + std::function init_fun + ) { emp_assert(!emp::Has(type_map, type_name)); - size_t type_id = type_map.size(); - type_map[type_name].type_id = type_id; - type_map[type_name].desc = desc; - type_map[type_name].init_fun = init_fun; - return type_id; + size_t index = type_map.size(); + type_map[type_name] = emp::NewPtr( index, desc, init_fun ); + return index; } /// Retrieve a unique type ID by providing the type name. - size_t GetTypeID(const std::string & type_name) { + size_t GetIndex(const std::string & type_name) { emp_assert(emp::Has(type_map, type_name)); - return type_map[type_name].type_id; + return type_map[type_name]->index; } /// To add a built-in function (at the root level) provide it with a name and description. @@ -735,8 +743,8 @@ namespace mabe { // Otherwise we have a module to add; treat it as a struct. Debug("Building var '", var_name, "' of type '", type_name, "'"); - ConfigScope & new_scope = scope.AddScope(var_name, type_map[type_name].desc, type_name); - ConfigType & new_obj = type_map[type_name].init_fun(var_name); + ConfigScope & new_scope = scope.AddScope(var_name, type_map[type_name]->desc, type_name); + ConfigType & new_obj = type_map[type_name]->init_fun(var_name); new_obj.SetupScope(new_scope); new_obj.LinkVar(new_obj._active, "_active", "Should we activate this module? (0=off, 1=on)", true); new_obj.LinkVar(new_obj._desc, "_desc", "Special description for those object.", true); From 229211202754941cb56928998e1252270cb636f3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 09:22:18 -0400 Subject: [PATCH 174/445] Started fleshing out Config::TypeInfo to allow member functions. --- source/config/Config.hpp | 58 +++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index dde3d20a..a068dcbf 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -85,15 +85,61 @@ namespace mabe { class Config { public: + // TypeInfo tracks a particular type to be used in the configuration langauge. struct TypeInfo { size_t index; std::string desc; + emp::TypeID type_id; using init_fun_t = std::function; init_fun_t init_fun; + using entry_ptr_t = emp::Ptr; + using member_fun_t = std::function &)>; + emp::map member_funs; + + // Constructor to allow a simple new configuration type + TypeInfo(size_t in_id, const std::string & in_desc) + : index(in_id), desc(in_desc) { } + + // Constructor to allow a new configuration type whose objects require initialization. TypeInfo(size_t in_id, const std::string & in_desc, init_fun_t in_init) - : index(in_id), desc(in_desc), init_fun(in_init) { } + : index(in_id), desc(in_desc), init_fun(in_init) + { + } + + // Link this TypeInfo object to a real C++ type. + template + void LinkType() { + static_assert(std::is_base_of(), + "Only ConfigType objects can be used as a custom config type."); + type_id = emp::GetTypeID(); + } + + // Add a member function that can be called on objects of this type. + template + void AddMemberFunction( + const std::string & name, + std::function fun + ) { + // ----- Make sure function is legal ----- + // Is return type legal? + static_assert(std::is_arithmetic() || std::is_same(), + "Config member functions must of a string or arithmetic return type"); + + // Is the first parameter the correct type? + emp_assert( type_id.IsType(), + "First parameter must match config type of member function being created!", + type_id, emp::GetTypeID() ); + + // Are remaining parameters legal? + constexpr bool params_ok = + ((std::is_arithmetic() || std::is_same()) && ...); + static_assert(params_ok, "Parameters 2+ in a member function must be string or arithmetic."); + + // ----- Transform this function into one that TypeInfo can make use of ---- + // @CAO CONTINUE HERE! + } }; using pos_t = emp::TokenStream::Iterator; @@ -235,11 +281,11 @@ namespace mabe { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!", nullptr ); - type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value", nullptr ); - type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable", nullptr ); - type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable", nullptr ); - type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure", nullptr ); + type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable" ); + type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable" ); + type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure" ); // Setup operator precedence. size_t cur_prec = 0; From 58f830872eda397a3e6715e8aa2a256899afeca9 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 6 Oct 2021 15:19:43 -0400 Subject: [PATCH 175/445] Emp update --- source/third-party/empirical | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/third-party/empirical b/source/third-party/empirical index 87a9b3cb..b5095369 160000 --- a/source/third-party/empirical +++ b/source/third-party/empirical @@ -1 +1 @@ -Subproject commit 87a9b3cb8c15a16e4e40f8ce2d8d1e2e217f4944 +Subproject commit b509536940feb0db1ecc495bd7f8aae378cd05e8 From 8fdb0199da51bcd3c2e6cb0a82e8e18e1534fd5b Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 6 Oct 2021 15:21:21 -0400 Subject: [PATCH 176/445] Remove old build files --- build/AllOnes.cpp | 98 --------------- build/NK.cpp | 100 --------------- build/OLD/AvidaGP-Evo.cc | 101 --------------- build/OLD/AvidaGP-Mancala.cc | 199 ----------------------------- build/OLD/AvidaGP-Resource.cc | 165 ------------------------ build/OLD/AvidaGP-StateGrid.cc | 223 --------------------------------- build/OLD/AvidaGP-Test.cc | 95 -------------- build/OLD/DiagnosticNiches.cc | 102 --------------- build/OLD/EvoSorter.cc | 141 --------------------- build/OLD/EvoSorter.cfg | 10 -- build/OLD/Fitness_Share_NK.cc | 100 --------------- build/OLD/Grid.cc | 51 -------- build/OLD/Grid.cfg | 12 -- build/OLD/MAP-Elites.cc | 83 ------------ build/OLD/Makefile | 69 ---------- build/OLD/NK.cc | 106 ---------------- build/OLD/NK.cfg | 10 -- build/OLD/Pools.cc | 52 -------- build/OLD/Roulette.cc | 50 -------- build/OLD/ShrinkPop.cc | 78 ------------ build/OLD/Systematics.cc | 37 ------ build/OLD/World.cc | 92 -------------- build/OLD/World2.cc | 78 ------------ 23 files changed, 2052 deletions(-) delete mode 100644 build/AllOnes.cpp delete mode 100644 build/NK.cpp delete mode 100644 build/OLD/AvidaGP-Evo.cc delete mode 100644 build/OLD/AvidaGP-Mancala.cc delete mode 100644 build/OLD/AvidaGP-Resource.cc delete mode 100644 build/OLD/AvidaGP-StateGrid.cc delete mode 100644 build/OLD/AvidaGP-Test.cc delete mode 100644 build/OLD/DiagnosticNiches.cc delete mode 100644 build/OLD/EvoSorter.cc delete mode 100644 build/OLD/EvoSorter.cfg delete mode 100644 build/OLD/Fitness_Share_NK.cc delete mode 100644 build/OLD/Grid.cc delete mode 100644 build/OLD/Grid.cfg delete mode 100644 build/OLD/MAP-Elites.cc delete mode 100644 build/OLD/Makefile delete mode 100644 build/OLD/NK.cc delete mode 100644 build/OLD/NK.cfg delete mode 100644 build/OLD/Pools.cc delete mode 100644 build/OLD/Roulette.cc delete mode 100644 build/OLD/ShrinkPop.cc delete mode 100644 build/OLD/Systematics.cc delete mode 100644 build/OLD/World.cc delete mode 100644 build/OLD/World2.cc diff --git a/build/AllOnes.cpp b/build/AllOnes.cpp deleted file mode 100644 index f7152676..00000000 --- a/build/AllOnes.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019 - * - * @file AllOnes.cc - * @brief Implementation of a simple all-ones problem using MABE. - */ - -#include - -#include "config/ArgManager.h" -#include "tools/BitVector.h" -#include "tools/Random.h" - -#include "../source/core/MABE.h" -#include "../source/evaluate/EvalAll1s.h" -#include "../source/orgs/BitsOrg.h" -#include "../source/interface/CommandLine.h" -#include "../source/placement/GrowthPlacement.h" -#include "../source/schema/Mutate.h" -#include "../source/select/SelectElite.h" -#include "../source/select/SelectTournament.h" - -int main(int argc, char* argv[]) -{ - mabe::MABE control(argc, argv); - control.AddModule(); - control.AddOrganismManager("BitOrg"); - control.AddModule(0, 1); - control.AddModule("bits", "fitness"); - control.AddModule("fitness", 1, 1); - control.AddModule("fitness", 7, 199); - control.AddModule(); - control.Setup(); - control.Inject("BitOrg", 200); - control.Update(100); - -/* - // emp::EAWorld pop(random, "NKWorld"); - emp::World pop(random, "NKWorld"); - pop.SetupFitnessFile().SetTimingRepeat(10); - pop.SetupSystematicsFile().SetTimingRepeat(10); - pop.SetupPopulationFile().SetTimingRepeat(10); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (uint32_t i = 0; i < POP_SIZE; i++) { - BitOrg next_org(N); - for (uint32_t j = 0; j < N; j++) next_org[j] = random.P(0.5); - pop.Inject(next_org); - } - - // Setup the mutation function. - std::function mut_fun = - [MUT_COUNT, N](BitOrg & org, emp::Random & random) { - size_t num_muts = 0; - for (uint32_t m = 0; m < MUT_COUNT; m++) { - const uint32_t pos = random.GetUInt(N); - if (random.P(0.5)) { - org[pos] ^= 1; - num_muts++; - } - } - return num_muts; - }; - pop.SetMutFun( mut_fun ); - pop.SetAutoMutate(); - - std::cout << 0 << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - std::function fit_fun = - [&landscape](BitOrg & org){ return landscape.GetFitness(org); }; - pop.SetFitFun( fit_fun ); - - // Loop through updates - for (uint32_t ud = 0; ud < MAX_GENS; ud++) { - // Print current state. - // for (uint32_t i = 0; i < pop.GetSize(); i++) std::cout << pop[i] << std::endl; - // std::cout << std::endl; - - // Keep the best individual. - emp::EliteSelect(pop, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(pop, 5, POP_SIZE-1); - pop.Update(); - std::cout << (ud+1) << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - } - - // pop.PrintLineage(0); - -// std::cout << MAX_GENS << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - // pop.GetSignalControl().PrintNames(); - */ -} diff --git a/build/NK.cpp b/build/NK.cpp deleted file mode 100644 index 320d741f..00000000 --- a/build/NK.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019 - * - * @file NK.cc - * @brief Implementation of NK-evolution using MABE. - */ - -#include - -#include "config/ArgManager.h" -#include "tools/BitVector.h" -#include "tools/Random.h" - -#include "../source/core/MABE.h" -#include "../source/evaluate/EvalNK.h" -#include "../source/orgs/BitsOrg.h" -#include "../source/interface/CommandLine.h" -#include "../source/placement/GrowthPlacement.h" -#include "../source/schema/Mutate.h" -#include "../source/select/SelectElite.h" -#include "../source/select/SelectTournament.h" - -int main(int argc, char* argv[]) -{ - mabe::MABE control(argc, argv); - control.AddPopulation("main_pop"); - control.AddPopulation("next_pop"); - control.AddModule(); - control.AddOrganismManager("BitOrg"); - control.AddModule(0, 1); - control.AddModule(20, 4, "bits", "fitness"); - control.AddModule("fitness", 1, 1); - control.AddModule("fitness", 7, 199); - control.AddModule(); - control.Setup(); - control.Inject("BitOrg", 200); - control.Update(100); - -/* - // emp::EAWorld pop(random, "NKWorld"); - emp::World pop(random, "NKWorld"); - pop.SetupFitnessFile().SetTimingRepeat(10); - pop.SetupSystematicsFile().SetTimingRepeat(10); - pop.SetupPopulationFile().SetTimingRepeat(10); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (uint32_t i = 0; i < POP_SIZE; i++) { - BitOrg next_org(N); - for (uint32_t j = 0; j < N; j++) next_org[j] = random.P(0.5); - pop.Inject(next_org); - } - - // Setup the mutation function. - std::function mut_fun = - [MUT_COUNT, N](BitOrg & org, emp::Random & random) { - size_t num_muts = 0; - for (uint32_t m = 0; m < MUT_COUNT; m++) { - const uint32_t pos = random.GetUInt(N); - if (random.P(0.5)) { - org[pos] ^= 1; - num_muts++; - } - } - return num_muts; - }; - pop.SetMutFun( mut_fun ); - pop.SetAutoMutate(); - - std::cout << 0 << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - std::function fit_fun = - [&landscape](BitOrg & org){ return landscape.GetFitness(org); }; - pop.SetFitFun( fit_fun ); - - // Loop through updates - for (uint32_t ud = 0; ud < MAX_GENS; ud++) { - // Print current state. - // for (uint32_t i = 0; i < pop.GetSize(); i++) std::cout << pop[i] << std::endl; - // std::cout << std::endl; - - // Keep the best individual. - emp::EliteSelect(pop, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(pop, 5, POP_SIZE-1); - pop.Update(); - std::cout << (ud+1) << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - } - - // pop.PrintLineage(0); - -// std::cout << MAX_GENS << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - // pop.GetSignalControl().PrintNames(); - */ -} diff --git a/build/OLD/AvidaGP-Evo.cc b/build/OLD/AvidaGP-Evo.cc deleted file mode 100644 index dcc109b4..00000000 --- a/build/OLD/AvidaGP-Evo.cc +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @note This file is part of Empirical, https://github.com/devosoft/Empirical - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 - * - * @file AvidaGP-Evo.cc - * @brief A test of AvidaGP with World; organisms must find squares of values. - */ - -#include - -#include "hardware/AvidaGP.h" -#include "hardware/InstLib.h" -#include "tools/Random.h" -#include "Evolve/World.h" - -void Print(const emp::AvidaGP & cpu) { - for (size_t i = 0; i < 16; i++) { - std::cout << "[" << cpu.GetReg(i) << "] "; - } - std::cout << " IP=" << cpu.GetIP() << std::endl; -} - -constexpr size_t POP_SIZE = 1000; -constexpr size_t GENOME_SIZE = 50; -constexpr size_t UPDATES = 500; - -int main() -{ - emp::Random random; - emp::World world(random, "AvidaWorld"); - world.SetPopStruct_Mixed(true); - - // Build a random initial popoulation. - for (size_t i = 0; i < POP_SIZE; i++) { - emp::AvidaGP cpu; - cpu.PushRandom(random, GENOME_SIZE); - world.Inject(cpu.GetGenome()); - } - - // Setup the mutation function. - world.SetMutFun( [](emp::AvidaGP & org, emp::Random & random) { - uint32_t num_muts = random.GetUInt(4); // 0 to 3 mutations. - for (uint32_t m = 0; m < num_muts; m++) { - const uint32_t pos = random.GetUInt(GENOME_SIZE); - org.RandomizeInst(pos, random); - } - return num_muts; - } ); - - // Setup the fitness function. - std::function fit_fun = - [](const emp::AvidaGP & org) { - int count = 0; - for (int i = 0; i < 16; i++) { - if (org.GetOutput(i) == (double) (i*i)) count++; - } - return (double) count; - }; - world.SetFitFun(fit_fun); - - emp::vector< std::function > fit_set(16); - for (size_t out_id = 0; out_id < 16; out_id++) { - // Setup the fitness function. - fit_set[out_id] = [out_id](const emp::AvidaGP & org) { - return (double) -std::abs(org.GetOutput((int)out_id) - (double) (out_id * out_id)); - }; - } - - - // Do the run... - for (size_t ud = 0; ud < UPDATES; ud++) { - // Update the status of all organisms. - world.ResetHardware(); - world.Process(200); - double fit0 = world.CalcFitnessID(0); - std::cout << (ud+1) << " : " << 0 << " : " << fit0 << std::endl; - - // Keep the best individual. - EliteSelect(world, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(world, 5, POP_SIZE-1); - // LexicaseSelect(world, fit_set, POP_SIZE-1); - // EcoSelect(world, fit_fun, fit_set, 100, 5, POP_SIZE-1); - world.Update(); - - // Mutate all but the first organism. - world.DoMutations(1); - } - - std::cout << std::endl; - world[0].PrintGenome(); - std::cout << std::endl; - for (int i = 0; i < 16; i++) { - std::cout << i << ":" << world[0].GetOutput(i) << " "; - } - std::cout << std::endl; - - return 0; -} diff --git a/build/OLD/AvidaGP-Mancala.cc b/build/OLD/AvidaGP-Mancala.cc deleted file mode 100644 index e18ebc51..00000000 --- a/build/OLD/AvidaGP-Mancala.cc +++ /dev/null @@ -1,199 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2017. -// Released under the MIT Software license; see doc/LICENSE - -#include - -#include "games/Mancala.h" -#include "hardware/AvidaGP.h" -#include "hardware/InstLib.h" -#include "tools/Random.h" -#include "Evolve/World.h" - -constexpr size_t POP_SIZE = 20; -constexpr size_t GENOME_SIZE = 100; -constexpr size_t EVAL_TIME = 500; -constexpr size_t UPDATES = 100; -constexpr size_t TOURNY_SIZE = 4; - -// Determine the next move of a human player. -size_t EvalMove(emp::Mancala & game, std::ostream & os=std::cout, std::istream & is=std::cin) { - // Present the current board. - game.Print(); - - // Request a move from the human. - char move; - os << "Move?" << std::endl; - is >> move; - - while (move < 'A' || move > 'F' || game.GetCurSide()[(size_t)(move-'A')] == 0) { - os << "Invalid move! (choose a value 'A' to 'F')" << std::endl; - is.clear(); - is.ignore(std::numeric_limits::max(), '\n'); - is >> move; - } - - return (size_t) (move - 'A'); -} - - -// Determine the next move of an AvidaGP player. -size_t EvalMove(emp::Mancala & game, emp::AvidaGP & org) { - // Setup the hardware with proper inputs. - org.ResetHardware(); - org.SetInputs(game.AsInput(game.GetCurPlayer())); - - // Run the code. - org.Process(EVAL_TIME); - - // Determine the chosen move. - int best_move = 0; - for (int i = 1; i < 6; i++) { - if (org.GetOutput(best_move) < org.GetOutput(i)) { best_move = i; } - } - - return (size_t) best_move; -} - -using mancala_ai_t = std::function< size_t(emp::Mancala & game) >; - -// Setup the fitness function for a whole game. -double EvalGame(mancala_ai_t & player0, mancala_ai_t & player1, - bool cur_player=0, bool verbose=false) { - emp::Mancala game(cur_player==0); - size_t round = 0, errors = 0; - while (game.IsDone() == false) { - // Determine the current player and their move. - auto & play_fun = (cur_player == 0) ? player0 : player1; - size_t best_move = play_fun(game); - - if (verbose) { - std::cout << "round = " << round++ << " errors = " << errors << std::endl; - game.Print(); - char move_sym = (char) ('A' + best_move); - std::cout << "Move = " << move_sym; - if (game.GetCurSide()[best_move] == 0) { - std::cout << " (illegal!)"; - } - std::cout << std::endl << std::endl; - } - - // If the chosen move is illegal, shift through other options. - while (game.GetCurSide()[best_move] == 0) { // Cannot make a move into an empty pit! - if (cur_player == 0) errors++; - if (++best_move > 5) best_move = 0; - } - - // Do the move and determine who goes next. - bool go_again = game.DoMove(cur_player, best_move); - if (!go_again) cur_player = !cur_player; - } - - if (verbose) { - std::cout << "Final scores -- A: " << game.ScoreA() - << " B: " << game.ScoreB() - << std::endl; - } - - return ((double) game.ScoreA()) - ((double) game.ScoreB()) - ((double) errors * 10.0); -} - -// Build wrappers for AvidaGP -double EvalGame(emp::AvidaGP & org0, emp::AvidaGP & org1, bool cur_player=0, bool verbose=false) { - mancala_ai_t org_fun0 = [&org0](emp::Mancala & game){ return EvalMove(game, org0); }; - mancala_ai_t org_fun1 = [&org1](emp::Mancala & game){ return EvalMove(game, org1); }; - return EvalGame(org_fun0, org_fun1, cur_player, verbose); -} - -// Otherwise assume a human opponent! -double EvalGame(emp::AvidaGP & org, bool cur_player=0) { - mancala_ai_t fun0 = [&org](emp::Mancala & game){ return EvalMove(game, org); }; - mancala_ai_t fun1 = [](emp::Mancala & game){ return EvalMove(game, std::cout, std::cin); }; - return EvalGame(fun0, fun1, cur_player, true); -} - - -int main() -{ - emp::Random random; - emp::World world(random, "AvidaWorld"); - world.SetPopStruct_Mixed(true); - - // Build a random initial popoulation. - for (size_t i = 0; i < POP_SIZE; i++) { - emp::AvidaGP cpu; - cpu.PushRandom(random, GENOME_SIZE); - world.Inject(cpu.GetGenome()); - } - - // Setup the mutation function. - world.SetMutFun( [](emp::AvidaGP & org, emp::Random& random) { - uint32_t num_muts = random.GetUInt(4); // 0 to 3 mutations. - for (uint32_t m = 0; m < num_muts; m++) { - const uint32_t pos = random.GetUInt(GENOME_SIZE); - org.RandomizeInst(pos, random); - } - return (num_muts > 0); - } ); - - // Setup the fitness function. - std::function fit_fun = - [&random, &world](emp::AvidaGP & org) { - emp::AvidaGP & rand_org = world.GetRandomOrg(); - bool cur_player = random.P(0.5); - return EvalGame(org, rand_org, cur_player); - }; - world.SetFitFun(fit_fun); - - emp::vector< std::function > fit_set(16); - for (size_t out_id = 0; out_id < 16; out_id++) { - // Setup the fitness function. - fit_set[out_id] = [out_id](emp::AvidaGP & org) { - return (double) -std::abs(org.GetOutput((int)out_id) - (double) (out_id * out_id)); - }; - } - - - // Do the run... - for (size_t ud = 0; ud < UPDATES; ud++) { - // Keep the best individual. - EliteSelect(world, 1, 1); - - // Run a tournament for each spot. - TournamentSelect(world, TOURNY_SIZE, POP_SIZE-1); - // LexicaseSelect(world, POP_SIZE-1); - // EcoSelect(world, fit_set, 100, TOURNY_SIZE, POP_SIZE-1); - world.Update(); - std::cout << (ud+1) << " : " << 0 << " : " << world.CalcFitnessID(0) << std::endl; - - // Mutate all but the first organism. - world.DoMutations(1); - } - - world.CalcFitnessID(0); - - std::cout << std::endl; - emp::Mancala game(0); - world[0].PrintGenome("mancala_save.org"); - - game.DoMove(0); - world.GetOrg(0).ResetHardware(); - world.GetOrg(0).SetInputs(game.AsInput(game.GetCurPlayer())); - world.GetOrg(0).Trace(1); - - game.DoMove(5); - world.GetOrg(0).ResetHardware(); - world.GetOrg(0).SetInputs(game.AsInput(game.GetCurPlayer())); - world.GetOrg(0).Trace(1); - - - // EvalGame(world[0], world[1], 0, true); - // - // // And try playing it! - // while (true) { - // std::cout << "NEW GAME: Human vs. AI!\n"; - // EvalGame(world[0]); - // } - - return 0; -} diff --git a/build/OLD/AvidaGP-Resource.cc b/build/OLD/AvidaGP-Resource.cc deleted file mode 100644 index 61b12625..00000000 --- a/build/OLD/AvidaGP-Resource.cc +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @note This file is part of Empirical, https://github.com/devosoft/Empirical - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 - * - * @file AvidaGP-Evo.cc - * @brief A test of AvidaGP with World; organisms must find squares of values. - */ - -#include - -#include "hardware/AvidaGP.h" -#include "hardware/InstLib.h" -#include "tools/Random.h" -#include "Evolve/World.h" -#include "Evolve/Resource.h" - -void Print(const emp::AvidaGP & cpu) { - for (size_t i = 0; i < 16; i++) { - std::cout << "[" << cpu.GetReg(i) << "] "; - } - std::cout << " IP=" << cpu.GetIP() << std::endl; -} - -constexpr size_t POP_SIZE = 500; -constexpr size_t GENOME_SIZE = 50; -constexpr size_t UPDATES = 10000; - -int main() -{ - emp::Random random; - emp::World world(random, "AvidaWorld"); - world.SetPopStruct_Mixed(true); - - emp::vector resources; - resources.push_back(emp::Resource(100, 100, .01)); - resources.push_back(emp::Resource(100, 100, .01)); - - world.OnUpdate([&resources](int ud){ - for (emp::Resource& res : resources) { - res.Update(); - } - }); - - // Build a random initial popoulation. - for (size_t i = 0; i < POP_SIZE; i++) { - emp::AvidaGP cpu; - cpu.PushRandom(random, GENOME_SIZE); - world.Inject(cpu.GetGenome()); - } - - // Setup the mutation function. - world.SetMutFun( [](emp::AvidaGP & org, emp::Random & random) { - uint32_t num_muts = random.GetUInt(4); // 0 to 3 mutations. - for (uint32_t m = 0; m < num_muts; m++) { - const uint32_t pos = random.GetUInt(GENOME_SIZE); - org.RandomizeInst(pos, random); - } - return num_muts; - } ); - - std::function goal_function = [](const emp::AvidaGP & org){ - double fitness = 0; - bool hit_end = false; - - for (uint32_t i = 0; i < 32; i++) { - if (org.GetOutput(i) == 1) { - fitness++; - } else { - if ((i >= 29) || - !(org.GetOutput(i+1)==1 && org.GetOutput(i+2)==0 && org.GetOutput(i+3)==1)) { - fitness = 0; - } - hit_end = true; - break; - } - } - - if (!hit_end) { - fitness = 0; - } - // std::cout << *org << " Fitness: " << fitness << std::endl; - - return fitness; -}; - -std::function good_hint = [](const emp::AvidaGP & org){ - double count = 0; - - for (int i = 0; i < 32; i++) { - if (org.GetOutput(i) == 1) {count++;} - } - - return count/32.0; -}; - -std::function bad_hint = [](const emp::AvidaGP & org){ - double count = 0; - - for (int i = 0; i < 32; i++) { - if (org.GetOutput(i) == 0) {count++;} - } - - return count/32.0; - -}; - - - - // Setup the fitness function. - // std::function fit_fun = - // [](const emp::AvidaGP & org) { - // int count = 0; - // for (int i = 0; i < 16; i++) { - // if (org.GetOutput(i) == (double) (i*i)) count++; - // } - // return (double) count; - // }; - - world.SetFitFun(goal_function); - - emp::vector< std::function > fit_set(2); - fit_set[0] = good_hint; - fit_set[1] = bad_hint; - - // emp::vector< std::function > fit_set(16); - // for (size_t out_id = 0; out_id < 16; out_id++) { - // // Setup the fitness function. - // fit_set[out_id] = [out_id](const emp::AvidaGP & org) { - // return (double) -std::abs(org.GetOutput((int)out_id) - (double) (out_id * out_id)); - // }; - // } - - // Do the run... - for (size_t ud = 0; ud < UPDATES; ud++) { - // Update the status of all organisms. - world.ResetHardware(); - world.Process(200); - double fit0 = world.CalcFitnessID(0); - std::cout << (ud+1) << " : " << resources[0].GetAmount() << " " << resources[1].GetAmount() << " : " << fit0 << std::endl; - - // Keep the best individual. - EliteSelect(world, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(world, 5, POP_SIZE-1); - // LexicaseSelect(world, fit_set, POP_SIZE-1); - // ResourceSelect(world, fit_set, resources, 5, POP_SIZE-1); - world.Update(); - - // Mutate all but the first organism. - world.DoMutations(1); - } - - std::cout << std::endl; - world[0].PrintGenome(); - world.GetOrg(0).Process(200); - std::cout << std::endl; - for (int i = 0; i < 32; i++) { - std::cout << i << ":" << world[0].GetOutput(i) << " "; - } - std::cout << std::endl; - - return 0; -} diff --git a/build/OLD/AvidaGP-StateGrid.cc b/build/OLD/AvidaGP-StateGrid.cc deleted file mode 100644 index 761bbdf7..00000000 --- a/build/OLD/AvidaGP-StateGrid.cc +++ /dev/null @@ -1,223 +0,0 @@ -/** - * @note This file is part of Empirical, https://github.com/devosoft/Empirical - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 - * - * @file AvidaGP-StateGrid.cc - * @brief A example of using AvidaGP evolving with a StateGrid. - * - * Example file of AvidaGP-based organisms (called SGOrg here) moving through a state grid, - * trying to consume as many resources as possible without stepping off patches. - * - * States: - * -1 = None (poison) - * 0 = Former food, now eaten. - * 1 = Current food - * 2 = Border, no longer has food. - * 3 = Border, with food. - */ - -#include - -#include "hardware/AvidaGP.h" -#include "hardware/InstLib.h" -#include "tools/Random.h" -#include "Evolve/StateGrid.h" -#include "Evolve/World.h" - -class SGOrg : public emp::AvidaCPU_Base { -protected: - emp::StateGridStatus sg_status; - emp::StateGrid state_grid; - double score; - -public: - using base_t = emp::AvidaCPU_Base; - - SGOrg() : sg_status(), state_grid(), score(0) { ; } - SGOrg(emp::Ptr< const emp::AvidaCPU_InstLib > inst_lib) - : base_t(inst_lib), sg_status(), state_grid(), score(0) { ; } - SGOrg(const base_t::genome_t & in_genome) : base_t(in_genome), sg_status(), state_grid(), score(0) { ; } - SGOrg(const SGOrg &) = default; - SGOrg(SGOrg &&) = default; - - emp::StateGridStatus & GetSGStatus() { return sg_status; } - emp::StateGridStatus GetSGStatus() const { return sg_status; } - emp::StateGrid & GetStateGrid() { return state_grid; } - const emp::StateGrid & GetStateGrid() const { return state_grid; } - double GetScore() const { return score; } - - void SetPosition(size_t x, size_t y) { sg_status.SetPos(x,y); } - void SetFacing(size_t facing) { sg_status.SetFacing(facing); } - void SetStateGrid(const emp::StateGrid & in_sg) { state_grid = in_sg; } - - double GetFitness() { // Setup the fitness function. - ResetHardware(); - Process(200); - return score; - } - - void ResetHardware() { - base_t::ResetHardware(); - score = 0; - } - - static void Inst_Move(SGOrg & org, const base_t::Instruction & inst) { - emp_assert(org.state_grid.GetSize() > 0, org.state_grid.GetWidth(), org.state_grid.GetHeight()); - org.sg_status.Move(org.state_grid, org.regs[inst.args[0]]); - } - - static void Inst_Rotate(SGOrg & org, const base_t::Instruction & inst) { - org.sg_status.Rotate(org.regs[inst.args[0]]); - } - - static void Inst_Scan(SGOrg & org, const base_t::Instruction & inst) { - int val = org.sg_status.Scan(org.state_grid); - org.regs[inst.args[0]] = val; - switch (val) { - case -1: org.score -= 0.5; break; // Poison - case 0: break; // Consumed food - case 1: org.score += 1.0; org.sg_status.SetState(org.state_grid, 0); break; // Food! (being eaten...) - case 2: break; // Empty border - case 3: org.score += 1.0; org.sg_status.SetState(org.state_grid, 2); break; // Border w/ food - } - } - -}; - -class SGWorld : public emp::World { -public: - using inst_lib_t = emp::AvidaCPU_InstLib; - - inst_lib_t inst_lib; - -public: - SGWorld(emp::Random & random, const std::string & name) - : emp::World(random, name), inst_lib() - { - // Build the instruction library... - inst_lib.AddInst("Inc", inst_lib_t::Inst_Inc, 1, "Increment value in reg Arg1"); - inst_lib.AddInst("Dec", inst_lib_t::Inst_Dec, 1, "Decrement value in reg Arg1"); - inst_lib.AddInst("Not", inst_lib_t::Inst_Not, 1, "Logically toggle value in reg Arg1"); - inst_lib.AddInst("SetReg", inst_lib_t::Inst_SetReg, 2, "Set reg Arg1 to numerical value Arg2"); - inst_lib.AddInst("Add", inst_lib_t::Inst_Add, 3, "regs: Arg3 = Arg1 + Arg2"); - inst_lib.AddInst("Sub", inst_lib_t::Inst_Sub, 3, "regs: Arg3 = Arg1 - Arg2"); - inst_lib.AddInst("Mult", inst_lib_t::Inst_Mult, 3, "regs: Arg3 = Arg1 * Arg2"); - inst_lib.AddInst("Div", inst_lib_t::Inst_Div, 3, "regs: Arg3 = Arg1 / Arg2"); - inst_lib.AddInst("Mod", inst_lib_t::Inst_Mod, 3, "regs: Arg3 = Arg1 % Arg2"); - inst_lib.AddInst("TestEqu", inst_lib_t::Inst_TestEqu, 3, "regs: Arg3 = (Arg1 == Arg2)"); - inst_lib.AddInst("TestNEqu", inst_lib_t::Inst_TestNEqu, 3, "regs: Arg3 = (Arg1 != Arg2)"); - inst_lib.AddInst("TestLess", inst_lib_t::Inst_TestLess, 3, "regs: Arg3 = (Arg1 < Arg2)"); - inst_lib.AddInst("If", inst_lib_t::Inst_If, 2, "If reg Arg1 != 0, scope -> Arg2; else skip scope", emp::ScopeType::BASIC, 1); - inst_lib.AddInst("While", inst_lib_t::Inst_While, 2, "Until reg Arg1 != 0, repeat scope Arg2; else skip", emp::ScopeType::LOOP, 1); - inst_lib.AddInst("Countdown", inst_lib_t::Inst_Countdown, 2, "Countdown reg Arg1 to zero; scope to Arg2", emp::ScopeType::LOOP, 1); - inst_lib.AddInst("Break", inst_lib_t::Inst_Break, 1, "Break out of scope Arg1"); - inst_lib.AddInst("Scope", inst_lib_t::Inst_Scope, 1, "Enter scope Arg1", emp::ScopeType::BASIC, 0); - inst_lib.AddInst("Define", inst_lib_t::Inst_Define, 2, "Build function Arg1 in scope Arg2", emp::ScopeType::FUNCTION, 1); - inst_lib.AddInst("Call", inst_lib_t::Inst_Call, 1, "Call previously defined function Arg1"); - inst_lib.AddInst("Push", inst_lib_t::Inst_Push, 2, "Push reg Arg1 onto stack Arg2"); - inst_lib.AddInst("Pop", inst_lib_t::Inst_Pop, 2, "Pop stack Arg1 into reg Arg2"); - inst_lib.AddInst("Input", inst_lib_t::Inst_Input, 2, "Pull next value from input Arg1 into reg Arg2"); - inst_lib.AddInst("Output", inst_lib_t::Inst_Output, 2, "Push reg Arg1 into output Arg2"); - inst_lib.AddInst("CopyVal", inst_lib_t::Inst_CopyVal, 2, "Copy reg Arg1 into reg Arg2"); - inst_lib.AddInst("ScopeReg", inst_lib_t::Inst_ScopeReg, 1, "Backup reg Arg1; restore at end of scope"); - - inst_lib.AddInst("Move", SGOrg::Inst_Move, 1, "Move forward in state grid."); - inst_lib.AddInst("Rotate", SGOrg::Inst_Rotate, 1, "Rotate in place in state grid."); - inst_lib.AddInst("Scan", SGOrg::Inst_Scan, 1, "Idenify state of current position in state grid."); - - // OnPlacement( [this](size_t world_id){ - // pop[world_id]->SetWorldID(world_id); // Tell organisms their position in environment. - // } ); - } - ~SGWorld() { ; } - - const inst_lib_t & GetInstLib() const { return inst_lib; } - -}; - -void Print(const emp::AvidaGP & cpu) { - for (size_t i = 0; i < 16; i++) { - std::cout << "[" << cpu.GetReg(i) << "] "; - } - std::cout << " IP=" << cpu.GetIP() << std::endl; -} - -constexpr size_t POP_SIZE = 1000; -constexpr size_t GENOME_SIZE = 50; -constexpr size_t UPDATES = 10000; - -int main() -{ - emp::Random random; - SGWorld world(random, "AvidaWorld"); - emp::StateGrid state_grid; - - state_grid.AddState(-1, '-', -0.5, "None", "Empty space; poisonous."); - state_grid.AddState( 0, '.', 0.0, "Consumed Food", "Previously had sustenance for an organism."); - state_grid.AddState( 1, '#', +1.0, "Food", "sustenance to an org."); - state_grid.AddState( 2, 'x', 0.0, "Consumed Edge", "Edge marker; previously had food."); - state_grid.AddState( 3, 'X', +1.0, "Edge", "Edge marker with food."); - - state_grid.Load("state_grids/islands_50x50.cfg"); - - // When an organism is added to the world, supply it with a state grid. - world.OnPlacement( [&state_grid, &world, &random](size_t pos){ - world.GetOrg(pos).SetStateGrid(state_grid); - // if (pos && random.P(0.1)) world.GetOrg(pos).GetSGStatus().Randomize(state_grid, random); - } ); - - world.SetPopStruct_Mixed(true); - - // Build a random initial population. - for (size_t i = 0; i < POP_SIZE; i++) { - SGOrg cpu(&(world.inst_lib)); - cpu.SetStateGrid(state_grid); - cpu.PushRandom(random, GENOME_SIZE); - world.Inject(cpu.GetGenome()); - } - - // Setup the mutation function. - world.SetMutFun( [](SGOrg & org, emp::Random & random) { - uint32_t num_muts = random.GetUInt(4); // 0 to 3 mutations. - for (uint32_t m = 0; m < num_muts; m++) { - const uint32_t pos = random.GetUInt(GENOME_SIZE); - org.RandomizeInst(pos, random); - } - return num_muts; - } ); - - // Do the run... - for (size_t ud = 0; ud < UPDATES; ud++) { - // Progress output... - std::cout << "Update " << ud; - - // Keep the best individual. - EliteSelect(world, 1, 1); - - std::cout << " fitness[0] = " << world[0].GetScore() - << std::endl; - - // Run a tournament for the rest... - TournamentSelect(world, 4, POP_SIZE-1); - - // Put new organisms in place. - world.Update(); - - // Mutate all but the first organism. - world.DoMutations(1); - } - - std::cout << "Final Fitness: " << world.CalcFitnessID(0) << std::endl; - world[0].GetStateGrid().Print(); - - std::cout << std::endl; - world[0].PrintGenome(); - std::cout << std::endl; - for (int i = 0; i < 16; i++) { - std::cout << i << ":" << world[0].GetOutput(i) << " "; - } - std::cout << std::endl; - - return 0; -} diff --git a/build/OLD/AvidaGP-Test.cc b/build/OLD/AvidaGP-Test.cc deleted file mode 100644 index 547d1e92..00000000 --- a/build/OLD/AvidaGP-Test.cc +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @note This file is part of Empirical, https://github.com/devosoft/Empirical - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2017 - * - * @file AvidaGP-Test.cc - * @brief A simple test of AvidaGP with World for copies and mutations. - * - * A few basic steps to examine AvidaGP organisms in a world. - * 1. Inject a lenght 10 organism - * 2. Copy that first oranism - * 3. Mutate the copy - * 4. Make a copy of the mutant. - * - * Each step of the way is printed. - */ - -#include - -#include "hardware/AvidaGP.h" -#include "hardware/InstLib.h" -#include "tools/Random.h" -#include "Evolve/World.h" - -void Print(const emp::AvidaGP & cpu) { - cpu.PrintGenome(); - for (size_t i = 0; i < 16; i++) { - std::cout << "[" << cpu.GetReg(i) << "] "; - } - std::cout << " IP=" << cpu.GetIP() << std::endl; -} - -constexpr size_t POP_SIZE = 1000; -constexpr size_t GENOME_SIZE = 50; -constexpr size_t UPDATES = 50; - -int main() -{ - emp::Random random; - emp::World world(random, "AvidaWorld"); - world.SetPopStruct_Mixed(true); - - // Add a random organism. - emp::AvidaGP cpu; - cpu.PushRandom(random, 10); - world.Inject(cpu.GetGenome()); - - // Setup a mutation function that always performs a single mutation. - world.SetMutFun( [](emp::AvidaGP & org, emp::Random & random) { - const uint32_t pos = random.GetUInt(org.GetSize()); - org.RandomizeInst(pos, random); - return 1; - } ); - - // Copy genome into cell 1 - world.Inject( world.GetGenomeAt(0) ); - - std::cout << std::endl << "GENOME 0" << std::endl; - Print(world[0]); - - std::cout << std::endl << "GENOME 1" << std::endl; - Print(world[1]); - - // Mutate cell 1 and see what happens. - world.DoMutations(1); - - std::cout << std::endl << "GENOME 1 (post mutations)" << std::endl; - Print(world[1]); - - // Copy mutated genome 1 into cell 2 - world.Inject( world.GetGenomeAt(1) ); - - std::cout << std::endl << "GENOME 2 (copy of mutant)" << std::endl; - Print(world[2]); - - // Let's do some selection; setup a neutral fitness function. - world.SetFitFun( [](const emp::AvidaGP &){ return 0.0; } ); - - world.ResetHardware(); - world.Process(200); - EliteSelect(world, 1, 3); - TournamentSelect(world, 3, 1); - - std::cout << std::endl << "GENOME 0 (after selection!)" << std::endl; - Print(world[0]); - - world.Update(); - double fit0 = world.CalcFitnessID(0); - std::cout << "Fitness 0 = " << fit0 << std::endl; - world.DoMutations(1); - - std::cout << std::endl << "GENOME 0 (and DoMutations, but not on this!)" << std::endl; - Print(world[0]); - -} diff --git a/build/OLD/DiagnosticNiches.cc b/build/OLD/DiagnosticNiches.cc deleted file mode 100644 index 5236e1d9..00000000 --- a/build/OLD/DiagnosticNiches.cc +++ /dev/null @@ -1,102 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2019. -// Released under the MIT Software license; see doc/LICENSE -// - -#include - -#include "config/ArgManager.h" -#include "Evolve/World.h" -#include "tools/BitVector.h" -#include "tools/Random.h" - -EMP_BUILD_CONFIG( DNConfig, - GROUP(DEFAULT, "Default settings for Diagnostic Niches model."), - VALUE(N, uint32_t, 200, "Number of bits in each organisms."), ALIAS(GENOME_SIZE), - VALUE(SEED, int, 0, "Random number seed (0 for based on time)"), - VALUE(POP_SIZE, uint32_t, 1000, "Number of organisms in the popoulation."), - VALUE(MAX_GENS, uint32_t, 2000, "How many generations should we process?"), - VALUE(MUT_COUNT, uint32_t, 3, "How many bit positions should be randomized?"), ALIAS(NUM_MUTS), -) - -using BitOrg = emp::BitVector; - -double CalcFitness(const BitOrg & org, size_t pos) { - emp_assert(pos < org.size()); - if (org[pos] == 0) return 0.0; - return (double) org.CountZeros(); -} - -/// Calculate the total fitness of all components summed together. -double CalcTotalFitness(const BitOrg & org) { - return org.CountOnes() * org.CountZeros(); -} - -int main(int argc, char* argv[]) -{ - DNConfig config; - config.Read("DiagnosticNiches.cfg"); - - auto args = emp::cl::ArgManager(argc, argv); - if (args.ProcessConfigOptions(config, std::cout, "NK.cfg", "NK-macros.h") == false) exit(0); - if (args.TestUnknown() == false) exit(0); // If there are leftover args, throw an error. - - const uint32_t N = config.N(); - const uint32_t POP_SIZE = config.POP_SIZE(); - const uint32_t MAX_GENS = config.MAX_GENS(); - const uint32_t MUT_COUNT = config.MUT_COUNT(); - - emp::Random random(config.SEED()); - - emp::World pop(random, "NKWorld"); - pop.SetupFitnessFile().SetTimingRepeat(10); -// pop.SetupSystematicsFile().SetTimingRepeat(10); - pop.SetupPopulationFile().SetTimingRepeat(10); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (uint32_t i = 0; i < POP_SIZE; i++) { - BitOrg next_org(N); - for (uint32_t j = 0; j < N; j++) next_org[j] = random.P(0.5); - pop.Inject(next_org); - } - - // Setup the mutation function. - std::function mut_fun = - [MUT_COUNT, N](BitOrg & org, emp::Random & random) { - size_t num_muts = 0; - for (uint32_t m = 0; m < MUT_COUNT; m++) { - const uint32_t pos = random.GetUInt(N); - if (random.P(0.5)) { - org[pos] ^= 1; - num_muts++; - } - } - return num_muts; - }; - pop.SetMutFun( mut_fun ); - pop.SetAutoMutate(); - - std::cout << 0 << " : " << pop[0] << " : " << CalcFitness(pop[0], 0) << std::endl; - - std::function fit_fun = - [](BitOrg & org){ return CalcFitness(org,0); }; - pop.SetFitFun( fit_fun ); - - // Loop through updates - for (uint32_t ud = 0; ud < MAX_GENS; ud++) { - // Print current state. - // for (uint32_t i = 0; i < pop.GetSize(); i++) std::cout << pop[i] << std::endl; - // std::cout << std::endl; - - // Keep the best individual. - emp::EliteSelect(pop, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(pop, 5, POP_SIZE-1); - pop.Update(); - std::cout << (ud+1) << " : " << pop[0] << " : " << CalcFitness(pop[0], 0) << std::endl; - } - -} diff --git a/build/OLD/EvoSorter.cc b/build/OLD/EvoSorter.cc deleted file mode 100644 index 1f577d42..00000000 --- a/build/OLD/EvoSorter.cc +++ /dev/null @@ -1,141 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2018. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the evolving BitSorter sorting networks. - -#include - -#include "config/ArgManager.h" -#include "Evolve/World.h" -#include "hardware/BitSorter.h" -#include "tools/Random.h" - -EMP_BUILD_CONFIG( EvoSortConfig, - GROUP(DEFAULT, "Default settings for EvoSorter model"), - VALUE(SEED, int, 0, "Random number seed (0 for based on time)"), - VALUE(POP_SIZE, uint32_t, 200, "Number of organisms in the popoulation."), - VALUE(MAX_GENS, uint32_t, 2000, "How many generations should we process?"), - VALUE(ORG_SIZE, size_t, 100, "Number of comparisons in an organism."), - VALUE(MUT_SUB_PROB, double, 0.5, "What is the probability for a comparison to be randomized?"), - VALUE(MUT_INS_PROB, double, 0.5, "What is the probability for a comparison to have a new one inserted after?"), - VALUE(MUT_DEL_PROB, double, 0.5, "What is the probability for a comparison to be deleted?"), -) - - -using SorterOrg = emp::BitSorter; - -void PrintOrg(size_t update, const SorterOrg & org) -{ - std::cout << (update+1) << " : " << org.AsString() - << " : SolveCount=" << org.CountSortable() - << " Size=" << org.GetSize() - << std::endl; -} - -int main(int argc, char* argv[]) -{ - EvoSortConfig config; - config.Read("EvoSorter.cfg"); - - auto args = emp::cl::ArgManager(argc, argv); - if (args.ProcessConfigOptions(config, std::cout, "EvoSorter.cfg", "EvoSorter-macros.h") == false) exit(0); - if (args.TestUnknown() == false) exit(0); // If there are leftover args, throw an error. - - const uint32_t POP_SIZE = config.POP_SIZE(); - const uint32_t MAX_GENS = config.MAX_GENS(); - const uint32_t ORG_SIZE = config.ORG_SIZE(); - - const double MUT_SUB_PROB = config.MUT_SUB_PROB(); - const double MUT_INS_PROB = config.MUT_INS_PROB(); - const double MUT_DEL_PROB = config.MUT_DEL_PROB(); - - emp::Random random(config.SEED()); - - emp::World pop(random, "SorterWorld"); - pop.SetupFitnessFile().SetTimingRepeat(10); - // pop.SetupSystematicsFile().SetTimingRepeat(10); - pop.SetupPopulationFile().SetTimingRepeat(10); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (uint32_t i = 0; i < POP_SIZE; i++) { - SorterOrg next_org; - for (size_t i = 0; i < ORG_SIZE; i++) { - next_org.AddCompare(random.GetUInt(16), random.GetUInt(16)); - } - pop.Inject(next_org); - } - - // Setup the mutation function. - std::function mut_fun = - [MUT_SUB_PROB,MUT_INS_PROB,MUT_DEL_PROB](SorterOrg & org, emp::Random & random) { - size_t num_muts = 0; - // Delete first (so as to not delete something we just changed or added) - if (random.P(MUT_DEL_PROB)) { - const uint32_t pos = random.GetUInt(org.GetSize()); - org.RemoveCompare(pos); - num_muts++; - } - // Substitute before insert (to not change something just added) - if (random.P(MUT_SUB_PROB)) { - const uint32_t pos = random.GetUInt(org.GetSize()); - org.EditCompare(pos, random.GetUInt(16), random.GetUInt(16)); - num_muts++; - } - // Finally, do any insertions. - if (random.P(MUT_INS_PROB)) { - const uint32_t pos = random.GetUInt(org.GetSize()); - org.InsertCompare(pos, random.GetUInt(16), random.GetUInt(16)); - num_muts++; - } - - return num_muts; - }; - pop.SetMutFun( mut_fun ); - pop.SetAutoMutate(1); - - // Build the main fitness function. - std::function fit_fun = - [](const SorterOrg & org){ return org.CountSortable() * 10 - org.GetSize(); }; - pop.SetFitFun( fit_fun ); - - // Setup a place to put the set of fitness functions for lexicase. - constexpr size_t num_fit_funs = 100; - emp::vector< std::function > fit_set(num_fit_funs); - - PrintOrg(0, pop[0]); - - // Loop through updates - for (uint32_t ud = 0; ud < MAX_GENS; ud++) { - - // Build the lexicase fitness functions (changing each update) - for (size_t i = 0; i < num_fit_funs; i++) { - // Setup the fitness function. - const size_t target_id = random.GetUInt(1<<16); - fit_set[i] = [target_id](const SorterOrg & org) { - return (double) org.TestSortable(target_id); - }; - } - - - // Keep the best individual. - emp::EliteSelect(pop, 1, 1); - - // Run a tournament for the rest... - // TournamentSelect(pop, 5, POP_SIZE-1); - emp::LexicaseSelect(pop, fit_set, POP_SIZE-1); - - pop.Update(); - // std::cout << (ud+1) << " : " << pop[0].AsString() << " : " << pop[0].CountSortable() << std::endl; - PrintOrg(ud+1, pop[0]); - } - - // pop.PrintLineage(0); - - std::cout << MAX_GENS << " : " << pop[0].AsString() << " : " << pop[0].CountSortable() << std::endl; - - // pop.GetSignalControl().PrintNames(); -} diff --git a/build/OLD/EvoSorter.cfg b/build/OLD/EvoSorter.cfg deleted file mode 100644 index 4029ad7f..00000000 --- a/build/OLD/EvoSorter.cfg +++ /dev/null @@ -1,10 +0,0 @@ -### DEFAULT ### -# Default settings for EvoSorter model - -set SEED 0 # Random number seed (0 for based on time) -set POP_SIZE 20 # Number of organisms in the popoulation. -set MAX_GENS 10000 # How many generations should we process? -set ORG_SIZE 60 # Number of comparisons in an organism. -set MUT_SUB_PROB 0.5 # What is the probability for a comparison to be randomized? -set MUT_INS_PROB 0.5 # What is the probability for a comparison to have a new one inserted after? -set MUT_DEL_PROB 0.5 # What is the probability for a comparison to be deleted? diff --git a/build/OLD/Fitness_Share_NK.cc b/build/OLD/Fitness_Share_NK.cc deleted file mode 100644 index 9db9e2d9..00000000 --- a/build/OLD/Fitness_Share_NK.cc +++ /dev/null @@ -1,100 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file uses the Fitness Sharing functionality defined in evo::World.h - -#include - -#include "Evolve/NK-const.h" -#include "Evolve/World.h" -#include "tools/BitSet.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - - -constexpr size_t K = 3; -constexpr size_t N = 50; - -using BitOrg = emp::BitSet; - -int main() -{ - size_t POP_SIZE = 100; - size_t UD_COUNT = 1000; - - emp::Random random; - emp::evo::NKLandscapeConst landscape(random); - emp::World pop(random); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (size_t i = 0; i < POP_SIZE; i++) { - BitOrg next_org; - for (size_t j = 0; j < N; j++) next_org[j] = random.P(0.5); - pop.Inject(next_org); - } - - // Setup the (shared) fitness function. - pop.SetSharedFitFun( [&landscape](BitOrg &org){ return landscape.GetFitness(org); }, - [](BitOrg& org1, BitOrg& org2){ return (double)(org1.XOR(org2)).CountOnes();}, - 10, 1 ); - - pop.SetMutFun( [](BitOrg & org, emp::Random & random){ - size_t count = 0; - if (random.P(0.5)) { org[random.GetUInt(N)].Toggle(); count++; } - if (random.P(0.5)) { org[random.GetUInt(N)].Toggle(); count++; } - if (random.P(0.5)) { org[random.GetUInt(N)].Toggle(); count++; } - return count; - } ); - - // Loop through updates - for (size_t ud = 0; ud < UD_COUNT; ud++) { - // Run a tournament... - emp::TournamentSelect(pop, 5, POP_SIZE-1); - pop.Update(); - pop.DoMutations(); - } - - - std::cout << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - - - std::cout << "--- Grid example ---\n"; - - POP_SIZE = 400; - - std::function print_fun = [](int & val, std::ostream & os) { - val %= 63; - if (val < 10) os << (char) ('0' + val); - else if (val < 36) os << (char) ('a' + (val - 10)); - else if (val < 62) os << (char) ('A' + (val - 36)); - else os << '+'; - }; - - emp::World grid_world(random); - const size_t side = (size_t) std::sqrt(POP_SIZE); - grid_world.SetPopStruct_Grid(side, side); - grid_world.SetPrintFun(print_fun); - - emp_assert(grid_world.GetSize() == POP_SIZE); // POP_SIZE needs to be a perfect square. - - - grid_world.InjectAt(30, side+1); - grid_world.InjectAt(4, side*(side+1)/2); - grid_world.PrintGrid(); - - auto fit_fun = [](int & org){ return (double) org; }; - grid_world.SetSharedFitFun(fit_fun, [](int & a, int & b){ return (double) (a>b)?(a-b):(b-a); }, 3, 1); - RouletteSelect(grid_world, 500); - - std::cout << std::endl; - grid_world.PrintGrid(); - std::cout << "Final Org Counts:\n"; - // grid_world.PrintOrgCounts(print_fun); - // std::cout << std::endl; - -} diff --git a/build/OLD/Grid.cc b/build/OLD/Grid.cc deleted file mode 100644 index 74d11bd4..00000000 --- a/build/OLD/Grid.cc +++ /dev/null @@ -1,51 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the grid options for emp::World.h - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - -int main() -{ - constexpr size_t POP_SIZE = 3600; - constexpr size_t GENS = 10000; - const size_t POP_SIDE = (size_t) std::sqrt(POP_SIZE); - - emp::Random random; - - std::function print_fun = [](int & val, std::ostream & os) { - char out_char = '+'; - val %= 63; - if (val < 10) out_char = (char) ('0' + val); - else if (val < 36) out_char = (char) ('a' + (val - 10)); - else if (val < 62) out_char = (char) ('A' + (val - 36)); - os << out_char; - }; - - emp::World grid_world(random); - grid_world.SetPopStruct_Grid(POP_SIDE, POP_SIDE); - grid_world.SetPrintFun(print_fun); - - for (size_t i = 0; i < POP_SIZE; i++) grid_world.InjectAt((int)i,i); - grid_world.PrintGrid(); - - for (size_t g = 0; g < GENS; g++) { - for (size_t i = 0; i < grid_world.GetSize(); ++i) { - size_t id = random.GetUInt(grid_world.GetSize()); - if (grid_world.IsOccupied(id)) grid_world.DoBirth(grid_world[id], id); - } - if (g % 1000 == 0) std::cout << "Generation: " << g << std::endl; - } - - std::cout << std::endl; - grid_world.PrintGrid(); - std::cout << "Final Org Counts:\n"; - grid_world.PrintOrgCounts(); - std::cout << std::endl; -} diff --git a/build/OLD/Grid.cfg b/build/OLD/Grid.cfg deleted file mode 100644 index 356d533c..00000000 --- a/build/OLD/Grid.cfg +++ /dev/null @@ -1,12 +0,0 @@ -### DEFAULT ### -# Default settings for NK model - -set K 10 # Level of epistasis in the NK model -set N 50 # Number of bits in each organisms (must be > K) -set SEED 0 # Random number seed (0 for based on time) -set POP_SIZE 1000 # Number of organisms in the popoulation. -set MAX_GENS 10000 # How many generations should we process? -set MUT_COUNT 0.05 # How many bit positions should be randomized? -set TOUR_SIZE 20 # How many organisms should be selected in each tour? -set NAME Result- # Name of file to print results to - diff --git a/build/OLD/MAP-Elites.cc b/build/OLD/MAP-Elites.cc deleted file mode 100644 index 4993c371..00000000 --- a/build/OLD/MAP-Elites.cc +++ /dev/null @@ -1,83 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2018. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the MAP-Elites selection scheme. -// -// In this example, we will be evolving 4-digit integers. -// The two traits measured are nunmber of bits and value mod 31. - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - -int main() -{ - constexpr size_t GENS = 1000; - - // Organisms are unsigned ints. - using org_t = uint64_t; - constexpr org_t MIN_ORG = 0; - constexpr org_t MAX_ORG = 8192; - constexpr org_t MAX_ORG_DIFF = MAX_ORG - MIN_ORG; - -std::cout << "START!" << std::endl; - - emp::Random random(1); - emp::World map_world(random); - -std::cout << "World build." << std::endl; - - // Fitness = value; trait 1 = num bits; trait 2 = value mod 31 - std::function fit_fun = [](org_t & val){ return (double) val; }; - std::function trait1_fun = [](org_t & val){ return (double) emp::count_bits(val); }; - std::function trait2_fun = [](org_t & val){ return (double) (val % 31); }; - - map_world.SetFitFun(fit_fun); - map_world.AddPhenotype("Num Bits", trait1_fun, 0, 14); - map_world.AddPhenotype("Mod 31", trait2_fun, 0, 31); - -std::cout << "Phenotype functions in place." << std::endl; - - emp::SetMapElites(map_world, {14,31}); - -std::cout << "Setup MAP-Elites" << std::endl; - - // Setup the print function to output the appropriate number of characters. - std::function print_fun = [](org_t & val, std::ostream & os) { - std::string out_str = emp::to_string(val); - while (out_str.size() < 4) out_str = emp::to_string('.', out_str); - os << out_str; - }; - map_world.SetPrintFun(print_fun); - - -std::cout << "Setup print functions." << std::endl; - - // Start off world with random organism. - map_world.Inject(random.GetUInt64(MAX_ORG_DIFF/4)); - map_world.PrintGrid(std::cout, "----"); - - for (size_t g = 0; g <= GENS; g++) { - for (size_t i = 0; i < map_world.GetSize(); ++i) { - size_t id = random.GetUInt(map_world.GetSize()); - if (map_world.IsOccupied(id)) { - org_t offspring = map_world[id] + random.GetUInt64(200) - 100; - if (offspring > MAX_ORG) continue; // Invalid mutation! No birth. - map_world.DoBirth(offspring , id); - } - } - if (g % 50 == 0) { - std::cout << "UD: " << g << std::endl; - map_world.PrintGrid(std::cout, "----"); - } - } - - - // std::cout << "Final Org Counts:\n"; - // map_world.PrintOrgCounts(); - std::cout << std::endl; -} diff --git a/build/OLD/Makefile b/build/OLD/Makefile deleted file mode 100644 index 2f91f803..00000000 --- a/build/OLD/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -EMP_DIR := ../../Empirical/source - -# Flags to use regardless of compiler -CFLAGS_all := -Wall -Wno-unused-function -I$(EMP_DIR)/ -CFLAGS_version := -std=c++17 - -# Emscripten compiler information -CXX_web := emcc -CXX_native := g++ - -OFLAGS_native_opt := -O3 -DNDEBUG -OFLAGS_native_debug := -g -pedantic -DEMP_TRACK_MEM -Wnon-virtual-dtor -Wcast-align -Woverloaded-virtual -OFLAGS_native_grumpy := -g -pedantic -DEMP_TRACK_MEM -Wnon-virtual-dtor -Wcast-align -Woverloaded-virtual -Wconversion -Weffc++ - -OFLAGS_web_opt := -Os -DNDEBUG -s TOTAL_MEMORY=67108864 -OFLAGS_web_debug := -g4 -pedantic -Wno-dollar-in-identifier-extension -s TOTAL_MEMORY=67108864 -s ASSERTIONS=2 -s DEMANGLE_SUPPORT=1 # -s SAFE_HEAP=1 - -CFLAGS_native_opt := $(CFLAGS_all) $(OFLAGS_native_opt) -CFLAGS_native_debug := $(CFLAGS_all) $(OFLAGS_native_debug) -CFLAGS_native_grumpy := $(CFLAGS_all) $(OFLAGS_native_grumpy) - -CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library $(EMP_DIR)/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 -CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library $(EMP_DIR)/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 -#CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library $(EMP_DIR)/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 - -TARGETS := AvidaGP-Evo AvidaGP-Mancala AvidaGP-Resource AvidaGP-StateGrid AvidaGP-Test DiagnosticNiches EvoSorter Fitness_Share_NK Grid NK MAP-Elites Pools Roulette ShrinkPop World World2 -#TARGETS := AvidaGP-Evo AvidaGP-Mancala AvidaGP-Test Fitness_Share_NK Grid NK Roulette Systematics World World2 - -EVO_DEPEND := $(EMP_DIR)/Evolve/World.h - -default: native - -CXX := $(CXX_native) -CFLAGS := $(CFLAGS_native_opt) - -debug: CFLAGS := $(CFLAGS_native_debug) -debug: all - -grumpy: CFLAGS := $(CFLAGS_native_grumpy) -grumpy: all - -web: CXX := $(CXX_web) -web: CFLAGS := $(CFLAGS_web_opt) -web: all - -web-debug: CXX := $(CXX_web) -web-debug: CFLAGS := $(CFLAGS_web_debug) -web-debug: all - -native: all - -all: $(TARGETS) - -$(TARGETS): % : %.cc $(EVO_DEPEND) - $(CXX) $(CFLAGS_version) $(CFLAGS) $< -o $@ - -$(JS_TARGETS): %.js : %.cc - $(CXX_web) $(CFLAGS_web) $< -o $@ - -debug-%: $*.cc $(EVO_DEPEND) - $(CXX) $(CFLAGS_version) $(CFLAGS_native_debug) $< -o $@ - -clean: - rm -rf debug-* *~ *.dSYM $(TARGETS) - rm -rf debug-* *~ *.dSYM $(JS_TARGETS) - -# Debugging information -#print-%: ; @echo $*=$($*) -print-%: ; @echo '$(subst ','\'',$*=$($*))' diff --git a/build/OLD/NK.cc b/build/OLD/NK.cc deleted file mode 100644 index 8ae70ffc..00000000 --- a/build/OLD/NK.cc +++ /dev/null @@ -1,106 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the template defined in evo::Population.h with an NK landscape. - -#include - -#include "config/ArgManager.h" -#include "Evolve/NK.h" -#include "Evolve/World.h" -#include "tools/BitVector.h" -#include "tools/Random.h" - -EMP_BUILD_CONFIG( NKConfig, - GROUP(DEFAULT, "Default settings for NK model"), - VALUE(K, uint32_t, 10, "Level of epistasis in the NK model"), - VALUE(N, uint32_t, 200, "Number of bits in each organisms (must be > K)"), ALIAS(GENOME_SIZE), - VALUE(SEED, int, 0, "Random number seed (0 for based on time)"), - VALUE(POP_SIZE, uint32_t, 1000, "Number of organisms in the popoulation."), - VALUE(MAX_GENS, uint32_t, 2000, "How many generations should we process?"), - VALUE(MUT_COUNT, uint32_t, 3, "How many bit positions should be randomized?"), ALIAS(NUM_MUTS), - VALUE(TEST, std::string, "TestString", "This is a test string.") -) - - -using BitOrg = emp::BitVector; - -int main(int argc, char* argv[]) -{ - NKConfig config; - config.Read("NK.cfg"); - - auto args = emp::cl::ArgManager(argc, argv); - if (args.ProcessConfigOptions(config, std::cout, "NK.cfg", "NK-macros.h") == false) exit(0); - if (args.TestUnknown() == false) exit(0); // If there are leftover args, throw an error. - - const uint32_t N = config.N(); - const uint32_t K = config.K(); - const uint32_t POP_SIZE = config.POP_SIZE(); - const uint32_t MAX_GENS = config.MAX_GENS(); - const uint32_t MUT_COUNT = config.MUT_COUNT(); - - emp::Random random(config.SEED()); - emp::NKLandscape landscape(N, K, random); - // emp::NKLandscapeMemo landscape(N, K, random); - - // emp::EAWorld pop(random, "NKWorld"); - emp::World pop(random, "NKWorld"); - pop.SetupFitnessFile().SetTimingRepeat(10); - pop.SetupSystematicsFile().SetTimingRepeat(10); - pop.SetupPopulationFile().SetTimingRepeat(10); - pop.SetPopStruct_Mixed(true); - pop.SetCache(); - - // Build a random initial population - for (uint32_t i = 0; i < POP_SIZE; i++) { - BitOrg next_org(N); - for (uint32_t j = 0; j < N; j++) next_org[j] = random.P(0.5); - pop.Inject(next_org); - } - - // Setup the mutation function. - std::function mut_fun = - [MUT_COUNT, N](BitOrg & org, emp::Random & random) { - size_t num_muts = 0; - for (uint32_t m = 0; m < MUT_COUNT; m++) { - const uint32_t pos = random.GetUInt(N); - if (random.P(0.5)) { - org[pos] ^= 1; - num_muts++; - } - } - return num_muts; - }; - pop.SetMutFun( mut_fun ); - pop.SetAutoMutate(); - - std::cout << 0 << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - std::function fit_fun = - [&landscape](BitOrg & org){ return landscape.GetFitness(org); }; - pop.SetFitFun( fit_fun ); - - // Loop through updates - for (uint32_t ud = 0; ud < MAX_GENS; ud++) { - // Print current state. - // for (uint32_t i = 0; i < pop.GetSize(); i++) std::cout << pop[i] << std::endl; - // std::cout << std::endl; - - // Keep the best individual. - emp::EliteSelect(pop, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(pop, 5, POP_SIZE-1); - pop.Update(); - std::cout << (ud+1) << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - } - - // pop.PrintLineage(0); - -// std::cout << MAX_GENS << " : " << pop[0] << " : " << landscape.GetFitness(pop[0]) << std::endl; - - // pop.GetSignalControl().PrintNames(); -} diff --git a/build/OLD/NK.cfg b/build/OLD/NK.cfg deleted file mode 100644 index 8832ca09..00000000 --- a/build/OLD/NK.cfg +++ /dev/null @@ -1,10 +0,0 @@ -### DEFAULT ### -# Default settings for NK model - -set K 10 # Level of epistasis in the NK model -set N 200 # Number of bits in each organisms (must be > K) -set SEED 1 # Random number seed (0 for based on time) -set POP_SIZE 1000 # Number of organisms in the popoulation. -set MAX_GENS 500 # How many generations should we process? -set MUT_COUNT 3 # How many bit positions should be randomized? -set MY_VAL 20 diff --git a/build/OLD/Pools.cc b/build/OLD/Pools.cc deleted file mode 100644 index 2d23e410..00000000 --- a/build/OLD/Pools.cc +++ /dev/null @@ -1,52 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2018. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the pool options for emp::World.h - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - -int main() -{ - constexpr size_t POP_SIZE = 3600; - constexpr size_t GENS = 10000; - const size_t POOL_SIZE = (size_t) std::sqrt(POP_SIZE); - const size_t NUM_POOLS = (size_t) std::sqrt(POP_SIZE); - - emp::Random random; - - std::function print_fun = [](int & val, std::ostream & os) { - char out_char = '+'; - val %= 63; - if (val < 10) out_char = (char) ('0' + val); - else if (val < 36) out_char = (char) ('a' + (val - 10)); - else if (val < 62) out_char = (char) ('A' + (val - 36)); - os << out_char; - }; - - emp::World pool_world(random); - emp::SetPools(pool_world, NUM_POOLS, POOL_SIZE); - pool_world.SetPrintFun(print_fun); - - for (size_t i = 0; i < POP_SIZE; i++) pool_world.InjectAt((int)i,i); - pool_world.PrintGrid(); - - for (size_t g = 0; g < GENS; g++) { - for (size_t i = 0; i < pool_world.GetSize(); ++i) { - size_t id = random.GetUInt(pool_world.GetSize()); - if (pool_world.IsOccupied(id)) pool_world.DoBirth(pool_world[id], id); - } - if (g % 1000 == 0) std::cout << "Generation: " << g << std::endl; - } - - std::cout << std::endl; - pool_world.PrintGrid(); - std::cout << "Final Org Counts:\n"; - pool_world.PrintOrgCounts(); - std::cout << std::endl; -} diff --git a/build/OLD/Roulette.cc b/build/OLD/Roulette.cc deleted file mode 100644 index 3f13d0b6..00000000 --- a/build/OLD/Roulette.cc +++ /dev/null @@ -1,50 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the RouletteSelect() function - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - -int main() -{ - constexpr size_t POP_SIZE = 400; - // constexpr size_t GENS = 10000; - - emp::Random random; - - std::function print_fun = [](int & val, std::ostream & os) { - val %= 63; - if (val < 10) os << (char) ('0' + val); - else if (val < 36) os << (char) ('a' + (val - 10)); - else if (val < 62) os << (char) ('A' + (val - 36)); - else os << '+'; - }; - - emp::World grid_world(random); - const size_t side = (size_t) std::sqrt(POP_SIZE); - grid_world.SetPopStruct_Grid(side, side); - grid_world.SetPrintFun(print_fun); - - emp_assert(grid_world.GetSize() == POP_SIZE); // POP_SIZE needs to be a perfect square. - - - grid_world.InjectAt(30, side+1); - grid_world.InjectAt(4, side*(side+1)/2); - grid_world.PrintGrid(); - - auto fit_fun = [](int & org){ return (double) org; }; - grid_world.SetFitFun(fit_fun); - RouletteSelect(grid_world, 500); - - std::cout << std::endl; - grid_world.PrintGrid(); - std::cout << "Final Org Counts:\n"; -// grid_world.PrintOrgCounts(print_fun); -// std::cout << std::endl; -} diff --git a/build/OLD/ShrinkPop.cc b/build/OLD/ShrinkPop.cc deleted file mode 100644 index 95279109..00000000 --- a/build/OLD/ShrinkPop.cc +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2016-2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the grid options for emp::World.h - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" -#include "tools/string_utils.h" - -int main() -{ - constexpr size_t POP_SIZE = 3600; - constexpr size_t GENS = 10000; - const size_t POP_SIDE = (size_t) std::sqrt(POP_SIZE); - - emp::Random random; - - std::function print_fun = [](int & val, std::ostream & os) { - char out_char = '+'; - val %= 63; - if (val < 10) out_char = (char) ('0' + val); - else if (val < 36) out_char = (char) ('a' + (val - 10)); - else if (val < 62) out_char = (char) ('A' + (val - 36)); - os << out_char; - }; - - emp::World grid_world(random); - grid_world.SetPopStruct_Grid(POP_SIDE, POP_SIDE); - grid_world.SetPrintFun(print_fun); - - for (size_t i = 0; i < POP_SIZE; i++) grid_world.InjectAt((int)i,i); - - // What does the grid look like after inject? - std::cout << "BEFORE SerialTransfer(0.01):" << std::endl; - grid_world.PrintGrid(); - - - grid_world.SerialTransfer(0.01); -// for (size_t g = 0; g < GENS; g++) { -// for (size_t i = 0; i < grid_world.GetSize(); ++i) { -// size_t id = random.GetUInt(grid_world.GetSize()); -// if (grid_world.IsOccupied(id)) grid_world.DoBirth(grid_world[id], id); -// } -// if (g % 1000 == 0) std::cout << "Generation: " << g << std::endl; -// } - - std::cout << std::endl; - std::cout << "AFTER SerialTransfer(0.01):" << std::endl; - grid_world.PrintGrid(); - std::cout << "Final Grid Org Counts:\n"; - grid_world.PrintOrgCounts(); - std::cout << std::endl; - - - - - - emp::World mass_world(random); - grid_world.SetPopStruct_Mixed(); - grid_world.SetPrintFun(print_fun); - - for (size_t i = 0; i < POP_SIZE; i++) grid_world.InjectAt((int)i%10,i); - - // What does the grid look like after inject? - std::cout << "Mass action, BEFORE Bottlneck(20):" << std::endl; - grid_world.PrintOrgCounts(); - - grid_world.DoBottleneck(20); - - std::cout << std::endl; - std::cout << "Mass action, AFTER Bottleneck" << std::endl; - grid_world.PrintOrgCounts(); - std::cout << std::endl; -} diff --git a/build/OLD/Systematics.cc b/build/OLD/Systematics.cc deleted file mode 100644 index 649c38bb..00000000 --- a/build/OLD/Systematics.cc +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file explores the grid options for emp::World.h - -#include - -#include "Evolve/Systematics.h" - -int main() -{ - emp::Systematics sys(true, true, true); - - std::cout << "\nAddOrg 25 (id1, no parent)\n"; - auto id1 = sys.AddOrg(25); - std::cout << "\nAddOrg -10 (id2; parent id1)\n"; - auto id2 = sys.AddOrg(-10, id1); - std::cout << "\nAddOrg 25 (id3; parent id1)\n"; - auto id3 = sys.AddOrg(25, id1); - std::cout << "\nAddOrg 25 (id4; parent id2)\n"; - auto id4 = sys.AddOrg(25, id2); - std::cout << "\nRemoveOrg (id2)\n"; - sys.RemoveOrg(id2); - std::cout << "\nRemoveOrg (id4)\n"; - sys.RemoveOrg(id4); - - std::cout << "id1 = " << id1 << std::endl; - std::cout << "id2 = " << id2 << std::endl; - std::cout << "id3 = " << id3 << std::endl; - std::cout << "id4 = " << id4 << std::endl; - - std::cout << "\nLineage:\n"; - sys.PrintLineage(id4); - sys.PrintStatus(); -} diff --git a/build/OLD/World.cc b/build/OLD/World.cc deleted file mode 100644 index 2f6131e4..00000000 --- a/build/OLD/World.cc +++ /dev/null @@ -1,92 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file is an example for using the re-vamped World template. - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" - -struct TestOrg1 { - int fitness; - - TestOrg1() : fitness(0) { ; } - TestOrg1(int f) : fitness(f) { ; } - double GetFitness() const { return (double) fitness; } - bool DoMutate(emp::Random&) { return false; } - - bool operator==(const TestOrg1 & in) const { return fitness == in.fitness; } - bool operator!=(const TestOrg1 & in) const { return fitness != in.fitness; } -}; - -int main() { - emp::World world; - world.SetPopStruct_Mixed(true); - - world.SetFitFun([](int & i){ return (double) i; }); - for (int i = 0; i < 100; i++) { - world.Inject(i+100,2); - } - - std::cout << "Start Size = " << world.GetSize() << std::endl << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - EliteSelect(world, 10, 10); - - std::cout << "\nElite Select(10,10)\n" << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - TournamentSelect(world, 5, 100); - world.Update(); - std::cout << "\nPost-Tourney Size = " << world.GetSize() << std::endl << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - EliteSelect(world, 10, 10); - world.Update(); - std::cout << "\nPost-Elite Size = " << world.GetSize() << std::endl << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl << std::endl; - - - emp::World ea_world; - ea_world.SetPopStruct_Mixed(true); - for (int i = 0; i < 100; i++) ea_world.Inject(i+200); - - std::cout << "\nStart Size = " << ea_world.GetSize() << std::endl; - for (size_t i = 0; i < ea_world.GetSize(); i++) std::cout << ea_world[i].GetFitness() << " "; - std::cout << std::endl; - - TournamentSelect(ea_world, 5, 100); - ea_world.Update(); - std::cout << "\nPost-Tourney Size = " << ea_world.GetSize() << std::endl; - for (size_t i = 0; i < ea_world.GetSize(); i++) std::cout << ea_world[i].GetFitness() << " "; - std::cout << std::endl; - - EliteSelect(ea_world, 10, 10); - ea_world.Update(); - std::cout << "Post-Elite Size = " << ea_world.GetSize() << std::endl; - for (size_t i = 0; i < ea_world.GetSize(); i++) std::cout << ea_world[i].GetFitness() << " "; - std::cout << std::endl << std::endl; - - // Test grid Populations - emp::Random random; - emp::World grid_world(random); - grid_world.SetPopStruct_Grid(10,10); - for (int i = 0; i < 10; i++) grid_world.Inject(i); - grid_world.PrintGrid(); - - for (size_t i = 0; i < grid_world.GetSize(); ++i) { - size_t id = random.GetUInt(grid_world.GetSize()); - if (grid_world.IsOccupied(id)) grid_world.DoBirth(grid_world[id], id); - } - std::cout << std::endl; - grid_world.PrintGrid(); - std::cout << "Num orgs=" << grid_world.GetNumOrgs() << std::endl; - std::cout << std::endl; -} diff --git a/build/OLD/World2.cc b/build/OLD/World2.cc deleted file mode 100644 index 9f605288..00000000 --- a/build/OLD/World2.cc +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of Empirical, https://github.com/devosoft/Empirical -// Copyright (C) Michigan State University, 2017. -// Released under the MIT Software license; see doc/LICENSE -// -// -// This file is an example for using the re-vamped World template. - -#ifndef EMP_TRACK_MEM -#define EMP_TRACK_MEM -#endif - -#include - -#include "Evolve/World.h" -#include "tools/Random.h" - -int main() { - emp::World world; - world.SetPopStruct_Mixed(true); - - // Inject from 100 to 199. - for (int i = 0; i < 100; i++) { world.Inject(i+200); } - - std::cout << "Start Size = " << world.GetSize() << std::endl << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - std::cout << "Num Ptr = " << emp::Ptr::DebugInfo().current - << " (total = " << emp::Ptr::DebugInfo().total << ")" - << std::endl; - std::cout << "Num Ptr> = " << emp::Ptr>::DebugInfo().current - << " (total = " << emp::Ptr>::DebugInfo().total << ")" - << std::endl; - - for (size_t UD = 0; UD < 2; UD++) { - - std::cout << "UD = " << UD << std::endl; - - EliteSelect(world, 50, 4); - - std::cout << "Post EliteSelect(50,4)" << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - std::cout << "Num Ptr = " << emp::Ptr::DebugInfo().current - << " (total = " << emp::Ptr::DebugInfo().total << ")" - << std::endl; - std::cout << "Num Ptr> = " << emp::Ptr>::DebugInfo().current - << " (total = " << emp::Ptr>::DebugInfo().total << ")" - << std::endl; - - world.Update(); - - std::cout << "Post Update()" << std::endl; - for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - std::cout << std::endl; - - std::cout << "Num Ptr = " << emp::Ptr::DebugInfo().current - << " (total = " << emp::Ptr::DebugInfo().total << ")" - << std::endl; - std::cout << "Num Ptr> = " << emp::Ptr>::DebugInfo().current - << " (total = " << emp::Ptr>::DebugInfo().total << ")" - << std::endl; - } - - // world.TournamentSelect(5, 100); - // world.Update(); - // std::cout << "\nPost-Tourney Size = " << world.GetSize() << std::endl << std::endl; - // for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - // std::cout << std::endl; - // - // EliteSelect(world, 10, 10); - // world.Update(); - // std::cout << "\nPost-Elite Size = " << world.GetSize() << std::endl << std::endl; - // for (size_t i = 0; i < world.GetSize(); i++) std::cout << world[i] << " "; - // std::cout << std::endl << std::endl; - -} From cc5266527e207f950e57131217d9ddf1ac622e10 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 6 Oct 2021 15:22:12 -0400 Subject: [PATCH 177/445] Remove old docs --- docs/BuildMain.md | 14 -------------- docs/BuildModule.md | 32 -------------------------------- docs/CoreOverview.md | 32 -------------------------------- 3 files changed, 78 deletions(-) delete mode 100644 docs/BuildMain.md delete mode 100644 docs/BuildModule.md delete mode 100644 docs/CoreOverview.md diff --git a/docs/BuildMain.md b/docs/BuildMain.md deleted file mode 100644 index be9e8d4f..00000000 --- a/docs/BuildMain.md +++ /dev/null @@ -1,14 +0,0 @@ -# How to build your own MABE executable - -## Overview - -## Including MABE Modules - -### What modules are available? -* Which types of modules are requires? -* What order should the modules go in? -* How does a custom main function relate to the standard MABE config? - -## Launching a mabe object. - -## Dealing with extra configuration files \ No newline at end of file diff --git a/docs/BuildModule.md b/docs/BuildModule.md deleted file mode 100644 index 87d1cb9a..00000000 --- a/docs/BuildModule.md +++ /dev/null @@ -1,32 +0,0 @@ -#How to build a MABE module - -## Basic Structure of a module - -## Building your constructor - -## The SetupConfig() and SetupModule() functions - -## Functions to trigger on events - -## Functions to respond to requests - -## Making your new module available for configuration. - -* Include the macro to setup the module -* Add the file to modules.h -* Place the module in an example .gen and .mabe pair of files. - -## Extras needed for official modules - -The rules above all assume that you are trying to build a MABE module that will -be functional for your own needs and for you to share with others. If you are -trying to build a module that you intend to be shipped with the core MABE -distribution, there are a few other things that you will need to do. - -1. The heading at the top of your file must be done in doxygen style, with a - release under the MIT Software license and an @brief description of the - module. - -1. The include guard should begin with MABE_* - -1. The module should be placed in the "mabe" namespace. diff --git a/docs/CoreOverview.md b/docs/CoreOverview.md deleted file mode 100644 index 2b9594d6..00000000 --- a/docs/CoreOverview.md +++ /dev/null @@ -1,32 +0,0 @@ -# An Overview of the MABE Core - -This file provides a high-level overview of the core files in MABE and how they all fit together. - -## The MABE object - -At the higest level a mabe run is managed by a MABE object, instances of which are typically -referred to as "control" in other objects. - -## Internal Tools - -### General Organisms -### "Empty" Organisms -### Populations -### Collections -### Traits - -## Modules - -### Evaluation Modules -### Selection Modules -### Placement Modules -### Interface Modules -### Analysis Modules -### Other Schema Modules -### Organism Managers - -## Configuration files - -## Organisms - -## Traits \ No newline at end of file From fa36137116e6fe6bbb2278cbd5dc1e4d02169ee0 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 6 Oct 2021 15:49:22 -0400 Subject: [PATCH 178/445] Remove messed up RR file --- source/evaluate/static/EvalRoyalRoad.hpp | 193 ----------------------- 1 file changed, 193 deletions(-) delete mode 100644 source/evaluate/static/EvalRoyalRoad.hpp diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp deleted file mode 100644 index 8681430c..00000000 --- a/source/evaluate/static/EvalRoyalRoad.hpp +++ /dev/null @@ -1,193 +0,0 @@ -/** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. - * - * @file EvalRoyalRoad.hpp -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> cleanup - * @brief MABE Evaluation module for evaluating the royal road problem. - * - * In royal road, the number of 1s from the beginning of a bitstring are counted, but only - * in groups of B (brick size). -<<<<<<< HEAD -======= - * @brief MABE Evaluation module for counting the number of ones (or zeros) in an output. ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= ->>>>>>> cleanup - */ - -#ifndef MABE_EVAL_ROYAL_ROAD_H -#define MABE_EVAL_ROYAL_ROAD_H - -#include "../../core/MABE.hpp" -#include "../../core/Module.hpp" - -#include "emp/datastructs/reference_vector.hpp" - -namespace mabe { - - class EvalRoyalRoad : public Module { - private: - Collection target_collect; - - std::string bits_trait; - std::string fitness_trait; - -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> cleanup - size_t brick_size = 8; - double extra_bit_cost = 0.5; - - public: - EvalRoyalRoad(mabe::MABE & control, - const std::string & name="EvalRoyalRoad", -<<<<<<< HEAD - const std::string & desc="Evaluate bitstrings by counting number of bricks (or zeros).") -======= - size_t brick_size = 8; - - public: - EvalRoyalRoad(mabe::MABE & control, - const std::string & name="EvalRoyalRoad", - const std::string & desc="Evaluate bitstrings using the Royal Road" - ) ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= - const std::string & desc="Evaluate bitstrings by counting ones (or zeros).") ->>>>>>> cleanup - : Module(control, name, desc) - , target_collect(control.GetPopulation(0)) - , bits_trait("bits") - , fitness_trait("fitness") - { - SetEvaluateMod(true); - } - ~EvalRoyalRoad() { } - - void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); - LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); -<<<<<<< HEAD -<<<<<<< HEAD - LinkVar(fitness_trait, "fitness_trait", "Which trait should we store Royal Road fitness in?"); - LinkVar(brick_size, "brick_size", "Number of ones to have a whole brick in the road."); - LinkVar(extra_bit_cost, "extra_bit_cost", "Penalty per-bit for extra-long roads."); -======= - LinkVar(fitness_trait, "fitness_trait", "Which trait should we store the fitness in?"); - LinkVar(brick_size, "brick_size", "Size of brick that we are using to build the road"); ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= - LinkVar(fitness_trait, "fitness_trait", "Which trait should we store Royal Road fitness in?"); - LinkVar(brick_size, "brick_size", "Number of ones to have a whole brick in the road."); - LinkVar(extra_bit_cost, "extra_bit_cost", "Penalty per-bit for extra-long roads."); ->>>>>>> cleanup - } - - void SetupModule() override { - AddRequiredTrait(bits_trait); -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> cleanup - AddOwnedTrait(fitness_trait, "Royal Road fitness value", 0.0); - } - - void OnUpdate(size_t /* update */) override { - // Loop through the population and evaluate each organism. - double max_fitness = 0.0; - mabe::Collection alive_collect = target_collect.GetAlive(); -<<<<<<< HEAD -======= - AddOwnedTrait(fitness_trait, "Royal Road Fitness Value", 0.0); - } - - void OnUpdate(size_t /* update */) override { - - // Loop through the population and evaluate each organism. - double max_fitness = 0.0; - mabe::Collection alive_collect( target_collect.GetAlive() ); ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= ->>>>>>> cleanup - for (Organism & org : alive_collect) { - // Make sure this organism has its bit sequence ready for us to access. - org.GenerateOutput(); - -<<<<<<< HEAD -<<<<<<< HEAD - // Count the number of ones in the bit sequence. - const emp::BitVector & bits = org.GetVar(bits_trait); -======= - // Count the number of ones in the bit sequence. - const emp::BitVector & bits = org.GetTrait(bits_trait); ->>>>>>> cleanup - int road_length = 0.0; - for (size_t i = 0; i < bits.size(); i++) { - if (bits[i] == 0) break; - road_length++; - } - - const int overage = road_length % brick_size; - - // Store the count on the organism in the fitness trait. - double fitness = road_length - overage * (extra_bit_cost + 1.0); -<<<<<<< HEAD -======= - const emp::BitVector & bits = org.GetVar(bits_trait); - - // size of successfull road built - int road_length = 0; - // Make the brick road - for (size_t i = 0; i < bits.size(); i++) { - if (bits[i] == 0) break; - road_length++; - - } - - // Count number of bits in an incomplete brick - const int bits_of_incomplete_brick = road_length % brick_size; - - // Fitness is the length of full bricks in the "road" - double fitness = road_length - bits_of_incomplete_brick; - - // Store the count on the organism in the fitness trait. ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f - org.SetVar(fitness_trait, fitness); -======= - org.SetTrait(fitness_trait, fitness); ->>>>>>> cleanup - - if (fitness > max_fitness) { - max_fitness = fitness; - } -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= ->>>>>>> cleanup - } - - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; - } - }; - - MABE_REGISTER_MODULE(EvalRoyalRoad, "Evaluate bitstrings by counting ones (or zeros)."); -} - -<<<<<<< HEAD -<<<<<<< HEAD -#endif -======= -#endif ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f -======= -#endif ->>>>>>> cleanup From 6da4849eaa6866299d0a9c5009a241f586a5fbe0 Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Wed, 6 Oct 2021 15:51:02 -0400 Subject: [PATCH 179/445] Fix RR --- source/evaluate/static/EvalRoyalRoad.hpp | 93 ++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 source/evaluate/static/EvalRoyalRoad.hpp diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp new file mode 100644 index 00000000..69233b70 --- /dev/null +++ b/source/evaluate/static/EvalRoyalRoad.hpp @@ -0,0 +1,93 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file EvalRoyalRoad.hpp + * @brief MABE Evaluation module for evaluating the royal road problem. + * + * In royal road, the number of 1s from the beginning of a bitstring are counted, but only + * in groups of B (brick size). + */ + +#ifndef MABE_EVAL_ROYAL_ROAD_H +#define MABE_EVAL_ROYAL_ROAD_H + +#include "../../core/MABE.hpp" +#include "../../core/Module.hpp" + +#include "emp/datastructs/reference_vector.hpp" + +namespace mabe { + + class EvalRoyalRoad : public Module { + private: + Collection target_collect; + + std::string bits_trait; + std::string fitness_trait; + + size_t brick_size = 8; + double extra_bit_cost = 0.5; + + public: + EvalRoyalRoad(mabe::MABE & control, + const std::string & name="EvalRoyalRoad", + const std::string & desc="Evaluate bitstrings by counting ones (or zeros).") + : Module(control, name, desc) + , target_collect(control.GetPopulation(0)) + , bits_trait("bits") + , fitness_trait("fitness") + { + SetEvaluateMod(true); + } + ~EvalRoyalRoad() { } + + void SetupConfig() override { + LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); + LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); + LinkVar(fitness_trait, "fitness_trait", "Which trait should we store Royal Road fitness in?"); + LinkVar(brick_size, "brick_size", "Number of ones to have a whole brick in the road."); + LinkVar(extra_bit_cost, "extra_bit_cost", "Penalty per-bit for extra-long roads."); + } + + void SetupModule() override { + AddRequiredTrait(bits_trait); + AddOwnedTrait(fitness_trait, "Royal Road fitness value", 0.0); + } + + void OnUpdate(size_t /* update */) override { + // Loop through the population and evaluate each organism. + double max_fitness = 0.0; + mabe::Collection alive_collect = target_collect.GetAlive(); + for (Organism & org : alive_collect) { + // Make sure this organism has its bit sequence ready for us to access. + org.GenerateOutput(); + + // Count the number of ones in the bit sequence. + const emp::BitVector & bits = org.GetTrait(bits_trait); + int road_length = 0.0; + for (size_t i = 0; i < bits.size(); i++) { + if (bits[i] == 0) break; + road_length++; + } + + const int overage = road_length % brick_size; + + // Store the count on the organism in the fitness trait. + double fitness = road_length - overage * (extra_bit_cost + 1.0); + org.SetTrait(fitness_trait, fitness); + + if (fitness > max_fitness) { + max_fitness = fitness; + } + } + + std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; + } + }; + + MABE_REGISTER_MODULE(EvalRoyalRoad, "Evaluate bitstrings by counting ones (or zeros)."); +} + +#endif From e79d3a0f959101e521be7e6752133bd018d5ee69 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:41:39 -0400 Subject: [PATCH 180/445] Renamed ConfigFunction to more accurate ConfigEntry_Function. --- ...igFunction.hpp => ConfigEntry_Function.hpp} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename source/config/{ConfigFunction.hpp => ConfigEntry_Function.hpp} (92%) diff --git a/source/config/ConfigFunction.hpp b/source/config/ConfigEntry_Function.hpp similarity index 92% rename from source/config/ConfigFunction.hpp rename to source/config/ConfigEntry_Function.hpp index 91892d4d..68414770 100644 --- a/source/config/ConfigFunction.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -1,9 +1,9 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * - * @file ConfigFunction.hpp + * @file ConfigEntry_Function.hpp * @brief Manages individual functions for config. * @note Status: ALPHA */ @@ -22,9 +22,9 @@ namespace mabe { - class ConfigFunction : public ConfigEntry { + class ConfigEntry_Function : public ConfigEntry { private: - using this_t = ConfigFunction; + using this_t = ConfigEntry_Function; using entry_ptr_t = emp::Ptr; using entry_vector_t = emp::vector; using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; @@ -34,19 +34,19 @@ namespace mabe { // size_t arg_count; public: - ConfigFunction(const std::string & _name, + ConfigEntry_Function(const std::string & _name, const std::string & _desc, - emp::Ptr _scope) + emp::Ptr _scope) : ConfigEntry(_name, _desc, _scope) { ; } template - ConfigFunction(const std::string & _name, + ConfigEntry_Function(const std::string & _name, std::function _fun, const std::string & _desc, - emp::Ptr _scope) + emp::Ptr _scope) : ConfigEntry(_name, _desc, _scope) { SetFunction(_fun); } - ConfigFunction(const ConfigFunction &) = default; + ConfigEntry_Function(const ConfigEntry_Function &) = default; emp::Ptr Clone() const override { return emp::NewPtr(*this); } From bb4fa5b2cd0ea37536dcc89ad0744aee94a60648 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:42:26 -0400 Subject: [PATCH 181/445] Renamed ConfigScope to more accurate ConfigEntry_Scope. --- ...{ConfigScope.hpp => ConfigEntry_Scope.hpp} | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) rename source/config/{ConfigScope.hpp => ConfigEntry_Scope.hpp} (89%) diff --git a/source/config/ConfigScope.hpp b/source/config/ConfigEntry_Scope.hpp similarity index 89% rename from source/config/ConfigScope.hpp rename to source/config/ConfigEntry_Scope.hpp index d4965478..ab6e791e 100644 --- a/source/config/ConfigScope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -3,7 +3,7 @@ * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigScope.hpp + * @file ConfigEntry_Scope.hpp * @brief Manages a full scope with many conig entries (or sub-scopes). * @note Status: ALPHA * @@ -18,12 +18,12 @@ #include "emp/base/map.hpp" #include "ConfigEntry.hpp" -#include "ConfigFunction.hpp" +#include "ConfigEntry_Function.hpp" namespace mabe { // Set of multiple config entries. - class ConfigScope : public ConfigEntry { + class ConfigEntry_Scope : public ConfigEntry { protected: using entry_ptr_t = emp::Ptr; emp::vector< entry_ptr_t > entry_list; ///< Entries in order. @@ -53,12 +53,12 @@ namespace mabe { return *new_ptr; } public: - ConfigScope(const std::string & _name, + ConfigEntry_Scope(const std::string & _name, const std::string & _desc, - emp::Ptr _scope, + emp::Ptr _scope, const std::string & _type="") : ConfigEntry(_name, _desc, _scope), type(_type) { } - ConfigScope(const ConfigScope & in) : ConfigEntry(in) { + ConfigEntry_Scope(const ConfigEntry_Scope & in) : ConfigEntry(in) { // Copy all defined variables/scopes/functions for (const auto & x : in.entry_list) { auto new_ptr = x->Clone(); @@ -72,9 +72,9 @@ namespace mabe { entry_map[x->GetName()] = new_ptr; } } - ConfigScope(ConfigScope &&) = default; + ConfigEntry_Scope(ConfigEntry_Scope &&) = default; - ~ConfigScope() { + ~ConfigEntry_Scope() { // Clear up all entries and built-ins. for (auto & x : entry_list) { x.Delete(); } for (auto & x : builtin_list) { x.Delete(); } @@ -86,7 +86,7 @@ namespace mabe { bool IsLocal() const override { return true; } // @CAO, for now assuming all scopes are local! /// Set this entry to be a correctly-types scope pointer. - emp::Ptr AsScopePtr() override { return this; } + emp::Ptr AsScopePtr() override { return this; } /// Get an entry out of this scope; entry_ptr_t GetEntry(std::string in_name) { @@ -164,24 +164,24 @@ namespace mabe { } /// Add a new scope inside of this one. - ConfigScope & AddScope(const std::string & name, const std::string & desc, const std::string & type="") { - return Add(name, desc, this, type); + ConfigEntry_Scope & AddScope(const std::string & name, const std::string & desc, const std::string & type="") { + return Add(name, desc, this, type); } /// Add a new user-defined function. template - ConfigFunction & AddFunction(const std::string & name, + ConfigEntry_Function & AddFunction(const std::string & name, std::function fun, const std::string & desc) { - return Add(name, fun, desc, this); + return Add(name, fun, desc, this); } /// Add a new function that is a standard part of the scripting language. template - ConfigFunction & AddBuiltinFunction(const std::string & name, + ConfigEntry_Function & AddBuiltinFunction(const std::string & name, std::function fun, const std::string & desc) { - return AddBuiltin(name, fun, desc, this); + return AddBuiltin(name, fun, desc, this); } /// Write out all of the parameters contained in this scope to the provided stream. @@ -225,7 +225,7 @@ namespace mabe { } /// Make a copy of this scope and all of the entries inside it. - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } + entry_ptr_t Clone() const override { return emp::NewPtr(*this); } }; } From b0c1512359eb45d7a2cd617049454914f53443e5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:43:23 -0400 Subject: [PATCH 182/445] Added more details for setting up member functions; moved ConfigFunction and ConfigScope to new names. --- source/config/Config.hpp | 82 +++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index a068dcbf..1f04ada9 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -76,9 +76,9 @@ #include "ConfigAST.hpp" #include "ConfigEvents.hpp" -#include "ConfigFunction.hpp" +#include "ConfigEntry_Function.hpp" #include "ConfigLexer.hpp" -#include "ConfigScope.hpp" +#include "ConfigEntry_Scope.hpp" #include "ConfigType.hpp" namespace mabe { @@ -138,18 +138,40 @@ namespace mabe { static_assert(params_ok, "Parameters 2+ in a member function must be string or arithmetic."); // ----- Transform this function into one that TypeInfo can make use of ---- - // @CAO CONTINUE HERE! + member_fun_t member_fun = + [name,fun](ConfigType & obj, const emp::vector & args) { + // Make sure we can convert the obj into the correct type. + emp::Ptr typed_ptr = dynamic_cast(&obj); + + // Make sure we have the correct number of arguments. + if (args.size() != sizeof...(PARAM_Ts)) { + std::cerr << "Error in call to function '" << name + << "'; expected " << sizeof...(PARAM_Ts) + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above error. + + // Call the provided function and return the result. + int arg_id = 0; + RETURN_T result = fun( *typed_ptr, args[arg_id++]->As()... ); + + return result; + }; + + // Add this member function to the library we are building. + member_funs[name] = member_fun; } }; using pos_t = emp::TokenStream::Iterator; protected: - std::string filename; ///< Source for for code to generate. - ConfigLexer lexer; ///< Lexer to process input code. - ConfigScope root_scope; ///< All variables from the root level. - ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = false; ///< Should we print full debug information? + std::string filename; ///< Source for for code to generate. + ConfigLexer lexer; ///< Lexer to process input code. + ConfigEntry_Scope root_scope; ///< All variables from the root level. + ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. + bool debug = false; ///< Should we print full debug information? /// A map of names to event groups. @@ -233,12 +255,12 @@ namespace mabe { /// If create_ok is true, create any variables that we don't find. Otherwise continue the /// search for them in successively outer (lower) scopes. [[nodiscard]] emp::Ptr ParseVar(pos_t & pos, - ConfigScope & cur_scope, + ConfigEntry_Scope & cur_scope, bool create_ok=false, bool scan_scopes=true); /// Load a value from the provided scope, which can come from a variable or a literal. - [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, ConfigScope & cur_scope); + [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, ConfigEntry_Scope & cur_scope); /// Calculate the result of the provided operation on two computed entries. [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, @@ -246,20 +268,21 @@ namespace mabe { emp::Ptr value2); /// Calculate a full expression found in a token sequence, using the provided scope. - [[nodiscard]] emp::Ptr ParseExpression(pos_t & pos, ConfigScope & cur_scope, size_t prec_limit=1000); + [[nodiscard]] emp::Ptr + ParseExpression(pos_t & pos, ConfigEntry_Scope & cur_scope, size_t prec_limit=1000); /// Parse the declaration of a variable and return the newly created ConfigEntry - ConfigEntry & ParseDeclaration(pos_t & pos, ConfigScope & scope); + ConfigEntry & ParseDeclaration(pos_t & pos, ConfigEntry_Scope & scope); /// Parse an event description. - emp::Ptr ParseEvent(pos_t & pos, ConfigScope & scope); + emp::Ptr ParseEvent(pos_t & pos, ConfigEntry_Scope & scope); /// Parse the next input in the specified Struct. A statement can be a variable declaration, /// an expression, or an event. - [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, ConfigScope & scope); + [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, ConfigEntry_Scope & scope); /// Keep parsing statements until there aren't any more or we leave this scope. - [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigScope & scope) { + [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigEntry_Scope & scope) { Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); auto cur_block = emp::NewPtr(scope); while (pos.IsValid() && AsChar(pos) != '}') { @@ -467,8 +490,8 @@ namespace mabe { root_scope.AddBuiltinFunction(name, fun, desc); } - ConfigScope & GetRootScope() { return root_scope; } - const ConfigScope & GetRootScope() const { return root_scope; } + ConfigEntry_Scope & GetRootScope() { return root_scope; } + const ConfigEntry_Scope & GetRootScope() const { return root_scope; } // Load a single, specified configuration file. void Load(const std::string & filename) { @@ -508,7 +531,7 @@ namespace mabe { } // Load the provided statement and run it. - std::string Eval(std::string_view statement, emp::Ptr scope=nullptr) { + std::string Eval(std::string_view statement, emp::Ptr scope=nullptr) { Debug("Running Eval()"); if (!scope) scope = &root_scope; // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. @@ -549,7 +572,7 @@ namespace mabe { // Load a variable name from the provided scope. emp::Ptr Config::ParseVar(pos_t & pos, - ConfigScope & cur_scope, + ConfigEntry_Scope & cur_scope, bool create_ok, bool scan_scopes) { Debug("Running ParseVar(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); @@ -558,7 +581,7 @@ namespace mabe { if (IsDots(pos)) { scan_scopes = false; // One or more initial dots specify scope; don't scan! size_t num_dots = GetSize(pos); // Extra dots shift scope. - emp::Ptr scope_ptr = &cur_scope; + emp::Ptr scope_ptr = &cur_scope; while (num_dots-- > 1) { scope_ptr = scope_ptr->GetScope(); if (scope_ptr.IsNull()) Error(pos, "Too many dots; goes beyond global scope."); @@ -602,7 +625,7 @@ namespace mabe { } // Load a value from the provided scope, which can come from a variable or a literal. - emp::Ptr Config::ParseValue(pos_t & pos, ConfigScope & cur_scope) { + emp::Ptr Config::ParseValue(pos_t & pos, ConfigEntry_Scope & cur_scope) { Debug("Running ParseValue(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ")"); // Anything that begins with an identifier or dots must represent a variable. Refer! @@ -733,7 +756,11 @@ namespace mabe { // Calculate an expression in the provided scope. - emp::Ptr Config::ParseExpression(pos_t & pos, ConfigScope & scope, size_t prec_limit) { + emp::Ptr Config::ParseExpression( + pos_t & pos, + ConfigEntry_Scope & scope, + size_t prec_limit + ) { Debug("Running ParseExpression(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); // @CAO Should test for unary operators at the beginning of an expression. @@ -754,6 +781,9 @@ namespace mabe { ++pos; // Move on to the next argument. } RequireChar(')', pos++, "Expected a ')' to end function call."); + + // cur_node should have evaluated itself to a function; a Call node will link that + // function with its arguments, run it, and return the result. cur_node = emp::NewPtr(cur_node, args); } @@ -772,7 +802,7 @@ namespace mabe { } // Parse an the declaration of a variable. - ConfigEntry & Config::ParseDeclaration(pos_t & pos, ConfigScope & scope) { + ConfigEntry & Config::ParseDeclaration(pos_t & pos, ConfigEntry_Scope & scope) { std::string type_name = AsLexeme(pos++); RequireID(pos, "Type name '", type_name, "' must be followed by variable to declare."); std::string var_name = AsLexeme(pos++); @@ -789,7 +819,7 @@ namespace mabe { // Otherwise we have a module to add; treat it as a struct. Debug("Building var '", var_name, "' of type '", type_name, "'"); - ConfigScope & new_scope = scope.AddScope(var_name, type_map[type_name]->desc, type_name); + ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_map[type_name]->desc, type_name); ConfigType & new_obj = type_map[type_name]->init_fun(var_name); new_obj.SetupScope(new_scope); new_obj.LinkVar(new_obj._active, "_active", "Should we activate this module? (0=off, 1=on)", true); @@ -800,7 +830,7 @@ namespace mabe { } // Parse an event description. - emp::Ptr Config::ParseEvent(pos_t & pos, ConfigScope & scope) { + emp::Ptr Config::ParseEvent(pos_t & pos, ConfigEntry_Scope & scope) { RequireChar('@', pos++, "All event declarations must being with an '@'."); RequireID(pos, "Events must start by specifying event name."); const std::string & event_name = AsLexeme(pos++); @@ -829,7 +859,7 @@ namespace mabe { } // Process the next input in the specified Struct. - emp::Ptr Config::ParseStatement(pos_t & pos, ConfigScope & scope) { + emp::Ptr Config::ParseStatement(pos_t & pos, ConfigEntry_Scope & scope) { Debug("Running ParseStatement(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); // Allow a statement with an empty line. From a3767ef1afa074cb585d681161c80717191de727 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:43:54 -0400 Subject: [PATCH 183/445] Updated to ConfigEntry_Scope in ConfigAST. --- source/config/ConfigAST.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index b0fcbfad..adcf554c 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -16,7 +16,7 @@ #include "emp/base/vector.hpp" #include "ConfigEntry.hpp" -#include "ConfigScope.hpp" +#include "ConfigEntry_Scope.hpp" namespace mabe { @@ -62,7 +62,7 @@ namespace mabe { virtual node_ptr_t GetChild(size_t /* id */) { emp_assert(false); return nullptr; } node_ptr_t GetParent() { return parent; } void SetParent(node_ptr_t in_parent) { parent = in_parent; } - virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } + virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } virtual entry_ptr_t Process() = 0; @@ -134,12 +134,12 @@ namespace mabe { class ASTNode_Block : public ASTNode_Internal { protected: - emp::Ptr scope_ptr; + emp::Ptr scope_ptr; public: - ASTNode_Block(ConfigScope & in_scope) : scope_ptr(&in_scope) { } + ASTNode_Block(ConfigEntry_Scope & in_scope) : scope_ptr(&in_scope) { } - emp::Ptr GetScope() override { return scope_ptr; } + emp::Ptr GetScope() override { return scope_ptr; } entry_ptr_t Process() override { for (auto node : children) { From 07fbb62261b98036ad63cdffc2f104294b93cb2f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:44:35 -0400 Subject: [PATCH 184/445] Updated to ConfigEntry_Scope and improved comments in ConfigEntry. --- source/config/ConfigEntry.hpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index e5be1c09..b8d9f82b 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -7,6 +7,9 @@ * @brief Manages a single configuration entry (e.g., variables + base for scopes and functions). * @note Status: ALPHA * + * The symbol table for the configuration language is managed as a collection of + * configuration entries. These include specializations for ConfigEntry_Function and + * ConfigEntry_Scope, both defined in their own files and derived from ConfigEntry. * * Development Notes: * - Currently we are not using Format; this would be useful if we want to type-check inputs more @@ -30,13 +33,13 @@ namespace mabe { - class ConfigScope; + class ConfigEntry_Scope; class ConfigEntry { protected: - std::string name; ///< Unique name for this entry; empty name implied temporary. - std::string desc; ///< Description to put in comments for this entry. - emp::Ptr scope; ///< Which scope was this variable defined in? + std::string name; ///< Unique name for entry; empty name implies temporary. + std::string desc; ///< Description to put in comments for this entry. + emp::Ptr scope; ///< Which scope was this variable defined in? bool is_temporary = false; ///< Is this ConfigEntry temporary and should be deleted? bool is_builtin = false; ///< Built-in entries should not be written to config files. @@ -76,14 +79,14 @@ namespace mabe { public: ConfigEntry(const std::string & _name, const std::string & _desc, - emp::Ptr _scope) + emp::Ptr _scope) : name(_name), desc(_desc), scope(_scope) { } ConfigEntry(const ConfigEntry &) = default; virtual ~ConfigEntry() { } const std::string & GetName() const noexcept { return name; } const std::string & GetDesc() const noexcept { return desc; } - emp::Ptr GetScope() { return scope; } + emp::Ptr GetScope() { return scope; } bool IsTemporary() const noexcept { return is_temporary; } bool IsBuiltIn() const noexcept { return is_builtin; } Format GetFormat() const noexcept { return format; } @@ -120,8 +123,8 @@ namespace mabe { virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual ConfigEntry & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } - virtual emp::Ptr AsScopePtr() { return nullptr; } - ConfigScope & AsScope() { + virtual emp::Ptr AsScopePtr() { return nullptr; } + ConfigEntry_Scope & AsScope() { emp_assert(AsScopePtr()); return *(AsScopePtr()); } @@ -133,7 +136,7 @@ namespace mabe { if constexpr (std::is_same>()) { return this; } else if constexpr (std::is_same()) { return *this; } else if constexpr (std::is_same()) { return AsString(); } - else if constexpr (std::is_same()) { return AsScope(); } + else if constexpr (std::is_same()) { return AsScope(); } else if constexpr (std::is_arithmetic()) { return (T) AsDouble(); } else { // Oh oh... we don't know this type... @@ -303,8 +306,10 @@ namespace mabe { using this_t = ConfigEntry_Var; template - ConfigEntry_Var(const std::string & in_name, T default_val, - const std::string & in_desc="", emp::Ptr in_scope=nullptr) + ConfigEntry_Var(const std::string & in_name, + T default_val, + const std::string & in_desc="", + emp::Ptr in_scope=nullptr) : ConfigEntry(in_name, in_desc, in_scope), value(default_val) { ; } ConfigEntry_Var(const ConfigEntry_Var &) = default; From 859acc9da509886c94d3bea850b87a599f4250ab Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:45:09 -0400 Subject: [PATCH 185/445] Updated to ConfigEntry_Scope in ConfigType. --- source/config/ConfigType.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 32bc3d91..5986c474 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file ConfigType.hpp * @brief Setup types for use in scripting. @@ -14,7 +14,7 @@ #include "emp/base/assert.hpp" #include "ConfigEntry.hpp" -#include "ConfigScope.hpp" +#include "ConfigEntry_Scope.hpp" namespace mabe { @@ -29,7 +29,7 @@ namespace mabe { // Base class for types that we want to be used for scripting. class ConfigType { private: - emp::Ptr cur_scope; + emp::Ptr cur_scope; public: // Some special, internal variables associated with each object. @@ -111,12 +111,12 @@ namespace mabe { } public: - virtual void SetupScope(ConfigScope & scope) { cur_scope = &scope; } + virtual void SetupScope(ConfigEntry_Scope & scope) { cur_scope = &scope; } virtual void SetupConfig() = 0; virtual ~ConfigType() { } - ConfigScope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - const ConfigScope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + ConfigEntry_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + const ConfigEntry_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } }; } From 2d6aa9bc1580a956aa0755e41d4eaa27480dd9c4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 16:55:36 -0400 Subject: [PATCH 186/445] Updated use of scopes in main MABE controller; all should compile again now. --- source/core/MABE.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 8c23ca88..3d9aa89e 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -114,7 +114,7 @@ namespace mabe { emp::vector config_settings; ///< Additional config commands to run. std::string gen_filename; ///< Name of output file to generate. Config config; ///< Configuration information for this run. - emp::Ptr cur_scope; ///< Which config scope are we currently using? + emp::Ptr cur_scope; ///< Which config scope are we currently using? // ----------- Helper Functions ----------- @@ -357,19 +357,19 @@ namespace mabe { // --- Manage configuration scope --- /// Access to the current configuration scope. - ConfigScope & GetCurScope() { return *cur_scope; } + ConfigEntry_Scope & GetCurScope() { return *cur_scope; } /// Add a new scope under the current one. - ConfigScope & AddScope(const std::string & name, const std::string & desc) { + ConfigEntry_Scope & AddScope(const std::string & name, const std::string & desc) { cur_scope = &(cur_scope->AddScope(name, desc)); return *cur_scope; } /// Move up one level of scope. - ConfigScope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } + ConfigEntry_Scope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } /// Return to the root scope. - ConfigScope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } + ConfigEntry_Scope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } /// Setup the configuration options for MABE, including for each module. void SetupConfig(); From e48a82e376b2a4c092c1d1b4d4931ad208136502 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 6 Oct 2021 17:47:49 -0400 Subject: [PATCH 187/445] Fixed spelling throughout config sub-system --- source/config/ConfigAST.hpp | 6 +++--- source/config/ConfigEntry.hpp | 2 +- source/config/ConfigEntry_Scope.hpp | 2 +- source/config/ConfigEvents.hpp | 4 ++-- source/config/ConfigLexer.hpp | 2 +- source/config/ConfigType.hpp | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index adcf554c..f1af9f70 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -4,7 +4,7 @@ * @date 2019-2021. * * @file ConfigAST.hpp - * @brief Manages Abstract Sytax Tree nodes for Config. + * @brief Manages Abstract Syntax Tree nodes for Config. * @note Status: ALPHA */ @@ -49,8 +49,8 @@ namespace mabe { virtual const std::string & GetName() const = 0; - virtual bool IsNumeric() const { return false; } // Can node be reprsented as a number? - virtual bool IsString() const { return false; } // Can node be reprsented as a string? + virtual bool IsNumeric() const { return false; } // Can node be represented as a number? + virtual bool IsString() const { return false; } // Can node be represented as a string? virtual bool HasValue() const { return false; } // Does node have any value (vs internal block) virtual bool HasNumericReturn() const { return false; } // Is node function with numeric return? virtual bool HasStringReturn() const { return false; } // Is node function with string return? diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index b8d9f82b..64d9a4b9 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -14,7 +14,7 @@ * Development Notes: * - Currently we are not using Format; this would be useful if we want to type-check inputs more * carefully. - * - When a ConfigEntry is used for a temporary value, it doesn't acutally need name or desc; + * - When a ConfigEntry is used for a temporary value, it doesn't actually need name or desc; * we can probably remove these pretty easily to save on memory if needed. */ diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index ab6e791e..f53acc35 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -4,7 +4,7 @@ * @date 2019-2021. * * @file ConfigEntry_Scope.hpp - * @brief Manages a full scope with many conig entries (or sub-scopes). + * @brief Manages a full scope with many config entries (or sub-scopes). * @note Status: ALPHA * * DEVELOPER NOTES: diff --git a/source/config/ConfigEvents.hpp b/source/config/ConfigEvents.hpp index b99c3ed2..4c288111 100644 --- a/source/config/ConfigEvents.hpp +++ b/source/config/ConfigEvents.hpp @@ -30,7 +30,7 @@ namespace mabe { // Structure to track the timings for a single event. struct TimedEvent { size_t id = 0; // A unique ID for this event. - emp::Ptr ast_action; // Parse tree to exectute when triggered. + emp::Ptr ast_action; // Parse tree to execute when triggered. double next = 0.0; // When should we start triggering this event. double repeat = 0.0; // How often should it repeat (0.0 for no repeat) double max = -1.0; // Maximum value that this value can reach (neg for no max) @@ -51,7 +51,7 @@ namespace mabe { if (max != -1.0 && next > max) repeat = 0.0; - // Return "active" if we ARE repeating and the next time is stiil within range. + // Return "active" if we ARE repeating and the next time is still within range. return (repeat != 0.0); } diff --git a/source/config/ConfigLexer.hpp b/source/config/ConfigLexer.hpp index 8489704b..32ccff5d 100644 --- a/source/config/ConfigLexer.hpp +++ b/source/config/ConfigLexer.hpp @@ -4,7 +4,7 @@ * @date 2019-2020. * * @file ConfigLexer.hpp - * @brief A Lexer that tokenizes MABE config files. + * @brief A Lexer used to tokenize MABE config files. **/ #ifndef MABE_CONFIG_LEXER_H diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 5986c474..279198c1 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -39,7 +39,7 @@ namespace mabe { // ---== Configuration Management ==--- /// Link a variable to a configuration entry - the value will default to the - /// variabels crrent value, but be updated when configs are loaded. + /// variables current value, but be updated when configs are loaded. template ConfigEntry_Linked & LinkVar(VAR_T & var, const std::string & name, From 313cf996cd16b947d372f77610ec4436db55b50d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:03:32 -0400 Subject: [PATCH 188/445] Cleanup on ConfigEntry_Function --- source/config/ConfigEntry_Function.hpp | 34 +++++++------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index 68414770..1335f83d 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -5,7 +5,7 @@ * * @file ConfigEntry_Function.hpp * @brief Manages individual functions for config. - * @note Status: ALPHA + * @note Status: BETA */ #ifndef MABE_CONFIG_FUNCTION_H @@ -61,7 +61,7 @@ namespace mabe { string_return = std::is_same(); // Convert the function call to using entry pointers. - fun = [in_fun, name=name, desc=desc](const emp::vector & args) -> emp::Ptr { + fun = [in_fun, name=name, desc=desc](const emp::vector & args) -> entry_ptr_t { // If arguments are passed in, we need to raise an error. if (args.size()) { return emp::NewPtr( @@ -69,17 +69,17 @@ namespace mabe { ); } - emp::Ptr out_entry = + entry_ptr_t out_entry = emp::NewPtr>("return value", in_fun(), desc, nullptr); out_entry->SetTemporary(); return out_entry; }; } - /// Helper function to convert ASTs into the proper arguments. + /// Helper function to convert ASTs into the proper function arguments. template void SetFunction_impl( std::function in_fun, emp::ValPack ) { - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> emp::Ptr { + fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { // The call needs to have the correct number of arguments or else it throws an error. constexpr int NUM_ARGS = sizeof...(ARGS); if (args.size() != NUM_ARGS) { @@ -89,7 +89,7 @@ namespace mabe { } RETURN_T result = in_fun((args[INDICES]->template As< std::decay_t >())...); - emp::Ptr out_entry = + entry_ptr_t out_entry = emp::NewPtr>("return value", result, desc, nullptr); out_entry->SetTemporary(); return out_entry; @@ -99,13 +99,13 @@ namespace mabe { /// Setup a function that takes AT LEAST ONE argument. template void SetFunction( std::function in_fun ) { - /// If we have only one argument and it is a `const emp::vector> &`, + /// If we have only one argument and it is a `const emp::vector &`, /// assume that the function will handle any conversions itself. if constexpr (std::is_same() && sizeof...(ARGS) == 0) { - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> emp::Ptr { + fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { RETURN_T result = in_fun(args); - emp::Ptr out_entry = + entry_ptr_t out_entry = emp::NewPtr>("return value", result, desc, nullptr); out_entry->SetTemporary(); return out_entry; @@ -115,22 +115,6 @@ namespace mabe { /// Convert the function call to using entry pointers. else { SetFunction_impl( in_fun, emp::ValPackCount() ); - // fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> emp::Ptr { - // // The call needs to have the correct number of arguments or else it throws an error. - // constexpr int NUM_ARGS = sizeof...(ARGS) + 1; - // if (args.size() != NUM_ARGS) { - // return emp::NewPtr( - // "Function '", name, "' called with ", args.size(), " args, but ", NUM_ARGS, " expected." - // ); - // } - - // size_t i = 1; - // RETURN_T result = in_fun(args[0]->As(), args[i++]->As()...); - // emp::Ptr out_entry = - // emp::NewPtr>("return value", result, desc, nullptr); - // out_entry->SetTemporary(); - // return out_entry; - // }; } } From 4f0202ff8b0013dd6d9d19b5f8e20399bd13519d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:04:25 -0400 Subject: [PATCH 189/445] Updated config sub-system to BETA status. --- source/config/Config.hpp | 2 +- source/config/ConfigAST.hpp | 2 +- source/config/ConfigEntry.hpp | 2 +- source/config/ConfigEntry_Scope.hpp | 2 +- source/config/ConfigEvents.hpp | 2 +- source/config/ConfigLexer.hpp | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 1f04ada9..516f08fa 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -5,7 +5,7 @@ * * @file Config.hpp * @brief Manages all configuration of MABE runs (full parser implementation here) - * @note Status: ALPHA + * @note Status: BETA * * Example usage: * Value a = 7; // a is a variable with the value 7 diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index f1af9f70..1799dda5 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -5,7 +5,7 @@ * * @file ConfigAST.hpp * @brief Manages Abstract Syntax Tree nodes for Config. - * @note Status: ALPHA + * @note Status: BETA */ #ifndef MABE_CONFIG_AST_H diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 64d9a4b9..fc125259 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -5,7 +5,7 @@ * * @file ConfigEntry.hpp * @brief Manages a single configuration entry (e.g., variables + base for scopes and functions). - * @note Status: ALPHA + * @note Status: BETA * * The symbol table for the configuration language is managed as a collection of * configuration entries. These include specializations for ConfigEntry_Function and diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index f53acc35..b0e3f23e 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -5,7 +5,7 @@ * * @file ConfigEntry_Scope.hpp * @brief Manages a full scope with many config entries (or sub-scopes). - * @note Status: ALPHA + * @note Status: BETA * * DEVELOPER NOTES: * - Need to fix Add() function to give a user-level error, rather than an assert on duplication. diff --git a/source/config/ConfigEvents.hpp b/source/config/ConfigEvents.hpp index 4c288111..a96737f1 100644 --- a/source/config/ConfigEvents.hpp +++ b/source/config/ConfigEvents.hpp @@ -5,7 +5,7 @@ * * @file ConfigEvents.hpp * @brief Manages events for configurations. - * @note Status: ALPHA + * @note Status: BETA * * DEVELOPER NOTES: * - We could use a more dynamic function to determine when an event should be triggered next, diff --git a/source/config/ConfigLexer.hpp b/source/config/ConfigLexer.hpp index 32ccff5d..8366ea75 100644 --- a/source/config/ConfigLexer.hpp +++ b/source/config/ConfigLexer.hpp @@ -1,10 +1,11 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file ConfigLexer.hpp * @brief A Lexer used to tokenize MABE config files. + * @note Status: BETA **/ #ifndef MABE_CONFIG_LEXER_H From b589fd9be893c6f015db4c8fa943e409095928fa Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:38:50 -0400 Subject: [PATCH 190/445] Renamed ConfigEntry_Functions to ConfigEntry_LinkedFunctions to distinguish from ConfigEntry_Function --- source/config/ConfigEntry.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index fc125259..a3f046d5 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -255,17 +255,18 @@ namespace mabe { bool CopyValue(const ConfigEntry & in) override { var = in.AsString(); return true; } }; - /// ConfigEntry can be linked to a pair of (Get and Set) functions. + /// ConfigEntry can be linked to a pair of (Get and Set) functions + /// rather than as direct variable. template - class ConfigEntry_Functions : public ConfigEntry { + class ConfigEntry_LinkedFunctions : public ConfigEntry { private: std::function get_fun; std::function set_fun; public: - using this_t = ConfigEntry_Functions; + using this_t = ConfigEntry_LinkedFunctions; template - ConfigEntry_Functions(const std::string & in_name, + ConfigEntry_LinkedFunctions(const std::string & in_name, std::function in_get, std::function in_set, ARGS &&... args) @@ -273,7 +274,7 @@ namespace mabe { , get_fun(in_get) , set_fun(in_set) { ; } - ConfigEntry_Functions(const this_t &) = default; + ConfigEntry_LinkedFunctions(const this_t &) = default; std::string GetTypename() const override { return "[[Function]]"; } From 15b0f98e6ba710e82e6fbc5a0b9a7fe55195a0f4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:39:28 -0400 Subject: [PATCH 191/445] Updated to ConfigEntry_LinkedFunctions throughout config. --- source/config/ConfigEntry_Function.hpp | 2 +- source/config/ConfigEntry_Scope.hpp | 8 +++--- source/config/ConfigType.hpp | 4 +-- source/core/Module.hpp | 36 ++++++++++++++++---------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index 1335f83d..43426cd9 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -27,7 +27,7 @@ namespace mabe { using this_t = ConfigEntry_Function; using entry_ptr_t = emp::Ptr; using entry_vector_t = emp::vector; - using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; + using fun_t = std::function< entry_ptr_t( const entry_vector_t & ) >; fun_t fun; bool numeric_return = false; bool string_return = false; diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index b0e3f23e..2eafd44c 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -144,13 +144,15 @@ namespace mabe { /// Link a configuration entry to a pair of functions - it sets the new default and /// automatically calls the set function when configs are loaded. template - ConfigEntry_Functions & LinkFuns(const std::string & name, + ConfigEntry_LinkedFunctions & LinkFuns(const std::string & name, std::function get_fun, std::function set_fun, const std::string & desc, bool is_builtin = false) { - if (is_builtin) return AddBuiltin>(name, get_fun, set_fun, desc, this); - return Add>(name, get_fun, set_fun, desc, this); + if (is_builtin) { + return AddBuiltin>(name, get_fun, set_fun, desc, this); + } + return Add>(name, get_fun, set_fun, desc, this); } /// Add a new variable of type String. diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 279198c1..201580d0 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -51,7 +51,7 @@ namespace mabe { /// Link a configuration entry to a pair of functions - it automatically calls the set /// function when configs are loaded, and the get function when current value is needed. template - ConfigEntry_Functions & LinkFuns(std::function get_fun, + ConfigEntry_LinkedFunctions & LinkFuns(std::function get_fun, std::function set_fun, const std::string & name, const std::string & desc, @@ -74,7 +74,7 @@ namespace mabe { /// Each option should include three arguments: /// The return value, the option name, and the option description. template - ConfigEntry_Functions & LinkMenu(VAR_T & var, + ConfigEntry_LinkedFunctions & LinkMenu(VAR_T & var, const std::string & name, const std::string & desc, const Ts &... entries) { diff --git a/source/core/Module.hpp b/source/core/Module.hpp index d67588ff..d88f897b 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -51,9 +51,11 @@ namespace mabe { // (Other ways of linking variable to config file are in ConfigType.h) /// Link a single population to a parameter by name. - ConfigEntry_Functions & LinkPop(int & var, - const std::string & name, - const std::string & desc) { + ConfigEntry_LinkedFunctions & LinkPop( + int & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.GetPopulation(var).GetName(); }; @@ -67,9 +69,11 @@ namespace mabe { } /// Link one or more populations (or portions of a population) to a parameter. - ConfigEntry_Functions & LinkCollection(mabe::Collection & var, - const std::string & name, - const std::string & desc) { + ConfigEntry_LinkedFunctions & LinkCollection( + mabe::Collection & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.ToString(var); }; @@ -82,9 +86,11 @@ namespace mabe { } /// Link another module to this one, by name (track using int ID) - ConfigEntry_Functions & LinkModule(int & var, - const std::string & name, - const std::string & desc) { + ConfigEntry_LinkedFunctions & LinkModule( + int & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.GetModule(var).GetName(); }; @@ -98,11 +104,13 @@ namespace mabe { } /// Link a range of values with a start, stop, and step. - ConfigEntry_Functions & LinkRange(int & start_var, - int & step_var, - int & stop_var, - const std::string & name, - const std::string & desc) { + ConfigEntry_LinkedFunctions & LinkRange( + int & start_var, + int & step_var, + int & stop_var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [&start_var,&step_var,&stop_var]() { // If stop_var is -1, don't bother printing it (i.e. NO stop) From 15f2d5b459b71d6ac87b4615d6b8f4bcdead831f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:47:08 -0400 Subject: [PATCH 192/445] Moved linked ConfigEntry specialized types into their own file. --- source/config/ConfigEntry_Linked.hpp | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 source/config/ConfigEntry_Linked.hpp diff --git a/source/config/ConfigEntry_Linked.hpp b/source/config/ConfigEntry_Linked.hpp new file mode 100644 index 00000000..b4ee0fe7 --- /dev/null +++ b/source/config/ConfigEntry_Linked.hpp @@ -0,0 +1,127 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file ConfigEntry_Linked.hpp + * @brief Manages a configuration entry linked to another variable or functions. + * @note Status: BETA + */ + +#ifndef MABE_CONFIG_ENTRY_LINKED_HPP +#define MABE_CONFIG_ENTRY_LINKED_HPP + +#include + +#include "ConfigEntry.hpp" + +namespace mabe { + + /// ConfigEntry can be linked directly to a real variable. + template + class ConfigEntry_Linked : public ConfigEntry { + private: + T & var; + public: + using this_t = ConfigEntry_Linked; + + template + ConfigEntry_Linked(const std::string & in_name, T & in_var, ARGS &&... args) + : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } + ConfigEntry_Linked(const this_t &) = default; + + std::string GetTypename() const override { + if constexpr (std::is_scalar_v) return "Value"; + else return "Unknown"; + } + + emp::Ptr Clone() const override { return emp::NewPtr(*this); } + + double AsDouble() const override { return (double) var; } + std::string AsString() const override { return emp::to_string(var); } + ConfigEntry & SetValue(double in) override { var = (T) in; return *this; } + ConfigEntry & SetString(const std::string & in) override { + var = emp::from_string(in); + return *this; + } + + bool IsNumeric() const override { return std::is_scalar_v; } + bool IsBool() const override { return std::is_same(); } + bool IsInt() const override { return std::is_same(); } + bool IsDouble() const override { return std::is_same(); } + + bool CopyValue(const ConfigEntry & in) override { var = in.AsDouble(); return true; } + }; + + /// Specialization for ConfigEntry linked to a string variable. + template <> + class ConfigEntry_Linked : public ConfigEntry { + private: + std::string & var; + public: + using this_t = ConfigEntry_Linked; + + template + ConfigEntry_Linked(const std::string & in_name, std::string & in_var, ARGS &&... args) + : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } + ConfigEntry_Linked(const this_t &) = default; + + std::string GetTypename() const override { return "String"; } + + emp::Ptr Clone() const override { return emp::NewPtr(*this); } + + double AsDouble() const override { return emp::from_string(var); } + std::string AsString() const override { return var; } + ConfigEntry & SetValue(double in) override { var = emp::to_string(in); return *this; } + ConfigEntry & SetString(const std::string & in) override { var = in; return *this; } + + bool IsString() const override { return true; } + + bool CopyValue(const ConfigEntry & in) override { var = in.AsString(); return true; } + }; + + /// ConfigEntry can be linked to a pair of (Get and Set) functions + /// rather than as direct variable. + template + class ConfigEntry_LinkedFunctions : public ConfigEntry { + private: + std::function get_fun; + std::function set_fun; + public: + using this_t = ConfigEntry_LinkedFunctions; + + template + ConfigEntry_LinkedFunctions(const std::string & in_name, + std::function in_get, + std::function in_set, + ARGS &&... args) + : ConfigEntry(in_name, std::forward(args)...) + , get_fun(in_get) + , set_fun(in_set) + { ; } + ConfigEntry_LinkedFunctions(const this_t &) = default; + + std::string GetTypename() const override { return "[[Function]]"; } + + emp::Ptr Clone() const override { return emp::NewPtr(*this); } + + double AsDouble() const override { return emp::ToDouble( get_fun() ); } + std::string AsString() const override { return emp::to_string( get_fun() ); } + ConfigEntry & SetValue(double in) override { set_fun(emp::FromDouble(in)); return *this; } + ConfigEntry & SetString(const std::string & in) override { + set_fun( emp::from_string(in) ); + return *this; + } + + bool IsNumeric() const override { return std::is_scalar_v; } + bool IsBool() const override { return std::is_same(); } + bool IsInt() const override { return std::is_same(); } + bool IsDouble() const override { return std::is_same(); } + bool IsString() const override { return std::is_same(); } + + bool CopyValue(const ConfigEntry & in) override { SetString( in.AsString() ); return true; } + }; + +} + +#endif From 6650ba1b8c102deff9c8116ce843f63e1bc1cc65 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:47:47 -0400 Subject: [PATCH 193/445] Removed linked ConfigEntry types from main ConfigEntry file (now that they're in their own file) --- source/config/ConfigEntry.hpp | 113 ++-------------------------------- 1 file changed, 4 insertions(+), 109 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index a3f046d5..c28e01d4 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -8,8 +8,9 @@ * @note Status: BETA * * The symbol table for the configuration language is managed as a collection of - * configuration entries. These include specializations for ConfigEntry_Function and - * ConfigEntry_Scope, both defined in their own files and derived from ConfigEntry. + * configuration entries. These include specializations for ConfigEntry_Linked (for linked + * variables), ConfigEntry_Function and ConfigEntry_Scope, all defined in their own files + * and derived from ConfigEntry. * * Development Notes: * - Currently we are not using Format; this would be useful if we want to type-check inputs more @@ -192,113 +193,7 @@ namespace mabe { } }; - /// ConfigEntry can be linked directly to a real variable. - template - class ConfigEntry_Linked : public ConfigEntry { - private: - T & var; - public: - using this_t = ConfigEntry_Linked; - - template - ConfigEntry_Linked(const std::string & in_name, T & in_var, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } - ConfigEntry_Linked(const this_t &) = default; - - std::string GetTypename() const override { - if constexpr (std::is_scalar_v) return "Value"; - else return "Unknown"; - } - - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - - double AsDouble() const override { return (double) var; } - std::string AsString() const override { return emp::to_string(var); } - ConfigEntry & SetValue(double in) override { var = (T) in; return *this; } - ConfigEntry & SetString(const std::string & in) override { - var = emp::from_string(in); - return *this; - } - - bool IsNumeric() const override { return std::is_scalar_v; } - bool IsBool() const override { return std::is_same(); } - bool IsInt() const override { return std::is_same(); } - bool IsDouble() const override { return std::is_same(); } - - bool CopyValue(const ConfigEntry & in) override { var = in.AsDouble(); return true; } - }; - - /// Specialization for ConfigEntry linked to a string variable. - template <> - class ConfigEntry_Linked : public ConfigEntry { - private: - std::string & var; - public: - using this_t = ConfigEntry_Linked; - - template - ConfigEntry_Linked(const std::string & in_name, std::string & in_var, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } - ConfigEntry_Linked(const this_t &) = default; - - std::string GetTypename() const override { return "String"; } - - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - - double AsDouble() const override { return emp::from_string(var); } - std::string AsString() const override { return var; } - ConfigEntry & SetValue(double in) override { var = emp::to_string(in); return *this; } - ConfigEntry & SetString(const std::string & in) override { var = in; return *this; } - - bool IsString() const override { return true; } - - bool CopyValue(const ConfigEntry & in) override { var = in.AsString(); return true; } - }; - - /// ConfigEntry can be linked to a pair of (Get and Set) functions - /// rather than as direct variable. - template - class ConfigEntry_LinkedFunctions : public ConfigEntry { - private: - std::function get_fun; - std::function set_fun; - public: - using this_t = ConfigEntry_LinkedFunctions; - - template - ConfigEntry_LinkedFunctions(const std::string & in_name, - std::function in_get, - std::function in_set, - ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...) - , get_fun(in_get) - , set_fun(in_set) - { ; } - ConfigEntry_LinkedFunctions(const this_t &) = default; - - std::string GetTypename() const override { return "[[Function]]"; } - - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - - double AsDouble() const override { return emp::ToDouble( get_fun() ); } - std::string AsString() const override { return emp::to_string( get_fun() ); } - ConfigEntry & SetValue(double in) override { set_fun(emp::FromDouble(in)); return *this; } - ConfigEntry & SetString(const std::string & in) override { - set_fun( emp::from_string(in) ); - return *this; - } - - bool IsNumeric() const override { return std::is_scalar_v; } - bool IsBool() const override { return std::is_same(); } - bool IsInt() const override { return std::is_same(); } - bool IsDouble() const override { return std::is_same(); } - bool IsString() const override { return std::is_same(); } - - bool CopyValue(const ConfigEntry & in) override { SetString( in.AsString() ); return true; } - }; - - - /// A generic version of a config entry for a maintained variable. + /// A generic version of a config entry for an internally maintained variable. template class ConfigEntry_Var : public ConfigEntry { private: From 2ab66c78023a3c82912b6cdee1abc4e4a3f954af Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 7 Oct 2021 09:48:15 -0400 Subject: [PATCH 194/445] Link new ConfigEntry_Linked.hpp into ConfigEntry_Scope, from which it's called. --- source/config/ConfigEntry_Scope.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index 2eafd44c..1497c923 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -19,6 +19,7 @@ #include "ConfigEntry.hpp" #include "ConfigEntry_Function.hpp" +#include "ConfigEntry_Linked.hpp" namespace mabe { From 72448eb5bc052272c2730b21d9eefd3e037ab15d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 14:32:32 -0400 Subject: [PATCH 195/445] Renamed BuiltIn to Builtin (no cap 'i') --- source/config/ConfigEntry.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index c28e01d4..3c6b81ab 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -89,7 +89,7 @@ namespace mabe { const std::string & GetDesc() const noexcept { return desc; } emp::Ptr GetScope() { return scope; } bool IsTemporary() const noexcept { return is_temporary; } - bool IsBuiltIn() const noexcept { return is_builtin; } + bool IsBuiltin() const noexcept { return is_builtin; } Format GetFormat() const noexcept { return format; } virtual std::string GetTypename() const { return "Unknown"; } @@ -111,7 +111,7 @@ namespace mabe { ConfigEntry & SetName(const std::string & in) { name = in; return *this; } ConfigEntry & SetDesc(const std::string & in) { desc = in; return *this; } ConfigEntry & SetTemporary(bool in=true) { is_temporary = in; return *this; } - ConfigEntry & SetBuiltIn(bool in=true) { is_builtin = in; return *this; } + ConfigEntry & SetBuiltin(bool in=true) { is_builtin = in; return *this; } virtual double AsDouble() const { emp_assert(false); return 0.0; } virtual std::string AsString() const { emp_assert(false); return ""; } @@ -174,7 +174,7 @@ namespace mabe { size_t comment_offset=32) const { // If this is a built-in entry, don't print it. - if (IsBuiltIn()) return *this; + if (IsBuiltin()) return *this; // Setup this entry. std::string cur_line = prefix; From 3d67fd0157f75b40c6ae021bcf810b6cac7a3aea Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 14:33:21 -0400 Subject: [PATCH 196/445] Removed vectors of entires from scope; now all in map. --- source/config/ConfigEntry_Scope.hpp | 94 ++++++++++++----------------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index 1497c923..1b3e48b4 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -27,9 +27,8 @@ namespace mabe { class ConfigEntry_Scope : public ConfigEntry { protected: using entry_ptr_t = emp::Ptr; - emp::vector< entry_ptr_t > entry_list; ///< Entries in order. - emp::vector< entry_ptr_t > builtin_list; ///< Built-in entries; not in config. - emp::map< std::string, entry_ptr_t > entry_map; ///< Entries with easy lookup. + using const_entry_ptr_t = emp::Ptr; + emp::map< std::string, entry_ptr_t > symbol_table; ///< Map of names to entries. ///< If this scope represents a structure, identify the type (otherwise type is "") const std::string type; @@ -37,48 +36,35 @@ namespace mabe { template T & Add(const std::string & name, ARGS &&... args) { auto new_ptr = emp::NewPtr(name, std::forward(args)...); - entry_list.push_back(new_ptr); - emp_assert(!emp::Has(entry_map, name), "Do not redeclare functions or variables!", + emp_assert(!emp::Has(symbol_table, name), "Do not redeclare functions or variables!", name); - entry_map[name] = new_ptr; + symbol_table[name] = new_ptr; return *new_ptr; } template T & AddBuiltin(const std::string & name, ARGS &&... args) { - auto new_ptr = emp::NewPtr(name, std::forward(args)...); - builtin_list.push_back(new_ptr); - emp_assert(!emp::Has(entry_map, name), "Do not redeclare built-in functions or variables!", - name); - entry_map[name] = new_ptr; - return *new_ptr; + T & result = Add(name, std::forward(args)...); + result.SetBuiltin(); + return result; } + public: ConfigEntry_Scope(const std::string & _name, const std::string & _desc, emp::Ptr _scope, const std::string & _type="") : ConfigEntry(_name, _desc, _scope), type(_type) { } + ConfigEntry_Scope(const ConfigEntry_Scope & in) : ConfigEntry(in) { // Copy all defined variables/scopes/functions - for (const auto & x : in.entry_list) { - auto new_ptr = x->Clone(); - entry_list.push_back(new_ptr); - entry_map[x->GetName()] = new_ptr; - } - // Copy all built-in variables/scopes/functions - for (const auto & x : in.builtin_list) { - auto new_ptr = x->Clone(); - builtin_list.push_back(new_ptr); - entry_map[x->GetName()] = new_ptr; - } + for (auto [name, ptr] : symbol_table) { symbol_table[name] = ptr->Clone(); } } ConfigEntry_Scope(ConfigEntry_Scope &&) = default; ~ConfigEntry_Scope() { - // Clear up all entries and built-ins. - for (auto & x : entry_list) { x.Delete(); } - for (auto & x : builtin_list) { x.Delete(); } + // Clear up the symbol table. + for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } std::string GetTypename() const override { return type; } @@ -90,49 +76,40 @@ namespace mabe { emp::Ptr AsScopePtr() override { return this; } /// Get an entry out of this scope; - entry_ptr_t GetEntry(std::string in_name) { - // Lookup this next entry is in the var list. - auto it = entry_map.find(in_name); - - // If this name is unknown, fail! - if (it == entry_map.end()) return nullptr; - - // Otherwise return the entry. - return it->second; - } + entry_ptr_t GetEntry(std::string name) { return emp::Find(symbol_table, name, nullptr); } /// Lookup a variable, scanning outer scopes if needed - entry_ptr_t LookupEntry(const std::string & in_name, bool scan_scopes=true) override { + entry_ptr_t LookupEntry(const std::string & name, bool scan_scopes=true) override { // See if this next entry is in the var list. - auto it = entry_map.find(in_name); + auto it = symbol_table.find(name); // If this name is unknown, check with the parent scope! - if (it == entry_map.end()) { + if (it == symbol_table.end()) { if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(in_name); + return scope->LookupEntry(name); } // Otherwise we found it! return it->second; } - /// Lookup a variable, scanning outer scopes if needed (in constant context!) - emp::Ptr LookupEntry(const std::string & in_name, bool scan_scopes=true) const override { + /// Lookup a variable, scanning outer scopes if needed (in const context!) + const_entry_ptr_t LookupEntry(const std::string & name, bool scan_scopes=true) const override { // See if this entry is in the var list. - auto it = entry_map.find(in_name); + auto it = symbol_table.find(name); // If this name is unknown, check with the parent scope! - if (it == entry_map.end()) { + if (it == symbol_table.end()) { if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(in_name); + return scope->LookupEntry(name); } // Otherwise we found it! return it->second; } - /// Link a variable to a configuration entry - it sets the new default and - /// automatically updates when configs are loaded. + /// Add a configuration entry that is linked to a variable - the incoming variable sets + /// the default and is automatically updated when configs are loaded. template ConfigEntry_Linked & LinkVar(const std::string & name, VAR_T & var, @@ -142,8 +119,8 @@ namespace mabe { return Add>(name, var, desc, this); } - /// Link a configuration entry to a pair of functions - it sets the new default and - /// automatically calls the set function when configs are loaded. + /// Add a configuration entry that interacts through a pair of functions - the functions + /// are automatically called any time the entry is accessed (get_fun) or changed (set_fun) template ConfigEntry_LinkedFunctions & LinkFuns(const std::string & name, std::function get_fun, @@ -156,17 +133,17 @@ namespace mabe { return Add>(name, get_fun, set_fun, desc, this); } - /// Add a new variable of type String. + /// Add an internal variable of type String. ConfigEntry_StringVar & AddStringVar(const std::string & name, const std::string & desc) { return Add(name, "", desc, this); } - /// Add a new variable of type Value. + /// Add an internal variable of type Value. ConfigEntry_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { return Add(name, 0.0, desc, this); } - /// Add a new scope inside of this one. + /// Add an internal scope inside of this one. ConfigEntry_Scope & AddScope(const std::string & name, const std::string & desc, const std::string & type="") { return Add(name, desc, this, type); } @@ -192,8 +169,9 @@ namespace mabe { size_t comment_offset=32) const { // Loop through all of the entires in this scope and Write them. - for (auto x : entry_list) { - x->Write(os, prefix, comment_offset); + for (auto [name, ptr] : symbol_table) { + if (ptr->IsBuiltin()) continue; // Skip writing built-in entries. + ptr->Write(os, prefix, comment_offset); } return *this; @@ -204,22 +182,24 @@ namespace mabe { size_t comment_offset=32) const override { // If this is a built-in scope, don't print it. - if (IsBuiltIn()) return *this; + if (IsBuiltin()) return *this; // Declare this scope. std::string cur_line = prefix; if (IsLocal()) cur_line += emp::to_string(GetTypename(), " "); cur_line += name; + bool has_body = emp::AnyOf(symbol_table, [](entry_ptr_t ptr){ return !ptr->IsBuiltin(); }; ); + // Only open this scope if there are contents. - cur_line += entry_list.size() ? " { " : ";"; + cur_line += has_body ? " { " : ";"; os << cur_line; // Indent the comment for the description (if there is one) WriteDesc(os, comment_offset, cur_line.size()); // If we have internal entries, write them out. - if (entry_list.size()) { + if (has_body) { WriteContents(os, prefix+" ", comment_offset); os << prefix << "}\n"; // Close the scope. } From 87b22bf82a95c42db91299b4cfccea4c10632810 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 15:55:31 -0400 Subject: [PATCH 197/445] Added a MakeTempEntry helper function. --- source/config/ConfigEntry.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 3c6b81ab..cbc633eb 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -191,6 +191,7 @@ namespace mabe { return *this; } + }; /// A generic version of a config entry for an internally maintained variable. @@ -289,6 +290,19 @@ namespace mabe { return emp::NewPtr("Cannot call a function on non-function '", name, "'."); } + + //////////////////////////////////////////////////// + // Helper functions == + + // Use ConfigEntry::MakeTempEntry(value) to quickly make a temporary entry with a given value. + // Note: Caller will be responsible for deleting the created entry! + template + emp::Ptr> MakeTempEntry(VALUE_T value) { + auto out_entry = emp::NewPtr>("__Temp", value, "", nullptr); + out_entry->SetTemporary(); + return out_entry; + } + } #endif From 0c935edabc57afd0ebd0a677e01ae4db8dfcf4d5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 15:56:36 -0400 Subject: [PATCH 198/445] Streamlined MakeTempDouble and MakeTempString into MakeTempEntry --- source/config/ConfigAST.hpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index 1799dda5..dec21cff 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -31,18 +31,6 @@ namespace mabe { node_ptr_t parent = nullptr; - // Helper functions. - emp::Ptr MakeTempDouble(double val) { - auto out_ptr = emp::NewPtr("temp", val, "Temporary double", nullptr); - out_ptr->SetTemporary(); - return out_ptr; - } - - emp::Ptr MakeTempString(const std::string & val) { - auto out_ptr = emp::NewPtr("temp", val, "Temporary string", nullptr); - out_ptr->SetTemporary(); - return out_ptr; - } public: ASTNode() { ; } virtual ~ASTNode() { ; } @@ -175,7 +163,7 @@ namespace mabe { entry_ptr_t input_entry = children[0]->Process(); // Process child to get input entry double output_value = fun(input_entry->AsDouble()); // Run the function to get ouput value if (input_entry->IsTemporary()) input_entry.Delete(); // If we are done with input; delete! - return MakeTempDouble(output_value); + return MakeTempEntry(output_value); } void Write(std::ostream & os, const std::string & offset) const override { @@ -205,11 +193,7 @@ namespace mabe { auto out_val = fun(in1->As(), in2->As()); // Run function; get ouput if (in1->IsTemporary()) in1.Delete(); // If we are done with in1; delete! if (in2->IsTemporary()) in2.Delete(); // If we are done with in2; delete! - if constexpr (std::is_same()) { - return MakeTempDouble(out_val); - } else { - return MakeTempString(out_val); - } + return MakeTempEntry(out_val); } void Write(std::ostream & os, const std::string & offset) const override { From 01f12de8a73e1d953b69effd5c15d16272700cf1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 15:57:33 -0400 Subject: [PATCH 199/445] Streamlined ConfigEntry_Function.hpp into using MakeTempEntry() --- source/config/ConfigEntry_Function.hpp | 31 ++++++-------------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index 43426cd9..c6497a58 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -34,11 +34,6 @@ namespace mabe { // size_t arg_count; public: - ConfigEntry_Function(const std::string & _name, - const std::string & _desc, - emp::Ptr _scope) - : ConfigEntry(_name, _desc, _scope) { ; } - template ConfigEntry_Function(const std::string & _name, std::function _fun, @@ -60,8 +55,8 @@ namespace mabe { numeric_return = std::is_scalar_v; string_return = std::is_same(); - // Convert the function call to using entry pointers. - fun = [in_fun, name=name, desc=desc](const emp::vector & args) -> entry_ptr_t { + // Convert the function call to return an entry pointer and save it. + fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { // If arguments are passed in, we need to raise an error. if (args.size()) { return emp::NewPtr( @@ -69,10 +64,7 @@ namespace mabe { ); } - entry_ptr_t out_entry = - emp::NewPtr>("return value", in_fun(), desc, nullptr); - out_entry->SetTemporary(); - return out_entry; + return MakeTempEntry(in_fun()); }; } @@ -87,12 +79,8 @@ namespace mabe { "Function '", name, "' called with ", args.size(), " args, but ", NUM_ARGS, " expected." ); } - - RETURN_T result = in_fun((args[INDICES]->template As< std::decay_t >())...); - entry_ptr_t out_entry = - emp::NewPtr>("return value", result, desc, nullptr); - out_entry->SetTemporary(); - return out_entry; + + return MakeTempEntry( in_fun((args[INDICES]->template As< std::decay_t >())...) ); }; } @@ -101,14 +89,9 @@ namespace mabe { void SetFunction( std::function in_fun ) { /// If we have only one argument and it is a `const emp::vector &`, /// assume that the function will handle any conversions itself. - if constexpr (std::is_same() && - sizeof...(ARGS) == 0) { + if constexpr (sizeof...(ARGS) == 0 && std::is_same()) { fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { - RETURN_T result = in_fun(args); - entry_ptr_t out_entry = - emp::NewPtr>("return value", result, desc, nullptr); - out_entry->SetTemporary(); - return out_entry; + return MakeTempEntry(in_fun(args)); }; } From 0589be2411082e7d9cbfe5cb02924b0c0e40383c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 8 Oct 2021 15:58:30 -0400 Subject: [PATCH 200/445] Combined MakeTempDouble() and MakeTempString() into MakeTempLeaf() --- source/config/Config.hpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 516f08fa..d7d6dbe5 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -28,7 +28,8 @@ * } // f has been initialized with six variables in its scope. * * --- The functionality below does not yet work and may change when implemented --- - * f["new"] = 22; // You can always add new fields to structures. + * f["g"] = 2.5; // You can also access elements though indexing. + * f["new"] = 22; // You can always add new fields to structures via indexing. * // d["bad"] = 4; // ERROR - You cannot add fields to non-structures. * k = [ 1 , 2 , 3]; // k is a vector of values (vectors must have all types the same!) * l = k[1]; // Vectors can be indexed into. @@ -98,6 +99,9 @@ namespace mabe { using member_fun_t = std::function &)>; emp::map member_funs; + emp::vector< entry_ptr_t > member_list; ///< Member functions for this type + emp::map< std::string, entry_ptr_t > entry_map; ///< Lookup table for member functions. + // Constructor to allow a simple new configuration type TypeInfo(size_t in_id, const std::string & in_desc) : index(in_id), desc(in_desc) { } @@ -612,13 +616,13 @@ namespace mabe { return emp::NewPtr(cur_entry); } - emp::Ptr MakeTempDouble(double val) { + emp::Ptr MakeTempLeaf(double val) { auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); } - emp::Ptr MakeTempString(const std::string & val) { + emp::Ptr MakeTempLeaf(const std::string & val) { auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); @@ -635,21 +639,21 @@ namespace mabe { if (IsNumber(pos)) { Debug("...value is a number: ", AsLexeme(pos)); double value = emp::from_string(AsLexeme(pos++)); // Calculate value. - return MakeTempDouble(value); // Return temporary ConfigEntry. + return MakeTempLeaf(value); // Return temporary ConfigEntry. } // A literal char should be converted to its ASCII value. if (IsChar(pos)) { Debug("...value is a char: ", AsLexeme(pos)); char lit_char = emp::from_literal_char(AsLexeme(pos++)); // Convert the literal char. - return MakeTempDouble((double) lit_char); // Return temporary ConfigEntry. + return MakeTempLeaf((double) lit_char); // Return temporary ConfigEntry. } // A literal string should be converted to a regular string and used. if (IsString(pos)) { Debug("...value is a string: ", AsLexeme(pos)); std::string str = emp::from_literal_string(AsLexeme(pos++)); // Convert the literal string. - return MakeTempString(str); // Return temporary ConfigEntry. + return MakeTempLeaf(str); // Return temporary ConfigEntry. } // If we have an open parenthesis, process everything inside into a single value... From a6a131836fd517cbb5cdfc13f3ad462e9d23f3f5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 10 Oct 2021 13:58:22 -0400 Subject: [PATCH 201/445] Setup ConfigEntry::As<>() to do proper error checking. --- source/config/ConfigEntry.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index cbc633eb..579c34ee 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -28,6 +28,7 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" #include "emp/math/Range.hpp" +#include "emp/meta/meta.hpp" #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" #include "emp/tools/value_utils.hpp" @@ -118,7 +119,8 @@ namespace mabe { template T As() const { if constexpr (std::is_same()) return AsDouble(); - else return AsString(); + else if constexpr (std::is_same()) return AsString(); + else static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); } virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } From 1897183ed666bd77c145226cc5c216194271705a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:12:53 -0400 Subject: [PATCH 202/445] Moved ConfigTypeInfo into its own file. --- source/config/ConfigTypeInfo.hpp | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 source/config/ConfigTypeInfo.hpp diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp new file mode 100644 index 00000000..767de23a --- /dev/null +++ b/source/config/ConfigTypeInfo.hpp @@ -0,0 +1,106 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file ConfigTypeInfo.hpp + * @brief Manages all of the information about a particular type in the config language. + * @note Status: BETA + */ + +#ifndef MABE_CONFIG_TYPE_INFO_H +#define MABE_CONFIG_TYPE_INFO_H + +#include + +#include "emp/base/assert.hpp" +#include "emp/base/map.hpp" +#include "emp/meta/TypeID.hpp" +#include "emp/tools/string_utils.hpp" + +namespace mabe { + + // ConfigTypeInfo tracks a particular type to be used in the configuration langauge. + struct ConfigTypeInfo { + size_t index; + std::string desc; + emp::TypeID type_id; + + using init_fun_t = std::function; + init_fun_t init_fun; + + using entry_ptr_t = emp::Ptr; + using member_fun_t = std::function &)>; + emp::map member_funs; + + emp::vector< entry_ptr_t > member_list; ///< Member functions for this type + emp::map< std::string, entry_ptr_t > entry_map; ///< Lookup table for member functions. + + // Constructor to allow a simple new configuration type + ConfigTypeInfo(size_t in_id, const std::string & in_desc) + : index(in_id), desc(in_desc) { } + + // Constructor to allow a new configuration type whose objects require initialization. + ConfigTypeInfo(size_t in_id, const std::string & in_desc, init_fun_t in_init) + : index(in_id), desc(in_desc), init_fun(in_init) + { + } + + // Link this ConfigTypeInfo object to a real C++ type. + template + void LinkType() { + static_assert(std::is_base_of(), + "Only ConfigType objects can be used as a custom config type."); + type_id = emp::GetTypeID(); + } + + // Add a member function that can be called on objects of this type. + template + void AddMemberFunction( + const std::string & name, + std::function fun + ) { + // ----- Make sure function is legal ----- + // Is return type legal? + static_assert(std::is_arithmetic() || std::is_same(), + "Config member functions must of a string or arithmetic return type"); + + // Is the first parameter the correct type? + emp_assert( type_id.IsType(), + "First parameter must match config type of member function being created!", + type_id, emp::GetTypeID() ); + + // Are remaining parameters legal? + constexpr bool params_ok = + ((std::is_arithmetic() || std::is_same()) && ...); + static_assert(params_ok, "Parameters 2+ in a member function must be string or arithmetic."); + + // ----- Transform this function into one that ConfigTypeInfo can make use of ---- + member_fun_t member_fun = + [name,fun](ConfigType & obj, const emp::vector & args) { + // Make sure we can convert the obj into the correct type. + emp::Ptr typed_ptr = dynamic_cast(&obj); + + // Make sure we have the correct number of arguments. + if (args.size() != sizeof...(PARAM_Ts)) { + std::cerr << "Error in call to function '" << name + << "'; expected " << sizeof...(PARAM_Ts) + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above error. + + // Call the provided function and return the result. + int arg_id = 0; + RETURN_T result = fun( *typed_ptr, args[arg_id++]->As()... ); + + return result; + }; + + // Add this member function to the library we are building. + member_funs[name] = member_fun; + } + }; + +} +#endif From 0c41b214cce01e988c63f39bcf7de3f561c080f0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:13:29 -0400 Subject: [PATCH 203/445] Added Routlette Selection as its own module. --- source/select/SelectRoulette.hpp | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 source/select/SelectRoulette.hpp diff --git a/source/select/SelectRoulette.hpp b/source/select/SelectRoulette.hpp new file mode 100644 index 00000000..1dbf8c7a --- /dev/null +++ b/source/select/SelectRoulette.hpp @@ -0,0 +1,78 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file SelectRoulette.hpp + * @brief MABE module to enable roulette selection. + */ + +#ifndef MABE_SELECT_ROULETTE_H +#define MABE_SELECT_ROULETTE_H + +#include "../core/MABE.hpp" +#include "../core/Module.hpp" + +#include "emp/datastructs/IndexMap.hpp" + +namespace mabe { + + /// Add roulette selection with the current population. + class SelectRoulette : public Module { + private: + std::string fitness_trait="fitness"; ///< Which trait should we select on? + size_t select_count=1; ///< How many times to run roulette? + size_t copy_count=1; ///< How many copies of each should we make? + int select_pop_id = 0; ///< Which population are we selecting from? + int birth_pop_id = 1; ///< Which population should births go into? + + public: + SelectRoulette( + mabe::MABE & control, + const std::string & name="SelectRoulette", + const std::string & desc="Module to choose random organisms for replication, based on fitness." + ) : Module(control, name, desc) + { + SetSelectMod(true); ///< Mark this module as a selection module. + } + ~SelectRoulette() { } + + void SetupConfig() override { + LinkPop(select_pop_id, "select_pop", "Which population should we select parents from?"); + LinkPop(birth_pop_id, "birth_pop", "Which population should births go into?"); + LinkVar(select_count, "select_count", "How many organisms should we choose to replicate?"); + LinkVar(copy_count, "copy_count", "Number of copies to make of replicated organisms"); + LinkVar(fitness_trait, "fitness_trait", "Which trait provides the fitness value to use?"); + } + + void SetupModule() override { + AddRequiredTrait(fitness_trait); ///< The fitness trait must be set by another module. + } + + void OnUpdate(size_t /* update */) override { + if (select_pop_id == birth_pop_id) { + AddError("For now, birth_pop and select_pop must be different."); + return; + } + + Population & select_pop = control.GetPopulation(select_pop_id); + emp::IndexMap fit_map(select_pop.GetSize(), 0.0); + for (size_t org_pos = 0; org_pos < select_pop.GetSize(); org_pos++) { + if (select_pop.IsEmpty(org_pos)) continue; + fit_map[org_pos] = select_pop[org_pos].GetTrait(fitness_trait); + } + + // Loop through picking IDs proportional to fitness_trait, replicating each + Population & birth_pop = control.GetPopulation(birth_pop_id); + emp::Random & random = control.GetRandom(); + for (size_t num_reps = 0; num_reps < select_count; num_reps++) { + size_t org_id = fit_map.Index( random.GetDouble(fit_map.GetWeight()) ); + control.Replicate(select_pop.IteratorAt(org_id), birth_pop, copy_count); + } + } + }; + + MABE_REGISTER_MODULE(SelectRoulette, "Randomly choose organisms to replicate weighted by fitness."); +} + +#endif From 0cb0fe4ef74ba0fb1415998eeff0e2c1922c8d46 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:14:05 -0400 Subject: [PATCH 204/445] Linked Roulette selection into the full modules list. --- source/modules.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/modules.hpp b/source/modules.hpp index c6dd3ed6..b46d5625 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -24,8 +24,9 @@ // Selection Modules #include "select/SelectElite.hpp" -#include "select/SelectTournament.hpp" #include "select/SelectLexicase.hpp" +#include "select/SelectRoulette.hpp" +#include "select/SelectTournament.hpp" // Other schema #include "schema/MovePopulation.hpp" From 897548d25f5e4b032af56825cadaa8b4c93a3f07 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:14:59 -0400 Subject: [PATCH 205/445] Started restructuring ConfigEntry::As<>() --- source/config/ConfigEntry.hpp | 46 +++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 579c34ee..21743940 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -36,6 +36,7 @@ namespace mabe { class ConfigEntry_Scope; + class ConfigType; class ConfigEntry { protected: @@ -117,10 +118,13 @@ namespace mabe { virtual double AsDouble() const { emp_assert(false); return 0.0; } virtual std::string AsString() const { emp_assert(false); return ""; } template - T As() const { + auto As() const { if constexpr (std::is_same()) return AsDouble(); else if constexpr (std::is_same()) return AsString(); - else static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); + else if constexpr (std::is_base_of()) { + + } + else static_assert(emp::dependent_false(), "Invalid conversion for const ConfigEntry::As()"); } virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } @@ -134,18 +138,34 @@ namespace mabe { /// A generic As() function that will call the appropriate converter. template - T As() { - using base_T = std::remove_const_t; - if constexpr (std::is_same>()) { return this; } - else if constexpr (std::is_same()) { return *this; } - else if constexpr (std::is_same()) { return AsString(); } - else if constexpr (std::is_same()) { return AsScope(); } - else if constexpr (std::is_arithmetic()) { return (T) AsDouble(); } + decltype(auto) As() { + // If a const type is requested, non-const can be converted, so work with that. + using decay_T = std::decay_t; + + // If we have a numeric or string request, run the appropriate conversion. + if constexpr (std::is_arithmetic()) { return (T) AsDouble(); } + else if constexpr (std::is_same()) { return AsString(); } + + // If we want either a pointer or reference to the current object, return it. + else if constexpr (std::is_same>()) { return this; } + else if constexpr (std::is_same()) { return *this; } + + // If we want a dervied ConfigEntry type, convert and return it. + else if constexpr (std::is_base_of()) { + emp::Ptr out_ptr = dynamic_cast(this); + emp_assert(out_ptr); + return *out_ptr; + } + + // If we want a user-defined type, it must be deriv4ed from ConfigType. + else if constexpr (std::is_base_of()) { + + } + + // Oh no! We don't know this type... else { - // Oh oh... we don't know this type... - emp_error("Trying to convert a ConfigEntry to an unknown type: ", - emp::GetTypeID().GetName()); - return base_T(); + static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); + return decay_T(); } } From b00c54ca680bce18dc87e8e0ad5020ef48e16261 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:15:29 -0400 Subject: [PATCH 206/445] Fixed typo in ConfigEntry_Scope. --- source/config/ConfigEntry_Scope.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index 1b3e48b4..7daf534b 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -189,7 +189,7 @@ namespace mabe { if (IsLocal()) cur_line += emp::to_string(GetTypename(), " "); cur_line += name; - bool has_body = emp::AnyOf(symbol_table, [](entry_ptr_t ptr){ return !ptr->IsBuiltin(); }; ); + bool has_body = emp::AnyOf(symbol_table, [](entry_ptr_t ptr){ return !ptr->IsBuiltin(); }); // Only open this scope if there are contents. cur_line += has_body ? " { " : ";"; From 305c750b5811dc1752b52726ce2677abce56e788 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 11 Oct 2021 16:16:49 -0400 Subject: [PATCH 207/445] Replaced Config::TypeInfo with ConfigTypeInfo (in its own file); removed unused GetIndex() --- source/config/Config.hpp | 105 ++++----------------------------------- 1 file changed, 9 insertions(+), 96 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index d7d6dbe5..4c644597 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -81,93 +81,12 @@ #include "ConfigLexer.hpp" #include "ConfigEntry_Scope.hpp" #include "ConfigType.hpp" +#include "ConfigTypeInfo.hpp" namespace mabe { class Config { public: - // TypeInfo tracks a particular type to be used in the configuration langauge. - struct TypeInfo { - size_t index; - std::string desc; - emp::TypeID type_id; - - using init_fun_t = std::function; - init_fun_t init_fun; - - using entry_ptr_t = emp::Ptr; - using member_fun_t = std::function &)>; - emp::map member_funs; - - emp::vector< entry_ptr_t > member_list; ///< Member functions for this type - emp::map< std::string, entry_ptr_t > entry_map; ///< Lookup table for member functions. - - // Constructor to allow a simple new configuration type - TypeInfo(size_t in_id, const std::string & in_desc) - : index(in_id), desc(in_desc) { } - - // Constructor to allow a new configuration type whose objects require initialization. - TypeInfo(size_t in_id, const std::string & in_desc, init_fun_t in_init) - : index(in_id), desc(in_desc), init_fun(in_init) - { - } - - // Link this TypeInfo object to a real C++ type. - template - void LinkType() { - static_assert(std::is_base_of(), - "Only ConfigType objects can be used as a custom config type."); - type_id = emp::GetTypeID(); - } - - // Add a member function that can be called on objects of this type. - template - void AddMemberFunction( - const std::string & name, - std::function fun - ) { - // ----- Make sure function is legal ----- - // Is return type legal? - static_assert(std::is_arithmetic() || std::is_same(), - "Config member functions must of a string or arithmetic return type"); - - // Is the first parameter the correct type? - emp_assert( type_id.IsType(), - "First parameter must match config type of member function being created!", - type_id, emp::GetTypeID() ); - - // Are remaining parameters legal? - constexpr bool params_ok = - ((std::is_arithmetic() || std::is_same()) && ...); - static_assert(params_ok, "Parameters 2+ in a member function must be string or arithmetic."); - - // ----- Transform this function into one that TypeInfo can make use of ---- - member_fun_t member_fun = - [name,fun](ConfigType & obj, const emp::vector & args) { - // Make sure we can convert the obj into the correct type. - emp::Ptr typed_ptr = dynamic_cast(&obj); - - // Make sure we have the correct number of arguments. - if (args.size() != sizeof...(PARAM_Ts)) { - std::cerr << "Error in call to function '" << name - << "'; expected " << sizeof...(PARAM_Ts) - << " arguments, but received " << args.size() << "." - << std::endl; - } - //@CAO should collect file position information for the above error. - - // Call the provided function and return the result. - int arg_id = 0; - RETURN_T result = fun( *typed_ptr, args[arg_id++]->As()... ); - - return result; - }; - - // Add this member function to the library we are building. - member_funs[name] = member_fun; - } - }; - using pos_t = emp::TokenStream::Iterator; protected: @@ -182,7 +101,7 @@ namespace mabe { std::map events_map; /// A map of all types available in the script. - std::unordered_map> type_map; + std::unordered_map> type_map; /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -308,11 +227,11 @@ namespace mabe { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable" ); - type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable" ); - type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure" ); + type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable" ); + type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable" ); + type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure" ); // Setup operator precedence. size_t cur_prec = 0; @@ -471,18 +390,12 @@ namespace mabe { const std::string & desc, std::function init_fun ) { - emp_assert(!emp::Has(type_map, type_name)); + emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - type_map[type_name] = emp::NewPtr( index, desc, init_fun ); + type_map[type_name] = emp::NewPtr( index, desc, init_fun ); return index; } - /// Retrieve a unique type ID by providing the type name. - size_t GetIndex(const std::string & type_name) { - emp_assert(emp::Has(type_map, type_name)); - return type_map[type_name]->index; - } - /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided std::function must take a From 5107946cdd9fa612bdf60a339d924af24fcf88be Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 12 Oct 2021 14:54:05 -0400 Subject: [PATCH 208/445] Built an easy-to-access base class for ConfigType. --- source/config/ConfigTypeBase.hpp | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 source/config/ConfigTypeBase.hpp diff --git a/source/config/ConfigTypeBase.hpp b/source/config/ConfigTypeBase.hpp new file mode 100644 index 00000000..4e9c0411 --- /dev/null +++ b/source/config/ConfigTypeBase.hpp @@ -0,0 +1,52 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file ConfigTypeBase.hpp + * @brief Base class for setting up custom types for use in scripting; usable throughout. + * @note Status: ALPHA + */ + +#ifndef MABE_CONFIG_TYPE_BASE_HPP +#define MABE_CONFIG_TYPE_BASE_HPP + +#include "emp/base/assert.hpp" + +namespace mabe { + + class ConfigEntry_Scope; + class ConfigTypeInfo; + + enum class BaseType { + INVALID = 0, + VOID, + VALUE, + STRING, + STRUCT + }; + + class ConfigTypeBase { + protected: + emp::Ptr cur_scope; + emp::Ptr type_info_ptr; + + // Some special, internal variables associated with each object. + bool _active=true; ///< Should this object be used in the current run? + std::string _desc=""; ///< Special description for this object. + + public: + virtual ~ConfigTypeBase() { } + + virtual void SetupConfig() = 0; + + ConfigEntry_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + const ConfigEntry_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + + ConfigTypeInfo & GetTypeInfo() { return *type_info_ptr; } + void SetTypeInfo( ConfigTypeInfo & _info ) { type_info_ptr = &_info; } + }; + +} + +#endif From d180f85f5b57b1ca10213ea6666cb0bb6b5d454f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 12 Oct 2021 14:54:30 -0400 Subject: [PATCH 209/445] Setup ConfigType to use new base class. --- source/config/ConfigType.hpp | 39 ++++++++++++------------------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 201580d0..8045d252 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -13,29 +13,24 @@ #include "emp/base/assert.hpp" -#include "ConfigEntry.hpp" +#include "ConfigTypeBase.hpp" #include "ConfigEntry_Scope.hpp" +#include "ConfigTypeInfo.hpp" namespace mabe { - enum class BaseType { - INVALID = 0, - VOID, - VALUE, - STRING, - STRUCT - }; - // Base class for types that we want to be used for scripting. - class ConfigType { - private: - emp::Ptr cur_scope; - - public: - // Some special, internal variables associated with each object. - bool _active=true; ///< Should this object be used in the current run? - std::string _desc=""; ///< Special description for this object. - + class ConfigType : public ConfigTypeBase { + public: + void SetupScope(ConfigEntry_Scope & scope) { + cur_scope = &scope; + + // Setup standard internal variables for this scope. + LinkVar(_active, "_active", "Should we activate this module? (0=off, 1=on)", true); + LinkVar(_desc, "_desc", "Special description for those object.", true); + } + + // ---== Configuration Management ==--- /// Link a variable to a configuration entry - the value will default to the @@ -109,14 +104,6 @@ namespace mabe { return GetScope().LinkFuns(name, get_fun, set_fun, new_desc.str()); } - - public: - virtual void SetupScope(ConfigEntry_Scope & scope) { cur_scope = &scope; } - virtual void SetupConfig() = 0; - virtual ~ConfigType() { } - - ConfigEntry_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - const ConfigEntry_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } }; } From 2cd984d5506df3a27ab49de99d5bcf80ab7efab0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 12 Oct 2021 14:54:58 -0400 Subject: [PATCH 210/445] Updated TODOs; added levelization map. --- source/config/TODO | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/source/config/TODO b/source/config/TODO index 5acef959..044f40f7 100644 --- a/source/config/TODO +++ b/source/config/TODO @@ -1,3 +1,29 @@ +Type System +Symbol Tables +Parser + +LEVEL MAP: + +ConfigEntry - [] +ConfigLexer - [] +ConfigTypeInfo - [] Basic information for a user-defined type. +ConfigTypeBase - [] + +ConfigEntry_Function - [ConfigEntry] +ConfigEntry_Linked - [ConfigEntry] + +ConfigEntry_Scope - [ConfigEntry, ConfigEntry_Function, ConfigEntry_Linked, ConfigTypeBase] + +ConfigAST - [ConfigEntry_Scope, ConfigEntry] +ConfigType - [ConfigEntry_Scope] + +ConfigEvents - [ConfigAST] + +Config - Main parser + + +TODO: + * Config as a whole should move from MABE to Empirical * We need a consistent and functional error system. @@ -16,26 +42,21 @@ parameter types. * Functions should be able to be defined inside of a config script. -* We need a symbol to turn off a whole statement. -The parser should keep going until the end of the statement, but then ignore everything -it said to do. This will allow us to more easily turn on and off whole objects that we -want to sometimes build. e.g.: !Population pop_special; - * Arrays (possibly including generators that act like open-ended arrays). -* Literal arrays/generators. 5:10 should be 5,15,25,35,... while 6:1:9 should be 6,7,8,9 +* Literal arrays/generators. +5:10 should be 5,15,25,35,... while 6:1:9 should be 6,7,8,9 +(More sophisticate schemes are also out there, and we could match them...) -* Built-in types for scopes, probably beginning with an underscope. +* Built-in types for scopes, probably beginning with an underscore. We now have _active, which if set to false will deactivate an object. We have _desc added to handle custom descriptions for object (different from the class description), -but it's not hooked in. We would also want things like my_array._size as the number of -elements in the array. my_scope._var_names will be an array of all variable names in the -current scope. +but it's not hooked in. -* Need a stand-alone config interpretor along with a full test suite to make sure it's +* Need a stand-alone config interpreter along with a full test suite to make sure it's working properly. -* Should be able to make new types (not just new objects) inside the interpretor. +* Should be able to make new types (not just new instances) inside the interpreter. * Should be able to adjust default values for existing types. This will allow for config files to be included in that just setup defaults, but don't actually build any new object. From f7be3c7607fe038f275ec46c52c1b20d5bd3931d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 12 Oct 2021 14:55:38 -0400 Subject: [PATCH 211/445] Renamed TODO to DeveloperNotes.md --- source/config/{TODO => DeveloperNotes.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename source/config/{TODO => DeveloperNotes.md} (100%) diff --git a/source/config/TODO b/source/config/DeveloperNotes.md similarity index 100% rename from source/config/TODO rename to source/config/DeveloperNotes.md From 2e16cb6396cc763b04b0ac8995516afef7332a79 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 12 Oct 2021 14:56:14 -0400 Subject: [PATCH 212/445] Cleaned up ConfigTypeInfo and set to take more info. --- source/config/ConfigTypeInfo.hpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp index 767de23a..a3e6a282 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/config/ConfigTypeInfo.hpp @@ -18,11 +18,15 @@ #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" +class ConfigEntry; + namespace mabe { // ConfigTypeInfo tracks a particular type to be used in the configuration langauge. - struct ConfigTypeInfo { + class ConfigTypeInfo { + private: size_t index; + std::string type_name; std::string desc; emp::TypeID type_id; @@ -36,16 +40,24 @@ namespace mabe { emp::vector< entry_ptr_t > member_list; ///< Member functions for this type emp::map< std::string, entry_ptr_t > entry_map; ///< Lookup table for member functions. + public: // Constructor to allow a simple new configuration type - ConfigTypeInfo(size_t in_id, const std::string & in_desc) - : index(in_id), desc(in_desc) { } + ConfigTypeInfo(size_t in_id, const std::string & in_name, const std::string & in_desc) + : index(in_id), type_name(in_name), desc(in_desc) { } // Constructor to allow a new configuration type whose objects require initialization. - ConfigTypeInfo(size_t in_id, const std::string & in_desc, init_fun_t in_init) - : index(in_id), desc(in_desc), init_fun(in_init) + ConfigTypeInfo(size_t in_id, const std::string & in_name, const std::string & in_desc, init_fun_t in_init) + : index(in_id), type_name(in_name), desc(in_desc), init_fun(in_init) { } + size_t GetIndex() const { return index; } + const std::string & GetTypeName() const { return type_name; } + const std::string & GetDesc() const { return desc; } + emp::TypeID GetType() const { return type_id; } + + ConfigType & MakeObj(const std::string & name) const { return init_fun(name); } + // Link this ConfigTypeInfo object to a real C++ type. template void LinkType() { From 24a03fe0e856fd10472f8ed339eccb2413ed6c72 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 13 Oct 2021 09:39:57 -0400 Subject: [PATCH 213/445] Updated As() to handle object pointers; remove const version. --- source/config/ConfigEntry.hpp | 47 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 21743940..e8931fbd 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -117,15 +117,6 @@ namespace mabe { virtual double AsDouble() const { emp_assert(false); return 0.0; } virtual std::string AsString() const { emp_assert(false); return ""; } - template - auto As() const { - if constexpr (std::is_same()) return AsDouble(); - else if constexpr (std::is_same()) return AsString(); - else if constexpr (std::is_base_of()) { - - } - else static_assert(emp::dependent_false(), "Invalid conversion for const ConfigEntry::As()"); - } virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual ConfigEntry & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } @@ -136,36 +127,50 @@ namespace mabe { return *(AsScopePtr()); } + virtual emp::Ptr GetObjectPtr() { return nullptr; } + virtual emp::Ptr GetObjectPtr() const { return nullptr; } + /// A generic As() function that will call the appropriate converter. template decltype(auto) As() { // If a const type is requested, non-const can be converted, so work with that. - using decay_T = std::decay_t; + using test_T = std::remove_cv_t; + constexpr bool is_nonconst_ref = !std::is_const_v && std::is_reference_v; // If we have a numeric or string request, run the appropriate conversion. - if constexpr (std::is_arithmetic()) { return (T) AsDouble(); } - else if constexpr (std::is_same()) { return AsString(); } + if constexpr (std::is_arithmetic() && !is_nonconst_ref) { + return static_cast(AsDouble()); + } + else if constexpr (std::is_same() || + std::is_same()) { + return AsString(); + } // If we want either a pointer or reference to the current object, return it. - else if constexpr (std::is_same>()) { return this; } - else if constexpr (std::is_same()) { return *this; } + else if constexpr (std::is_same>()) { return this; } + else if constexpr (std::is_same()) { return *this; } // If we want a dervied ConfigEntry type, convert and return it. - else if constexpr (std::is_base_of()) { - emp::Ptr out_ptr = dynamic_cast(this); - emp_assert(out_ptr); + else if constexpr (std::is_base_of()) { + emp::Ptr out_ptr = dynamic_cast(this); + emp_assert(out_ptr); // @CAO: Should provide a user error. return *out_ptr; } // If we want a user-defined type, it must be deriv4ed from ConfigType. else if constexpr (std::is_base_of()) { - + emp::Ptr obj_ptr = GetObjectPtr(); + emp_assert(obj_ptr); // @CAO: Should provide a user error. + emp::Ptr out_ptr = dynamic_cast(obj_ptr); + emp_assert(out_ptr); // @CAO: Should provide a user error. + return *out_ptr; } // Oh no! We don't know this type... else { static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); - return decay_T(); + // emp_error(emp::GetTypeID()); // Run time error to print type info. + return test_T(); } } @@ -287,8 +292,8 @@ namespace mabe { }; using ConfigEntry_StringVar = ConfigEntry_Var; - /// A ConfigEntry to transmit an error. The description provides the error and the IsError() flag - /// is set to true. + /// A ConfigEntry to transmit an error due to invalid parsing. + /// The description provides the error and the IsError() flag is set to true. class ConfigEntry_Error : public ConfigEntry { private: using this_t = ConfigEntry_Error; From 6c703055220bd0b9d4aec63d7e86392cfe6dfa4e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 13 Oct 2021 09:40:30 -0400 Subject: [PATCH 214/445] Stop decaying function arguments beforee passing into As(), so refs can be managed. --- source/config/ConfigEntry_Function.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index c6497a58..5e7ca142 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -80,7 +80,7 @@ namespace mabe { ); } - return MakeTempEntry( in_fun((args[INDICES]->template As< std::decay_t >())...) ); + return MakeTempEntry( in_fun((args[INDICES]->template As())...) ); }; } From 2654ab18c317a9929a26610cb8ceb2893a64450e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 13 Oct 2021 09:41:22 -0400 Subject: [PATCH 215/445] Make scopes track any associated object pointers. --- source/config/ConfigEntry_Scope.hpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index 7daf534b..4a6994d2 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -20,9 +20,12 @@ #include "ConfigEntry.hpp" #include "ConfigEntry_Function.hpp" #include "ConfigEntry_Linked.hpp" +#include "ConfigTypeBase.hpp" namespace mabe { + class ConfigType; + // Set of multiple config entries. class ConfigEntry_Scope : public ConfigEntry { protected: @@ -30,8 +33,8 @@ namespace mabe { using const_entry_ptr_t = emp::Ptr; emp::map< std::string, entry_ptr_t > symbol_table; ///< Map of names to entries. - ///< If this scope represents a structure, identify the type (otherwise type is "") - const std::string type; + ///< If this scope represents a structure, point to it; otherwise set to null. + emp::Ptr obj_ptr = nullptr; template T & Add(const std::string & name, ARGS &&... args) { @@ -53,8 +56,8 @@ namespace mabe { ConfigEntry_Scope(const std::string & _name, const std::string & _desc, emp::Ptr _scope, - const std::string & _type="") - : ConfigEntry(_name, _desc, _scope), type(_type) { } + emp::Ptr _obj=nullptr) + : ConfigEntry(_name, _desc, _scope), obj_ptr(_obj) { } ConfigEntry_Scope(const ConfigEntry_Scope & in) : ConfigEntry(in) { // Copy all defined variables/scopes/functions @@ -67,7 +70,8 @@ namespace mabe { for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } - std::string GetTypename() const override { return type; } + emp::Ptr GetObjectPtr() override { return obj_ptr; } + emp::Ptr GetObjectPtr() const override { return obj_ptr; } bool IsScope() const override { return true; } bool IsLocal() const override { return true; } // @CAO, for now assuming all scopes are local! @@ -144,8 +148,12 @@ namespace mabe { } /// Add an internal scope inside of this one. - ConfigEntry_Scope & AddScope(const std::string & name, const std::string & desc, const std::string & type="") { - return Add(name, desc, this, type); + ConfigEntry_Scope & AddScope( + const std::string & name, + const std::string & desc, + emp::Ptr obj_ptr=nullptr + ) { + return Add(name, desc, this, obj_ptr); } /// Add a new user-defined function. From f9a5396d7d37229dceefefc2707036f608f55f6b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 13 Oct 2021 09:42:20 -0400 Subject: [PATCH 216/445] Provide ConfigTypeInfo with its type name. --- source/config/Config.hpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 4c644597..26b1301f 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -227,11 +227,11 @@ namespace mabe { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Numeric variable" ); - type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String variable" ); - type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "User-made structure" ); + type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "Struct", "User-made structure" ); // Setup operator precedence. size_t cur_prec = 0; @@ -392,7 +392,7 @@ namespace mabe { ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - type_map[type_name] = emp::NewPtr( index, desc, init_fun ); + type_map[type_name] = emp::NewPtr( index, type_name, desc, init_fun ); return index; } @@ -734,13 +734,21 @@ namespace mabe { return scope.AddScope(var_name, "Local struct"); } - // Otherwise we have a module to add; treat it as a struct. + // Otherwise we have an object of a custom type to add. Debug("Building var '", var_name, "' of type '", type_name, "'"); - ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_map[type_name]->desc, type_name); - ConfigType & new_obj = type_map[type_name]->init_fun(var_name); + + // Retrieve the information about the requested type. + ConfigTypeInfo & type_info = *type_map[type_name]; + + // Use the ConfigTypeInfo associated with the provided type name to build an instance. + ConfigType & new_obj = type_info.MakeObj(var_name); + new_obj.SetTypeInfo(type_info); + + // Setup a scope for this new type, linking the object to it. + ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_map[type_name]->GetDesc(), &new_obj); + + // Let the new object know about its scope. new_obj.SetupScope(new_scope); - new_obj.LinkVar(new_obj._active, "_active", "Should we activate this module? (0=off, 1=on)", true); - new_obj.LinkVar(new_obj._desc, "_desc", "Special description for those object.", true); new_obj.SetupConfig(); return new_scope; From e786fe61adf7e377d33e114109fd6e114fa011c8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 13 Oct 2021 12:41:28 -0400 Subject: [PATCH 217/445] More adjustments to As() -- seems to finally work. --- source/config/ConfigEntry.hpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index e8931fbd..ec4fff47 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -134,34 +134,34 @@ namespace mabe { template decltype(auto) As() { // If a const type is requested, non-const can be converted, so work with that. - using test_T = std::remove_cv_t; + using decay_T = std::decay_t; constexpr bool is_nonconst_ref = !std::is_const_v && std::is_reference_v; // If we have a numeric or string request, run the appropriate conversion. - if constexpr (std::is_arithmetic() && !is_nonconst_ref) { + if constexpr (std::is_arithmetic() && !is_nonconst_ref) { return static_cast(AsDouble()); } - else if constexpr (std::is_same() || + else if constexpr (std::is_same() || std::is_same()) { return AsString(); } // If we want either a pointer or reference to the current object, return it. - else if constexpr (std::is_same>()) { return this; } - else if constexpr (std::is_same()) { return *this; } + else if constexpr (std::is_same>()) { return this; } + else if constexpr (std::is_same()) { return *this; } // If we want a dervied ConfigEntry type, convert and return it. - else if constexpr (std::is_base_of()) { - emp::Ptr out_ptr = dynamic_cast(this); + else if constexpr (std::is_base_of()) { + emp::Ptr out_ptr = dynamic_cast(this); emp_assert(out_ptr); // @CAO: Should provide a user error. return *out_ptr; } // If we want a user-defined type, it must be deriv4ed from ConfigType. - else if constexpr (std::is_base_of()) { + else if constexpr (std::is_base_of()) { emp::Ptr obj_ptr = GetObjectPtr(); emp_assert(obj_ptr); // @CAO: Should provide a user error. - emp::Ptr out_ptr = dynamic_cast(obj_ptr); + emp::Ptr out_ptr = obj_ptr.DynamicCast(); emp_assert(out_ptr); // @CAO: Should provide a user error. return *out_ptr; } @@ -169,8 +169,9 @@ namespace mabe { // Oh no! We don't know this type... else { static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); - // emp_error(emp::GetTypeID()); // Run time error to print type info. - return test_T(); + emp_error(emp::GetTypeID()); // Print more info when above line is commented out. + auto out = emp::NewPtr>(); + return (T) *out; } } From 4e71ee86edb1ecc85d4dcb6c735ba407c30e49cd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:04:42 -0400 Subject: [PATCH 218/445] Cleanup on ConfigEntry with entry_ptr_t typedef. --- source/config/ConfigEntry.hpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index ec4fff47..555ff76b 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -57,6 +57,8 @@ namespace mabe { emp::Range range; ///< Min and max values allowed for this config entry (if numerical). bool integer_only=false; ///< Should we only allow integer values? + using entry_ptr_t = emp::Ptr; + // Helper functions. /// Write out the provided description at the comment_offset. The start_pos is where the @@ -157,7 +159,7 @@ namespace mabe { return *out_ptr; } - // If we want a user-defined type, it must be deriv4ed from ConfigType. + // If we want a user-defined type, it must be derived from ConfigType. else if constexpr (std::is_base_of()) { emp::Ptr obj_ptr = GetObjectPtr(); emp_assert(obj_ptr); // @CAO: Should provide a user error. @@ -182,8 +184,7 @@ namespace mabe { virtual bool CopyValue(const ConfigEntry & ) { return false; } /// If this entry is a scope, we should be able to lookup other entries inside it. - virtual emp::Ptr - LookupEntry(const std::string & in_name, bool /* scan_scopes */=true) { + virtual entry_ptr_t LookupEntry(const std::string & in_name, bool /* scan_scopes */=true) { return (in_name == "") ? this : nullptr; } virtual emp::Ptr @@ -193,10 +194,10 @@ namespace mabe { virtual bool Has(const std::string & in_name) const { return (bool) LookupEntry(in_name); } /// If this entry is a function, we should be able to call it. - virtual emp::Ptr Call( emp::vector> args ); + virtual entry_ptr_t Call(const emp::vector & args); /// Allocate a duplicate of this class. - virtual emp::Ptr Clone() const = 0; + virtual entry_ptr_t Clone() const = 0; virtual const ConfigEntry & Write(std::ostream & os=std::cout, const std::string & prefix="", size_t comment_offset=32) const @@ -228,6 +229,8 @@ namespace mabe { private: T value = 0; public: + static_assert(std::is_arithmetic(), "ConfigEntry_Var must use std::string or arithmetic values."); + using this_t = ConfigEntry_Var; template @@ -243,7 +246,7 @@ namespace mabe { else return "Unknown"; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + entry_ptr_t Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return (double) value; } std::string AsString() const override { return emp::to_string(value); } @@ -279,7 +282,7 @@ namespace mabe { std::string GetTypename() const override { return "String"; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + entry_ptr_t Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return emp::from_string(value); } std::string AsString() const override { return value; } @@ -307,14 +310,14 @@ namespace mabe { bool IsError() const override { return true; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + entry_ptr_t Clone() const override { return emp::NewPtr(*this); } }; //////////////////////////////////////////////////// // Function definitions... - emp::Ptr ConfigEntry::Call( emp::vector> /* args */ ) { + emp::Ptr ConfigEntry::Call( const emp::vector & /* args */ ) { return emp::NewPtr("Cannot call a function on non-function '", name, "'."); } From ba027f79118f6294c5407da72ae3ed964a5eca2c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:06:09 -0400 Subject: [PATCH 219/445] Made ConfigTypeBase::SetupConfig optional to override; removed SetTypeInfo --- source/config/ConfigTypeBase.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/config/ConfigTypeBase.hpp b/source/config/ConfigTypeBase.hpp index 4e9c0411..b28a9346 100644 --- a/source/config/ConfigTypeBase.hpp +++ b/source/config/ConfigTypeBase.hpp @@ -38,13 +38,13 @@ namespace mabe { public: virtual ~ConfigTypeBase() { } - virtual void SetupConfig() = 0; + // Optional function to override to add configuration options associated with an object. + virtual void SetupConfig() { }; ConfigEntry_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } const ConfigEntry_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - ConfigTypeInfo & GetTypeInfo() { return *type_info_ptr; } - void SetTypeInfo( ConfigTypeInfo & _info ) { type_info_ptr = &_info; } + const ConfigTypeInfo & GetTypeInfo() const { return *type_info_ptr; } }; } From ce6f6fdc83ee26a010b7509ec0ef2c295912f18b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:07:03 -0400 Subject: [PATCH 220/445] Expanded ConfigType::Setup to setup scope, info, and member functions. --- source/config/ConfigType.hpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 8045d252..5430cce2 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -21,13 +21,29 @@ namespace mabe { // Base class for types that we want to be used for scripting. class ConfigType : public ConfigTypeBase { - public: - void SetupScope(ConfigEntry_Scope & scope) { - cur_scope = &scope; + public: + // Setup a new ConfigType object; provide it with its scope and type information. + void Setup(ConfigEntry_Scope & _scope, ConfigTypeInfo & _info) { + cur_scope = &_scope; + type_info_ptr = &_info; - // Setup standard internal variables for this scope. + // Link standard internal variables for this scope. LinkVar(_active, "_active", "Should we activate this module? (0=off, 1=on)", true); LinkVar(_desc, "_desc", "Special description for those object.", true); + + // Link specialized variable for the derived type. + SetupConfig(); + + // Load in any member function for this object into the scope. + using entry_ptr_t = emp::Ptr; + using member_fun_t = std::function &)>; + const auto & member_map = type_info_ptr->GetMemberFunctions(); + for (const MemberFunInfo & member_info : member_map) { + member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ + return member_info.fun(*this, args); + }; + cur_scope->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); + } } From 263736f266174faf42cf335c4ccfe4d7919c43db Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:08:17 -0400 Subject: [PATCH 221/445] Setup ConfigEntry_Function::SetFunction to handle more return types and errors. --- source/config/ConfigEntry_Function.hpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index 5e7ca142..26d4589d 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -91,7 +91,23 @@ namespace mabe { /// assume that the function will handle any conversions itself. if constexpr (sizeof...(ARGS) == 0 && std::is_same()) { fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { - return MakeTempEntry(in_fun(args)); + // If this function already returns a ConfigEntry pointer, pass it along. + if constexpr (std::is_same>()) { + return in_fun(args); + } + + // If this function returns a basic type, wrap it in a temp entry. + else if constexpr (std::is_same() || + std::is_arithmetic()) { + return MakeTempEntry(in_fun(args)); + } + + // For now these are the only legal return type; raise error otherwise! + else { + emp::ShowType{}; + static_assert(emp::dependent_false(), + "Invalid return value in ConfigEntry_Function::SetFunction()"); + } }; } @@ -101,7 +117,7 @@ namespace mabe { } } - entry_ptr_t Call( emp::vector args ) override { return fun(args); } + entry_ptr_t Call( const emp::vector & args ) override { return fun(args); } }; From 3ea53b19aa3d249f3d6f478390b4524bbc6d100d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:08:57 -0400 Subject: [PATCH 222/445] Major reorganization of ConfigTypeInfo; added MemberFunInfo struct. --- source/config/ConfigTypeInfo.hpp | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp index a3e6a282..80a6e5e1 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/config/ConfigTypeInfo.hpp @@ -14,7 +14,6 @@ #include #include "emp/base/assert.hpp" -#include "emp/base/map.hpp" #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" @@ -22,23 +21,30 @@ class ConfigEntry; namespace mabe { + // Information about a member function. + struct MemberFunInfo { + using entry_ptr_t = emp::Ptr; + using fun_t = std::function &)>; + + std::string name; + std::string desc; + fun_t fun; + }; + // ConfigTypeInfo tracks a particular type to be used in the configuration langauge. class ConfigTypeInfo { private: + using entry_ptr_t = emp::Ptr; + using init_fun_t = std::function; + size_t index; std::string type_name; std::string desc; emp::TypeID type_id; - using init_fun_t = std::function; init_fun_t init_fun; - using entry_ptr_t = emp::Ptr; - using member_fun_t = std::function &)>; - emp::map member_funs; - - emp::vector< entry_ptr_t > member_list; ///< Member functions for this type - emp::map< std::string, entry_ptr_t > entry_map; ///< Lookup table for member functions. + emp::vector< MemberFunInfo > member_funs; public: // Constructor to allow a simple new configuration type @@ -55,6 +61,7 @@ namespace mabe { const std::string & GetTypeName() const { return type_name; } const std::string & GetDesc() const { return desc; } emp::TypeID GetType() const { return type_id; } + const emp::vector & GetMemberFunctions() const { return member_funs; } ConfigType & MakeObj(const std::string & name) const { return init_fun(name); } @@ -70,29 +77,32 @@ namespace mabe { template void AddMemberFunction( const std::string & name, - std::function fun + std::function fun, + const std::string & desc ) { // ----- Make sure function is legal ----- // Is return type legal? - static_assert(std::is_arithmetic() || std::is_same(), - "Config member functions must of a string or arithmetic return type"); + static_assert(std::is_arithmetic() || + std::is_same() || + std::is_same>(), + "Config member function return types must be string, arithmetic, or Ptr"); // Is the first parameter the correct type? + static_assert(std::is_base_of::type>(), + "Member functions must take a reference to the associated ConfigType"); emp_assert( type_id.IsType(), "First parameter must match config type of member function being created!", type_id, emp::GetTypeID() ); - // Are remaining parameters legal? - constexpr bool params_ok = - ((std::is_arithmetic() || std::is_same()) && ...); - static_assert(params_ok, "Parameters 2+ in a member function must be string or arithmetic."); - // ----- Transform this function into one that ConfigTypeInfo can make use of ---- - member_fun_t member_fun = - [name,fun](ConfigType & obj, const emp::vector & args) { + MemberFunInfo::fun_t member_fun = + [name,fun](ConfigType & obj, const emp::vector & args) -> RETURN_T { // Make sure we can convert the obj into the correct type. emp::Ptr typed_ptr = dynamic_cast(&obj); + emp_assert(typed_ptr, "Internal Error: member function called on wrong object type!", + name); + // Make sure we have the correct number of arguments. if (args.size() != sizeof...(PARAM_Ts)) { std::cerr << "Error in call to function '" << name @@ -100,17 +110,15 @@ namespace mabe { << " arguments, but received " << args.size() << "." << std::endl; } - //@CAO should collect file position information for the above error. + //@CAO should collect file position information for the above errors. // Call the provided function and return the result. int arg_id = 0; - RETURN_T result = fun( *typed_ptr, args[arg_id++]->As()... ); - - return result; + return fun( *typed_ptr, args[arg_id++]->As()... ); }; // Add this member function to the library we are building. - member_funs[name] = member_fun; + member_funs.emplace_back(name, desc, member_fun); } }; From ed540b86330969aec79af02a86b34aca22ccc99b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 14 Oct 2021 17:09:54 -0400 Subject: [PATCH 223/445] Setup Config::AddType() to return new type info; cleanup on building new config objects. --- source/config/Config.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 26b1301f..cb9e711f 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -385,7 +385,7 @@ namespace mabe { /// To add a type, provide the type name (that can be referred to in a script) and a function /// that should be called (with the variable name) when an instance of that type is created. /// The function must return a reference to the newly created instance. - size_t AddType( + ConfigTypeInfo & AddType( const std::string & type_name, const std::string & desc, std::function init_fun @@ -393,7 +393,7 @@ namespace mabe { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); type_map[type_name] = emp::NewPtr( index, type_name, desc, init_fun ); - return index; + return *type_map[type_name]; } /// To add a built-in function (at the root level) provide it with a name and description. @@ -517,7 +517,7 @@ namespace mabe { // Lookup this variable. emp::Ptr cur_entry = cur_scope.LookupEntry(var_name, scan_scopes); - // If we can't find this variable, either build it or throw an error. + // If we can't find this variable, throw an error. if (cur_entry.IsNull()) { Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type."); } @@ -742,14 +742,12 @@ namespace mabe { // Use the ConfigTypeInfo associated with the provided type name to build an instance. ConfigType & new_obj = type_info.MakeObj(var_name); - new_obj.SetTypeInfo(type_info); // Setup a scope for this new type, linking the object to it. ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_map[type_name]->GetDesc(), &new_obj); // Let the new object know about its scope. - new_obj.SetupScope(new_scope); - new_obj.SetupConfig(); + new_obj.Setup(new_scope, type_info); return new_scope; } From 3183f836d25ecd35fef22e437e06bbde75d0aac9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 20 Oct 2021 23:20:02 -0400 Subject: [PATCH 224/445] Cleaned upsome comments in Organism.hpp --- source/core/Organism.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index b466ab20..01c00b87 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -60,13 +60,12 @@ namespace mabe { return OrgType::Recombine(other_parents, random); } - /// Produce an asexual offspring WITH MUTATIONS. By default, use Clone() and then Mutate(). + /// Produce an asexual offspring WITH MUTATIONS. [[nodiscard]] emp::Ptr MakeOffspringOrganism(emp::Random & random) const { return OrgType::MakeOffspring(random).DynamicCast(); } - /// Produce an sexual (two parent) offspring WITH MUTATIONS. By default, use Recombine() and - /// then Mutate(). + /// Produce an sexual (two parent) offspring WITH MUTATIONS. [[nodiscard]] emp::Ptr MakeOffspringOrganism(emp::Ptr parent2, emp::Random & random) const { return OrgType::MakeOffspring(parent2, random).DynamicCast(); From 0e40e69e6dfa9bce01b66b9e3c10bbcbef5ee881 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 20 Oct 2021 23:27:42 -0400 Subject: [PATCH 225/445] Added a ConfigTools for common configuration tasks. --- source/config/ConfigTools.hpp | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 source/config/ConfigTools.hpp diff --git a/source/config/ConfigTools.hpp b/source/config/ConfigTools.hpp new file mode 100644 index 00000000..c94d8e35 --- /dev/null +++ b/source/config/ConfigTools.hpp @@ -0,0 +1,94 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file ConfigTools.hpp + * @brief Tools for working with ConfigEntry objects. + * @note Status: BETA + */ + +#ifndef MABE_CONFIG_TOOLS_HPP +#define MABE_CONFIG_TOOLS_HPP + +#include + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/datastructs/tuple_utils.hpp" +#include "emp/meta/FunInfo.hpp" +#include "emp/meta/ValPack.hpp" + +#include "ConfigEntry.hpp" +#include "ConfigTools.hpp" + +namespace mabe { +namespace ConfigTools { + + using entry_ptr_t = emp::Ptr; + using entry_vector_t = emp::vector; + using target_t = entry_ptr_t( const entry_vector_t & ); + + // Use ConfigTools::MakeTempEntry(value) to quickly allocate a temporary entry with a + // given value. NOTE: Caller is responsible for deleting the created entry! + template + static emp::Ptr> MakeTempEntry(VALUE_T value) { + auto out_entry = emp::NewPtr>("__Temp", value, "", nullptr); + out_entry->SetTemporary(); + return out_entry; + } + + + template struct WrapFunction_impl; + + template + struct WrapFunction_impl { + + static auto ConvertReturn( RETURN_T && return_value ) { + // If a return value is already an entry pointer, just pass it through. + if constexpr (std::is_same()) { + return return_value; + } + + // If a return value is a basic type, wrap it in a temporary entry + if constexpr (std::is_same() || + std::is_arithmetic()) { + return MakeTempEntry(return_value); + } + + // For now these are the only legal return type; raise error otherwise! + else { + emp::ShowType{}; + static_assert(emp::dependent_false(), + "Invalid return value in ConfigEntry_Function::SetFunction()"); + } + } + + template + static auto ConvertFun(FUN_T fun) { + // If this function is already the correct type, just pass it along. + if constexpr (std::is_same()) { return fun; } + + // Otherwise convert types as needed. + else { + return [fun=fun](const entry_vector_t & args) { + emp_assert(args.size() == sizeof...(PARAM_Ts), "Wrong argument count!"); + size_t i = 0; + return ConvertReturn( fun(args[i]->As()...) ); + }; + } + + } + }; + + // Wrap a provided function to make sure it takes a vector of Ptr and returns a + // single Ptr representing the result. + template + static auto WrapFunction(FUN_T fun) { + return WrapFunction_impl::fun_t>::ConvertFun(fun); + } + +} +} + +#endif From 271370480e73057054e8335d0c7290f970bb30ce Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 21 Oct 2021 23:38:48 -0400 Subject: [PATCH 226/445] Updated ConfigTools to be able to convert member functions. --- source/config/ConfigTools.hpp | 92 ++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/source/config/ConfigTools.hpp b/source/config/ConfigTools.hpp index c94d8e35..a7997d93 100644 --- a/source/config/ConfigTools.hpp +++ b/source/config/ConfigTools.hpp @@ -38,47 +38,90 @@ namespace ConfigTools { return out_entry; } + template + static auto ConvertReturn( RETURN_T && return_value ) { + // If a return value is already an entry pointer, just pass it through. + if constexpr (std::is_same()) { + return return_value; + } - template struct WrapFunction_impl; + // If a return value is a basic type, wrap it in a temporary entry + if constexpr (std::is_same() || + std::is_arithmetic()) { + return MakeTempEntry(return_value); + } - template - struct WrapFunction_impl { + // For now these are the only legal return type; raise error otherwise! + else { + emp::ShowType{}; + static_assert(emp::dependent_false(), + "Invalid return value in ConfigEntry_Function::SetFunction()"); + } + } - static auto ConvertReturn( RETURN_T && return_value ) { - // If a return value is already an entry pointer, just pass it through. - if constexpr (std::is_same()) { - return return_value; - } - // If a return value is a basic type, wrap it in a temporary entry - if constexpr (std::is_same() || - std::is_arithmetic()) { - return MakeTempEntry(return_value); - } + template struct WrapFunction_impl; - // For now these are the only legal return type; raise error otherwise! - else { - emp::ShowType{}; - static_assert(emp::dependent_false(), - "Invalid return value in ConfigEntry_Function::SetFunction()"); - } + // Specialization for functions with NO arguments + template + struct WrapFunction_impl { + + template + static auto ConvertFun(FUN_T fun) { + return [fun=fun]([[maybe_unused]] const entry_vector_t & args) { + emp_assert(args.size() == 0, "Too many arguments (expected 0)", args.size()); + return ConvertReturn( fun() ); + }; } + }; + + // Specialization for functions with AT LEAST ONE argument. + template + struct WrapFunction_impl { + using this_fun_t = RETURN_T(PARAM1_T, PARAM_Ts...); + template static auto ConvertFun(FUN_T fun) { // If this function is already the correct type, just pass it along. - if constexpr (std::is_same()) { return fun; } + if constexpr (std::is_same()) { return fun; } // Otherwise convert types as needed. else { return [fun=fun](const entry_vector_t & args) { - emp_assert(args.size() == sizeof...(PARAM_Ts), "Wrong argument count!"); + emp_assert(args.size() == 1+sizeof...(PARAM_Ts), "Wrong argument count!"); size_t i = 0; - return ConvertReturn( fun(args[i]->As()...) ); + return ConvertReturn( fun(args[i++]->As(), args[i++]->As()...) ); }; } + } + template + static auto ConvertMemberFun(FUN_T fun) { + // If this function is already the correct type, just pass it along. + if constexpr (sizeof...(PARAM_Ts) == 1) { + using arg1_t = typename emp::FunInfo::template arg_t<1>; + if (std::is_reference_v && + std::is_base_of_v> && + std::is_same_v) { + return fun; + } + } + + // Otherwise convert types as needed. + else { + return [fun=fun](ConfigType & obj, const entry_vector_t & args) { + emp_assert(args.size() == sizeof...(PARAM_Ts), "Wrong argument count!", + args.size(), sizeof...(PARAM_Ts)); + emp::Ptr obj_ptr(&obj); + auto out_ptr = obj_ptr.DynamicCast(); + emp_assert(out_ptr, "Internal error: member function call on wrong object type!"); + size_t i = 0; + return ConvertReturn( fun(*out_ptr, args[i++]->As()...) ); + }; + } } + }; // Wrap a provided function to make sure it takes a vector of Ptr and returns a @@ -88,6 +131,11 @@ namespace ConfigTools { return WrapFunction_impl::fun_t>::ConvertFun(fun); } + template + static auto WrapMemberFunction(FUN_T fun) { + return WrapFunction_impl::fun_t>::ConvertMemberFun(fun); + } + } } From 7f764162c9bf3aa077ab46911af5655ee6d581a0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:44:13 -0400 Subject: [PATCH 227/445] Shifted AST calls to MakeTempEntry() to use ConfigTools versions. --- source/config/ConfigAST.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp index dec21cff..2186af8c 100644 --- a/source/config/ConfigAST.hpp +++ b/source/config/ConfigAST.hpp @@ -17,6 +17,7 @@ #include "ConfigEntry.hpp" #include "ConfigEntry_Scope.hpp" +#include "ConfigTools.hpp" namespace mabe { @@ -163,7 +164,7 @@ namespace mabe { entry_ptr_t input_entry = children[0]->Process(); // Process child to get input entry double output_value = fun(input_entry->AsDouble()); // Run the function to get ouput value if (input_entry->IsTemporary()) input_entry.Delete(); // If we are done with input; delete! - return MakeTempEntry(output_value); + return ConfigTools::MakeTempEntry(output_value); } void Write(std::ostream & os, const std::string & offset) const override { @@ -193,7 +194,7 @@ namespace mabe { auto out_val = fun(in1->As(), in2->As()); // Run function; get ouput if (in1->IsTemporary()) in1.Delete(); // If we are done with in1; delete! if (in2->IsTemporary()) in2.Delete(); // If we are done with in2; delete! - return MakeTempEntry(out_val); + return ConfigTools::MakeTempEntry(out_val); } void Write(std::ostream & os, const std::string & offset) const override { From 4b8c2277085ff6fb3a1548a3285377221ea5dfdb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:44:50 -0400 Subject: [PATCH 228/445] Added ConfigTools into levelization map. --- source/config/DeveloperNotes.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/config/DeveloperNotes.md b/source/config/DeveloperNotes.md index 044f40f7..60b6b114 100644 --- a/source/config/DeveloperNotes.md +++ b/source/config/DeveloperNotes.md @@ -6,20 +6,22 @@ LEVEL MAP: ConfigEntry - [] ConfigLexer - [] -ConfigTypeInfo - [] Basic information for a user-defined type. ConfigTypeBase - [] +ConfigTools - [ConfigEntry] + +ConfigTypeInfo - [ConfigEntry,ConfigTools] Basic information for a user-defined type. ConfigEntry_Function - [ConfigEntry] ConfigEntry_Linked - [ConfigEntry] -ConfigEntry_Scope - [ConfigEntry, ConfigEntry_Function, ConfigEntry_Linked, ConfigTypeBase] +ConfigEntry_Scope - [ConfigEntry,ConfigEntry_Function,ConfigEntry_Linked,ConfigTypeBase] -ConfigAST - [ConfigEntry_Scope, ConfigEntry] -ConfigType - [ConfigEntry_Scope] +ConfigAST - [ConfigEntry_Scope,ConfigEntry,ConfigTools] +ConfigType - [ConfigEntry_Scope,ConfigTypeInfo] ConfigEvents - [ConfigAST] -Config - Main parser +Config - [ALL] Main parser TODO: From d5b6d9efaf41e99fa3bb5c90e96041e2dd1b15c1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:45:44 -0400 Subject: [PATCH 229/445] Shifted ConfigEntry_Function.hpp to use ConfigTools version of MakeTempEntry. --- source/config/ConfigEntry_Function.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index 26d4589d..f27acd9b 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -19,6 +19,7 @@ #include "emp/meta/ValPack.hpp" #include "ConfigEntry.hpp" +#include "ConfigTools.hpp" namespace mabe { @@ -64,7 +65,7 @@ namespace mabe { ); } - return MakeTempEntry(in_fun()); + return ConfigTools::MakeTempEntry(in_fun()); }; } @@ -80,7 +81,7 @@ namespace mabe { ); } - return MakeTempEntry( in_fun((args[INDICES]->template As())...) ); + return ConfigTools::MakeTempEntry( in_fun((args[INDICES]->template As())...) ); }; } @@ -99,7 +100,7 @@ namespace mabe { // If this function returns a basic type, wrap it in a temp entry. else if constexpr (std::is_same() || std::is_arithmetic()) { - return MakeTempEntry(in_fun(args)); + return ConfigTools::MakeTempEntry(in_fun(args)); } // For now these are the only legal return type; raise error otherwise! From 540aa43cd40c6fb18d67341232125da5d62c7b98 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:46:51 -0400 Subject: [PATCH 230/445] Removed MakeTempEntry() from ConfigEntry; added implict converters to other types. --- source/config/ConfigEntry.hpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp index 555ff76b..8a0b156c 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/config/ConfigEntry.hpp @@ -196,6 +196,14 @@ namespace mabe { /// If this entry is a function, we should be able to call it. virtual entry_ptr_t Call(const emp::vector & args); + // --- Implicit conversion operators --- + operator double() const { return AsDouble(); } + operator int() const { return static_cast(AsDouble()); } + operator size_t() const { return static_cast(AsDouble()); } + operator std::string() const { return AsString(); } + operator emp::Ptr() { return this; } + operator ConfigType&() { return *GetObjectPtr(); } + /// Allocate a duplicate of this class. virtual entry_ptr_t Clone() const = 0; @@ -321,19 +329,6 @@ namespace mabe { return emp::NewPtr("Cannot call a function on non-function '", name, "'."); } - - //////////////////////////////////////////////////// - // Helper functions == - - // Use ConfigEntry::MakeTempEntry(value) to quickly make a temporary entry with a given value. - // Note: Caller will be responsible for deleting the created entry! - template - emp::Ptr> MakeTempEntry(VALUE_T value) { - auto out_entry = emp::NewPtr>("__Temp", value, "", nullptr); - out_entry->SetTemporary(); - return out_entry; - } - } #endif From fa031a1fd2d2e2f804830fa82af3dadaca5ec5f6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:47:41 -0400 Subject: [PATCH 231/445] Overhauled WrapMemberFun(); should work properly now. --- source/config/ConfigTools.hpp | 47 +++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/source/config/ConfigTools.hpp b/source/config/ConfigTools.hpp index a7997d93..1a997140 100644 --- a/source/config/ConfigTools.hpp +++ b/source/config/ConfigTools.hpp @@ -97,9 +97,11 @@ namespace ConfigTools { } template - static auto ConvertMemberFun(FUN_T fun) { + static auto ConvertMemberFun(const std::string & name, FUN_T fun) { + constexpr size_t NUM_PARAMS = sizeof...(PARAM_Ts); + // If this function is already the correct type, just pass it along. - if constexpr (sizeof...(PARAM_Ts) == 1) { + if constexpr (NUM_PARAMS == 1) { using arg1_t = typename emp::FunInfo::template arg_t<1>; if (std::is_reference_v && std::is_base_of_v> && @@ -110,30 +112,55 @@ namespace ConfigTools { // Otherwise convert types as needed. else { - return [fun=fun](ConfigType & obj, const entry_vector_t & args) { + return [name=name,fun=fun](ConfigType & obj, const entry_vector_t & args) { emp_assert(args.size() == sizeof...(PARAM_Ts), "Wrong argument count!", args.size(), sizeof...(PARAM_Ts)); emp::Ptr obj_ptr(&obj); - auto out_ptr = obj_ptr.DynamicCast(); - emp_assert(out_ptr, "Internal error: member function call on wrong object type!"); - size_t i = 0; - return ConvertReturn( fun(*out_ptr, args[i++]->As()...) ); + auto typed_ptr = obj_ptr.DynamicCast>(); + emp_assert(typed_ptr, "Internal error: member function call on wrong object type!"); + + // Make sure we have the correct number of arguments. + if (args.size() != NUM_PARAMS) { + std::cerr << "Error in call to function '" << name + << "'; expected " << NUM_PARAMS + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + size_t arg_id = 0; + return ConvertReturn( fun(*typed_ptr, args[arg_id++]->As()...) ); }; } } }; - // Wrap a provided function to make sure it takes a vector of Ptr and returns a + // Wrap a provided function to make it take a vector of Ptr and return a // single Ptr representing the result. template static auto WrapFunction(FUN_T fun) { return WrapFunction_impl::fun_t>::ConvertFun(fun); } + // Wrap a provided MEMBER function to make it take a reference to the object it is a member of + // and a vector of Ptr and return a single Ptr representing the result. template - static auto WrapMemberFunction(FUN_T fun) { - return WrapFunction_impl::fun_t>::ConvertMemberFun(fun); + static auto WrapMemberFunction(emp::TypeID class_type, const std::string & name, FUN_T fun) { + // Do some checks that will produce reasonable errors. + using info_t = emp::FunInfo; + static_assert(info_t::num_args >= 1, "Member function add must always begin with an object reference."); + using object_t = typename info_t::template arg_t<0>; + using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; + + // Is the first parameter the correct type? + static_assert(std::is_base_of(), + "Member functions must take a reference to the associated ConfigType"); + emp_assert( class_type.IsType(), + "First parameter must match class type of member function being created!", + emp::GetTypeID(), class_type ); + + return WrapFunction_impl::fun_t>::ConvertMemberFun(name, fun); } } From a1cbb63394f381f3f0338f9c0ebff0eaf8bc3230 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 22 Oct 2021 14:49:23 -0400 Subject: [PATCH 232/445] Refactored ConfigTypeInfo to include ConfigEntry; member function wrapping now in ConfigTools. --- source/config/ConfigTypeInfo.hpp | 46 ++++++-------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp index 80a6e5e1..d5de7d03 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/config/ConfigTypeInfo.hpp @@ -17,7 +17,8 @@ #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" -class ConfigEntry; +#include "ConfigEntry.hpp" +#include "ConfigTools.hpp" namespace mabe { @@ -29,6 +30,9 @@ namespace mabe { std::string name; std::string desc; fun_t fun; + + MemberFunInfo(const std::string & in_name, const std::string & in_desc, fun_t in_fun) + : name(in_name), desc(in_desc), fun(in_fun) {} }; // ConfigTypeInfo tracks a particular type to be used in the configuration langauge. @@ -74,48 +78,14 @@ namespace mabe { } // Add a member function that can be called on objects of this type. - template + template void AddMemberFunction( const std::string & name, - std::function fun, + FUN_T fun, const std::string & desc ) { - // ----- Make sure function is legal ----- - // Is return type legal? - static_assert(std::is_arithmetic() || - std::is_same() || - std::is_same>(), - "Config member function return types must be string, arithmetic, or Ptr"); - - // Is the first parameter the correct type? - static_assert(std::is_base_of::type>(), - "Member functions must take a reference to the associated ConfigType"); - emp_assert( type_id.IsType(), - "First parameter must match config type of member function being created!", - type_id, emp::GetTypeID() ); - // ----- Transform this function into one that ConfigTypeInfo can make use of ---- - MemberFunInfo::fun_t member_fun = - [name,fun](ConfigType & obj, const emp::vector & args) -> RETURN_T { - // Make sure we can convert the obj into the correct type. - emp::Ptr typed_ptr = dynamic_cast(&obj); - - emp_assert(typed_ptr, "Internal Error: member function called on wrong object type!", - name); - - // Make sure we have the correct number of arguments. - if (args.size() != sizeof...(PARAM_Ts)) { - std::cerr << "Error in call to function '" << name - << "'; expected " << sizeof...(PARAM_Ts) - << " arguments, but received " << args.size() << "." - << std::endl; - } - //@CAO should collect file position information for the above errors. - - // Call the provided function and return the result. - int arg_id = 0; - return fun( *typed_ptr, args[arg_id++]->As()... ); - }; + MemberFunInfo::fun_t member_fun = ConfigTools::WrapMemberFunction(type_id, name, fun); // Add this member function to the library we are building. member_funs.emplace_back(name, desc, member_fun); From b3026ee8a46a3fae7bdddabe1168998763b4a55f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 23 Oct 2021 11:58:01 -0400 Subject: [PATCH 233/445] Moved most ConfigEntry_Function code over to ConfigTools for generality. --- source/config/ConfigEntry_Function.hpp | 68 ++------------------------ 1 file changed, 3 insertions(+), 65 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index f27acd9b..e4ef9d66 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -50,72 +50,10 @@ namespace mabe { bool HasNumericReturn() const override { return numeric_return; } bool HasStringReturn() const override { return string_return; } - /// Setup a function that takes NO arguments. - template - void SetFunction( std::function in_fun ) { - numeric_return = std::is_scalar_v; - string_return = std::is_same(); - - // Convert the function call to return an entry pointer and save it. - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { - // If arguments are passed in, we need to raise an error. - if (args.size()) { - return emp::NewPtr( - "Function '", name, "' called with ", args.size(), " args, but ZERO expected." - ); - } - - return ConfigTools::MakeTempEntry(in_fun()); - }; - } - - /// Helper function to convert ASTs into the proper function arguments. - template - void SetFunction_impl( std::function in_fun, emp::ValPack ) { - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { - // The call needs to have the correct number of arguments or else it throws an error. - constexpr int NUM_ARGS = sizeof...(ARGS); - if (args.size() != NUM_ARGS) { - return emp::NewPtr( - "Function '", name, "' called with ", args.size(), " args, but ", NUM_ARGS, " expected." - ); - } - - return ConfigTools::MakeTempEntry( in_fun((args[INDICES]->template As())...) ); - }; - } - /// Setup a function that takes AT LEAST ONE argument. - template - void SetFunction( std::function in_fun ) { - /// If we have only one argument and it is a `const emp::vector &`, - /// assume that the function will handle any conversions itself. - if constexpr (sizeof...(ARGS) == 0 && std::is_same()) { - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> entry_ptr_t { - // If this function already returns a ConfigEntry pointer, pass it along. - if constexpr (std::is_same>()) { - return in_fun(args); - } - - // If this function returns a basic type, wrap it in a temp entry. - else if constexpr (std::is_same() || - std::is_arithmetic()) { - return ConfigTools::MakeTempEntry(in_fun(args)); - } - - // For now these are the only legal return type; raise error otherwise! - else { - emp::ShowType{}; - static_assert(emp::dependent_false(), - "Invalid return value in ConfigEntry_Function::SetFunction()"); - } - }; - } - - /// Convert the function call to using entry pointers. - else { - SetFunction_impl( in_fun, emp::ValPackCount() ); - } + template + void SetFunction( FUN in_fun ) { + fun = ConfigTools::WrapFunction(name, in_fun); } entry_ptr_t Call( const emp::vector & args ) override { return fun(args); } From 346d136210b2fb09cd291e0fc7bc32a9ea456586 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 23 Oct 2021 12:10:41 -0400 Subject: [PATCH 234/445] Streamlined ConfigEntry_Function; restored tracking of return type qualities. --- source/config/ConfigEntry_Function.hpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp index e4ef9d66..b98a291b 100644 --- a/source/config/ConfigEntry_Function.hpp +++ b/source/config/ConfigEntry_Function.hpp @@ -27,37 +27,33 @@ namespace mabe { private: using this_t = ConfigEntry_Function; using entry_ptr_t = emp::Ptr; - using entry_vector_t = emp::vector; - using fun_t = std::function< entry_ptr_t( const entry_vector_t & ) >; + using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; fun_t fun; bool numeric_return = false; bool string_return = false; // size_t arg_count; public: - template + template ConfigEntry_Function(const std::string & _name, - std::function _fun, - const std::string & _desc, - emp::Ptr _scope) - : ConfigEntry(_name, _desc, _scope) { SetFunction(_fun); } + FUN_T _fun, + const std::string & _desc, + emp::Ptr _scope) + : ConfigEntry(_name, _desc, _scope), fun(ConfigTools::WrapFunction(_name, _fun)) + { + using return_t = typename emp::FunInfo::return_t; + numeric_return = std::is_scalar_v; + string_return = std::is_same(); + } ConfigEntry_Function(const ConfigEntry_Function &) = default; - emp::Ptr Clone() const override { return emp::NewPtr(*this); } bool IsFunction() const override { return true; } bool HasNumericReturn() const override { return numeric_return; } bool HasStringReturn() const override { return string_return; } - /// Setup a function that takes AT LEAST ONE argument. - template - void SetFunction( FUN in_fun ) { - fun = ConfigTools::WrapFunction(name, in_fun); - } - entry_ptr_t Call( const emp::vector & args ) override { return fun(args); } - }; } From f811eedeb9bb50f6f407f14e76f9796a805c558a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 24 Oct 2021 23:09:55 -0400 Subject: [PATCH 235/445] Added type_id as one of the details to track in ModulesInfo. --- source/core/ModuleBase.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index c17fd010..8b81a517 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -324,6 +324,7 @@ namespace mabe { std::string name; std::string desc; std::function init_fun; + emp::TypeID type_id; bool operator<(const ModuleInfo & in) const { return name < in.name; } }; From 6e1bd31ba6398a53e92c1b71dc7d0e9b7a29ef80 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 24 Oct 2021 23:10:47 -0400 Subject: [PATCH 236/445] Set type_id when making a new module. --- source/core/Module.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index d88f897b..24c15a79 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -421,6 +421,7 @@ namespace mabe { new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { return control.AddModule(name, desc); }; + new_info.type_id = emp::GetTypeID(); GetModuleInfo().insert(new_info); } }; From 804c10278f0fd48d67b1925d3b773ef689341b8c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 24 Oct 2021 23:15:06 -0400 Subject: [PATCH 237/445] Config::AddType now requires type information (either via template or TypeID arg) --- source/config/Config.hpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index cb9e711f..74eab416 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -388,14 +388,30 @@ namespace mabe { ConfigTypeInfo & AddType( const std::string & type_name, const std::string & desc, - std::function init_fun + std::function init_fun, + emp::TypeID type_id ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - type_map[type_name] = emp::NewPtr( index, type_name, desc, init_fun ); + auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun ); + info_ptr->LinkType(type_id); + type_map[type_name] = info_ptr; return *type_map[type_name]; } + /// If the linked type can be provided as a template parameter, we can also double check that + /// it is derived from ConfigType (as it needs to be...) + template + ConfigTypeInfo & AddType( + const std::string & type_name, + const std::string & desc, + std::function init_fun + ) { + static_assert(std::is_base_of(), + "Only ConfigType objects can be used as a custom config type."); + return AddType(type_name, desc, init_fun, emp::GetTypeID()); + } + /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided std::function must take a From bb73d07520938c5777036dd1ddfb167899a1907c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 24 Oct 2021 23:16:31 -0400 Subject: [PATCH 238/445] Cleaned up and finished implementing function wrapping to ConfigTools --- source/config/ConfigTools.hpp | 136 +++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 50 deletions(-) diff --git a/source/config/ConfigTools.hpp b/source/config/ConfigTools.hpp index 1a997140..d54a46c4 100644 --- a/source/config/ConfigTools.hpp +++ b/source/config/ConfigTools.hpp @@ -46,7 +46,7 @@ namespace ConfigTools { } // If a return value is a basic type, wrap it in a temporary entry - if constexpr (std::is_same() || + else if constexpr (std::is_same() || std::is_arithmetic()) { return MakeTempEntry(return_value); } @@ -60,16 +60,16 @@ namespace ConfigTools { } - template struct WrapFunction_impl; + template struct WrapFunction_impl; // Specialization for functions with NO arguments template - struct WrapFunction_impl { + struct WrapFunction_impl> { template - static auto ConvertFun(FUN_T fun) { - return [fun=fun]([[maybe_unused]] const entry_vector_t & args) { - emp_assert(args.size() == 0, "Too many arguments (expected 0)", args.size()); + static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun) { + return [name=name,fun=fun]([[maybe_unused]] const entry_vector_t & args) { + emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); return ConvertReturn( fun() ); }; } @@ -77,49 +77,78 @@ namespace ConfigTools { }; // Specialization for functions with AT LEAST ONE argument. - template - struct WrapFunction_impl { + template + struct WrapFunction_impl> { using this_fun_t = RETURN_T(PARAM1_T, PARAM_Ts...); + static_assert( sizeof...(PARAM_Ts) == sizeof...(INDEX_VALS), + "Need one index for each parameter." ); template - static auto ConvertFun(FUN_T fun) { - // If this function is already the correct type, just pass it along. - if constexpr (std::is_same()) { return fun; } - - // Otherwise convert types as needed. - else { - return [fun=fun](const entry_vector_t & args) { - emp_assert(args.size() == 1+sizeof...(PARAM_Ts), "Wrong argument count!"); - size_t i = 0; - return ConvertReturn( fun(args[i++]->As(), args[i++]->As()...) ); - }; - } + static auto ConvertFun(const std::string & name, FUN_T fun) { + return [name=name,fun=fun](const entry_vector_t & args) { + // If this function already takes a const entry_vector_t & as its only parameter, + // just pass it along. + if constexpr (sizeof...(PARAM_Ts) == 0 && + std::is_same_v) { + return ConvertReturn( fun(args) ); + } + + // Otherwise make sure we have the correct arguments. + else { + constexpr size_t NUM_PARAMS = 1 + sizeof...(PARAM_Ts); + if (args.size() != NUM_PARAMS) { + std::cerr << "Error in call to function '" << name + << "'; expected " << NUM_PARAMS + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + return ConvertReturn( fun(args[0]->As(), + args[INDEX_VALS]->template As()...) ); + } + }; } template static auto ConvertMemberFun(const std::string & name, FUN_T fun) { - constexpr size_t NUM_PARAMS = sizeof...(PARAM_Ts); - - // If this function is already the correct type, just pass it along. - if constexpr (NUM_PARAMS == 1) { - using arg1_t = typename emp::FunInfo::template arg_t<1>; - if (std::is_reference_v && - std::is_base_of_v> && - std::is_same_v) { - return fun; + using info_t = emp::FunInfo; + + static_assert(std::is_reference_v, + "First parameter for supplied member functions must be reference to object"); + static_assert(std::is_base_of_v>, + "First parameter for supplied member functions must derived from ConfigType"); + static_assert(info_t::num_args == sizeof...(PARAM_Ts) + 1, + "PARAM_Ts must match the extra arguments in member function."); + + return [name=name,fun=fun](ConfigType & obj, const entry_vector_t & args) { + // Make sure the correct object type is used for first argument. + emp::Ptr obj_ptr(&obj); + auto typed_ptr = obj_ptr.DynamicCast>(); + emp_assert(typed_ptr, "Internal error: member function call on wrong object type!", name); + + // If this member function takes no additional arguments just call it without any! + if constexpr (sizeof...(PARAM_Ts) == 0) { + if (args.size() != 0) { + std::cerr << "Error in call to function '" << name + << "'; expected ZERO arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + return ConvertReturn( fun(*typed_ptr) ); + } + + // If this function already takes a const entry_vector_t & as its only extra parameter, + // just pass it along. + else if constexpr (sizeof...(PARAM_Ts) == 1 && + std::is_same_v, const entry_vector_t &>) { + return ConvertReturn( fun(*typed_ptr, args) ); } - } - - // Otherwise convert types as needed. - else { - return [name=name,fun=fun](ConfigType & obj, const entry_vector_t & args) { - emp_assert(args.size() == sizeof...(PARAM_Ts), "Wrong argument count!", - args.size(), sizeof...(PARAM_Ts)); - emp::Ptr obj_ptr(&obj); - auto typed_ptr = obj_ptr.DynamicCast>(); - emp_assert(typed_ptr, "Internal error: member function call on wrong object type!"); - - // Make sure we have the correct number of arguments. + + // Otherwise make sure we have the correct arguments. + else { + constexpr size_t NUM_PARAMS = sizeof...(PARAM_Ts); if (args.size() != NUM_PARAMS) { std::cerr << "Error in call to function '" << name << "'; expected " << NUM_PARAMS @@ -128,10 +157,9 @@ namespace ConfigTools { } //@CAO should collect file position information for the above errors. - size_t arg_id = 0; - return ConvertReturn( fun(*typed_ptr, args[arg_id++]->As()...) ); - }; - } + return ConvertReturn( fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); + } + }; } }; @@ -139,8 +167,15 @@ namespace ConfigTools { // Wrap a provided function to make it take a vector of Ptr and return a // single Ptr representing the result. template - static auto WrapFunction(FUN_T fun) { - return WrapFunction_impl::fun_t>::ConvertFun(fun); + static auto WrapFunction(const std::string & name, FUN_T fun) { + using info_t = emp::FunInfo; + using fun_t = typename info_t::fun_t; + if constexpr (info_t::num_args == 0) { + return WrapFunction_impl>::ConvertFun(name, fun); + } else { + using index_t = emp::ValPackCount; + return WrapFunction_impl::ConvertFun(name, fun); + } } // Wrap a provided MEMBER function to make it take a reference to the object it is a member of @@ -149,18 +184,19 @@ namespace ConfigTools { static auto WrapMemberFunction(emp::TypeID class_type, const std::string & name, FUN_T fun) { // Do some checks that will produce reasonable errors. using info_t = emp::FunInfo; + using index_t = emp::ValPackCount; static_assert(info_t::num_args >= 1, "Member function add must always begin with an object reference."); - using object_t = typename info_t::template arg_t<0>; - using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; // Is the first parameter the correct type? + using object_t = typename info_t::template arg_t<0>; + using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; static_assert(std::is_base_of(), "Member functions must take a reference to the associated ConfigType"); emp_assert( class_type.IsType(), "First parameter must match class type of member function being created!", emp::GetTypeID(), class_type ); - return WrapFunction_impl::fun_t>::ConvertMemberFun(name, fun); + return WrapFunction_impl::fun_t, index_t>::ConvertMemberFun(name, fun); } } From cd552dd95fcb27d398795fd06f1f3b6f1e647bdf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 25 Oct 2021 19:08:28 -0400 Subject: [PATCH 239/445] Fixed forwarding of args for wrapped functions. --- source/config/ConfigTools.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config/ConfigTools.hpp b/source/config/ConfigTools.hpp index d54a46c4..2d5f4b5f 100644 --- a/source/config/ConfigTools.hpp +++ b/source/config/ConfigTools.hpp @@ -105,7 +105,7 @@ namespace ConfigTools { //@CAO should collect file position information for the above errors. return ConvertReturn( fun(args[0]->As(), - args[INDEX_VALS]->template As()...) ); + args[INDEX_VALS+1]->template As()...) ); } }; } From c4a29bba3423c60a3ca620df99702ba4f4fa8b9b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 09:54:34 -0400 Subject: [PATCH 240/445] Added a default static InitType() to ConfigType for optional overriding. --- source/config/ConfigType.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index 5430cce2..dc5e6185 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -19,10 +19,20 @@ namespace mabe { + class Config; + // Base class for types that we want to be used for scripting. class ConfigType : public ConfigTypeBase { public: - // Setup a new ConfigType object; provide it with its scope and type information. + /// Setup the TYPE of object in the config. This is a stub class, but any new class derived from + /// ConfigType can create its own version to automatically load in member functions, etc. + static void InitType(Config & /*config*/, ConfigTypeInfo & /*info*/) { + // If you create a version of this function for your own ConfigType, this is where you would + // create member functions. + } + + + /// Setup an instance of a new ConfigType object; provide it with its scope and type information. void Setup(ConfigEntry_Scope & _scope, ConfigTypeInfo & _info) { cur_scope = &_scope; type_info_ptr = &_info; From 1a326a102ee9ae450f36ba81cf3e88be7570ef07 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 09:55:57 -0400 Subject: [PATCH 241/445] Automatically call InitType() on ConfigType classes whose derived type is provided at compile time. --- source/config/Config.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 74eab416..83e3da31 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -409,7 +409,9 @@ namespace mabe { ) { static_assert(std::is_base_of(), "Only ConfigType objects can be used as a custom config type."); - return AddType(type_name, desc, init_fun, emp::GetTypeID()); + ConfigTypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID()); + OBJECT_T::InitType(*this, info); + return info; } /// To add a built-in function (at the root level) provide it with a name and description. From 8fc6fa1cc79c3ce0023f9410603ac6409a2ccef2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 10:19:29 -0400 Subject: [PATCH 242/445] Updated AddType() calls to properly include C++ type information. --- source/core/MABE.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 3d9aa89e..8947b315 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -599,7 +599,7 @@ namespace mabe { [this](const std::string & name) -> ConfigType & { return AddPopulation(name); }; - config.AddType("Population", "Collection of organisms", pop_init_fun); + config.AddType("Population", "Collection of organisms", pop_init_fun); // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { @@ -607,7 +607,7 @@ namespace mabe { [this,&mod](const std::string & name) -> ConfigType & { return mod.init_fun(*this,name); }; - config.AddType(mod.name, mod.desc, mod_init_fun); + config.AddType(mod.name, mod.desc, mod_init_fun, mod.type_id); } @@ -867,7 +867,10 @@ namespace mabe { size_t copy_count) { int pop_id = GetPopID(pop_name); if (pop_id == -1) { - error_man.AddError("Invalid population name used in inject '", pop_name, "'."); + error_man.AddError("Invalid population name used in inject: ", + "org_type= '", type_name, "'; ", + "pop_name= '", pop_name, "'; ", + "copy_count=", copy_count); } Population & pop = GetPopulation(pop_id); OrgPosition pos = Inject(type_name, pop, copy_count); // Inject a copy of the organism. From c4a28c033a8a2edb76603bb7ace881ed5f030f0e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 10:20:20 -0400 Subject: [PATCH 243/445] Setup population to provide its own config member functions (just SIZE() so far) --- source/core/Population.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 91f633f4..e426e48d 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -153,9 +153,6 @@ namespace mabe { iterator_t IteratorAt(size_t pos) { return iterator_t(this, pos); } const_iterator_t ConstIteratorAt(size_t pos) const { return const_iterator_t(this, pos); } - /// Required SetupConfig function; for now population don't have any config options. - void SetupConfig() override { } - private: // ---== To be used by friend class MABEBase only! ==--- void SetOrg(size_t pos, emp::Ptr org_ptr) { @@ -202,6 +199,14 @@ namespace mabe { void SetEmpty(emp::Ptr in_empty) { empty_org = in_empty; } public: + // Setup member functions associated with population. + static void InitType(Config & /*config*/, ConfigTypeInfo & info) { + std::function fun_size = + [](Population & target) { return target.GetSize(); }; + info.AddMemberFunction("SIZE", fun_size, "Return the size of the population."); + } + + // ------ DEBUG FUNCTIONS ------ bool OK() const { // We may have a handful of populations, but assume error if we have more than a billion. @@ -242,6 +247,8 @@ namespace mabe { return true; } + + static std::string EMPGetTypeName() { return "mabe::Population"; } }; From fd5f66ad19a4308079b89cfb92eadd9d12e6cd3a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:46:05 -0400 Subject: [PATCH 244/445] Reordered inputs for signals place_birth and place_inject to put Population first. --- source/core/MABEBase.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 3df1c3cf..364ae9af 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -77,10 +77,10 @@ namespace mabe { // TraceEval() SigListener trace_eval_sig; - // OrgPosition DoPlaceBirth(Organism & offspring, OrgPosition parent_position, Population & target_pop); - SigListener do_place_birth_sig; - // OrgPosition DoPlaceInject(Organism & new_organism) - SigListener do_place_inject_sig; + // OrgPosition DoPlaceBirth(Population & target_pop, Organism & offspring, OrgPosition parent_position); + SigListener do_place_birth_sig; + // OrgPosition DoPlaceInject(Population & target_pop, Organism & new_organism) + SigListener do_place_inject_sig; // OrgPosition DoFindNeighbor(OrgPosition target_organism) { SigListener do_find_neighbor_sig; From bd26fcae5958d3769bd6acc66ef56ab40bf08c38 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:46:59 -0400 Subject: [PATCH 245/445] Updated base functions DoPlaceBirth() and DoPlaceInject() to put Population first. --- source/core/ModuleBase.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 8b81a517..47d74095 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -61,9 +61,9 @@ * ... * * - Various Do* functions run in modules until one of them returns a valid answer. - * DoPlaceBirth(Organism & offspring, OrgPosition parent_pos, Population & target_pop) + * DoPlaceBirth(Population & target_pop, Organism & offspring, OrgPosition parent_pos) * : Place a new offspring about to be born. - * DoPlaceInject(Organism & new_org, Population & pop) + * DoPlaceInject(Population & pop, Organism & new_org) * : Place a new offspring about to be injected. * DoFindNeighbor(OrgPosition target_pos) * : Find a random neighbor to a designated position. @@ -264,8 +264,8 @@ namespace mabe { virtual void OnHelp() = 0; virtual void TraceEval(Organism &, std::ostream &) = 0; - virtual OrgPosition DoPlaceBirth(Organism &, OrgPosition, Population &) = 0; - virtual OrgPosition DoPlaceInject(Organism &, Population &) = 0; + virtual OrgPosition DoPlaceBirth(Population &, Organism &, OrgPosition) = 0; + virtual OrgPosition DoPlaceInject(Population &, Organism &) = 0; virtual OrgPosition DoFindNeighbor(OrgPosition) = 0; virtual void Deactivate() = 0; ///< Turn off all signals in this function. From ddc388c22ae4c9e422bf137cabad661046e91af2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:47:46 -0400 Subject: [PATCH 246/445] Updated implementation of DoPlaceBirth() and DoPlaceInject() to take Population parameter first. --- source/core/Module.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 24c15a79..c2026d3d 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -348,19 +348,20 @@ namespace mabe { // be queried in order until one of them returns a valid result. // Function: Place a new organism about to be born. - // Args: Organism that will be placed, position of parent, population to place. + // Args: Population to place in, Organism to place, position of parent // Return: Position to place offspring or an invalid position if failed. - OrgPosition DoPlaceBirth(Organism &, OrgPosition, Population &) override { + OrgPosition DoPlaceBirth(Population &, Organism &, OrgPosition) override { has_signal[SIG_DoPlaceBirth] = false; control.RescanSignals(); return OrgPosition(); } // Function: Place a new organism about to be injected. - // Args: Organism that will be placed, position to place. + // Args: Population to place in, Organism that will be placed. + // Return: Position to place injected organism, or an invalid position if failed. - OrgPosition DoPlaceInject(Organism &, Population &) override { + OrgPosition DoPlaceInject(Population &, Organism &) override { has_signal[SIG_DoPlaceInject] = false; control.RescanSignals(); return OrgPosition(); From 62aba0303ba440567cea7a4444bee5967384a1f2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:55:36 -0400 Subject: [PATCH 247/445] Removed template version of LinkType; added commented-out debug printing. --- source/config/ConfigTypeInfo.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp index d5de7d03..abdd959d 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/config/ConfigTypeInfo.hpp @@ -70,12 +70,8 @@ namespace mabe { ConfigType & MakeObj(const std::string & name) const { return init_fun(name); } // Link this ConfigTypeInfo object to a real C++ type. - template - void LinkType() { - static_assert(std::is_base_of(), - "Only ConfigType objects can be used as a custom config type."); - type_id = emp::GetTypeID(); - } + // @CAO It would be nice to test to make sure this is a ConfigType, but not possible with a TypeID. + void LinkType(emp::TypeID in_id) { type_id = in_id; } // Add a member function that can be called on objects of this type. template @@ -84,6 +80,11 @@ namespace mabe { FUN_T fun, const std::string & desc ) { + // std::cout << "Adding member function '" << name + // << "' to type '" << type_name << "'." + // << " (Entry #" << member_funs.size() << ")" + // << std::endl; + // ----- Transform this function into one that ConfigTypeInfo can make use of ---- MemberFunInfo::fun_t member_fun = ConfigTools::WrapMemberFunction(type_id, name, fun); From fde239e5081d121bbc5154b811888ab0aeac50ab Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:56:06 -0400 Subject: [PATCH 248/445] Updated GrowthPlacement's DoPlaceBirth() and DoPlaceInject(). --- source/placement/GrowthPlacement.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/placement/GrowthPlacement.hpp b/source/placement/GrowthPlacement.hpp index f42008ef..06df9671 100644 --- a/source/placement/GrowthPlacement.hpp +++ b/source/placement/GrowthPlacement.hpp @@ -51,8 +51,8 @@ namespace mabe { // For now, nothing here. } - OrgPosition DoPlaceBirth(Organism & /* org */, OrgPosition /* ppos */, - Population & target_pop) override + OrgPosition DoPlaceBirth(Population & target_pop, + Organism & /* org */, OrgPosition /* ppos */) override { // If birth is going to a monitored population, place it in a new, empty cell! if (target_collect.HasPopulation(target_pop)) return control.PushEmpty(target_pop); @@ -62,7 +62,7 @@ namespace mabe { } // Injections always go into the active population. - OrgPosition DoPlaceInject(Organism & /* org */, Population & target_pop) override { + OrgPosition DoPlaceInject(Population & target_pop, Organism & /* org */) override { // If inject is going to a monitored population, place it in a new, empty cell! if (target_collect.HasPopulation(target_pop)) return control.PushEmpty(target_pop); From 51a2cda9cc8195e3dc24efaa8011756cfa0a6bcb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:57:12 -0400 Subject: [PATCH 249/445] Added new type for managing DataFiles. --- source/config/ConfigDataFile.hpp | 89 ++++++++++++++++++++++++++++++++ source/config/ConfigType.hpp | 8 +++ 2 files changed, 97 insertions(+) create mode 100644 source/config/ConfigDataFile.hpp diff --git a/source/config/ConfigDataFile.hpp b/source/config/ConfigDataFile.hpp new file mode 100644 index 00000000..6c623bf6 --- /dev/null +++ b/source/config/ConfigDataFile.hpp @@ -0,0 +1,89 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file ConfigDataFile.hpp + * @brief Manages a DataFile object for config. + * @note Status: BETA + */ + +#ifndef MABE_CONFIG_DATA_FILE_H +#define MABE_CONFIG_DATA_FILE_H + +#include +#include + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" + +#include "ConfigType.hpp" + +namespace mabe { + + /// A ConfigDataFile maintains an output file that has specified columns and can be generate + /// dynamically. + class ConfigDataFile : public ConfigType { + private: + using fun_t = std::function; + struct ColumnInfo { + std::string header; + fun_t fun; + }; + + std::string name=""; ///< Unique name for this object. + emp::StreamManager & files; ///< Global file manager. + + std::string filename; ///< Name of output file. + emp::vector cols; ///< Data about columns maintainted. + + public: + ConfigDataFile() = delete; + ConfigDataFile(const std::string & in_name, emp::StreamManager & _files) + : name(in_name), files(_files) { } + ~ConfigDataFile() { } + + std::string GetName() const { return name; } + + // Setup member functions associated with population. + static void InitType(Config & /*config*/, ConfigTypeInfo & info) { + auto fun_num_cols = [](ConfigDataFile & target) { return target.cols.size(); }; + info.AddMemberFunction("NUM_COLS", fun_num_cols, "Return the number of columns in this file."); + } + + void SetupConfig() override { + LinkVar(filename, "filename", "Name to use for this file."); + } + + size_t AddColumn(const std::string & header, fun_t fun) { + size_t col_id = cols.size(); + cols.push_back(ColumnInfo{header,fun}); + return col_id; + } + + void Write() { + const bool file_exists = files.Has(filename); // Is file is already setup? + std::ostream & file = files.GetOutputStream(filename); // File to write to. + + // If we need headers, set them up! + if (!file_exists) { + for (size_t i = 0; i < cols.size(); ++i) { + if (i) file << ", "; + file << cols[i].header; + } + file << '\n'; + } + + // Now print out each entry. + for (size_t i = 0; i < cols.size(); ++i) { + if (i) file << ", "; + file << cols[i].fun(); + } + file << std::endl; + } + + static std::string EMPGetTypeName() { return "mabe::ConfigDataFile"; } + }; +} + +#endif diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp index dc5e6185..1345dd2a 100644 --- a/source/config/ConfigType.hpp +++ b/source/config/ConfigType.hpp @@ -48,11 +48,19 @@ namespace mabe { using entry_ptr_t = emp::Ptr; using member_fun_t = std::function &)>; const auto & member_map = type_info_ptr->GetMemberFunctions(); + + // std::cout << "Loading member functions for '" << _scope.GetName() << "'; " + // << member_map.size() << " found." + // << std::endl; + for (const MemberFunInfo & member_info : member_map) { member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ return member_info.fun(*this, args); }; cur_scope->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); + + // std::cout << "Adding member function '" << member_info.name << "' to object '" + // << cur_scope->GetName() << "'." << std::endl; } } From 02ad8c277d8782f2c1919e4a7047fe3ee0a01eff Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 26 Oct 2021 23:57:45 -0400 Subject: [PATCH 250/445] Setup Config to build-in DataFile type; renamed EVAL to EXEC. --- source/config/Config.hpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 83e3da31..d115b2e5 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -72,10 +72,12 @@ #include "emp/base/assert.hpp" #include "emp/base/map.hpp" +#include "emp/io/StreamManager.hpp" #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" #include "ConfigAST.hpp" +#include "ConfigDataFile.hpp" #include "ConfigEvents.hpp" #include "ConfigEntry_Function.hpp" #include "ConfigLexer.hpp" @@ -96,13 +98,16 @@ namespace mabe { ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. bool debug = false; ///< Should we print full debug information? - /// A map of names to event groups. std::map events_map; /// A map of all types available in the script. std::unordered_map> type_map; + /// Management of built-in types. + emp::StreamManager files; ///< Track all of the file streams used in MABE. + emp::vector file_map; + /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -245,12 +250,19 @@ namespace mabe { precedence_map["||"] = cur_prec++; precedence_map["="] = cur_prec++; + // Setup default DataFile type. + files.SetOutputDefaultFile(); // Stream manager should default to files for output. + std::function df_init = + [this](const std::string & name) -> ConfigType & { return this->AddDataFile(name); }; + + auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init); + // Setup default functions. - // 'EVAL' dynamically evaluates the contents of a string. + // 'EXEC' dynamically executes the contents of a string. std::function eval_fun = [this](const std::string & expression) { return Eval(expression); }; - AddFunction("EVAL", eval_fun, "Dynamically evaluate the string passed in."); + AddFunction("EXEC", eval_fun, "Dynamically execute the string passed in."); // 'PRINT' is a simple debugging command to output the value of a variable. std::function> &)> print_fun = @@ -414,6 +426,15 @@ namespace mabe { return info; } + /// Add in a new data file. + ConfigDataFile & AddDataFile(const std::string & obj_name) { + file_map.emplace_back(obj_name, files); + return file_map.back(); + } + + /// Also allow direct file management. + emp::StreamManager & GetFileManager() { return files; } + /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided std::function must take a @@ -537,7 +558,8 @@ namespace mabe { // If we can't find this variable, throw an error. if (cur_entry.IsNull()) { - Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type."); + Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type.", + " Current scope is '", cur_scope.GetName(), "'"); } // If this variable just provided a scope, keep going. From 268d01f51b0e405c5c8e85a8c85e4b692622ab73 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 27 Oct 2021 00:00:20 -0400 Subject: [PATCH 251/445] All placement now takes pop as 1st arg; INJECT now Population method; file handling to Config. --- source/core/MABE.hpp | 97 +++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 8947b315..2a2e379a 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -28,7 +28,6 @@ #include "emp/data/DataMap.hpp" #include "emp/data/DataMapParser.hpp" #include "emp/datastructs/vector_utils.hpp" -#include "emp/io/StreamManager.hpp" #include "emp/math/Random.hpp" #include "emp/tools/string_utils.hpp" @@ -64,7 +63,6 @@ namespace mabe { bool show_help = false; ///< Should we show "help" before exiting? bool exit_now = false; ///< Do we need to immediately clean up and exit the run? ErrorManager error_man; ///< Object to manage warnings and errors. - emp::StreamManager files; ///< Track all of the file streams used in MABE. // Setup helper types. using trait_equation_t = std::function; @@ -183,11 +181,11 @@ namespace mabe { // -- World Structure -- - OrgPosition FindBirthPosition(Organism & offspring, OrgPosition ppos, Population & pop) { - return do_place_birth_sig.FindPosition(offspring, ppos, pop); + OrgPosition FindBirthPosition(Population & pop, Organism & offspring, OrgPosition ppos) { + return do_place_birth_sig.FindPosition(pop, offspring, ppos); } - OrgPosition FindInjectPosition(Organism & new_org, Population & pop) { - return do_place_inject_sig.FindPosition(new_org, pop); + OrgPosition FindInjectPosition(Population & pop, Organism & new_org) { + return do_place_inject_sig.FindPosition(pop, new_org); } OrgPosition FindNeighbor(OrgPosition pos) { return do_find_neighbor_sig.FindPosition(pos); @@ -218,23 +216,23 @@ namespace mabe { /// Inject a copy of the provided organism and return the position it was placed in; /// if more than one is added, return the position of the final injection. - OrgPosition Inject(const Organism & org, Population & pop, size_t copy_count=1); + OrgPosition Inject(Population & pop, const Organism & org, size_t copy_count=1); /// Inject this specific instance of an organism and turn over the pointer to be managed /// by MABE. Teturn the position the organism was placed in. - OrgPosition InjectInstance(emp::Ptr org_ptr, Population & pop); + OrgPosition InjectInstance(Population & pop, emp::Ptr org_ptr); /// Add an organsim of a specified type to the world (provide the type name and the /// MABE controller will create instances of it.) Returns the position of the last /// organism placed. - OrgPosition Inject(const std::string & type_name, Population & pop, size_t copy_count=1); + OrgPosition Inject(Population & pop, const std::string & type_name, size_t copy_count=1); /// Add an organism of a specified type and population (provide names of both and they /// will be properly setup.) - OrgPosition Inject(const std::string & type_name, - const std::string & pop_name, - size_t copy_count=1); + OrgPosition InjectByName(const std::string & pop_name, + const std::string & type_name, + size_t copy_count=1); /// Inject a copy of the provided organism at a specified position. void InjectAt(const Organism & org, OrgPosition pos) { @@ -599,7 +597,17 @@ namespace mabe { [this](const std::string & name) -> ConfigType & { return AddPopulation(name); }; - config.AddType("Population", "Collection of organisms", pop_init_fun); + auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun); + + // 'INJECT' allows a user to add an organism to a population. + std::function inject_fun = + [this](Population & pop, const std::string & org_type_name, size_t count) { + Inject(pop, org_type_name, count); + return 0; + }; + pop_type.AddMemberFunction("INJECT", inject_fun, + "Inject organisms into population (args: org_name, org_count)."); + // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { @@ -612,6 +620,7 @@ namespace mabe { // ------ DEPRECATED FUNCTION NAMES ------ + Deprecate("EVAL", "EXEC"); Deprecate("exit", "EXIT"); Deprecate("inject", "INJECT"); Deprecate("print", "PRINT"); @@ -623,31 +632,15 @@ namespace mabe { config.AddFunction("EXIT", exit_fun, "Exit from this MABE run."); - // 'INJECT' allows a user to add an organism to a population. - std::function inject_fun = - [this](const std::string & org_type_name, const std::string & pop_name, size_t count) { - Inject(org_type_name, pop_name, count); - return 0; - }; - config.AddFunction("INJECT", inject_fun, - "Inject organisms into a population (args: org_name, pop_name, org_count)."); - - std::function preprocess_fun = [this](const std::string & str) { return Preprocess(str); }; config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); - // @CAO Should be a method on a Population or Collection, not called by name. - std::function pop_size_fun = - [this](const std::string & target) { return ToCollection(target).GetSize(); }; - config.AddFunction("SIZE", pop_size_fun, "Return the size of the target population."); - - // 'WRITE' will collect data and write it to a file. - files.SetOutputDefaultFile(); // Stream manager should default to files for output. + auto & files = config.GetFileManager(); std::function write_fun = - [this](const std::string & filename, const std::string & collection, std::string format) { + [this,&files](const std::string & filename, const std::string & collection, std::string format) { const bool file_exists = files.Has(filename); // Is file is already setup? std::ostream & file = files.GetOutputStream(filename); // File to write to. OutputTraitData(file, ToCollection(collection), format, !file_exists); @@ -660,14 +653,14 @@ namespace mabe { // --- ORGANISM-BASED FUNCTIONS --- std::function trace_eval_fun = - [this](const std::string & filename, const std::string & target, double id) { + [this,&files](const std::string & filename, const std::string & target, double id) { Collection c = ToCollection(target); // Collection with organisms Organism & org = c.At((size_t) id); // Specific organism to analyze. std::ostream & file = files.GetOutputStream(filename); // File to write to. TraceEval(org, file); return 0; }; - config.AddFunction("TRACE_EVAL", trace_eval_fun, "Return the size of the target population."); + config.AddFunction("TRACE_EVAL", trace_eval_fun, "Collect information about how evaluations are performed."); // --- TRAIT-BASED FUNCTIONS --- @@ -687,22 +680,6 @@ namespace mabe { }; config.AddFunction("TRAIT_VALUE", trait_value_fun, "Collect information about a specified trait."); - // std::function trait_mean_fun = - // [this](const std::string & target, const std::string & trait) { - // if constexpr (std::is_arithmetic_v) { - // double total = 0.0; - // size_t count = 0; - // for (const auto & entry : container) { - // total += (double) get_fun(entry); - // count++; - // } - // return emp::to_string( total / count ); - // } - // return 0.0; // @CAO: or Nan? - // }; - // config.AddFunction("trait_mean", trait_mean_fun, "Return the size of the target population."); - - // Add in built-in event triggers; these are used to indicate when events should happen. config.AddEventType("start"); // Triggered at the beginning of a run. config.AddEventType("update"); // Tested every update. @@ -812,13 +789,13 @@ namespace mabe { /// Inject a copy of the provided organism and return the position it was placed in; /// if more than one is added, return the position of the final injection. - OrgPosition MABE::Inject(const Organism & org, Population & pop, size_t copy_count) { + OrgPosition MABE::Inject(Population & pop, const Organism & org, size_t copy_count) { emp_assert(org.GetDataMap().SameLayout(org_data_map)); OrgPosition pos; for (size_t i = 0; i < copy_count; i++) { emp::Ptr inject_org = org.CloneOrganism(); on_inject_ready_sig.Trigger(*inject_org, pop); - pos = FindInjectPosition(*inject_org, pop); + pos = FindInjectPosition(pop, *inject_org); if (pos.IsValid()) { AddOrgAt( inject_org, pos); } else { @@ -831,10 +808,10 @@ namespace mabe { /// Inject this specific instance of an organism and turn over the pointer to be managed /// by MABE. Teturn the position the organism was placed in. - OrgPosition MABE::InjectInstance(emp::Ptr org_ptr, Population & pop) { + OrgPosition MABE::InjectInstance(Population & pop, emp::Ptr org_ptr) { emp_assert(org_ptr->GetDataMap().SameLayout(org_data_map)); on_inject_ready_sig.Trigger(*org_ptr, pop); - OrgPosition pos = FindInjectPosition(*org_ptr, pop); + OrgPosition pos = FindInjectPosition(pop, *org_ptr); if (pos.IsValid()) AddOrgAt( org_ptr, pos); else { org_ptr.Delete(); @@ -847,7 +824,7 @@ namespace mabe { /// Add an organsim of a specified type to the world (provide the type name and the /// MABE controller will create instances of it.) Returns the position of the last /// organism placed. - OrgPosition MABE::Inject(const std::string & type_name, Population & pop, size_t copy_count) { + OrgPosition MABE::Inject(Population & pop, const std::string & type_name, size_t copy_count) { Verbose("Injecting ", copy_count, " orgs of type '", type_name, "' into population ", pop.GetID()); @@ -855,16 +832,16 @@ namespace mabe { OrgPosition pos; // Place to save injection position. for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. auto org_ptr = org_manager.Make(random); // ...Build an org of this type. - pos = InjectInstance(org_ptr, pop); // ...Inject it into the population. + pos = InjectInstance(pop, org_ptr); // ...Inject it into the population. } return pos; // Return last position injected. } /// Add an organism of a specified type and population (provide names of both and they /// will be properly setup.) - OrgPosition MABE::Inject(const std::string & type_name, - const std::string & pop_name, - size_t copy_count) { + OrgPosition MABE::InjectByName(const std::string & pop_name, + const std::string & type_name, + size_t copy_count) { int pop_id = GetPopID(pop_name); if (pop_id == -1) { error_man.AddError("Invalid population name used in inject: ", @@ -873,7 +850,7 @@ namespace mabe { "copy_count=", copy_count); } Population & pop = GetPopulation(pop_id); - OrgPosition pos = Inject(type_name, pop, copy_count); // Inject a copy of the organism. + OrgPosition pos = Inject(pop, type_name, copy_count); // Inject a copy of the organism. return pos; // Return last position injected. } @@ -894,7 +871,7 @@ namespace mabe { // Alert modules that offspring is ready, then find its birth position. on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); - pos = FindBirthPosition(*new_org, ppos, target_pop); + pos = FindBirthPosition(target_pop, *new_org, ppos); // If this placement is valid, do so. Otherwise delete the organism. if (pos.IsValid()) AddOrgAt(new_org, pos, ppos); From 95f61312042fdf5f898830c79f877f017ece641a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 27 Oct 2021 13:13:31 -0400 Subject: [PATCH 252/445] Added a WRITE member function to ConfigDataFile. --- source/config/ConfigDataFile.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/config/ConfigDataFile.hpp b/source/config/ConfigDataFile.hpp index 6c623bf6..0b361cd7 100644 --- a/source/config/ConfigDataFile.hpp +++ b/source/config/ConfigDataFile.hpp @@ -49,6 +49,8 @@ namespace mabe { static void InitType(Config & /*config*/, ConfigTypeInfo & info) { auto fun_num_cols = [](ConfigDataFile & target) { return target.cols.size(); }; info.AddMemberFunction("NUM_COLS", fun_num_cols, "Return the number of columns in this file."); + info.AddMemberFunction("WRITE", [](ConfigDataFile & target) { return target.Write(); }, + "Add on the next line of data."); } void SetupConfig() override { @@ -61,7 +63,7 @@ namespace mabe { return col_id; } - void Write() { + size_t Write() { const bool file_exists = files.Has(filename); // Is file is already setup? std::ostream & file = files.GetOutputStream(filename); // File to write to. @@ -80,6 +82,8 @@ namespace mabe { file << cols[i].fun(); } file << std::endl; + + return 1; } static std::string EMPGetTypeName() { return "mabe::ConfigDataFile"; } From 9fb78073b504ce60c6817a773826599927c2515f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 27 Oct 2021 13:15:02 -0400 Subject: [PATCH 253/445] Renamed Eval() to Execute() in Config; setup DataFile type. --- source/config/Config.hpp | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index d115b2e5..98a4f811 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -250,19 +250,12 @@ namespace mabe { precedence_map["||"] = cur_prec++; precedence_map["="] = cur_prec++; - // Setup default DataFile type. - files.SetOutputDefaultFile(); // Stream manager should default to files for output. - std::function df_init = - [this](const std::string & name) -> ConfigType & { return this->AddDataFile(name); }; - - auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init); - // Setup default functions. // 'EXEC' dynamically executes the contents of a string. - std::function eval_fun = - [this](const std::string & expression) { return Eval(expression); }; - AddFunction("EXEC", eval_fun, "Dynamically execute the string passed in."); + std::function exec_fun = + [this](const std::string & expression) { return Execute(expression); }; + AddFunction("EXEC", exec_fun, "Dynamically execute the string passed in."); // 'PRINT' is a simple debugging command to output the value of a variable. std::function> &)> print_fun = @@ -346,6 +339,22 @@ namespace mabe { AddFunction("TO_SCALE", math3_fun, "Scale arg1 to arg2-arg3 as unit distance" ); math3_fun = [](double x, double y, double z){ return (x-y) / (z-y); }; AddFunction("FROM_SCALE", math3_fun, "Scale arg1 from arg2-arg3 as unit distance" ); + + // Setup default DataFile type. + files.SetOutputDefaultFile(); // Stream manager should default to files for output. + std::function df_init = + [this](const std::string & name) -> ConfigType & { return this->AddDataFile(name); }; + + auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init); + df_type.AddMemberFunction("ADD_COLUMN", + [exec_fun](ConfigDataFile & file, const std::string & title, const std::string & expression){ + return file.AddColumn(title, [exec_fun,expression](){ + std::string out_string = exec_fun(expression); + return out_string; + }); + }, + "Add a column to the associated DataFile. Args: title, string to execute for result" + ); } // Prevent copy or move since we are using lambdas that capture 'this' @@ -487,8 +496,8 @@ namespace mabe { } // Load the provided statement and run it. - std::string Eval(std::string_view statement, emp::Ptr scope=nullptr) { - Debug("Running Eval()"); + std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { + Debug("Running Execute()"); if (!scope) scope = &root_scope; // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. From 2fe7ac0b54147a14f3887d899b94763d4d66204d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 09:21:34 -0400 Subject: [PATCH 254/445] Shifted ConfigTypeInfo to initialize objects by Ptr, not Ref, and track if pointer is owned. --- source/config/ConfigTypeInfo.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/source/config/ConfigTypeInfo.hpp b/source/config/ConfigTypeInfo.hpp index abdd959d..0f8c8ace 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/config/ConfigTypeInfo.hpp @@ -39,7 +39,7 @@ namespace mabe { class ConfigTypeInfo { private: using entry_ptr_t = emp::Ptr; - using init_fun_t = std::function; + using init_fun_t = std::function (const std::string &)>; size_t index; std::string type_name; @@ -47,27 +47,29 @@ namespace mabe { emp::TypeID type_id; init_fun_t init_fun; + bool config_owned = false; // Should objects of this type be managed by Emplode? emp::vector< MemberFunInfo > member_funs; public: // Constructor to allow a simple new configuration type - ConfigTypeInfo(size_t in_id, const std::string & in_name, const std::string & in_desc) - : index(in_id), type_name(in_name), desc(in_desc) { } + ConfigTypeInfo(size_t _id, const std::string & _name, const std::string & _desc) + : index(_id), type_name(_name), desc(_desc) { } // Constructor to allow a new configuration type whose objects require initialization. - ConfigTypeInfo(size_t in_id, const std::string & in_name, const std::string & in_desc, init_fun_t in_init) - : index(in_id), type_name(in_name), desc(in_desc), init_fun(in_init) - { - } + ConfigTypeInfo(size_t _id, const std::string & _name, const std::string & _desc, + init_fun_t _init, bool _config_owned=false) + : index(_id), type_name(_name), desc(_desc), init_fun(_init), config_owned(_config_owned) + { } size_t GetIndex() const { return index; } const std::string & GetTypeName() const { return type_name; } const std::string & GetDesc() const { return desc; } emp::TypeID GetType() const { return type_id; } + bool GetConfigOwned() const { return config_owned; } const emp::vector & GetMemberFunctions() const { return member_funs; } - ConfigType & MakeObj(const std::string & name) const { return init_fun(name); } + emp::Ptr MakeObj(const std::string & name) const { return init_fun(name); } // Link this ConfigTypeInfo object to a real C++ type. // @CAO It would be nice to test to make sure this is a ConfigType, but not possible with a TypeID. From cddc19447dd933f9c04ef3abe2d4b253934da17d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 09:22:12 -0400 Subject: [PATCH 255/445] Scopes now track if they own associated objects as delete as appropriate in destructor. --- source/config/ConfigEntry_Scope.hpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/config/ConfigEntry_Scope.hpp b/source/config/ConfigEntry_Scope.hpp index 4a6994d2..0c7b90e2 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/config/ConfigEntry_Scope.hpp @@ -35,6 +35,7 @@ namespace mabe { ///< If this scope represents a structure, point to it; otherwise set to null. emp::Ptr obj_ptr = nullptr; + bool obj_owned = false; template T & Add(const std::string & name, ARGS &&... args) { @@ -56,8 +57,9 @@ namespace mabe { ConfigEntry_Scope(const std::string & _name, const std::string & _desc, emp::Ptr _scope, - emp::Ptr _obj=nullptr) - : ConfigEntry(_name, _desc, _scope), obj_ptr(_obj) { } + emp::Ptr _obj=nullptr, + bool _owned=false) + : ConfigEntry(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } ConfigEntry_Scope(const ConfigEntry_Scope & in) : ConfigEntry(in) { // Copy all defined variables/scopes/functions @@ -66,6 +68,9 @@ namespace mabe { ConfigEntry_Scope(ConfigEntry_Scope &&) = default; ~ConfigEntry_Scope() { + // If this scope owns its object pointer, delete it now. + if (obj_owned) obj_ptr.Delete(); + // Clear up the symbol table. for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } @@ -151,9 +156,10 @@ namespace mabe { ConfigEntry_Scope & AddScope( const std::string & name, const std::string & desc, - emp::Ptr obj_ptr=nullptr + emp::Ptr obj_ptr=nullptr, + bool obj_owned=false ) { - return Add(name, desc, this, obj_ptr); + return Add(name, desc, this, obj_ptr, obj_owned); } /// Add a new user-defined function. From b1f76b0993a2d4c276649dd5a88e3941862a2ca1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 09:23:08 -0400 Subject: [PATCH 256/445] Shifted all object initialization functions in Config to return (and use) pointers. --- source/config/Config.hpp | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/source/config/Config.hpp b/source/config/Config.hpp index 98a4f811..93687170 100644 --- a/source/config/Config.hpp +++ b/source/config/Config.hpp @@ -106,7 +106,6 @@ namespace mabe { /// Management of built-in types. emp::StreamManager files; ///< Track all of the file streams used in MABE. - emp::vector file_map; /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -342,14 +341,16 @@ namespace mabe { // Setup default DataFile type. files.SetOutputDefaultFile(); // Stream manager should default to files for output. - std::function df_init = - [this](const std::string & name) -> ConfigType & { return this->AddDataFile(name); }; + std::function (const std::string &)> df_init = + [this](const std::string & name) { return emp::NewPtr(name, files); }; - auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init); - df_type.AddMemberFunction("ADD_COLUMN", + auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); + df_type.AddMemberFunction( + "ADD_COLUMN", [exec_fun](ConfigDataFile & file, const std::string & title, const std::string & expression){ return file.AddColumn(title, [exec_fun,expression](){ std::string out_string = exec_fun(expression); + if (!emp::is_number(out_string)) return emp::to_literal(out_string); return out_string; }); }, @@ -406,15 +407,17 @@ namespace mabe { /// To add a type, provide the type name (that can be referred to in a script) and a function /// that should be called (with the variable name) when an instance of that type is created. /// The function must return a reference to the newly created instance. + template ConfigTypeInfo & AddType( const std::string & type_name, const std::string & desc, - std::function init_fun, - emp::TypeID type_id + FUN_T init_fun, + emp::TypeID type_id, + bool is_config_owned=false ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun ); + auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); info_ptr->LinkType(type_id); type_map[type_name] = info_ptr; return *type_map[type_name]; @@ -422,25 +425,20 @@ namespace mabe { /// If the linked type can be provided as a template parameter, we can also double check that /// it is derived from ConfigType (as it needs to be...) - template + template ConfigTypeInfo & AddType( const std::string & type_name, const std::string & desc, - std::function init_fun + FUN_T init_fun, + bool is_config_owned=false ) { static_assert(std::is_base_of(), "Only ConfigType objects can be used as a custom config type."); - ConfigTypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID()); + ConfigTypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); OBJECT_T::InitType(*this, info); return info; } - /// Add in a new data file. - ConfigDataFile & AddDataFile(const std::string & obj_name) { - file_map.emplace_back(obj_name, files); - return file_map.back(); - } - /// Also allow direct file management. emp::StreamManager & GetFileManager() { return files; } @@ -787,16 +785,18 @@ namespace mabe { Debug("Building var '", var_name, "' of type '", type_name, "'"); // Retrieve the information about the requested type. - ConfigTypeInfo & type_info = *type_map[type_name]; + ConfigTypeInfo & type_info = *type_map[type_name]; + const std::string & type_desc = type_map[type_name]->GetDesc(); + const bool is_config_owned = type_info.GetConfigOwned(); // Use the ConfigTypeInfo associated with the provided type name to build an instance. - ConfigType & new_obj = type_info.MakeObj(var_name); + emp::Ptr new_obj = type_info.MakeObj(var_name); // Setup a scope for this new type, linking the object to it. - ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_map[type_name]->GetDesc(), &new_obj); + ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_desc, new_obj, is_config_owned); // Let the new object know about its scope. - new_obj.Setup(new_scope, type_info); + new_obj->Setup(new_scope, type_info); return new_scope; } From 0ee34d946b9a985b8092f48e8eb00abf90a70bf2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 09:23:55 -0400 Subject: [PATCH 257/445] Changed init functions in MABE to use pointers; renamed Eval() to Execute() --- source/core/MABE.hpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 2a2e379a..2ec68da6 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -562,7 +562,7 @@ namespace mabe { size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? const std::string replacement_text = - config.Eval(emp::view_string_range(out_string, i+2, end_pos)); + config.Execute(emp::view_string_range(out_string, i+2, end_pos)); out_string.replace(i, end_pos-i+1, replacement_text); i += replacement_text.size(); // Continue from the end point... @@ -593,9 +593,9 @@ namespace mabe { , cur_scope(&(config.GetRootScope())) { // Setup "Population" as a type in the config file. - std::function pop_init_fun = - [this](const std::string & name) -> ConfigType & { - return AddPopulation(name); + auto pop_init_fun = + [this](const std::string & name) -> emp::Ptr { + return &AddPopulation(name); }; auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun); @@ -611,10 +611,9 @@ namespace mabe { // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { - std::function mod_init_fun = - [this,&mod](const std::string & name) -> ConfigType & { - return mod.init_fun(*this,name); - }; + auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { + return mod.init_fun(*this,name); + }; config.AddType(mod.name, mod.desc, mod_init_fun, mod.type_id); } From ca86a28977ab3cf896262a1182cf02813df93056 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 09:25:21 -0400 Subject: [PATCH 258/445] More cleanup for ConfigTypes to initialize with pointers, not refs --- source/core/ManagerModule.hpp | 4 ++-- source/core/Module.hpp | 4 ++-- source/core/ModuleBase.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index b0c70547..7b53c71d 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -119,8 +119,8 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { - return control.AddModule(name, desc); + new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + return &control.AddModule(name, desc); }; GetModuleInfo().insert(new_info); } diff --git a/source/core/Module.hpp b/source/core/Module.hpp index c2026d3d..5e350e60 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -419,8 +419,8 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { - return control.AddModule(name, desc); + new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + return &control.AddModule(name, desc); }; new_info.type_id = emp::GetTypeID(); GetModuleInfo().insert(new_info); diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 47d74095..19136f89 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -323,7 +323,7 @@ namespace mabe { struct ModuleInfo { std::string name; std::string desc; - std::function init_fun; + std::function(MABE &, const std::string &)> init_fun; emp::TypeID type_id; bool operator<(const ModuleInfo & in) const { return name < in.name; } }; From ad4fa73acb20273d478c542721dbb9239de41838 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 12:26:46 -0400 Subject: [PATCH 259/445] HUGE overhaul of MABE Config, turning it into Emplode and changing/simplifying various class names. --- .../{config/ConfigAST.hpp => Emplode/AST.hpp} | 106 +++++----- .../DataFile.hpp} | 30 +-- source/{config => Emplode}/DeveloperNotes.md | 0 .../Config.hpp => Emplode/Emplode.hpp} | 188 ++++++++-------- .../EmplodeTools.hpp} | 75 ++++--- .../EmplodeType.hpp} | 44 ++-- .../EmplodeTypeBase.hpp} | 28 +-- .../ConfigEvents.hpp => Emplode/Events.hpp} | 22 +- .../ConfigLexer.hpp => Emplode/Lexer.hpp} | 16 +- .../ConfigEntry.hpp => Emplode/Symbol.hpp} | 200 +++++++++--------- source/Emplode/Symbol_Function.hpp | 61 ++++++ .../Symbol_Linked.hpp} | 72 +++---- .../Symbol_Scope.hpp} | 119 ++++++----- .../TypeInfo.hpp} | 41 ++-- source/config/ConfigEntry_Function.hpp | 61 ------ 15 files changed, 530 insertions(+), 533 deletions(-) rename source/{config/ConfigAST.hpp => Emplode/AST.hpp} (71%) rename source/{config/ConfigDataFile.hpp => Emplode/DataFile.hpp} (70%) rename source/{config => Emplode}/DeveloperNotes.md (100%) rename source/{config/Config.hpp => Emplode/Emplode.hpp} (85%) rename source/{config/ConfigTools.hpp => Emplode/EmplodeTools.hpp} (75%) rename source/{config/ConfigType.hpp => Emplode/EmplodeType.hpp} (80%) rename source/{config/ConfigTypeBase.hpp => Emplode/EmplodeTypeBase.hpp} (53%) rename source/{config/ConfigEvents.hpp => Emplode/Events.hpp} (92%) rename source/{config/ConfigLexer.hpp => Emplode/Lexer.hpp} (88%) rename source/{config/ConfigEntry.hpp => Emplode/Symbol.hpp} (53%) create mode 100644 source/Emplode/Symbol_Function.hpp rename source/{config/ConfigEntry_Linked.hpp => Emplode/Symbol_Linked.hpp} (50%) rename source/{config/ConfigEntry_Scope.hpp => Emplode/Symbol_Scope.hpp} (58%) rename source/{config/ConfigTypeInfo.hpp => Emplode/TypeInfo.hpp} (63%) delete mode 100644 source/config/ConfigEntry_Function.hpp diff --git a/source/config/ConfigAST.hpp b/source/Emplode/AST.hpp similarity index 71% rename from source/config/ConfigAST.hpp rename to source/Emplode/AST.hpp index 2186af8c..9904d95f 100644 --- a/source/config/ConfigAST.hpp +++ b/source/Emplode/AST.hpp @@ -1,31 +1,31 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigAST.hpp - * @brief Manages Abstract Syntax Tree nodes for Config. + * @file AST.hpp + * @brief Manages Abstract Syntax Tree nodes for Emplode. * @note Status: BETA */ -#ifndef MABE_CONFIG_AST_H -#define MABE_CONFIG_AST_H +#ifndef EMPLODE_AST_HPP +#define EMPLODE_AST_HPP #include "emp/base/assert.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "ConfigEntry.hpp" -#include "ConfigEntry_Scope.hpp" -#include "ConfigTools.hpp" +#include "Symbol.hpp" +#include "Symbol_Scope.hpp" +#include "EmplodeTools.hpp" -namespace mabe { +namespace emplode { /// Base class for all AST Nodes. class ASTNode { protected: - using entry_ptr_t = emp::Ptr; - using entry_vector_t = emp::vector; + using symbol_ptr_t = emp::Ptr; + using symbol_vector_t = emp::vector; using node_ptr_t = emp::Ptr; using node_vector_t = emp::vector; @@ -51,9 +51,9 @@ namespace mabe { virtual node_ptr_t GetChild(size_t /* id */) { emp_assert(false); return nullptr; } node_ptr_t GetParent() { return parent; } void SetParent(node_ptr_t in_parent) { parent = in_parent; } - virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } + virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } - virtual entry_ptr_t Process() = 0; + virtual symbol_ptr_t Process() = 0; virtual void Write(std::ostream & /* os */=std::cout, const std::string & /* offset */="") const { } @@ -84,38 +84,38 @@ namespace mabe { /// An ASTNode representing a leaf in the tree (i.e., a variable or literal) class ASTNode_Leaf : public ASTNode { protected: - entry_ptr_t entry_ptr; ///< Pointer to ConfigEntry at this leaf. - bool own_entry; ///< Should this node be in charge of deleting the entry pointer? + symbol_ptr_t symbol_ptr; ///< Pointer to Symbol at this leaf. + bool own_symbol; ///< Should this node be in charge of deleting the symbol? public: - ASTNode_Leaf(entry_ptr_t _ptr) : entry_ptr(_ptr), own_entry(_ptr->IsTemporary()) { - entry_ptr->SetTemporary(false); // If this entry was temporary, it is now owned. + ASTNode_Leaf(symbol_ptr_t _ptr) : symbol_ptr(_ptr), own_symbol(_ptr->IsTemporary()) { + symbol_ptr->SetTemporary(false); // If this symbol was temporary, it is now owned. } - ~ASTNode_Leaf() { if (own_entry) entry_ptr.Delete(); } + ~ASTNode_Leaf() { if (own_symbol) symbol_ptr.Delete(); } - const std::string & GetName() const override { return entry_ptr->GetName(); } - ConfigEntry & GetEntry() { return *entry_ptr; } + const std::string & GetName() const override { return symbol_ptr->GetName(); } + Symbol & GetSymbol() { return *symbol_ptr; } - bool IsNumeric() const override { return entry_ptr->IsNumeric(); } - bool IsString() const override { return entry_ptr->IsString(); } + bool IsNumeric() const override { return symbol_ptr->IsNumeric(); } + bool IsString() const override { return symbol_ptr->IsString(); } bool HasValue() const override { return true; } - bool HasNumericReturn() const override { return entry_ptr->HasNumericReturn(); } - bool HasStringReturn() const override { return entry_ptr->HasStringReturn(); } + bool HasNumericReturn() const override { return symbol_ptr->HasNumericReturn(); } + bool HasStringReturn() const override { return symbol_ptr->HasStringReturn(); } bool IsLeaf() const override { return true; } - entry_ptr_t Process() override { return entry_ptr; }; + symbol_ptr_t Process() override { return symbol_ptr; }; void Write(std::ostream & os, const std::string &) const override { // If this is a variable, print the variable name, - std::string output = entry_ptr->GetName(); + std::string output = symbol_ptr->GetName(); // If it is a literal, print the value. if (output == "") { - output = entry_ptr->AsString(); + output = symbol_ptr->AsString(); - // If the entry is a string, convert it to a string literal. - if (entry_ptr->IsString()) output = emp::to_literal(output); + // If the symbol is a string, convert it to a string literal. + if (symbol_ptr->IsString()) output = emp::to_literal(output); } os << output; } @@ -123,16 +123,16 @@ namespace mabe { class ASTNode_Block : public ASTNode_Internal { protected: - emp::Ptr scope_ptr; + emp::Ptr scope_ptr; public: - ASTNode_Block(ConfigEntry_Scope & in_scope) : scope_ptr(&in_scope) { } + ASTNode_Block(Symbol_Scope & in_scope) : scope_ptr(&in_scope) { } - emp::Ptr GetScope() override { return scope_ptr; } + emp::Ptr GetScope() override { return scope_ptr; } - entry_ptr_t Process() override { + symbol_ptr_t Process() override { for (auto node : children) { - entry_ptr_t out = node->Process(); + symbol_ptr_t out = node->Process(); if (out && out->IsTemporary()) out.Delete(); } return nullptr; @@ -159,12 +159,12 @@ namespace mabe { void SetFun(std::function< double(double) > _fun) { fun = _fun; } - entry_ptr_t Process() override { + symbol_ptr_t Process() override { emp_assert(children.size() == 1); - entry_ptr_t input_entry = children[0]->Process(); // Process child to get input entry - double output_value = fun(input_entry->AsDouble()); // Run the function to get ouput value - if (input_entry->IsTemporary()) input_entry.Delete(); // If we are done with input; delete! - return ConfigTools::MakeTempEntry(output_value); + symbol_ptr_t input_symbol = children[0]->Process(); // Process child to get input symbol + double output_value = fun(input_symbol->AsDouble()); // Run the function to get ouput value + if (input_symbol->IsTemporary()) input_symbol.Delete(); // If we are done with input; delete! + return EmplodeTools::MakeTempSymbol(output_value); } void Write(std::ostream & os, const std::string & offset) const override { @@ -187,14 +187,14 @@ namespace mabe { void SetFun(std::function< RETURN_T(ARG1_T, ARG2_T) > _fun) { fun = _fun; } - entry_ptr_t Process() override { + symbol_ptr_t Process() override { emp_assert(children.size() == 2); - entry_ptr_t in1 = children[0]->Process(); // Process 1st child to input entry - entry_ptr_t in2 = children[1]->Process(); // Process 2nd child to input entry + symbol_ptr_t in1 = children[0]->Process(); // Process 1st child to input symbol + symbol_ptr_t in2 = children[1]->Process(); // Process 2nd child to input symbol auto out_val = fun(in1->As(), in2->As()); // Run function; get ouput if (in1->IsTemporary()) in1.Delete(); // If we are done with in1; delete! if (in2->IsTemporary()) in2.Delete(); // If we are done with in2; delete! - return ConfigTools::MakeTempEntry(out_val); + return EmplodeTools::MakeTempSymbol(out_val); } void Write(std::ostream & os, const std::string & offset) const override { @@ -220,10 +220,10 @@ namespace mabe { bool HasNumericReturn() const override { return children[0]->HasNumericReturn(); } bool HasStringReturn() const override { return children[0]->HasStringReturn(); } - entry_ptr_t Process() override { + symbol_ptr_t Process() override { emp_assert(children.size() == 2); - entry_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. - entry_ptr_t rhs = children[1]->Process(); // Determine the right-hand-side value. + symbol_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. + symbol_ptr_t rhs = children[1]->Process(); // Determine the right-hand-side value. // @CAO Should make sure that lhs is properly assignable. lhs->CopyValue(*rhs); if (rhs->IsTemporary()) rhs.Delete(); @@ -250,16 +250,16 @@ namespace mabe { // @CAO Technically, one function can return another, so we should check // HasNumericReturn() and HasStringReturn() on return values... but hard to implement. - entry_ptr_t Process() override { + symbol_ptr_t Process() override { emp_assert(children.size() >= 1); - entry_ptr_t fun = children[0]->Process(); + symbol_ptr_t fun = children[0]->Process(); // Collect all arguments and call - entry_vector_t args; + symbol_vector_t args; for (size_t i = 1; i < children.size(); i++) { args.push_back(children[i]->Process()); } - entry_ptr_t result = fun->Call(args); + symbol_ptr_t result = fun->Call(args); // Cleanup and return for (auto arg : args) if (arg->IsTemporary()) arg.Delete(); @@ -279,7 +279,7 @@ namespace mabe { class ASTNode_Event : public ASTNode_Internal { protected: - using setup_fun_t = std::function; + using setup_fun_t = std::function; setup_fun_t setup_event; public: @@ -290,9 +290,9 @@ namespace mabe { for (auto arg : args) AddChild(arg); } - entry_ptr_t Process() override { + symbol_ptr_t Process() override { emp_assert(children.size() >= 1); - entry_vector_t arg_entries; + symbol_vector_t arg_entries; for (size_t id = 1; id < children.size(); id++) { arg_entries.push_back( children[id]->Process() ); } diff --git a/source/config/ConfigDataFile.hpp b/source/Emplode/DataFile.hpp similarity index 70% rename from source/config/ConfigDataFile.hpp rename to source/Emplode/DataFile.hpp index 0b361cd7..82aba7ad 100644 --- a/source/config/ConfigDataFile.hpp +++ b/source/Emplode/DataFile.hpp @@ -1,15 +1,15 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file ConfigDataFile.hpp + * @file DataFile.hpp * @brief Manages a DataFile object for config. * @note Status: BETA */ -#ifndef MABE_CONFIG_DATA_FILE_H -#define MABE_CONFIG_DATA_FILE_H +#ifndef EMPLODE_DATA_FILE_HPP +#define EMPLODE_DATA_FILE_HPP #include #include @@ -17,13 +17,13 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "ConfigType.hpp" +#include "EmplodeType.hpp" -namespace mabe { +namespace emplode { - /// A ConfigDataFile maintains an output file that has specified columns and can be generate + /// A DataFile maintains an output file that has specified columns and can be generate /// dynamically. - class ConfigDataFile : public ConfigType { + class DataFile : public EmplodeType { private: using fun_t = std::function; struct ColumnInfo { @@ -38,18 +38,18 @@ namespace mabe { emp::vector cols; ///< Data about columns maintainted. public: - ConfigDataFile() = delete; - ConfigDataFile(const std::string & in_name, emp::StreamManager & _files) + DataFile() = delete; + DataFile(const std::string & in_name, emp::StreamManager & _files) : name(in_name), files(_files) { } - ~ConfigDataFile() { } + ~DataFile() { } std::string GetName() const { return name; } // Setup member functions associated with population. - static void InitType(Config & /*config*/, ConfigTypeInfo & info) { - auto fun_num_cols = [](ConfigDataFile & target) { return target.cols.size(); }; + static void InitType(Emplode & /*config*/, TypeInfo & info) { + auto fun_num_cols = [](DataFile & target) { return target.cols.size(); }; info.AddMemberFunction("NUM_COLS", fun_num_cols, "Return the number of columns in this file."); - info.AddMemberFunction("WRITE", [](ConfigDataFile & target) { return target.Write(); }, + info.AddMemberFunction("WRITE", [](DataFile & target) { return target.Write(); }, "Add on the next line of data."); } @@ -86,7 +86,7 @@ namespace mabe { return 1; } - static std::string EMPGetTypeName() { return "mabe::ConfigDataFile"; } + static std::string EMPGetTypeName() { return "emplode::DataFile"; } }; } diff --git a/source/config/DeveloperNotes.md b/source/Emplode/DeveloperNotes.md similarity index 100% rename from source/config/DeveloperNotes.md rename to source/Emplode/DeveloperNotes.md diff --git a/source/config/Config.hpp b/source/Emplode/Emplode.hpp similarity index 85% rename from source/config/Config.hpp rename to source/Emplode/Emplode.hpp index 93687170..c1dab5dc 100644 --- a/source/config/Config.hpp +++ b/source/Emplode/Emplode.hpp @@ -1,10 +1,10 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file Config.hpp - * @brief Manages all configuration of MABE runs (full parser implementation here) + * @file Emplode.hpp + * @brief Manages all configuration with Emplode language. * @note Status: BETA * * Example usage: @@ -65,8 +65,8 @@ * } */ -#ifndef MABE_CONFIG_H -#define MABE_CONFIG_H +#ifndef EMPLODE_HPP +#define EMPLODE_HPP #include @@ -76,36 +76,36 @@ #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" -#include "ConfigAST.hpp" -#include "ConfigDataFile.hpp" -#include "ConfigEvents.hpp" -#include "ConfigEntry_Function.hpp" -#include "ConfigLexer.hpp" -#include "ConfigEntry_Scope.hpp" -#include "ConfigType.hpp" -#include "ConfigTypeInfo.hpp" +#include "AST.hpp" +#include "DataFile.hpp" +#include "EmplodeType.hpp" +#include "Symbol_Function.hpp" +#include "Symbol_Scope.hpp" +#include "Events.hpp" +#include "Lexer.hpp" +#include "TypeInfo.hpp" -namespace mabe { +namespace emplode { - class Config { + class Emplode { public: using pos_t = emp::TokenStream::Iterator; protected: std::string filename; ///< Source for for code to generate. - ConfigLexer lexer; ///< Lexer to process input code. - ConfigEntry_Scope root_scope; ///< All variables from the root level. + Lexer lexer; ///< Lexer to process input code. + Symbol_Scope root_scope; ///< All variables from the root level. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. bool debug = false; ///< Should we print full debug information? /// A map of names to event groups. - std::map events_map; + std::map events_map; /// A map of all types available in the script. - std::unordered_map> type_map; + std::unordered_map> type_map; /// Management of built-in types. - emp::StreamManager files; ///< Track all of the file streams used in MABE. + emp::StreamManager files; ///< Track all file streams. /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -182,12 +182,12 @@ namespace mabe { /// If create_ok is true, create any variables that we don't find. Otherwise continue the /// search for them in successively outer (lower) scopes. [[nodiscard]] emp::Ptr ParseVar(pos_t & pos, - ConfigEntry_Scope & cur_scope, + Symbol_Scope & cur_scope, bool create_ok=false, bool scan_scopes=true); /// Load a value from the provided scope, which can come from a variable or a literal. - [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, ConfigEntry_Scope & cur_scope); + [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, Symbol_Scope & cur_scope); /// Calculate the result of the provided operation on two computed entries. [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, @@ -196,20 +196,20 @@ namespace mabe { /// Calculate a full expression found in a token sequence, using the provided scope. [[nodiscard]] emp::Ptr - ParseExpression(pos_t & pos, ConfigEntry_Scope & cur_scope, size_t prec_limit=1000); + ParseExpression(pos_t & pos, Symbol_Scope & cur_scope, size_t prec_limit=1000); - /// Parse the declaration of a variable and return the newly created ConfigEntry - ConfigEntry & ParseDeclaration(pos_t & pos, ConfigEntry_Scope & scope); + /// Parse the declaration of a variable and return the newly created Symbol + Symbol & ParseDeclaration(pos_t & pos, Symbol_Scope & scope); /// Parse an event description. - emp::Ptr ParseEvent(pos_t & pos, ConfigEntry_Scope & scope); + emp::Ptr ParseEvent(pos_t & pos, Symbol_Scope & scope); /// Parse the next input in the specified Struct. A statement can be a variable declaration, /// an expression, or an event. - [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, ConfigEntry_Scope & scope); + [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, Symbol_Scope & scope); /// Keep parsing statements until there aren't any more or we leave this scope. - [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, ConfigEntry_Scope & scope) { + [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, Symbol_Scope & scope) { Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); auto cur_block = emp::NewPtr(scope); while (pos.IsValid() && AsChar(pos) != '}') { @@ -223,19 +223,19 @@ namespace mabe { } public: - Config(std::string in_filename="") + Emplode(std::string in_filename="") : filename(in_filename) - , root_scope("MABE", "Outer-most, global scope.", nullptr) + , root_scope("Emplode", "Outer-most, global scope.", nullptr) , ast_root(root_scope) { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "/*ERROR*/", "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "Struct", "User-made structure" ); + type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "Struct", "User-made structure" ); // Setup operator precedence. size_t cur_prec = 0; @@ -257,8 +257,8 @@ namespace mabe { AddFunction("EXEC", exec_fun, "Dynamically execute the string passed in."); // 'PRINT' is a simple debugging command to output the value of a variable. - std::function> &)> print_fun = - [](const emp::vector> & args) { + std::function> &)> print_fun = + [](const emp::vector> & args) { for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); return 0; }; @@ -341,13 +341,13 @@ namespace mabe { // Setup default DataFile type. files.SetOutputDefaultFile(); // Stream manager should default to files for output. - std::function (const std::string &)> df_init = - [this](const std::string & name) { return emp::NewPtr(name, files); }; + std::function (const std::string &)> df_init = + [this](const std::string & name) { return emp::NewPtr(name, files); }; - auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); + auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); df_type.AddMemberFunction( "ADD_COLUMN", - [exec_fun](ConfigDataFile & file, const std::string & title, const std::string & expression){ + [exec_fun](DataFile & file, const std::string & title, const std::string & expression){ return file.AddColumn(title, [exec_fun,expression](){ std::string out_string = exec_fun(expression); if (!emp::is_number(out_string)) return emp::to_literal(out_string); @@ -359,18 +359,18 @@ namespace mabe { } // Prevent copy or move since we are using lambdas that capture 'this' - Config(const Config &) = delete; - Config(Config &&) = delete; - Config & operator=(const Config &) = delete; - Config & operator=(Config &&) = delete; + Emplode(const Emplode &) = delete; + Emplode(Emplode &&) = delete; + Emplode & operator=(const Emplode &) = delete; + Emplode & operator=(Emplode &&) = delete; - ~Config() { + ~Emplode() { // Clean up type information. for (auto [name, ptr] : type_map) ptr.Delete(); } /// Create a new type of event that can be used in the scripting language. - ConfigEvents & AddEventType(const std::string & name) { + Events & AddEventType(const std::string & name) { emp_assert(!emp::Has(events_map, name)); Debug ("Adding event type '", name, "'"); return events_map[name]; @@ -408,7 +408,7 @@ namespace mabe { /// that should be called (with the variable name) when an instance of that type is created. /// The function must return a reference to the newly created instance. template - ConfigTypeInfo & AddType( + TypeInfo & AddType( const std::string & type_name, const std::string & desc, FUN_T init_fun, @@ -417,24 +417,24 @@ namespace mabe { ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); + auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); info_ptr->LinkType(type_id); type_map[type_name] = info_ptr; return *type_map[type_name]; } /// If the linked type can be provided as a template parameter, we can also double check that - /// it is derived from ConfigType (as it needs to be...) + /// it is derived from EmplodeType (as it needs to be...) template - ConfigTypeInfo & AddType( + TypeInfo & AddType( const std::string & type_name, const std::string & desc, FUN_T init_fun, bool is_config_owned=false ) { - static_assert(std::is_base_of(), - "Only ConfigType objects can be used as a custom config type."); - ConfigTypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); + static_assert(std::is_base_of(), + "Only EmplodeType objects can be used as a custom config type."); + TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); OBJECT_T::InitType(*this, info); return info; } @@ -453,8 +453,8 @@ namespace mabe { root_scope.AddBuiltinFunction(name, fun, desc); } - ConfigEntry_Scope & GetRootScope() { return root_scope; } - const ConfigEntry_Scope & GetRootScope() const { return root_scope; } + Symbol_Scope & GetRootScope() { return root_scope; } + const Symbol_Scope & GetRootScope() const { return root_scope; } // Load a single, specified configuration file. void Load(const std::string & filename) { @@ -494,32 +494,32 @@ namespace mabe { } // Load the provided statement and run it. - std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { + std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { Debug("Running Execute()"); if (!scope) scope = &root_scope; // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. auto cur_block = ParseStatement(pos, root_scope); // Convert tokens to AST - auto result_ptr = cur_block->Process(); // Process AST to get result entry. + auto result_ptr = cur_block->Process(); // Process AST to get result symbol. std::string result = ""; // Default result to an empty string. if (result_ptr) { result = result_ptr->AsString(); // Convert result to output string. - if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete the result entry if done. + if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete the result symbol if done. } cur_block.Delete(); // Delete the AST. return result; // Return the result string. } - Config & Write(std::ostream & os=std::cout) { + Emplode & Write(std::ostream & os=std::cout) { root_scope.WriteContents(os); os << '\n'; PrintEvents(os); return *this; } - Config & Write(const std::string & filename) { + Emplode & Write(const std::string & filename) { // If the filename is empty or "_", output to standard out. if (filename == "" || filename == "_") return Write(); @@ -530,12 +530,12 @@ namespace mabe { }; ////////////////////////////////////////////////////////// - // --== Config member function Implementations! ==-- + // --== Emplode member function Implementations! ==-- // Load a variable name from the provided scope. - emp::Ptr Config::ParseVar(pos_t & pos, - ConfigEntry_Scope & cur_scope, + emp::Ptr Emplode::ParseVar(pos_t & pos, + Symbol_Scope & cur_scope, bool create_ok, bool scan_scopes) { Debug("Running ParseVar(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); @@ -544,7 +544,7 @@ namespace mabe { if (IsDots(pos)) { scan_scopes = false; // One or more initial dots specify scope; don't scan! size_t num_dots = GetSize(pos); // Extra dots shift scope. - emp::Ptr scope_ptr = &cur_scope; + emp::Ptr scope_ptr = &cur_scope; while (num_dots-- > 1) { scope_ptr = scope_ptr->GetScope(); if (scope_ptr.IsNull()) Error(pos, "Too many dots; goes beyond global scope."); @@ -561,35 +561,35 @@ namespace mabe { std::string var_name = AsLexeme(pos++); // Lookup this variable. - emp::Ptr cur_entry = cur_scope.LookupEntry(var_name, scan_scopes); + emp::Ptr cur_symbol = cur_scope.LookupSymbol(var_name, scan_scopes); // If we can't find this variable, throw an error. - if (cur_entry.IsNull()) { + if (cur_symbol.IsNull()) { Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type.", " Current scope is '", cur_scope.GetName(), "'"); } // If this variable just provided a scope, keep going. - if (IsDots(pos)) return ParseVar(pos, cur_entry->AsScope(), create_ok, false); + if (IsDots(pos)) return ParseVar(pos, cur_symbol->AsScope(), create_ok, false); // Otherwise return the variable as a leaf! - return emp::NewPtr(cur_entry); + return emp::NewPtr(cur_symbol); } emp::Ptr MakeTempLeaf(double val) { - auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); + auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); } emp::Ptr MakeTempLeaf(const std::string & val) { - auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); + auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); } // Load a value from the provided scope, which can come from a variable or a literal. - emp::Ptr Config::ParseValue(pos_t & pos, ConfigEntry_Scope & cur_scope) { + emp::Ptr Emplode::ParseValue(pos_t & pos, Symbol_Scope & cur_scope) { Debug("Running ParseValue(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ")"); // Anything that begins with an identifier or dots must represent a variable. Refer! @@ -599,21 +599,21 @@ namespace mabe { if (IsNumber(pos)) { Debug("...value is a number: ", AsLexeme(pos)); double value = emp::from_string(AsLexeme(pos++)); // Calculate value. - return MakeTempLeaf(value); // Return temporary ConfigEntry. + return MakeTempLeaf(value); // Return temporary Symbol. } // A literal char should be converted to its ASCII value. if (IsChar(pos)) { Debug("...value is a char: ", AsLexeme(pos)); char lit_char = emp::from_literal_char(AsLexeme(pos++)); // Convert the literal char. - return MakeTempLeaf((double) lit_char); // Return temporary ConfigEntry. + return MakeTempLeaf((double) lit_char); // Return temporary Symbol. } // A literal string should be converted to a regular string and used. if (IsString(pos)) { Debug("...value is a string: ", AsLexeme(pos)); std::string str = emp::from_literal_string(AsLexeme(pos++)); // Convert the literal string. - return MakeTempLeaf(str); // Return temporary ConfigEntry. + return MakeTempLeaf(str); // Return temporary Symbol. } // If we have an open parenthesis, process everything inside into a single value... @@ -629,8 +629,8 @@ namespace mabe { return nullptr; } - // Process a single provided operation on two ConfigEntry objects. - emp::Ptr Config::ProcessOperation(const std::string & symbol, + // Process a single provided operation on two Symbol objects. + emp::Ptr Emplode::ProcessOperation(const std::string & symbol, emp::Ptr in_node1, emp::Ptr in_node2) { @@ -720,9 +720,9 @@ namespace mabe { // Calculate an expression in the provided scope. - emp::Ptr Config::ParseExpression( + emp::Ptr Emplode::ParseExpression( pos_t & pos, - ConfigEntry_Scope & scope, + Symbol_Scope & scope, size_t prec_limit ) { Debug("Running ParseExpression(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); @@ -766,7 +766,7 @@ namespace mabe { } // Parse an the declaration of a variable. - ConfigEntry & Config::ParseDeclaration(pos_t & pos, ConfigEntry_Scope & scope) { + Symbol & Emplode::ParseDeclaration(pos_t & pos, Symbol_Scope & scope) { std::string type_name = AsLexeme(pos++); RequireID(pos, "Type name '", type_name, "' must be followed by variable to declare."); std::string var_name = AsLexeme(pos++); @@ -785,15 +785,15 @@ namespace mabe { Debug("Building var '", var_name, "' of type '", type_name, "'"); // Retrieve the information about the requested type. - ConfigTypeInfo & type_info = *type_map[type_name]; + TypeInfo & type_info = *type_map[type_name]; const std::string & type_desc = type_map[type_name]->GetDesc(); - const bool is_config_owned = type_info.GetConfigOwned(); + const bool is_config_owned = type_info.GetOwned(); - // Use the ConfigTypeInfo associated with the provided type name to build an instance. - emp::Ptr new_obj = type_info.MakeObj(var_name); + // Use the TypeInfo associated with the provided type name to build an instance. + emp::Ptr new_obj = type_info.MakeObj(var_name); // Setup a scope for this new type, linking the object to it. - ConfigEntry_Scope & new_scope = scope.AddScope(var_name, type_desc, new_obj, is_config_owned); + Symbol_Scope & new_scope = scope.AddScope(var_name, type_desc, new_obj, is_config_owned); // Let the new object know about its scope. new_obj->Setup(new_scope, type_info); @@ -802,7 +802,7 @@ namespace mabe { } // Parse an event description. - emp::Ptr Config::ParseEvent(pos_t & pos, ConfigEntry_Scope & scope) { + emp::Ptr Emplode::ParseEvent(pos_t & pos, Symbol_Scope & scope) { RequireChar('@', pos++, "All event declarations must being with an '@'."); RequireID(pos, "Events must start by specifying event name."); const std::string & event_name = AsLexeme(pos++); @@ -820,7 +820,7 @@ namespace mabe { Debug("Building event '", event_name, "' with args ", args); auto setup_event = [this, event_name](emp::Ptr action, - const emp::vector> & args) { + const emp::vector> & args) { AddEvent(event_name, action, (args.size() > 0) ? args[0]->AsDouble() : 0.0, (args.size() > 1) ? args[1]->AsDouble() : 0.0, @@ -831,7 +831,7 @@ namespace mabe { } // Process the next input in the specified Struct. - emp::Ptr Config::ParseStatement(pos_t & pos, ConfigEntry_Scope & scope) { + emp::Ptr Emplode::ParseStatement(pos_t & pos, Symbol_Scope & scope) { Debug("Running ParseStatement(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); // Allow a statement with an empty line. @@ -851,7 +851,7 @@ namespace mabe { // Allow this statement to be a declaration if it begins with a type. if (IsType(pos)) { - ConfigEntry & new_entry = ParseDeclaration(pos, scope); + Symbol & new_symbol = ParseDeclaration(pos, scope); // If the next symbol is a ';' this is a declaration without an assignment. if (AsChar(pos) == ';') { @@ -859,13 +859,13 @@ namespace mabe { return nullptr; // We are done! } - // If this entry is a new scope, it should be populated now. - if (new_entry.IsScope()) { - RequireChar('{', pos, "Expected scope '", new_entry.GetName(), + // If this symbol is a new scope, it should be populated now. + if (new_symbol.IsScope()) { + RequireChar('{', pos, "Expected scope '", new_symbol.GetName(), "' definition to start with a '{'; found ''", AsLexeme(pos), "'."); pos++; - emp::Ptr out_node = ParseStatementList(pos, new_entry.AsScope()); - RequireChar('}', pos++, "Expected scope '", new_entry.GetName(), "' to end with a '}'."); + emp::Ptr out_node = ParseStatementList(pos, new_symbol.AsScope()); + RequireChar('}', pos++, "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); return out_node; } diff --git a/source/config/ConfigTools.hpp b/source/Emplode/EmplodeTools.hpp similarity index 75% rename from source/config/ConfigTools.hpp rename to source/Emplode/EmplodeTools.hpp index 2d5f4b5f..56ca47d7 100644 --- a/source/config/ConfigTools.hpp +++ b/source/Emplode/EmplodeTools.hpp @@ -1,15 +1,15 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file ConfigTools.hpp - * @brief Tools for working with ConfigEntry objects. + * @file EmplodeTools.hpp + * @brief Tools for working with Symbol objects, especially for wrapping functions. * @note Status: BETA */ -#ifndef MABE_CONFIG_TOOLS_HPP -#define MABE_CONFIG_TOOLS_HPP +#ifndef EMPLODE_TOOLS_HPP +#define EMPLODE_TOOLS_HPP #include @@ -19,43 +19,42 @@ #include "emp/meta/FunInfo.hpp" #include "emp/meta/ValPack.hpp" -#include "ConfigEntry.hpp" -#include "ConfigTools.hpp" +#include "Symbol.hpp" -namespace mabe { -namespace ConfigTools { +namespace emplode { +namespace EmplodeTools { - using entry_ptr_t = emp::Ptr; - using entry_vector_t = emp::vector; - using target_t = entry_ptr_t( const entry_vector_t & ); + using symbol_ptr_t = emp::Ptr; + using symbol_vector_t = emp::vector; + using target_t = symbol_ptr_t( const symbol_vector_t & ); - // Use ConfigTools::MakeTempEntry(value) to quickly allocate a temporary entry with a - // given value. NOTE: Caller is responsible for deleting the created entry! + // Use EmplodeTools::MakeTempSymbol(value) to quickly allocate a temporary symbol with a + // given value. NOTE: Caller is responsible for deleting the created symbol! template - static emp::Ptr> MakeTempEntry(VALUE_T value) { - auto out_entry = emp::NewPtr>("__Temp", value, "", nullptr); - out_entry->SetTemporary(); - return out_entry; + static emp::Ptr> MakeTempSymbol(VALUE_T value) { + auto out_symbol = emp::NewPtr>("__Temp", value, "", nullptr); + out_symbol->SetTemporary(); + return out_symbol; } template static auto ConvertReturn( RETURN_T && return_value ) { - // If a return value is already an entry pointer, just pass it through. - if constexpr (std::is_same()) { + // If a return value is already a symbol pointer, just pass it through. + if constexpr (std::is_same()) { return return_value; } - // If a return value is a basic type, wrap it in a temporary entry + // If a return value is a basic type, wrap it in a temporary symbol else if constexpr (std::is_same() || std::is_arithmetic()) { - return MakeTempEntry(return_value); + return MakeTempSymbol(return_value); } // For now these are the only legal return type; raise error otherwise! else { emp::ShowType{}; static_assert(emp::dependent_false(), - "Invalid return value in ConfigEntry_Function::SetFunction()"); + "Invalid return value in Symbol_Function::SetFunction()"); } } @@ -68,7 +67,7 @@ namespace ConfigTools { template static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun) { - return [name=name,fun=fun]([[maybe_unused]] const entry_vector_t & args) { + return [name=name,fun=fun]([[maybe_unused]] const symbol_vector_t & args) { emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); return ConvertReturn( fun() ); }; @@ -85,11 +84,11 @@ namespace ConfigTools { template static auto ConvertFun(const std::string & name, FUN_T fun) { - return [name=name,fun=fun](const entry_vector_t & args) { - // If this function already takes a const entry_vector_t & as its only parameter, + return [name=name,fun=fun](const symbol_vector_t & args) { + // If this function already takes a const symbol_vector_t & as its only parameter, // just pass it along. if constexpr (sizeof...(PARAM_Ts) == 0 && - std::is_same_v) { + std::is_same_v) { return ConvertReturn( fun(args) ); } @@ -116,14 +115,14 @@ namespace ConfigTools { static_assert(std::is_reference_v, "First parameter for supplied member functions must be reference to object"); - static_assert(std::is_base_of_v>, - "First parameter for supplied member functions must derived from ConfigType"); + static_assert(std::is_base_of_v>, + "First parameter for supplied member functions must derived from EmplodeType"); static_assert(info_t::num_args == sizeof...(PARAM_Ts) + 1, "PARAM_Ts must match the extra arguments in member function."); - return [name=name,fun=fun](ConfigType & obj, const entry_vector_t & args) { + return [name=name,fun=fun](EmplodeType & obj, const symbol_vector_t & args) { // Make sure the correct object type is used for first argument. - emp::Ptr obj_ptr(&obj); + emp::Ptr obj_ptr(&obj); auto typed_ptr = obj_ptr.DynamicCast>(); emp_assert(typed_ptr, "Internal error: member function call on wrong object type!", name); @@ -139,10 +138,10 @@ namespace ConfigTools { return ConvertReturn( fun(*typed_ptr) ); } - // If this function already takes a const entry_vector_t & as its only extra parameter, + // If this function already takes a const symbol_vector_t & as its only extra parameter, // just pass it along. else if constexpr (sizeof...(PARAM_Ts) == 1 && - std::is_same_v, const entry_vector_t &>) { + std::is_same_v, const symbol_vector_t &>) { return ConvertReturn( fun(*typed_ptr, args) ); } @@ -164,8 +163,8 @@ namespace ConfigTools { }; - // Wrap a provided function to make it take a vector of Ptr and return a - // single Ptr representing the result. + // Wrap a provided function to make it take a vector of Ptr and return a + // single Ptr representing the result. template static auto WrapFunction(const std::string & name, FUN_T fun) { using info_t = emp::FunInfo; @@ -179,7 +178,7 @@ namespace ConfigTools { } // Wrap a provided MEMBER function to make it take a reference to the object it is a member of - // and a vector of Ptr and return a single Ptr representing the result. + // and a vector of Ptr and return a single Ptr representing the result. template static auto WrapMemberFunction(emp::TypeID class_type, const std::string & name, FUN_T fun) { // Do some checks that will produce reasonable errors. @@ -190,8 +189,8 @@ namespace ConfigTools { // Is the first parameter the correct type? using object_t = typename info_t::template arg_t<0>; using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; - static_assert(std::is_base_of(), - "Member functions must take a reference to the associated ConfigType"); + static_assert(std::is_base_of(), + "Member functions must take a reference to the associated EmplodeType"); emp_assert( class_type.IsType(), "First parameter must match class type of member function being created!", emp::GetTypeID(), class_type ); diff --git a/source/config/ConfigType.hpp b/source/Emplode/EmplodeType.hpp similarity index 80% rename from source/config/ConfigType.hpp rename to source/Emplode/EmplodeType.hpp index 1345dd2a..ec6b8fa6 100644 --- a/source/config/ConfigType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -1,39 +1,39 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigType.hpp + * @file EmplodeType.hpp * @brief Setup types for use in scripting. - * @note Status: ALPHA + * @note Status: BETA */ -#ifndef MABE_CONFIG_TYPE_H -#define MABE_CONFIG_TYPE_H +#ifndef EMPLODE_TYPE_HPP +#define EMPLODE_TYPE_HPP #include "emp/base/assert.hpp" -#include "ConfigTypeBase.hpp" -#include "ConfigEntry_Scope.hpp" -#include "ConfigTypeInfo.hpp" +#include "EmplodeTypeBase.hpp" +#include "Symbol_Scope.hpp" +#include "TypeInfo.hpp" -namespace mabe { +namespace emplode { - class Config; + class Emplode; // Base class for types that we want to be used for scripting. - class ConfigType : public ConfigTypeBase { + class EmplodeType : public EmplodeTypeBase { public: /// Setup the TYPE of object in the config. This is a stub class, but any new class derived from - /// ConfigType can create its own version to automatically load in member functions, etc. - static void InitType(Config & /*config*/, ConfigTypeInfo & /*info*/) { - // If you create a version of this function for your own ConfigType, this is where you would + /// EmplodeType can create its own version to automatically load in member functions, etc. + static void InitType(Emplode & /*config*/, TypeInfo & /*info*/) { + // If you create a version of this function for your own EmplodeType, this is where you would // create member functions. } - /// Setup an instance of a new ConfigType object; provide it with its scope and type information. - void Setup(ConfigEntry_Scope & _scope, ConfigTypeInfo & _info) { + /// Setup an instance of a new EmplodeType object; provide it with its scope and type information. + void Setup(Symbol_Scope & _scope, TypeInfo & _info) { cur_scope = &_scope; type_info_ptr = &_info; @@ -45,8 +45,8 @@ namespace mabe { SetupConfig(); // Load in any member function for this object into the scope. - using entry_ptr_t = emp::Ptr; - using member_fun_t = std::function &)>; + using symbol_ptr_t = emp::Ptr; + using member_fun_t = std::function &)>; const auto & member_map = type_info_ptr->GetMemberFunctions(); // std::cout << "Loading member functions for '" << _scope.GetName() << "'; " @@ -54,7 +54,7 @@ namespace mabe { // << std::endl; for (const MemberFunInfo & member_info : member_map) { - member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ + member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ return member_info.fun(*this, args); }; cur_scope->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); @@ -70,7 +70,7 @@ namespace mabe { /// Link a variable to a configuration entry - the value will default to the /// variables current value, but be updated when configs are loaded. template - ConfigEntry_Linked & LinkVar(VAR_T & var, + Symbol_Linked & LinkVar(VAR_T & var, const std::string & name, const std::string & desc, bool is_builtin = false) { @@ -80,7 +80,7 @@ namespace mabe { /// Link a configuration entry to a pair of functions - it automatically calls the set /// function when configs are loaded, and the get function when current value is needed. template - ConfigEntry_LinkedFunctions & LinkFuns(std::function get_fun, + Symbol_LinkedFunctions & LinkFuns(std::function get_fun, std::function set_fun, const std::string & name, const std::string & desc, @@ -103,7 +103,7 @@ namespace mabe { /// Each option should include three arguments: /// The return value, the option name, and the option description. template - ConfigEntry_LinkedFunctions & LinkMenu(VAR_T & var, + Symbol_LinkedFunctions & LinkMenu(VAR_T & var, const std::string & name, const std::string & desc, const Ts &... entries) { diff --git a/source/config/ConfigTypeBase.hpp b/source/Emplode/EmplodeTypeBase.hpp similarity index 53% rename from source/config/ConfigTypeBase.hpp rename to source/Emplode/EmplodeTypeBase.hpp index b28a9346..33dc7ff2 100644 --- a/source/config/ConfigTypeBase.hpp +++ b/source/Emplode/EmplodeTypeBase.hpp @@ -1,22 +1,22 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file ConfigTypeBase.hpp + * @file EmplodeTypeBase.hpp * @brief Base class for setting up custom types for use in scripting; usable throughout. * @note Status: ALPHA */ -#ifndef MABE_CONFIG_TYPE_BASE_HPP -#define MABE_CONFIG_TYPE_BASE_HPP +#ifndef EMPLODE_TYPE_BASE_HPP +#define EMPLODE_TYPE_BASE_HPP #include "emp/base/assert.hpp" -namespace mabe { +namespace emplode { - class ConfigEntry_Scope; - class ConfigTypeInfo; + class Symbol_Scope; + class TypeInfo; enum class BaseType { INVALID = 0, @@ -26,25 +26,25 @@ namespace mabe { STRUCT }; - class ConfigTypeBase { + class EmplodeTypeBase { protected: - emp::Ptr cur_scope; - emp::Ptr type_info_ptr; + emp::Ptr cur_scope; + emp::Ptr type_info_ptr; // Some special, internal variables associated with each object. bool _active=true; ///< Should this object be used in the current run? std::string _desc=""; ///< Special description for this object. public: - virtual ~ConfigTypeBase() { } + virtual ~EmplodeTypeBase() { } // Optional function to override to add configuration options associated with an object. virtual void SetupConfig() { }; - ConfigEntry_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - const ConfigEntry_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + Symbol_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + const Symbol_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - const ConfigTypeInfo & GetTypeInfo() const { return *type_info_ptr; } + const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } }; } diff --git a/source/config/ConfigEvents.hpp b/source/Emplode/Events.hpp similarity index 92% rename from source/config/ConfigEvents.hpp rename to source/Emplode/Events.hpp index a96737f1..968cded9 100644 --- a/source/config/ConfigEvents.hpp +++ b/source/Emplode/Events.hpp @@ -1,9 +1,9 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigEvents.hpp + * @file Events.hpp * @brief Manages events for configurations. * @note Status: BETA * @@ -12,19 +12,19 @@ * rather than assuming all repeating events will be evenly spaced. */ -#ifndef MABE_CONFIG_EVENTS_H -#define MABE_CONFIG_EVENTS_H +#ifndef EMPLODE_EVENTS_HPP +#define EMPLODE_EVENTS_HPP #include #include "emp/base/map.hpp" #include "emp/base/Ptr.hpp" -#include "ConfigAST.hpp" +#include "AST.hpp" -namespace mabe { +namespace emplode { - class ConfigEvents { + class Events { private: // Structure to track the timings for a single event. @@ -45,8 +45,8 @@ namespace mabe { // Trigger a single event as having occurred; return true/false base on whether this event // should continue to be considered active. bool Trigger() { - auto result_entry = ast_action->Process(); - if (result_entry && result_entry->IsTemporary()) result_entry.Delete(); + auto result_symbol = ast_action->Process(); + if (result_symbol && result_symbol->IsTemporary()) result_symbol.Delete(); next += repeat; if (max != -1.0 && next > max) repeat = 0.0; @@ -83,8 +83,8 @@ namespace mabe { } public: - ConfigEvents() { ; } - ~ConfigEvents() { + Events() { ; } + ~Events() { // Must delete all events in the queue. for (auto [time, event_ptr] : queue) { event_ptr.Delete(); diff --git a/source/config/ConfigLexer.hpp b/source/Emplode/Lexer.hpp similarity index 88% rename from source/config/ConfigLexer.hpp rename to source/Emplode/Lexer.hpp index 8366ea75..c0a253cc 100644 --- a/source/config/ConfigLexer.hpp +++ b/source/Emplode/Lexer.hpp @@ -1,21 +1,21 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigLexer.hpp - * @brief A Lexer used to tokenize MABE config files. + * @file Lexer.hpp + * @brief A Lexer used to tokenize Emplode config files. * @note Status: BETA **/ -#ifndef MABE_CONFIG_LEXER_H -#define MABE_CONFIG_LEXER_H +#ifndef EMPLODE_LEXER_HPP +#define EMPLODE_LEXER_HPP #include "emp/compiler/Lexer.hpp" -namespace mabe { +namespace emplode { - class ConfigLexer : public emp::Lexer { + class Lexer : public emp::Lexer { private: int token_identifier = -1; ///< Token id for identifiers int token_number = -1; ///< Token id for literal numbers @@ -25,7 +25,7 @@ namespace mabe { int token_symbol = -1; ///< Token id for other symbols public: - ConfigLexer() { + Lexer() { // Whitespace and comments should always be dismissed (top priority) IgnoreToken("Whitespace", "[ \t\n\r]+"); IgnoreToken("//-Comments", "//.*"); diff --git a/source/config/ConfigEntry.hpp b/source/Emplode/Symbol.hpp similarity index 53% rename from source/config/ConfigEntry.hpp rename to source/Emplode/Symbol.hpp index 8a0b156c..d1ba732c 100644 --- a/source/config/ConfigEntry.hpp +++ b/source/Emplode/Symbol.hpp @@ -1,26 +1,26 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigEntry.hpp + * @file Symbol.hpp * @brief Manages a single configuration entry (e.g., variables + base for scopes and functions). * @note Status: BETA * * The symbol table for the configuration language is managed as a collection of - * configuration entries. These include specializations for ConfigEntry_Linked (for linked - * variables), ConfigEntry_Function and ConfigEntry_Scope, all defined in their own files - * and derived from ConfigEntry. + * configuration entries. These include specializations for Symbol_Linked (for linked + * variables), Symbol_Function and Symbol_Scope, all defined in their own files + * and derived from Symbol. * * Development Notes: * - Currently we are not using Format; this would be useful if we want to type-check inputs more * carefully. - * - When a ConfigEntry is used for a temporary value, it doesn't actually need name or desc; + * - When a Symbol is used for a temporary value, it doesn't actually need name or desc; * we can probably remove these pretty easily to save on memory if needed. */ -#ifndef MABE_CONFIG_ENTRY_H -#define MABE_CONFIG_ENTRY_H +#ifndef EMPLODE_SYMBOL_HPP +#define EMPLODE_SYMBOL_HPP #include @@ -33,18 +33,18 @@ #include "emp/tools/string_utils.hpp" #include "emp/tools/value_utils.hpp" -namespace mabe { +namespace emplode { - class ConfigEntry_Scope; - class ConfigType; + class Symbol_Scope; + class EmplodeType; - class ConfigEntry { + class Symbol { protected: - std::string name; ///< Unique name for entry; empty name implies temporary. - std::string desc; ///< Description to put in comments for this entry. - emp::Ptr scope; ///< Which scope was this variable defined in? + std::string name; ///< Unique name for symbol; empty name implies temporary. + std::string desc; ///< Description to put in comments for this symbol. + emp::Ptr scope; ///< Which scope was this variable defined in? - bool is_temporary = false; ///< Is this ConfigEntry temporary and should be deleted? + bool is_temporary = false; ///< Is this Symbol temporary and should be deleted? bool is_builtin = false; ///< Built-in entries should not be written to config files. enum class Format { NONE=0, SCOPE, @@ -57,7 +57,7 @@ namespace mabe { emp::Range range; ///< Min and max values allowed for this config entry (if numerical). bool integer_only=false; ///< Should we only allow integer values? - using entry_ptr_t = emp::Ptr; + using symbol_ptr_t = emp::Ptr; // Helper functions. @@ -82,55 +82,55 @@ namespace mabe { } public: - ConfigEntry(const std::string & _name, + Symbol(const std::string & _name, const std::string & _desc, - emp::Ptr _scope) + emp::Ptr _scope) : name(_name), desc(_desc), scope(_scope) { } - ConfigEntry(const ConfigEntry &) = default; - virtual ~ConfigEntry() { } + Symbol(const Symbol &) = default; + virtual ~Symbol() { } const std::string & GetName() const noexcept { return name; } const std::string & GetDesc() const noexcept { return desc; } - emp::Ptr GetScope() { return scope; } + emp::Ptr GetScope() { return scope; } bool IsTemporary() const noexcept { return is_temporary; } bool IsBuiltin() const noexcept { return is_builtin; } Format GetFormat() const noexcept { return format; } virtual std::string GetTypename() const { return "Unknown"; } - virtual bool IsNumeric() const { return false; } ///< Is entry any kind of number? - virtual bool IsBool() const { return false; } ///< Is entry a Boolean value? - virtual bool IsInt() const { return false; } ///< Is entry a integer value? - virtual bool IsDouble() const { return false; } ///< Is entry a floting point value? - virtual bool IsString() const { return false; } ///< Is entry a string? + virtual bool IsNumeric() const { return false; } ///< Is symbol any kind of number? + virtual bool IsBool() const { return false; } ///< Is symbol a Boolean value? + virtual bool IsInt() const { return false; } ///< Is symbol a integer value? + virtual bool IsDouble() const { return false; } ///< Is symbol a floting point value? + virtual bool IsString() const { return false; } ///< Is symbol a string? - virtual bool IsLocal() const { return false; } ///< Was entry defined in config file? - virtual bool IsFunction() const { return false; } ///< Is entry a function? - virtual bool IsScope() const { return false; } ///< Is entry a full scope? - virtual bool IsError() const { return false; } ///< Does entry flag an error? + virtual bool IsLocal() const { return false; } ///< Was symbol defined in config file? + virtual bool IsFunction() const { return false; } ///< Is symbol a function? + virtual bool IsScope() const { return false; } ///< Is symbol a full scope? + virtual bool IsError() const { return false; } ///< Does symbol flag an error? - virtual bool HasNumericReturn() const { return false; } ///< Is entry a function that returns a number? - virtual bool HasStringReturn() const { return false; } ///< Is entry a function that returns a string? + virtual bool HasNumericReturn() const { return false; } ///< Is symbol a function that returns a number? + virtual bool HasStringReturn() const { return false; } ///< Is symbol a function that returns a string? - ConfigEntry & SetName(const std::string & in) { name = in; return *this; } - ConfigEntry & SetDesc(const std::string & in) { desc = in; return *this; } - ConfigEntry & SetTemporary(bool in=true) { is_temporary = in; return *this; } - ConfigEntry & SetBuiltin(bool in=true) { is_builtin = in; return *this; } + Symbol & SetName(const std::string & in) { name = in; return *this; } + Symbol & SetDesc(const std::string & in) { desc = in; return *this; } + Symbol & SetTemporary(bool in=true) { is_temporary = in; return *this; } + Symbol & SetBuiltin(bool in=true) { is_builtin = in; return *this; } virtual double AsDouble() const { emp_assert(false); return 0.0; } virtual std::string AsString() const { emp_assert(false); return ""; } - virtual ConfigEntry & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } - virtual ConfigEntry & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } + virtual Symbol & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } + virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } - virtual emp::Ptr AsScopePtr() { return nullptr; } - ConfigEntry_Scope & AsScope() { + virtual emp::Ptr AsScopePtr() { return nullptr; } + Symbol_Scope & AsScope() { emp_assert(AsScopePtr()); return *(AsScopePtr()); } - virtual emp::Ptr GetObjectPtr() { return nullptr; } - virtual emp::Ptr GetObjectPtr() const { return nullptr; } + virtual emp::Ptr GetObjectPtr() { return nullptr; } + virtual emp::Ptr GetObjectPtr() const { return nullptr; } /// A generic As() function that will call the appropriate converter. template @@ -149,19 +149,19 @@ namespace mabe { } // If we want either a pointer or reference to the current object, return it. - else if constexpr (std::is_same>()) { return this; } - else if constexpr (std::is_same()) { return *this; } + else if constexpr (std::is_same>()) { return this; } + else if constexpr (std::is_same()) { return *this; } - // If we want a dervied ConfigEntry type, convert and return it. - else if constexpr (std::is_base_of()) { + // If we want a dervied Symbol type, convert and return it. + else if constexpr (std::is_base_of()) { emp::Ptr out_ptr = dynamic_cast(this); emp_assert(out_ptr); // @CAO: Should provide a user error. return *out_ptr; } - // If we want a user-defined type, it must be derived from ConfigType. - else if constexpr (std::is_base_of()) { - emp::Ptr obj_ptr = GetObjectPtr(); + // If we want a user-defined type, it must be derived from EmplodeType. + else if constexpr (std::is_base_of()) { + emp::Ptr obj_ptr = GetObjectPtr(); emp_assert(obj_ptr); // @CAO: Should provide a user error. emp::Ptr out_ptr = obj_ptr.DynamicCast(); emp_assert(out_ptr); // @CAO: Should provide a user error. @@ -170,50 +170,50 @@ namespace mabe { // Oh no! We don't know this type... else { - static_assert(emp::dependent_false(), "Invalid conversion for ConfigEntry::As()"); + static_assert(emp::dependent_false(), "Invalid conversion for Symbol::As()"); emp_error(emp::GetTypeID()); // Print more info when above line is commented out. auto out = emp::NewPtr>(); return (T) *out; } } - ConfigEntry & SetMin(double min) { range.SetLower(min); return *this; } - ConfigEntry & SetMax(double max) { range.SetLower(max); return *this; } + Symbol & SetMin(double min) { range.SetLower(min); return *this; } + Symbol & SetMax(double max) { range.SetLower(max); return *this; } - // Try to copy another config entry into this one; return true if successful. - virtual bool CopyValue(const ConfigEntry & ) { return false; } + // Try to copy another config symbol into this one; return true if successful. + virtual bool CopyValue(const Symbol & ) { return false; } - /// If this entry is a scope, we should be able to lookup other entries inside it. - virtual entry_ptr_t LookupEntry(const std::string & in_name, bool /* scan_scopes */=true) { + /// If this symbol is a scope, we should be able to lookup other entries inside it. + virtual symbol_ptr_t LookupSymbol(const std::string & in_name, bool /* scan_scopes */=true) { return (in_name == "") ? this : nullptr; } - virtual emp::Ptr - LookupEntry(const std::string & in_name, bool /* scan_scopes */=true) const { + virtual emp::Ptr + LookupSymbol(const std::string & in_name, bool /* scan_scopes */=true) const { return (in_name == "") ? this : nullptr; } - virtual bool Has(const std::string & in_name) const { return (bool) LookupEntry(in_name); } + virtual bool Has(const std::string & in_name) const { return (bool) LookupSymbol(in_name); } - /// If this entry is a function, we should be able to call it. - virtual entry_ptr_t Call(const emp::vector & args); + /// If this symbol is a function, we should be able to call it. + virtual symbol_ptr_t Call(const emp::vector & args); // --- Implicit conversion operators --- operator double() const { return AsDouble(); } operator int() const { return static_cast(AsDouble()); } operator size_t() const { return static_cast(AsDouble()); } operator std::string() const { return AsString(); } - operator emp::Ptr() { return this; } - operator ConfigType&() { return *GetObjectPtr(); } + operator emp::Ptr() { return this; } + operator EmplodeType&() { return *GetObjectPtr(); } /// Allocate a duplicate of this class. - virtual entry_ptr_t Clone() const = 0; + virtual symbol_ptr_t Clone() const = 0; - virtual const ConfigEntry & Write(std::ostream & os=std::cout, const std::string & prefix="", + virtual const Symbol & Write(std::ostream & os=std::cout, const std::string & prefix="", size_t comment_offset=32) const { - // If this is a built-in entry, don't print it. + // If this is a built-in symbol, don't print it. if (IsBuiltin()) return *this; - // Setup this entry. + // Setup this symbol. std::string cur_line = prefix; if (IsLocal()) cur_line += emp::to_string(GetTypename(), " ", name, " = "); else cur_line += emp::to_string(name, " = "); @@ -231,35 +231,35 @@ namespace mabe { }; - /// A generic version of a config entry for an internally maintained variable. + /// A generic version of a symbol for an internally maintained variable. template - class ConfigEntry_Var : public ConfigEntry { + class Symbol_Var : public Symbol { private: T value = 0; public: - static_assert(std::is_arithmetic(), "ConfigEntry_Var must use std::string or arithmetic values."); + static_assert(std::is_arithmetic(), "Symbol_Var must use std::string or arithmetic values."); - using this_t = ConfigEntry_Var; + using this_t = Symbol_Var; template - ConfigEntry_Var(const std::string & in_name, + Symbol_Var(const std::string & in_name, T default_val, const std::string & in_desc="", - emp::Ptr in_scope=nullptr) - : ConfigEntry(in_name, in_desc, in_scope), value(default_val) { ; } - ConfigEntry_Var(const ConfigEntry_Var &) = default; + emp::Ptr in_scope=nullptr) + : Symbol(in_name, in_desc, in_scope), value(default_val) { ; } + Symbol_Var(const Symbol_Var &) = default; std::string GetTypename() const override { if constexpr (std::is_scalar_v) return "Value"; else return "Unknown"; } - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return (double) value; } std::string AsString() const override { return emp::to_string(value); } - ConfigEntry & SetValue(double in) override { value = (T) in; return *this; } - ConfigEntry & SetString(const std::string & in) override { + Symbol & SetValue(double in) override { value = (T) in; return *this; } + Symbol & SetString(const std::string & in) override { value = emp::from_string(in); return *this; } @@ -271,62 +271,62 @@ namespace mabe { bool IsLocal() const override { return true; } - bool CopyValue(const ConfigEntry & in) override { SetValue(in.AsDouble()); return true; } + bool CopyValue(const Symbol & in) override { SetValue(in.AsDouble()); return true; } }; - using ConfigEntry_DoubleVar = ConfigEntry_Var; + using Symbol_DoubleVar = Symbol_Var; - /// ConfigEntry as a temporary variable of type STRING. + /// Symbol as a temporary variable of type STRING. template<> - class ConfigEntry_Var : public ConfigEntry { + class Symbol_Var : public Symbol { private: std::string value; public: - using this_t = ConfigEntry_Var; + using this_t = Symbol_Var; template - ConfigEntry_Var(const std::string & in_name, const std::string & in_val, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...), value(in_val) { ; } - ConfigEntry_Var(const ConfigEntry_Var &) = default; + Symbol_Var(const std::string & in_name, const std::string & in_val, ARGS &&... args) + : Symbol(in_name, std::forward(args)...), value(in_val) { ; } + Symbol_Var(const Symbol_Var &) = default; std::string GetTypename() const override { return "String"; } - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return emp::from_string(value); } std::string AsString() const override { return value; } - ConfigEntry & SetValue(double in) override { value = emp::to_string(in); return *this; } - ConfigEntry & SetString(const std::string & in) override { value = in; return *this; } + Symbol & SetValue(double in) override { value = emp::to_string(in); return *this; } + Symbol & SetString(const std::string & in) override { value = in; return *this; } bool IsString() const override { return true; } bool IsLocal() const override { return true; } - bool CopyValue(const ConfigEntry & in) override { value = in.AsString(); return true; } + bool CopyValue(const Symbol & in) override { value = in.AsString(); return true; } }; - using ConfigEntry_StringVar = ConfigEntry_Var; + using Symbol_StringVar = Symbol_Var; - /// A ConfigEntry to transmit an error due to invalid parsing. + /// A Symbol to transmit an error due to invalid parsing. /// The description provides the error and the IsError() flag is set to true. - class ConfigEntry_Error : public ConfigEntry { + class Symbol_Error : public Symbol { private: - using this_t = ConfigEntry_Error; + using this_t = Symbol_Error; public: template - ConfigEntry_Error(ARGS &&... args) - : ConfigEntry("__Error", emp::to_string(args...), nullptr) { is_temporary = true; } + Symbol_Error(ARGS &&... args) + : Symbol("__Error", emp::to_string(args...), nullptr) { is_temporary = true; } std::string GetTypename() const override { return "[[Error]]"; } bool IsError() const override { return true; } - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } }; //////////////////////////////////////////////////// // Function definitions... - emp::Ptr ConfigEntry::Call( const emp::vector & /* args */ ) { - return emp::NewPtr("Cannot call a function on non-function '", name, "'."); + emp::Ptr Symbol::Call( const emp::vector & /* args */ ) { + return emp::NewPtr("Cannot call a function on non-function '", name, "'."); } } diff --git a/source/Emplode/Symbol_Function.hpp b/source/Emplode/Symbol_Function.hpp new file mode 100644 index 00000000..f1b931da --- /dev/null +++ b/source/Emplode/Symbol_Function.hpp @@ -0,0 +1,61 @@ +/** + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2019-2021. + * + * @file Symbol_Function.hpp + * @brief Manages individual functions for config. + * @note Status: BETA + */ + +#ifndef EMPLODE_SYMBOL_FUNCTION_HPP +#define EMPLODE_SYMBOL_FUNCTION_HPP + +#include + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/datastructs/tuple_utils.hpp" +#include "emp/meta/ValPack.hpp" + +#include "Symbol.hpp" +#include "EmplodeTools.hpp" + +namespace emplode { + + class Symbol_Function : public Symbol { + private: + using this_t = Symbol_Function; + using symbol_ptr_t = emp::Ptr; + using fun_t = std::function< symbol_ptr_t( const emp::vector & ) >; + fun_t fun; + bool numeric_return = false; + bool string_return = false; + // size_t arg_count; + + public: + template + Symbol_Function(const std::string & _name, + FUN_T _fun, + const std::string & _desc, + emp::Ptr _scope) + : Symbol(_name, _desc, _scope), fun(EmplodeTools::WrapFunction(_name, _fun)) + { + using return_t = typename emp::FunInfo::return_t; + numeric_return = std::is_scalar_v; + string_return = std::is_same(); + } + + Symbol_Function(const Symbol_Function &) = default; + emp::Ptr Clone() const override { return emp::NewPtr(*this); } + + bool IsFunction() const override { return true; } + bool HasNumericReturn() const override { return numeric_return; } + bool HasStringReturn() const override { return string_return; } + + symbol_ptr_t Call( const emp::vector & args ) override { return fun(args); } + }; + +} + +#endif diff --git a/source/config/ConfigEntry_Linked.hpp b/source/Emplode/Symbol_Linked.hpp similarity index 50% rename from source/config/ConfigEntry_Linked.hpp rename to source/Emplode/Symbol_Linked.hpp index b4ee0fe7..eca96e4e 100644 --- a/source/config/ConfigEntry_Linked.hpp +++ b/source/Emplode/Symbol_Linked.hpp @@ -1,46 +1,46 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file ConfigEntry_Linked.hpp + * @file Symbol_Linked.hpp * @brief Manages a configuration entry linked to another variable or functions. * @note Status: BETA */ -#ifndef MABE_CONFIG_ENTRY_LINKED_HPP -#define MABE_CONFIG_ENTRY_LINKED_HPP +#ifndef EMPLODE_SYMBOL_LINKED_HPP +#define EMPLODE_SYMBOL_LINKED_HPP #include -#include "ConfigEntry.hpp" +#include "Symbol.hpp" -namespace mabe { +namespace emplode { - /// ConfigEntry can be linked directly to a real variable. + /// Symbol can be linked directly to a real variable. template - class ConfigEntry_Linked : public ConfigEntry { + class Symbol_Linked : public Symbol { private: T & var; public: - using this_t = ConfigEntry_Linked; + using this_t = Symbol_Linked; template - ConfigEntry_Linked(const std::string & in_name, T & in_var, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } - ConfigEntry_Linked(const this_t &) = default; + Symbol_Linked(const std::string & in_name, T & in_var, ARGS &&... args) + : Symbol(in_name, std::forward(args)...), var(in_var) { ; } + Symbol_Linked(const this_t &) = default; std::string GetTypename() const override { if constexpr (std::is_scalar_v) return "Value"; else return "Unknown"; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + emp::Ptr Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return (double) var; } std::string AsString() const override { return emp::to_string(var); } - ConfigEntry & SetValue(double in) override { var = (T) in; return *this; } - ConfigEntry & SetString(const std::string & in) override { + Symbol & SetValue(double in) override { var = (T) in; return *this; } + Symbol & SetString(const std::string & in) override { var = emp::from_string(in); return *this; } @@ -50,65 +50,65 @@ namespace mabe { bool IsInt() const override { return std::is_same(); } bool IsDouble() const override { return std::is_same(); } - bool CopyValue(const ConfigEntry & in) override { var = in.AsDouble(); return true; } + bool CopyValue(const Symbol & in) override { var = in.AsDouble(); return true; } }; - /// Specialization for ConfigEntry linked to a string variable. + /// Specialization for Symbol linked to a string variable. template <> - class ConfigEntry_Linked : public ConfigEntry { + class Symbol_Linked : public Symbol { private: std::string & var; public: - using this_t = ConfigEntry_Linked; + using this_t = Symbol_Linked; template - ConfigEntry_Linked(const std::string & in_name, std::string & in_var, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...), var(in_var) { ; } - ConfigEntry_Linked(const this_t &) = default; + Symbol_Linked(const std::string & in_name, std::string & in_var, ARGS &&... args) + : Symbol(in_name, std::forward(args)...), var(in_var) { ; } + Symbol_Linked(const this_t &) = default; std::string GetTypename() const override { return "String"; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + emp::Ptr Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return emp::from_string(var); } std::string AsString() const override { return var; } - ConfigEntry & SetValue(double in) override { var = emp::to_string(in); return *this; } - ConfigEntry & SetString(const std::string & in) override { var = in; return *this; } + Symbol & SetValue(double in) override { var = emp::to_string(in); return *this; } + Symbol & SetString(const std::string & in) override { var = in; return *this; } bool IsString() const override { return true; } - bool CopyValue(const ConfigEntry & in) override { var = in.AsString(); return true; } + bool CopyValue(const Symbol & in) override { var = in.AsString(); return true; } }; - /// ConfigEntry can be linked to a pair of (Get and Set) functions + /// Symbol can be linked to a pair of (Get and Set) functions /// rather than as direct variable. template - class ConfigEntry_LinkedFunctions : public ConfigEntry { + class Symbol_LinkedFunctions : public Symbol { private: std::function get_fun; std::function set_fun; public: - using this_t = ConfigEntry_LinkedFunctions; + using this_t = Symbol_LinkedFunctions; template - ConfigEntry_LinkedFunctions(const std::string & in_name, + Symbol_LinkedFunctions(const std::string & in_name, std::function in_get, std::function in_set, ARGS &&... args) - : ConfigEntry(in_name, std::forward(args)...) + : Symbol(in_name, std::forward(args)...) , get_fun(in_get) , set_fun(in_set) { ; } - ConfigEntry_LinkedFunctions(const this_t &) = default; + Symbol_LinkedFunctions(const this_t &) = default; std::string GetTypename() const override { return "[[Function]]"; } - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + emp::Ptr Clone() const override { return emp::NewPtr(*this); } double AsDouble() const override { return emp::ToDouble( get_fun() ); } std::string AsString() const override { return emp::to_string( get_fun() ); } - ConfigEntry & SetValue(double in) override { set_fun(emp::FromDouble(in)); return *this; } - ConfigEntry & SetString(const std::string & in) override { + Symbol & SetValue(double in) override { set_fun(emp::FromDouble(in)); return *this; } + Symbol & SetString(const std::string & in) override { set_fun( emp::from_string(in) ); return *this; } @@ -119,7 +119,7 @@ namespace mabe { bool IsDouble() const override { return std::is_same(); } bool IsString() const override { return std::is_same(); } - bool CopyValue(const ConfigEntry & in) override { SetString( in.AsString() ); return true; } + bool CopyValue(const Symbol & in) override { SetString( in.AsString() ); return true; } }; } diff --git a/source/config/ConfigEntry_Scope.hpp b/source/Emplode/Symbol_Scope.hpp similarity index 58% rename from source/config/ConfigEntry_Scope.hpp rename to source/Emplode/Symbol_Scope.hpp index 0c7b90e2..1264a343 100644 --- a/source/config/ConfigEntry_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -1,40 +1,39 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2019-2021. * - * @file ConfigEntry_Scope.hpp - * @brief Manages a full scope with many config entries (or sub-scopes). + * @file Symbol_Scope.hpp + * @brief Manages a full scope with many internal symbols (including sub-scopes). * @note Status: BETA * * DEVELOPER NOTES: * - Need to fix Add() function to give a user-level error, rather than an assert on duplication. - * */ -#ifndef MABE_CONFIG_SCOPE_H -#define MABE_CONFIG_SCOPE_H +#ifndef EMPLODE_SYMBOL_SCOPE_HPP +#define EMPLODE_SYMBOL_SCOPE_HPP #include "emp/base/map.hpp" -#include "ConfigEntry.hpp" -#include "ConfigEntry_Function.hpp" -#include "ConfigEntry_Linked.hpp" -#include "ConfigTypeBase.hpp" +#include "Symbol.hpp" +#include "Symbol_Function.hpp" +#include "Symbol_Linked.hpp" +#include "EmplodeTypeBase.hpp" -namespace mabe { +namespace emplode { - class ConfigType; + class EmplodeType; // Set of multiple config entries. - class ConfigEntry_Scope : public ConfigEntry { + class Symbol_Scope : public Symbol { protected: - using entry_ptr_t = emp::Ptr; - using const_entry_ptr_t = emp::Ptr; - emp::map< std::string, entry_ptr_t > symbol_table; ///< Map of names to entries. + using symbol_ptr_t = emp::Ptr; + using const_symbol_ptr_t = emp::Ptr; + emp::map< std::string, symbol_ptr_t > symbol_table; ///< Map of names to entries. ///< If this scope represents a structure, point to it; otherwise set to null. - emp::Ptr obj_ptr = nullptr; + emp::Ptr obj_ptr = nullptr; bool obj_owned = false; template @@ -54,20 +53,20 @@ namespace mabe { } public: - ConfigEntry_Scope(const std::string & _name, + Symbol_Scope(const std::string & _name, const std::string & _desc, - emp::Ptr _scope, - emp::Ptr _obj=nullptr, + emp::Ptr _scope, + emp::Ptr _obj=nullptr, bool _owned=false) - : ConfigEntry(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } + : Symbol(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } - ConfigEntry_Scope(const ConfigEntry_Scope & in) : ConfigEntry(in) { + Symbol_Scope(const Symbol_Scope & in) : Symbol(in) { // Copy all defined variables/scopes/functions for (auto [name, ptr] : symbol_table) { symbol_table[name] = ptr->Clone(); } } - ConfigEntry_Scope(ConfigEntry_Scope &&) = default; + Symbol_Scope(Symbol_Scope &&) = default; - ~ConfigEntry_Scope() { + ~Symbol_Scope() { // If this scope owns its object pointer, delete it now. if (obj_owned) obj_ptr.Delete(); @@ -75,27 +74,27 @@ namespace mabe { for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } - emp::Ptr GetObjectPtr() override { return obj_ptr; } - emp::Ptr GetObjectPtr() const override { return obj_ptr; } + emp::Ptr GetObjectPtr() override { return obj_ptr; } + emp::Ptr GetObjectPtr() const override { return obj_ptr; } bool IsScope() const override { return true; } bool IsLocal() const override { return true; } // @CAO, for now assuming all scopes are local! - /// Set this entry to be a correctly-types scope pointer. - emp::Ptr AsScopePtr() override { return this; } + /// Set this symbol to be a correctly-typed scope pointer. + emp::Ptr AsScopePtr() override { return this; } - /// Get an entry out of this scope; - entry_ptr_t GetEntry(std::string name) { return emp::Find(symbol_table, name, nullptr); } + /// Get a symbol out of this scope; + symbol_ptr_t GetSymbol(std::string name) { return emp::Find(symbol_table, name, nullptr); } /// Lookup a variable, scanning outer scopes if needed - entry_ptr_t LookupEntry(const std::string & name, bool scan_scopes=true) override { - // See if this next entry is in the var list. + symbol_ptr_t LookupSymbol(const std::string & name, bool scan_scopes=true) override { + // See if this next symbol is in the var list. auto it = symbol_table.find(name); // If this name is unknown, check with the parent scope! if (it == symbol_table.end()) { if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(name); + return scope->LookupSymbol(name); } // Otherwise we found it! @@ -103,83 +102,83 @@ namespace mabe { } /// Lookup a variable, scanning outer scopes if needed (in const context!) - const_entry_ptr_t LookupEntry(const std::string & name, bool scan_scopes=true) const override { - // See if this entry is in the var list. + const_symbol_ptr_t LookupSymbol(const std::string & name, bool scan_scopes=true) const override { + // See if this symbol is in the var list. auto it = symbol_table.find(name); // If this name is unknown, check with the parent scope! if (it == symbol_table.end()) { if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(name); + return scope->LookupSymbol(name); } // Otherwise we found it! return it->second; } - /// Add a configuration entry that is linked to a variable - the incoming variable sets + /// Add a configuration symbol that is linked to a variable - the incoming variable sets /// the default and is automatically updated when configs are loaded. template - ConfigEntry_Linked & LinkVar(const std::string & name, + Symbol_Linked & LinkVar(const std::string & name, VAR_T & var, const std::string & desc, bool is_builtin = false) { - if (is_builtin) return AddBuiltin>(name, var, desc, this); - return Add>(name, var, desc, this); + if (is_builtin) return AddBuiltin>(name, var, desc, this); + return Add>(name, var, desc, this); } - /// Add a configuration entry that interacts through a pair of functions - the functions - /// are automatically called any time the entry is accessed (get_fun) or changed (set_fun) + /// Add a configuration symbol that interacts through a pair of functions - the functions are + /// automatically called any time the symbol value is accessed (get_fun) or changed (set_fun) template - ConfigEntry_LinkedFunctions & LinkFuns(const std::string & name, + Symbol_LinkedFunctions & LinkFuns(const std::string & name, std::function get_fun, std::function set_fun, const std::string & desc, bool is_builtin = false) { if (is_builtin) { - return AddBuiltin>(name, get_fun, set_fun, desc, this); + return AddBuiltin>(name, get_fun, set_fun, desc, this); } - return Add>(name, get_fun, set_fun, desc, this); + return Add>(name, get_fun, set_fun, desc, this); } /// Add an internal variable of type String. - ConfigEntry_StringVar & AddStringVar(const std::string & name, const std::string & desc) { - return Add(name, "", desc, this); + Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { + return Add(name, "", desc, this); } /// Add an internal variable of type Value. - ConfigEntry_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { - return Add(name, 0.0, desc, this); + Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { + return Add(name, 0.0, desc, this); } /// Add an internal scope inside of this one. - ConfigEntry_Scope & AddScope( + Symbol_Scope & AddScope( const std::string & name, const std::string & desc, - emp::Ptr obj_ptr=nullptr, + emp::Ptr obj_ptr=nullptr, bool obj_owned=false ) { - return Add(name, desc, this, obj_ptr, obj_owned); + return Add(name, desc, this, obj_ptr, obj_owned); } /// Add a new user-defined function. template - ConfigEntry_Function & AddFunction(const std::string & name, + Symbol_Function & AddFunction(const std::string & name, std::function fun, const std::string & desc) { - return Add(name, fun, desc, this); + return Add(name, fun, desc, this); } /// Add a new function that is a standard part of the scripting language. template - ConfigEntry_Function & AddBuiltinFunction(const std::string & name, + Symbol_Function & AddBuiltinFunction(const std::string & name, std::function fun, const std::string & desc) { - return AddBuiltin(name, fun, desc, this); + return AddBuiltin(name, fun, desc, this); } /// Write out all of the parameters contained in this scope to the provided stream. - const ConfigEntry & WriteContents(std::ostream & os=std::cout, const std::string & prefix="", + const Symbol & WriteContents(std::ostream & os=std::cout, const std::string & prefix="", size_t comment_offset=32) const { // Loop through all of the entires in this scope and Write them. @@ -192,7 +191,7 @@ namespace mabe { } /// Write out this scope AND it's contents to the provided stream. - const ConfigEntry & Write(std::ostream & os=std::cout, const std::string & prefix="", + const Symbol & Write(std::ostream & os=std::cout, const std::string & prefix="", size_t comment_offset=32) const override { // If this is a built-in scope, don't print it. @@ -203,7 +202,7 @@ namespace mabe { if (IsLocal()) cur_line += emp::to_string(GetTypename(), " "); cur_line += name; - bool has_body = emp::AnyOf(symbol_table, [](entry_ptr_t ptr){ return !ptr->IsBuiltin(); }); + bool has_body = emp::AnyOf(symbol_table, [](symbol_ptr_t ptr){ return !ptr->IsBuiltin(); }); // Only open this scope if there are contents. cur_line += has_body ? " { " : ";"; @@ -222,7 +221,7 @@ namespace mabe { } /// Make a copy of this scope and all of the entries inside it. - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } }; } diff --git a/source/config/ConfigTypeInfo.hpp b/source/Emplode/TypeInfo.hpp similarity index 63% rename from source/config/ConfigTypeInfo.hpp rename to source/Emplode/TypeInfo.hpp index 0f8c8ace..51a7847d 100644 --- a/source/config/ConfigTypeInfo.hpp +++ b/source/Emplode/TypeInfo.hpp @@ -1,15 +1,15 @@ /** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file ConfigTypeInfo.hpp + * @file TypeInfo.hpp * @brief Manages all of the information about a particular type in the config language. * @note Status: BETA */ -#ifndef MABE_CONFIG_TYPE_INFO_H -#define MABE_CONFIG_TYPE_INFO_H +#ifndef EMPLODE_TYPE_INFO_HPP +#define EMPLODE_TYPE_INFO_HPP #include @@ -17,15 +17,15 @@ #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" -#include "ConfigEntry.hpp" -#include "ConfigTools.hpp" +#include "Symbol.hpp" +#include "EmplodeTools.hpp" -namespace mabe { +namespace emplode { // Information about a member function. struct MemberFunInfo { - using entry_ptr_t = emp::Ptr; - using fun_t = std::function &)>; + using symbol_ptr_t = emp::Ptr; + using fun_t = std::function &)>; std::string name; std::string desc; @@ -35,11 +35,10 @@ namespace mabe { : name(in_name), desc(in_desc), fun(in_fun) {} }; - // ConfigTypeInfo tracks a particular type to be used in the configuration langauge. - class ConfigTypeInfo { + // TypeInfo tracks a particular type to be used in the configuration langauge. + class TypeInfo { private: - using entry_ptr_t = emp::Ptr; - using init_fun_t = std::function (const std::string &)>; + using init_fun_t = std::function (const std::string &)>; size_t index; std::string type_name; @@ -53,11 +52,11 @@ namespace mabe { public: // Constructor to allow a simple new configuration type - ConfigTypeInfo(size_t _id, const std::string & _name, const std::string & _desc) + TypeInfo(size_t _id, const std::string & _name, const std::string & _desc) : index(_id), type_name(_name), desc(_desc) { } // Constructor to allow a new configuration type whose objects require initialization. - ConfigTypeInfo(size_t _id, const std::string & _name, const std::string & _desc, + TypeInfo(size_t _id, const std::string & _name, const std::string & _desc, init_fun_t _init, bool _config_owned=false) : index(_id), type_name(_name), desc(_desc), init_fun(_init), config_owned(_config_owned) { } @@ -66,13 +65,13 @@ namespace mabe { const std::string & GetTypeName() const { return type_name; } const std::string & GetDesc() const { return desc; } emp::TypeID GetType() const { return type_id; } - bool GetConfigOwned() const { return config_owned; } + bool GetOwned() const { return config_owned; } const emp::vector & GetMemberFunctions() const { return member_funs; } - emp::Ptr MakeObj(const std::string & name) const { return init_fun(name); } + emp::Ptr MakeObj(const std::string & name) const { return init_fun(name); } - // Link this ConfigTypeInfo object to a real C++ type. - // @CAO It would be nice to test to make sure this is a ConfigType, but not possible with a TypeID. + // Link this TypeInfo object to a real C++ type. + // @CAO It would be nice to test to make sure this is an EmplodeType, but not possible with a TypeID. void LinkType(emp::TypeID in_id) { type_id = in_id; } // Add a member function that can be called on objects of this type. @@ -87,8 +86,8 @@ namespace mabe { // << " (Entry #" << member_funs.size() << ")" // << std::endl; - // ----- Transform this function into one that ConfigTypeInfo can make use of ---- - MemberFunInfo::fun_t member_fun = ConfigTools::WrapMemberFunction(type_id, name, fun); + // ----- Transform this function into one that TypeInfo can make use of ---- + MemberFunInfo::fun_t member_fun = EmplodeTools::WrapMemberFunction(type_id, name, fun); // Add this member function to the library we are building. member_funs.emplace_back(name, desc, member_fun); diff --git a/source/config/ConfigEntry_Function.hpp b/source/config/ConfigEntry_Function.hpp deleted file mode 100644 index b98a291b..00000000 --- a/source/config/ConfigEntry_Function.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @note This file is part of MABE, https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2021. - * - * @file ConfigEntry_Function.hpp - * @brief Manages individual functions for config. - * @note Status: BETA - */ - -#ifndef MABE_CONFIG_FUNCTION_H -#define MABE_CONFIG_FUNCTION_H - -#include - -#include "emp/base/Ptr.hpp" -#include "emp/base/vector.hpp" -#include "emp/datastructs/tuple_utils.hpp" -#include "emp/meta/ValPack.hpp" - -#include "ConfigEntry.hpp" -#include "ConfigTools.hpp" - -namespace mabe { - - class ConfigEntry_Function : public ConfigEntry { - private: - using this_t = ConfigEntry_Function; - using entry_ptr_t = emp::Ptr; - using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; - fun_t fun; - bool numeric_return = false; - bool string_return = false; - // size_t arg_count; - - public: - template - ConfigEntry_Function(const std::string & _name, - FUN_T _fun, - const std::string & _desc, - emp::Ptr _scope) - : ConfigEntry(_name, _desc, _scope), fun(ConfigTools::WrapFunction(_name, _fun)) - { - using return_t = typename emp::FunInfo::return_t; - numeric_return = std::is_scalar_v; - string_return = std::is_same(); - } - - ConfigEntry_Function(const ConfigEntry_Function &) = default; - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - - bool IsFunction() const override { return true; } - bool HasNumericReturn() const override { return numeric_return; } - bool HasStringReturn() const override { return string_return; } - - entry_ptr_t Call( const emp::vector & args ) override { return fun(args); } - }; - -} - -#endif From bb3193d252d95a7c392083cd7aef95420d756595 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 12:29:38 -0400 Subject: [PATCH 260/445] Updated MABE files to use renamed Emplode configuration tools. --- source/core/DeveloperNotes.md | 4 +--- source/core/MABE.hpp | 29 +++++++++++++++++------------ source/core/ManagerModule.hpp | 4 +--- source/core/Module.hpp | 14 ++++++-------- source/core/ModuleBase.hpp | 8 +++++--- source/core/OrgIterator.hpp | 2 -- source/core/Population.hpp | 16 +++++++++------- 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 7e8f7129..58e314a0 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -36,7 +36,7 @@ A new specialty organism class must use OrganismsTemplate as a base class ## Adding Modules -## Adding Managed Config Types +## Adding Managed Configuration Types # Core MABE Development @@ -77,5 +77,3 @@ MABE.hpp: * Problem to resolve: Different types of organisms will have different traits that they need to deal with. For example, "Wolves" vs "Sheep". Do unused values just stay at a default? Or can we have organism categories that each have their own trait layouts? * Setup Organisms to be composed of brains, genomes, and adaptors (to be assembled during configuration). Right now organisms must be built as a whole class, which is much less flexible. - -* Update Config system to allow more generic categories. diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 2ec68da6..a64a698a 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -31,7 +31,7 @@ #include "emp/math/Random.hpp" #include "emp/tools/string_utils.hpp" -#include "../config/Config.hpp" +#include "../Emplode/Emplode.hpp" #include "Collection.hpp" #include "data_collect.hpp" @@ -53,6 +53,10 @@ namespace mabe { /// Note that this class is derived from MABEBase, which handles all population /// manipulation and signal management. + using emplode::Emplode; + using emplode::EmplodeType; + using EmplodeScope = emplode::Symbol_Scope; + class MABE : public MABEBase { private: const std::string VERSION = "0.0.1"; @@ -67,6 +71,7 @@ namespace mabe { // Setup helper types. using trait_equation_t = std::function; using trait_summary_t = std::function; + using symbol_ptr_t = emp::Ptr; // Setup a cache for functions used to collect data for files. std::unordered_map> file_fun_cache; @@ -111,8 +116,8 @@ namespace mabe { emp::vector config_filenames; ///< Names of configuration files to load. emp::vector config_settings; ///< Additional config commands to run. std::string gen_filename; ///< Name of output file to generate. - Config config; ///< Configuration information for this run. - emp::Ptr cur_scope; ///< Which config scope are we currently using? + Emplode config; ///< Configuration information for this run. + emp::Ptr cur_scope; ///< Which config scope are we currently using? // ----------- Helper Functions ----------- @@ -355,19 +360,19 @@ namespace mabe { // --- Manage configuration scope --- /// Access to the current configuration scope. - ConfigEntry_Scope & GetCurScope() { return *cur_scope; } + EmplodeScope & GetCurScope() { return *cur_scope; } /// Add a new scope under the current one. - ConfigEntry_Scope & AddScope(const std::string & name, const std::string & desc) { + EmplodeScope & AddScope(const std::string & name, const std::string & desc) { cur_scope = &(cur_scope->AddScope(name, desc)); return *cur_scope; } /// Move up one level of scope. - ConfigEntry_Scope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } + EmplodeScope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } /// Return to the root scope. - ConfigEntry_Scope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } + EmplodeScope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } /// Setup the configuration options for MABE, including for each module. void SetupConfig(); @@ -445,7 +450,7 @@ namespace mabe { std::cout << "'--generate' must be followed by a single filename.\n"; exit_now = true; } else { - // MABE Config files should be generated FROM a *.gen file, typically creating a *.mabe + // MABE config files can be generated FROM a *.gen file, typically creating a *.mabe // file. If output file is *.gen assume an error. (for now; override should be allowed) if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { error_man.AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); @@ -572,8 +577,8 @@ namespace mabe { } void MABE::Deprecate(const std::string & old_name, const std::string & new_name) { - std::function> &)> dep_fun = - [this,old_name,new_name](const emp::vector> &){ + std::function &)> dep_fun = + [this,old_name,new_name](const emp::vector &){ std::cerr << "Function '" << old_name << "' deprecated; use '" << new_name << "'\n"; exit_now = true; return 0; @@ -594,7 +599,7 @@ namespace mabe { { // Setup "Population" as a type in the config file. auto pop_init_fun = - [this](const std::string & name) -> emp::Ptr { + [this](const std::string & name) -> emp::Ptr { return &AddPopulation(name); }; auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun); @@ -611,7 +616,7 @@ namespace mabe { // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { - auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { + auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { return mod.init_fun(*this,name); }; config.AddType(mod.name, mod.desc, mod_init_fun, mod.type_id); diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index 7b53c71d..0a93fe8e 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -12,8 +12,6 @@ #include "emp/meta/TypeID.hpp" -#include "../config/Config.hpp" - #include "MABE.hpp" #include "Module.hpp" @@ -119,7 +117,7 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { return &control.AddModule(name, desc); }; GetModuleInfo().insert(new_info); diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 5e350e60..c7afad35 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -29,8 +29,6 @@ #include "emp/datastructs/map_utils.hpp" #include "emp/datastructs/reference_vector.hpp" -#include "../config/Config.hpp" - #include "MABE.hpp" #include "ModuleBase.hpp" #include "Population.hpp" @@ -48,10 +46,10 @@ namespace mabe { protected: // Specialized configuration links for MABE-specific modules. - // (Other ways of linking variable to config file are in ConfigType.h) + // (Other ways of linking variable to config file are in EmplodeType.h) /// Link a single population to a parameter by name. - ConfigEntry_LinkedFunctions & LinkPop( + emplode::Symbol_LinkedFunctions & LinkPop( int & var, const std::string & name, const std::string & desc @@ -69,7 +67,7 @@ namespace mabe { } /// Link one or more populations (or portions of a population) to a parameter. - ConfigEntry_LinkedFunctions & LinkCollection( + emplode::Symbol_LinkedFunctions & LinkCollection( mabe::Collection & var, const std::string & name, const std::string & desc @@ -86,7 +84,7 @@ namespace mabe { } /// Link another module to this one, by name (track using int ID) - ConfigEntry_LinkedFunctions & LinkModule( + emplode::Symbol_LinkedFunctions & LinkModule( int & var, const std::string & name, const std::string & desc @@ -104,7 +102,7 @@ namespace mabe { } /// Link a range of values with a start, stop, and step. - ConfigEntry_LinkedFunctions & LinkRange( + emplode::Symbol_LinkedFunctions & LinkRange( int & start_var, int & step_var, int & stop_var, @@ -419,7 +417,7 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { return &control.AddModule(name, desc); }; new_info.type_id = emp::GetTypeID(); diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 19136f89..d26af41f 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -81,7 +81,7 @@ #include "emp/datastructs/map_utils.hpp" #include "emp/datastructs/reference_vector.hpp" -#include "../config/Config.hpp" +#include "../Emplode/Emplode.hpp" #include "ErrorManager.hpp" #include "TraitInfo.hpp" @@ -94,7 +94,9 @@ namespace mabe { class OrgPosition; class Population; - class ModuleBase : public mabe::ConfigType { + using emplode::EmplodeType; + + class ModuleBase : public EmplodeType { friend MABE; protected: std::string name; ///< Unique name for this module. @@ -323,7 +325,7 @@ namespace mabe { struct ModuleInfo { std::string name; std::string desc; - std::function(MABE &, const std::string &)> init_fun; + std::function(MABE &, const std::string &)> init_fun; emp::TypeID type_id; bool operator<(const ModuleInfo & in) const { return name < in.name; } }; diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index 9a242e2c..baa9cf56 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -24,8 +24,6 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "../config/ConfigType.hpp" - #include "Organism.hpp" namespace mabe { diff --git a/source/core/Population.hpp b/source/core/Population.hpp index e426e48d..4c7ce2ba 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file Population.hpp * @brief Container for a group of arbitrary MABE organisms. @@ -21,13 +21,15 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "../config/ConfigType.hpp" +#include "../Emplode/EmplodeType.hpp" #include "Organism.hpp" #include "OrgIterator.hpp" namespace mabe { + using emplode::EmplodeType; + class PopIterator : public OrgIterator_Interface { protected: using base_t = OrgIterator_Interface; @@ -79,9 +81,9 @@ namespace mabe { ConstPopIterator & operator=(const ConstPopIterator & in) = default; }; - /// A Population maintains a collection of organisms. It is derived from ConfigType so that it + /// A Population maintains a collection of organisms. It is derived from EmplodeType so that it /// can be easily used in the MABE scripting language. - class Population : public ConfigType, public OrgContainer { + class Population : public EmplodeType, public OrgContainer { friend class MABEBase; private: std::string name=""; ///< Unique name for this population. @@ -200,7 +202,7 @@ namespace mabe { public: // Setup member functions associated with population. - static void InitType(Config & /*config*/, ConfigTypeInfo & info) { + static void InitType(emplode::Emplode & /*config*/, emplode::TypeInfo & info) { std::function fun_size = [](Population & target) { return target.GetSize(); }; info.AddMemberFunction("SIZE", fun_size, "Return the size of the population."); @@ -209,8 +211,8 @@ namespace mabe { // ------ DEBUG FUNCTIONS ------ bool OK() const { - // We may have a handful of populations, but assume error if we have more than a billion. - if (pop_id > 1000000000) { + // We may have a handful of populations, but assume error if we have more than a million. + if (pop_id > 1000000) { std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; return false; } From a25f95542ba7eda6a34caebc59eeeb5f76ded28a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 17:51:03 -0400 Subject: [PATCH 261/445] Simplified scope to take any function type, not just std::function. --- source/Emplode/Symbol_Scope.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index 1264a343..c5d2988d 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -47,7 +47,7 @@ namespace emplode { template T & AddBuiltin(const std::string & name, ARGS &&... args) { - T & result = Add(name, std::forward(args)...); + T & result = Add(name, std::forward(args)...); result.SetBuiltin(); return result; } @@ -162,17 +162,17 @@ namespace emplode { } /// Add a new user-defined function. - template + template Symbol_Function & AddFunction(const std::string & name, - std::function fun, + FUN_T fun, const std::string & desc) { return Add(name, fun, desc, this); } /// Add a new function that is a standard part of the scripting language. - template + template Symbol_Function & AddBuiltinFunction(const std::string & name, - std::function fun, + FUN_T fun, const std::string & desc) { return AddBuiltin(name, fun, desc, this); } From 5af0602c03ca411931f164fd67a76124fc10cd02 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 17:52:12 -0400 Subject: [PATCH 262/445] Removed uses of std::function; now pass lambdas straight in to functions. --- source/Emplode/Emplode.hpp | 241 ++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 138 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index c1dab5dc..99942482 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -92,11 +92,11 @@ namespace emplode { using pos_t = emp::TokenStream::Iterator; protected: - std::string filename; ///< Source for for code to generate. - Lexer lexer; ///< Lexer to process input code. - Symbol_Scope root_scope; ///< All variables from the root level. - ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = false; ///< Should we print full debug information? + std::string filename; ///< Source for for code to generate. + Lexer lexer; ///< Lexer to process input code. + Symbol_Scope root_scope; ///< All variables from the root level. + ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. + bool debug = false; ///< Should we print full debug information? /// A map of names to event groups. std::map events_map; @@ -191,8 +191,8 @@ namespace emplode { /// Calculate the result of the provided operation on two computed entries. [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, - emp::Ptr value1, - emp::Ptr value2); + emp::Ptr value1, + emp::Ptr value2); /// Calculate a full expression found in a token sequence, using the provided scope. [[nodiscard]] emp::Ptr @@ -252,98 +252,65 @@ namespace emplode { // Setup default functions. // 'EXEC' dynamically executes the contents of a string. - std::function exec_fun = - [this](const std::string & expression) { return Execute(expression); }; + auto exec_fun = [this](const std::string & expression) { return Execute(expression); }; AddFunction("EXEC", exec_fun, "Dynamically execute the string passed in."); // 'PRINT' is a simple debugging command to output the value of a variable. - std::function> &)> print_fun = - [](const emp::vector> & args) { + auto print_fun = [](const emp::vector> & args) { for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); return 0; }; AddFunction("PRINT", print_fun, "Print out the provided variables."); // Default 1-input math functions - std::function math1_fun = [](double x){ return std::abs(x); }; - AddFunction("ABS", math1_fun, "Absolute Value" ); - math1_fun = [](double x){ return emp::Pow(emp::E, x); }; - AddFunction("EXP", math1_fun, "Exponentiation" ); - math1_fun = [](double x){ return std::log(x); }; - AddFunction("LOG2", math1_fun, "Log base-2" ); - math1_fun = [](double x){ return std::log10(x); }; - AddFunction("LOG10", math1_fun, "Log base-10" ); - - math1_fun = [](double x){ return std::sqrt(x); }; - AddFunction("SQRT", math1_fun, "Square Root" ); - math1_fun = [](double x){ return std::cbrt(x); }; - AddFunction("CBRT", math1_fun, "Cube Root" ); - - math1_fun = [](double x){ return std::sin(x); }; - AddFunction("SIN", math1_fun, "Sine" ); - math1_fun = [](double x){ return std::cos(x); }; - AddFunction("COS", math1_fun, "Cosine" ); - math1_fun = [](double x){ return std::tan(x); }; - AddFunction("TAN", math1_fun, "Tangent" ); - math1_fun = [](double x){ return std::asin(x); }; - AddFunction("ASIN", math1_fun, "Arc Sine" ); - math1_fun = [](double x){ return std::acos(x); }; - AddFunction("ACOS", math1_fun, "Arc Cosine" ); - math1_fun = [](double x){ return std::atan(x); }; - AddFunction("ATAN", math1_fun, "Arc Tangent" ); - math1_fun = [](double x){ return std::sinh(x); }; - AddFunction("SINH", math1_fun, "Hyperbolic Sine" ); - math1_fun = [](double x){ return std::cosh(x); }; - AddFunction("COSH", math1_fun, "Hyperbolic Cosine" ); - math1_fun = [](double x){ return std::tanh(x); }; - AddFunction("TANH", math1_fun, "Hyperbolic Tangent" ); - math1_fun = [](double x){ return std::asinh(x); }; - AddFunction("ASINH", math1_fun, "Hyperbolic Arc Sine" ); - math1_fun = [](double x){ return std::acosh(x); }; - AddFunction("ACOSH", math1_fun, "Hyperbolic Arc Cosine" ); - math1_fun = [](double x){ return std::atanh(x); }; - AddFunction("ATANH", math1_fun, "Hyperbolic Arc Tangent" ); - - math1_fun = [](double x){ return std::ceil(x); }; - AddFunction("CEIL", math1_fun, "Round UP" ); - math1_fun = [](double x){ return std::floor(x); }; - AddFunction("FLOOR", math1_fun, "Round DOWN" ); - math1_fun = [](double x){ return std::round(x); }; - AddFunction("ROUND", math1_fun, "Round to nearest" ); - - math1_fun = [](double x){ return std::isinf(x); }; - AddFunction("ISINF", math1_fun, "Test if Infinite" ); - math1_fun = [](double x){ return std::isnan(x); }; - AddFunction("ISNAN", math1_fun, "Test if Not-a-number" ); + AddFunction("ABS", [](double x){ return std::abs(x); }, "Absolute Value" ); + AddFunction("EXP", [](double x){ return emp::Pow(emp::E, x); }, "Exponentiation" ); + AddFunction("LOG2", [](double x){ return std::log(x); }, "Log base-2" ); + AddFunction("LOG10", [](double x){ return std::log10(x); }, "Log base-10" ); + + AddFunction("SQRT", [](double x){ return std::sqrt(x); }, "Square Root" ); + AddFunction("CBRT", [](double x){ return std::cbrt(x); }, "Cube Root" ); + + AddFunction("SIN", [](double x){ return std::sin(x); }, "Sine" ); + AddFunction("COS", [](double x){ return std::cos(x); }, "Cosine" ); + AddFunction("TAN", [](double x){ return std::tan(x); }, "Tangent" ); + AddFunction("ASIN", [](double x){ return std::asin(x); }, "Arc Sine" ); + AddFunction("ACOS", [](double x){ return std::acos(x); }, "Arc Cosine" ); + AddFunction("ATAN", [](double x){ return std::atan(x); }, "Arc Tangent" ); + AddFunction("SINH", [](double x){ return std::sinh(x); }, "Hyperbolic Sine" ); + AddFunction("COSH", [](double x){ return std::cosh(x); }, "Hyperbolic Cosine" ); + AddFunction("TANH", [](double x){ return std::tanh(x); }, "Hyperbolic Tangent" ); + AddFunction("ASINH", [](double x){ return std::asinh(x); }, "Hyperbolic Arc Sine" ); + AddFunction("ACOSH", [](double x){ return std::acosh(x); }, "Hyperbolic Arc Cosine" ); + AddFunction("ATANH", [](double x){ return std::atanh(x); }, "Hyperbolic Arc Tangent" ); + + AddFunction("CEIL", [](double x){ return std::ceil(x); }, "Round UP" ); + AddFunction("FLOOR", [](double x){ return std::floor(x); }, "Round DOWN" ); + AddFunction("ROUND", [](double x){ return std::round(x); }, "Round to nearest" ); + + AddFunction("ISINF", [](double x){ return std::isinf(x); }, "Test if Infinite" ); + AddFunction("ISNAN", [](double x){ return std::isnan(x); }, "Test if Not-a-number" ); // Default 2-input math functions - std::function math2_fun = [](double x, double y){ return std::hypot(x,y); }; - AddFunction("HYPOT", math2_fun, "Given sides, find hypotenuse" ); - math2_fun = [](double x, double y){ return emp::Pow(x,y); }; - AddFunction("LOG", math2_fun, "Take log of arg1 with base arg2" ); - math2_fun = [](double x, double y){ return (xy) ? x : y; }; - AddFunction("MAX", math2_fun, "Return greater value" ); - math2_fun = [](double x, double y){ return emp::Pow(x,y); }; - AddFunction("POW", math2_fun, "Take arg1 to the arg2 power" ); + AddFunction("HYPOT", [](double x, double y){ return std::hypot(x,y); }, "Given sides, find hypotenuse" ); + AddFunction("LOG", [](double x, double y){ return emp::Pow(x,y); }, "Take log of arg1 with base arg2" ); + AddFunction("MIN", [](double x, double y){ return (xy) ? x : y; }, "Return greater value" ); + AddFunction("POW", [](double x, double y){ return emp::Pow(x,y); }, "Take arg1 to the arg2 power" ); // Default 3-input math functions - std::function math3_fun = - [](double x, double y, double z){ return (x!=0.0) ? y : z; }; - AddFunction("IF", math3_fun, "If arg1 is true, return arg2, else arg3" ); - math3_fun = [](double x, double y, double z){ return (xz) ? z : x; }; - AddFunction("CLAMP", math3_fun, "Return arg1, forced into range [arg2,arg3]" ); - math3_fun = [](double x, double y, double z){ return (z-y)*x+y; }; - AddFunction("TO_SCALE", math3_fun, "Scale arg1 to arg2-arg3 as unit distance" ); - math3_fun = [](double x, double y, double z){ return (x-y) / (z-y); }; - AddFunction("FROM_SCALE", math3_fun, "Scale arg1 from arg2-arg3 as unit distance" ); + auto math3_if = [](double x, double y, double z){ return (x!=0.0) ? y : z; }; + AddFunction("IF", math3_if, "If arg1 is true, return arg2, else arg3" ); + auto math3_clamp = [](double x, double y, double z){ return (xz) ? z : x; }; + AddFunction("CLAMP", math3_clamp, "Return arg1, forced into range [arg2,arg3]" ); + auto math3_to_scale = [](double x, double y, double z){ return (z-y)*x+y; }; + AddFunction("TO_SCALE", math3_to_scale, "Scale arg1 to arg2-arg3 as unit distance" ); + auto math3_from_scale = [](double x, double y, double z){ return (x-y) / (z-y); }; + AddFunction("FROM_SCALE", math3_from_scale, "Scale arg1 from arg2-arg3 as unit distance" ); // Setup default DataFile type. files.SetOutputDefaultFile(); // Stream manager should default to files for output. - std::function (const std::string &)> df_init = - [this](const std::string & name) { return emp::NewPtr(name, files); }; - + auto df_init = [this](const std::string & name) { return emp::NewPtr(name, files); }; auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); df_type.AddMemberFunction( "ADD_COLUMN", @@ -439,18 +406,22 @@ namespace emplode { return info; } + /// If init_fun is not specified in add type, build our own and assume that we own the object. + template + TypeInfo & AddType(const std::string & type_name, const std::string & desc) { + return AddType(type_name, desc, [](){ return emp::NewPtr(); }, true); + } + /// Also allow direct file management. emp::StreamManager & GetFileManager() { return files; } /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be - /// converted properly. For a variadic function, the provided std::function must take a + /// converted properly. For a variadic function, the provided function must take a /// vector of ASTNode pointers, but may return any known type. - template - void AddFunction(const std::string & name, - std::function fun, - const std::string & desc) { - root_scope.AddBuiltinFunction(name, fun, desc); + template + void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { + root_scope.AddBuiltinFunction(name, fun, desc); } Symbol_Scope & GetRootScope() { return root_scope; } @@ -644,75 +615,69 @@ namespace emplode { if (in_node1->IsNumeric()) { // Determine the output value and put it in a temporary node. - std::function fun; - if (symbol == "+") fun = [](double val1, double val2){ return val1 + val2; }; - else if (symbol == "-") fun = [](double val1, double val2){ return val1 - val2; }; - else if (symbol == "**") fun = [](double val1, double val2){ return emp::Pow(val1, val2); }; - else if (symbol == "*") fun = [](double val1, double val2){ return val1 * val2; }; - else if (symbol == "/") fun = [](double val1, double val2){ return val1 / val2; }; - else if (symbol == "%") fun = [](double val1, double val2){ return emp::Mod(val1, val2); }; - else if (symbol == "==") fun = [](double val1, double val2){ return val1 == val2; }; - else if (symbol == "!=") fun = [](double val1, double val2){ return val1 != val2; }; - else if (symbol == "<") fun = [](double val1, double val2){ return val1 < val2; }; - else if (symbol == "<=") fun = [](double val1, double val2){ return val1 <= val2; }; - else if (symbol == ">") fun = [](double val1, double val2){ return val1 > val2; }; - else if (symbol == ">=") fun = [](double val1, double val2){ return val1 >= val2; }; + emp::Ptr out_val = emp::NewPtr(symbol); + + if (symbol == "+") out_val->SetFun( [](double v1, double v2){ return v1 + v2; } ); + else if (symbol == "-") out_val->SetFun( [](double v1, double v2){ return v1 - v2; } ); + else if (symbol == "**") out_val->SetFun( [](double v1, double v2){ return emp::Pow(v1, v2); } ); + else if (symbol == "*") out_val->SetFun( [](double v1, double v2){ return v1 * v2; } ); + else if (symbol == "/") out_val->SetFun( [](double v1, double v2){ return v1 / v2; } ); + else if (symbol == "%") out_val->SetFun( [](double v1, double v2){ return emp::Mod(v1, v2); } ); + else if (symbol == "==") out_val->SetFun( [](double v1, double v2){ return v1 == v2; } ); + else if (symbol == "!=") out_val->SetFun( [](double v1, double v2){ return v1 != v2; } ); + else if (symbol == "<") out_val->SetFun( [](double v1, double v2){ return v1 < v2; } ); + else if (symbol == "<=") out_val->SetFun( [](double v1, double v2){ return v1 <= v2; } ); + else if (symbol == ">") out_val->SetFun( [](double v1, double v2){ return v1 > v2; } ); + else if (symbol == ">=") out_val->SetFun( [](double v1, double v2){ return v1 >= v2; } ); // @CAO: Need to still handle these last two differently for short-circuiting. - else if (symbol == "&&") fun = [](double val1, double val2){ return val1 && val2; }; - else if (symbol == "||") fun = [](double val1, double val2){ return val1 || val2; }; + else if (symbol == "&&") out_val->SetFun( [](double v1, double v2){ return v1 && v2; } ); + else if (symbol == "||") out_val->SetFun( [](double v1, double v2){ return v1 || v2; } ); - emp::Ptr out_value = emp::NewPtr(symbol); - out_value->SetFun(fun); - out_value->AddChild(in_node1); - out_value->AddChild(in_node2); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); - return out_value; + return out_val; } // Otherwise assume that we are dealing with strings. if (symbol == "+") { - std::function fun; - fun = [](std::string val1, std::string val2){ return val1 + val2; }; + auto out_val = emp::NewPtr>(symbol); + out_val->SetFun([](std::string val1, std::string val2){ return val1 + val2; }); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); - auto out_value = emp::NewPtr>(symbol); - out_value->SetFun(fun); - out_value->AddChild(in_node1); - out_value->AddChild(in_node2); - - return out_value; + return out_val; } else if (symbol == "*") { - std::function fun; - fun = [](std::string val1, double val2) { + auto fun = [](std::string val1, double val2) { std::string out_string; out_string.reserve(val1.size() * (size_t) val2); for (size_t i = 0; i < (size_t) val2; i++) out_string += val1; return out_string; }; - auto out_value = emp::NewPtr>(symbol); - out_value->SetFun(fun); - out_value->AddChild(in_node1); - out_value->AddChild(in_node2); + auto out_val = emp::NewPtr>(symbol); + out_val->SetFun(fun); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); - return out_value; + return out_val; } else { - std::function fun; - if (symbol == "==") fun = [](std::string val1, std::string val2){ return val1 == val2; }; - else if (symbol == "!=") fun = [](std::string val1, std::string val2){ return val1 != val2; }; - else if (symbol == "<") fun = [](std::string val1, std::string val2){ return val1 < val2; }; - else if (symbol == "<=") fun = [](std::string val1, std::string val2){ return val1 <= val2; }; - else if (symbol == ">") fun = [](std::string val1, std::string val2){ return val1 > val2; }; - else if (symbol == ">=") fun = [](std::string val1, std::string val2){ return val1 >= val2; }; - - auto out_value = emp::NewPtr>(symbol); - out_value->SetFun(fun); - out_value->AddChild(in_node1); - out_value->AddChild(in_node2); - - return out_value; + auto out_val = emp::NewPtr>(symbol); + + if (symbol == "==") out_val->SetFun([](std::string v1, std::string v2){ return v1 == v2; }); + else if (symbol == "!=") out_val->SetFun([](std::string v1, std::string v2){ return v1 != v2; }); + else if (symbol == "<") out_val->SetFun([](std::string v1, std::string v2){ return v1 < v2; }); + else if (symbol == "<=") out_val->SetFun([](std::string v1, std::string v2){ return v1 <= v2; }); + else if (symbol == ">") out_val->SetFun([](std::string v1, std::string v2){ return v1 > v2; }); + else if (symbol == ">=") out_val->SetFun([](std::string v1, std::string v2){ return v1 >= v2; }); + + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; } return nullptr; From 48521e4e479047b5a6adab6d57544868c804d5c0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 28 Oct 2021 17:53:12 -0400 Subject: [PATCH 263/445] Moved EmplodeType base class from Population down to OrgContainer level. --- source/core/OrgIterator.hpp | 2 +- source/core/Population.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index baa9cf56..b5c3feb8 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -29,7 +29,7 @@ namespace mabe { /// Base class for all organsim containers, including population. - struct OrgContainer { + struct OrgContainer : public EmplodeType { virtual ~OrgContainer() { } virtual std::string GetName() const { return ""; } diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 4c7ce2ba..29e0c2e4 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -83,7 +83,7 @@ namespace mabe { /// A Population maintains a collection of organisms. It is derived from EmplodeType so that it /// can be easily used in the MABE scripting language. - class Population : public EmplodeType, public OrgContainer { + class Population : public OrgContainer { friend class MABEBase; private: std::string name=""; ///< Unique name for this population. From 48cc4cb64263d8ed7e00745498f06328c4ce6807 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 15:57:25 -0400 Subject: [PATCH 264/445] Fixed the simplest version of AddType to use a proper lambda. --- source/Emplode/Emplode.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 99942482..2fcc9787 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -409,7 +409,9 @@ namespace emplode { /// If init_fun is not specified in add type, build our own and assume that we own the object. template TypeInfo & AddType(const std::string & type_name, const std::string & desc) { - return AddType(type_name, desc, [](){ return emp::NewPtr(); }, true); + return AddType(type_name, desc, + [](const std::string & /*name*/){ return emp::NewPtr(); }, + true); } /// Also allow direct file management. From 74d28ef37b883772eb71592a9f8dfa047c392410 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 15:57:55 -0400 Subject: [PATCH 265/445] Fixed comments. --- source/Emplode/EmplodeType.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index ec6b8fa6..98c7d91d 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -28,7 +28,8 @@ namespace emplode { /// EmplodeType can create its own version to automatically load in member functions, etc. static void InitType(Emplode & /*config*/, TypeInfo & /*info*/) { // If you create a version of this function for your own EmplodeType, this is where you would - // create member functions. + // create member functions. Note that this is a static function, so just make a version of it + // in your own class; you are NOT overriding a virtual function. } From 69bc1d010be01534a0ae6f48bd722073c80f8bd3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:05:43 -0400 Subject: [PATCH 266/445] Created a separate symbol type for scopes managing C++ objects. --- source/Emplode/Symbol_Object.hpp | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 source/Emplode/Symbol_Object.hpp diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp new file mode 100644 index 00000000..8a2ef7e0 --- /dev/null +++ b/source/Emplode/Symbol_Object.hpp @@ -0,0 +1,74 @@ +/** + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file Symbol_Object.hpp + * @brief Extension of scope when there is an external object associated with the structure. + * @note Status: BETA + */ + +#ifndef EMPLODE_SYMBOL_OBJECT_HPP +#define EMPLODE_SYMBOL_OBJECT_HPP + +#include "emp/base/map.hpp" + +#include "Symbol_Scope.hpp" + +namespace emplode { + + class EmplodeType; + + // Set of multiple config entries. + class Symbol_Object : public Symbol_Scope { + protected: + ///< Point to associated object and track ownership + emp::Ptr obj_ptr = nullptr; + bool obj_owned = false; + + public: + Symbol_Object(const std::string & _name, + const std::string & _desc, + emp::Ptr _scope, + emp::Ptr _obj, + bool _owned) + : Symbol_Scope(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } + + Symbol_Object(const Symbol_Object & in) : Symbol_Scope(in) { + // Copy the internal object. + // @CAO MUST DO THIS!!!!!!!!!!!!!!!!!!!!! + } + Symbol_Object(Symbol_Object && in) + : Symbol_Scope(std::move(in)), obj_ptr(in.obj_ptr), obj_owned(in.obj_owned) + { + // Remove the object from the incoming symbol. + in.obj_ptr = nullptr; + in.obj_owned = false; + } + + ~Symbol_Object() { + // If this scope owns its object pointer, delete it now. + if (obj_owned) obj_ptr.Delete(); + } + + emp::Ptr GetObjectPtr() override { return obj_ptr; } + emp::Ptr GetObjectPtr() const override { return obj_ptr; } + + bool IsObject() const override { return true; } + + /// Make a copy of this scope and all of the entries inside it. + emp::Ptr Clone() const override { return emp::NewPtr(*this); } + }; + + // Definition needed to add an object to an existing scope. + Symbol_Object & Symbol_Scope::AddObject( + const std::string & name, + const std::string & desc, + emp::Ptr obj_ptr, + bool obj_owned + ) { + return Add(name, desc, this, obj_ptr, obj_owned); + } + +} +#endif From 5f79ca303b996ab0fcf9d840b0ca97dae1407348 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:06:01 -0400 Subject: [PATCH 267/445] Removed object handling from base scope objects. --- source/Emplode/Symbol_Scope.hpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index c5d2988d..fd24f261 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -19,11 +19,11 @@ #include "Symbol.hpp" #include "Symbol_Function.hpp" #include "Symbol_Linked.hpp" -#include "EmplodeTypeBase.hpp" namespace emplode { class EmplodeType; + class Symbol_Object; // Set of multiple config entries. class Symbol_Scope : public Symbol { @@ -32,10 +32,6 @@ namespace emplode { using const_symbol_ptr_t = emp::Ptr; emp::map< std::string, symbol_ptr_t > symbol_table; ///< Map of names to entries. - ///< If this scope represents a structure, point to it; otherwise set to null. - emp::Ptr obj_ptr = nullptr; - bool obj_owned = false; - template T & Add(const std::string & name, ARGS &&... args) { auto new_ptr = emp::NewPtr(name, std::forward(args)...); @@ -53,12 +49,8 @@ namespace emplode { } public: - Symbol_Scope(const std::string & _name, - const std::string & _desc, - emp::Ptr _scope, - emp::Ptr _obj=nullptr, - bool _owned=false) - : Symbol(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } + Symbol_Scope(const std::string & _name, const std::string & _desc, emp::Ptr _scope) + : Symbol(_name, _desc, _scope) { } Symbol_Scope(const Symbol_Scope & in) : Symbol(in) { // Copy all defined variables/scopes/functions @@ -67,16 +59,10 @@ namespace emplode { Symbol_Scope(Symbol_Scope &&) = default; ~Symbol_Scope() { - // If this scope owns its object pointer, delete it now. - if (obj_owned) obj_ptr.Delete(); - // Clear up the symbol table. for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } - emp::Ptr GetObjectPtr() override { return obj_ptr; } - emp::Ptr GetObjectPtr() const override { return obj_ptr; } - bool IsScope() const override { return true; } bool IsLocal() const override { return true; } // @CAO, for now assuming all scopes are local! @@ -152,14 +138,17 @@ namespace emplode { } /// Add an internal scope inside of this one. - Symbol_Scope & AddScope( + Symbol_Scope & AddScope(const std::string & name, const std::string & desc) { + return Add(name, desc, this); + } + + /// Add an internal scope inside of this one (defined in Symbol_Object.hpp) + Symbol_Object & AddObject( const std::string & name, const std::string & desc, emp::Ptr obj_ptr=nullptr, bool obj_owned=false - ) { - return Add(name, desc, this, obj_ptr, obj_owned); - } + ); /// Add a new user-defined function. template From 6771c859bf01984b231c593585d510ff7c0658a1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:07:30 -0400 Subject: [PATCH 268/445] Added IsObject() to base symbol type. --- source/Emplode/Symbol.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index d1ba732c..6762214a 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -107,6 +107,7 @@ namespace emplode { virtual bool IsLocal() const { return false; } ///< Was symbol defined in config file? virtual bool IsFunction() const { return false; } ///< Is symbol a function? virtual bool IsScope() const { return false; } ///< Is symbol a full scope? + virtual bool IsObject() const { return false; } ///< Is symbol associated with C++ object? virtual bool IsError() const { return false; } ///< Does symbol flag an error? virtual bool HasNumericReturn() const { return false; } ///< Is symbol a function that returns a number? From 366410902ff1c7a6f02ab84332028869894af419 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:08:20 -0400 Subject: [PATCH 269/445] Changed Emplode to creating object with Symbol_Object, not Symbol_Scope --- source/Emplode/Emplode.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 2fcc9787..c3c05057 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -760,12 +760,12 @@ namespace emplode { emp::Ptr new_obj = type_info.MakeObj(var_name); // Setup a scope for this new type, linking the object to it. - Symbol_Scope & new_scope = scope.AddScope(var_name, type_desc, new_obj, is_config_owned); + Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); // Let the new object know about its scope. - new_obj->Setup(new_scope, type_info); + new_obj->Setup(new_obj_symbol, type_info); - return new_scope; + return new_obj_symbol; } // Parse an event description. From b92d99cdbd42830d3cbb97d08f493d9ccd810099 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:09:31 -0400 Subject: [PATCH 270/445] Changed EmplodeType to work with Symbol_Object, not Symbol_Scope --- source/Emplode/EmplodeType.hpp | 18 +++++++++--------- source/Emplode/EmplodeTypeBase.hpp | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 98c7d91d..a4dc3431 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -14,7 +14,7 @@ #include "emp/base/assert.hpp" #include "EmplodeTypeBase.hpp" -#include "Symbol_Scope.hpp" +#include "Symbol_Object.hpp" #include "TypeInfo.hpp" namespace emplode { @@ -33,24 +33,24 @@ namespace emplode { } - /// Setup an instance of a new EmplodeType object; provide it with its scope and type information. - void Setup(Symbol_Scope & _scope, TypeInfo & _info) { - cur_scope = &_scope; + /// Setup an instance of a new EmplodeType object; provide it with its symbol and type information. + void Setup(Symbol_Object & in_symbol, TypeInfo & _info) { + symbol_ptr = &in_symbol; type_info_ptr = &_info; - // Link standard internal variables for this scope. + // Link standard internal variables for this object. LinkVar(_active, "_active", "Should we activate this module? (0=off, 1=on)", true); LinkVar(_desc, "_desc", "Special description for those object.", true); // Link specialized variable for the derived type. SetupConfig(); - // Load in any member function for this object into the scope. + // Load in any member function for this object into the object. using symbol_ptr_t = emp::Ptr; using member_fun_t = std::function &)>; const auto & member_map = type_info_ptr->GetMemberFunctions(); - // std::cout << "Loading member functions for '" << _scope.GetName() << "'; " + // std::cout << "Loading member functions for '" << in_symbol.GetName() << "'; " // << member_map.size() << " found." // << std::endl; @@ -58,10 +58,10 @@ namespace emplode { member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ return member_info.fun(*this, args); }; - cur_scope->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); + symbol_ptr->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); // std::cout << "Adding member function '" << member_info.name << "' to object '" - // << cur_scope->GetName() << "'." << std::endl; + // << symbol_ptr->GetName() << "'." << std::endl; } } diff --git a/source/Emplode/EmplodeTypeBase.hpp b/source/Emplode/EmplodeTypeBase.hpp index 33dc7ff2..94a0ef94 100644 --- a/source/Emplode/EmplodeTypeBase.hpp +++ b/source/Emplode/EmplodeTypeBase.hpp @@ -15,7 +15,7 @@ namespace emplode { - class Symbol_Scope; + class Symbol_Object; class TypeInfo; enum class BaseType { @@ -28,7 +28,7 @@ namespace emplode { class EmplodeTypeBase { protected: - emp::Ptr cur_scope; + emp::Ptr symbol_ptr; emp::Ptr type_info_ptr; // Some special, internal variables associated with each object. @@ -41,8 +41,8 @@ namespace emplode { // Optional function to override to add configuration options associated with an object. virtual void SetupConfig() { }; - Symbol_Scope & GetScope() { emp_assert(!cur_scope.IsNull()); return *cur_scope; } - const Symbol_Scope & GetScope() const { emp_assert(!cur_scope.IsNull()); return *cur_scope; } + Symbol_Object & GetScope() { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } + const Symbol_Object & GetScope() const { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } }; From 955df41e114e2b44283112fe4c5edd49287f0129 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 17:24:52 -0400 Subject: [PATCH 271/445] Merged EmplodeTypeBase back into EmplodeType. --- source/Emplode/EmplodeType.hpp | 20 ++++++++++-- source/Emplode/EmplodeTypeBase.hpp | 52 ------------------------------ 2 files changed, 18 insertions(+), 54 deletions(-) delete mode 100644 source/Emplode/EmplodeTypeBase.hpp diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index a4dc3431..90469ab5 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -13,7 +13,6 @@ #include "emp/base/assert.hpp" -#include "EmplodeTypeBase.hpp" #include "Symbol_Object.hpp" #include "TypeInfo.hpp" @@ -22,7 +21,15 @@ namespace emplode { class Emplode; // Base class for types that we want to be used for scripting. - class EmplodeType : public EmplodeTypeBase { + class EmplodeType { + protected: + emp::Ptr symbol_ptr; + emp::Ptr type_info_ptr; + + // Some special, internal variables associated with each object. + bool _active=true; ///< Should this object be used in the current run? + std::string _desc=""; ///< Special description for this object. + public: /// Setup the TYPE of object in the config. This is a stub class, but any new class derived from /// EmplodeType can create its own version to automatically load in member functions, etc. @@ -32,6 +39,15 @@ namespace emplode { // in your own class; you are NOT overriding a virtual function. } + virtual ~EmplodeType() { } + + // Optional function to override to add configuration options associated with an object. + virtual void SetupConfig() { }; + + Symbol_Object & GetScope() { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } + const Symbol_Object & GetScope() const { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } + + const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } /// Setup an instance of a new EmplodeType object; provide it with its symbol and type information. void Setup(Symbol_Object & in_symbol, TypeInfo & _info) { diff --git a/source/Emplode/EmplodeTypeBase.hpp b/source/Emplode/EmplodeTypeBase.hpp deleted file mode 100644 index 94a0ef94..00000000 --- a/source/Emplode/EmplodeTypeBase.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. - * - * @file EmplodeTypeBase.hpp - * @brief Base class for setting up custom types for use in scripting; usable throughout. - * @note Status: ALPHA - */ - -#ifndef EMPLODE_TYPE_BASE_HPP -#define EMPLODE_TYPE_BASE_HPP - -#include "emp/base/assert.hpp" - -namespace emplode { - - class Symbol_Object; - class TypeInfo; - - enum class BaseType { - INVALID = 0, - VOID, - VALUE, - STRING, - STRUCT - }; - - class EmplodeTypeBase { - protected: - emp::Ptr symbol_ptr; - emp::Ptr type_info_ptr; - - // Some special, internal variables associated with each object. - bool _active=true; ///< Should this object be used in the current run? - std::string _desc=""; ///< Special description for this object. - - public: - virtual ~EmplodeTypeBase() { } - - // Optional function to override to add configuration options associated with an object. - virtual void SetupConfig() { }; - - Symbol_Object & GetScope() { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } - const Symbol_Object & GetScope() const { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } - - const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } - }; - -} - -#endif From 76102ff567337ad37665737d9714df480e868104 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 29 Oct 2021 18:42:13 -0400 Subject: [PATCH 272/445] Updated Developer Notes with filename changes. --- source/Emplode/DeveloperNotes.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/source/Emplode/DeveloperNotes.md b/source/Emplode/DeveloperNotes.md index 60b6b114..b9bf5506 100644 --- a/source/Emplode/DeveloperNotes.md +++ b/source/Emplode/DeveloperNotes.md @@ -4,32 +4,34 @@ Parser LEVEL MAP: -ConfigEntry - [] -ConfigLexer - [] -ConfigTypeBase - [] +Symbol - [] +Lexer - [] -ConfigTools - [ConfigEntry] +EmplodeTools - [Symbol] -ConfigTypeInfo - [ConfigEntry,ConfigTools] Basic information for a user-defined type. -ConfigEntry_Function - [ConfigEntry] -ConfigEntry_Linked - [ConfigEntry] +TypeInfo - [Symbol,EmplodeTools] Basic information for a user-defined type. +Symbol_Function - [Symbol] +Symbol_Linked - [Symbol] -ConfigEntry_Scope - [ConfigEntry,ConfigEntry_Function,ConfigEntry_Linked,ConfigTypeBase] +Symbol_Scope - [Symbol,Symbol_Function,Symbol_Linked] -ConfigAST - [ConfigEntry_Scope,ConfigEntry,ConfigTools] -ConfigType - [ConfigEntry_Scope,ConfigTypeInfo] +Symbol_Object - [Symbol_Scope] -ConfigEvents - [ConfigAST] +AST - [Symbol_Object,Symbol,EmplodeTools] +EmplodeType - [Symbol_Object,TypeInfo] -Config - [ALL] Main parser +Events - [AST] +DataFile - [EmplodeType] + +Emplode - [ALL] Main parser TODO: -* Config as a whole should move from MABE to Empirical +* Emplode as a whole should move from MABE to Empirical * We need a consistent and functional error system. -Right now we either output direct to the command line OR create a ConfigEntry_Error +Right now we either output direct to the command line OR create a Symbol_Error with a meaningful error included, but it's then ignored and never printed. What should happen is that an error message is raised and the appropriate interface module handles it. @@ -58,7 +60,7 @@ but it's not hooked in. * Need a stand-alone config interpreter along with a full test suite to make sure it's working properly. -* Should be able to make new types (not just new instances) inside the interpreter. +* Should be able to make whole new types (not just new instances) inside the interpreter. * Should be able to adjust default values for existing types. This will allow for config files to be included in that just setup defaults, but don't actually build any new object. From 4410fe44f8d41f2ba29c10e723fd14e6636842de Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 30 Oct 2021 11:47:24 -0400 Subject: [PATCH 273/445] Removed unused type enum from Emplode. --- source/Emplode/Emplode.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index c3c05057..393306f0 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -231,11 +231,11 @@ namespace emplode { if (filename != "") Load(filename); // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( (size_t) BaseType::INVALID, "/*ERROR*/", "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( (size_t) BaseType::VOID, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( (size_t) BaseType::VALUE, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( (size_t) BaseType::STRING, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( (size_t) BaseType::STRUCT, "Struct", "User-made structure" ); + type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); // Setup operator precedence. size_t cur_prec = 0; From e4bcbafe5a5f6835d6e7371b22e37f4612dcba50 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 31 Oct 2021 10:17:19 -0400 Subject: [PATCH 274/445] Added a const version of Symbol::AsScope() --- source/Emplode/Symbol.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index 6762214a..7cc6609f 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -125,10 +125,15 @@ namespace emplode { virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } virtual emp::Ptr AsScopePtr() { return nullptr; } + virtual emp::Ptr AsScopePtr() const { return nullptr; } Symbol_Scope & AsScope() { emp_assert(AsScopePtr()); return *(AsScopePtr()); } + const Symbol_Scope & AsScope() const { + emp_assert(AsScopePtr()); + return *(AsScopePtr()); + } virtual emp::Ptr GetObjectPtr() { return nullptr; } virtual emp::Ptr GetObjectPtr() const { return nullptr; } From 28841359f6742e116727c667586fdd49fe2a33ca Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 1 Nov 2021 16:39:56 -0400 Subject: [PATCH 275/445] Allow Structs in config to be assigned at creation. --- source/Emplode/Emplode.hpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 393306f0..636bc8c5 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -826,17 +826,22 @@ namespace emplode { return nullptr; // We are done! } - // If this symbol is a new scope, it should be populated now. + // If this symbol is a new scope, it can be populated now either directly (with in braces) + // or indirectly (with and assignment) if (new_symbol.IsScope()) { - RequireChar('{', pos, "Expected scope '", new_symbol.GetName(), - "' definition to start with a '{'; found ''", AsLexeme(pos), "'."); - pos++; - emp::Ptr out_node = ParseStatementList(pos, new_symbol.AsScope()); - RequireChar('}', pos++, "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); - return out_node; - } + if (AsChar(pos) == '{') { + pos++; + emp::Ptr out_node = ParseStatementList(pos, new_symbol.AsScope()); + RequireChar('}', pos++, "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); + return out_node; + } + + RequireChar('=', pos, "Expected scope '", new_symbol.GetName(), + "' definition to start with a '{' or '='; found ''", AsLexeme(pos), "'."); + + } - // Otherwise rewind so that variable can be used to start an expression. + // Otherwise rewind so that the new variable can be used to start an expression. --pos; } From 06d5281d0bb7ecbb3da4dfc9186edfac09748cb7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:14:33 -0400 Subject: [PATCH 276/445] Adjusted EmplodeType to no longer require an Emplode object. --- source/Emplode/EmplodeType.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 90469ab5..53abb3c6 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -33,7 +33,7 @@ namespace emplode { public: /// Setup the TYPE of object in the config. This is a stub class, but any new class derived from /// EmplodeType can create its own version to automatically load in member functions, etc. - static void InitType(Emplode & /*config*/, TypeInfo & /*info*/) { + static void InitType(TypeInfo & /*info*/) { // If you create a version of this function for your own EmplodeType, this is where you would // create member functions. Note that this is a static function, so just make a version of it // in your own class; you are NOT overriding a virtual function. From 2b70b73a58ce865bfbd3a718deba695c7a4e28c7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:15:18 -0400 Subject: [PATCH 277/445] Setup Population and DataFile to no longer receive (and ignore) and Emplode object in InitType() --- source/Emplode/DataFile.hpp | 6 +++--- source/core/Population.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Emplode/DataFile.hpp b/source/Emplode/DataFile.hpp index 82aba7ad..0b4fe113 100644 --- a/source/Emplode/DataFile.hpp +++ b/source/Emplode/DataFile.hpp @@ -46,7 +46,7 @@ namespace emplode { std::string GetName() const { return name; } // Setup member functions associated with population. - static void InitType(Emplode & /*config*/, TypeInfo & info) { + static void InitType(TypeInfo & info) { auto fun_num_cols = [](DataFile & target) { return target.cols.size(); }; info.AddMemberFunction("NUM_COLS", fun_num_cols, "Return the number of columns in this file."); info.AddMemberFunction("WRITE", [](DataFile & target) { return target.Write(); }, @@ -70,7 +70,7 @@ namespace emplode { // If we need headers, set them up! if (!file_exists) { for (size_t i = 0; i < cols.size(); ++i) { - if (i) file << ", "; + if (i) file << ","; file << cols[i].header; } file << '\n'; @@ -78,7 +78,7 @@ namespace emplode { // Now print out each entry. for (size_t i = 0; i < cols.size(); ++i) { - if (i) file << ", "; + if (i) file << ","; file << cols[i].fun(); } file << std::endl; diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 29e0c2e4..d587b7c6 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -202,7 +202,7 @@ namespace mabe { public: // Setup member functions associated with population. - static void InitType(emplode::Emplode & /*config*/, emplode::TypeInfo & info) { + static void InitType(emplode::TypeInfo & info) { std::function fun_size = [](Population & target) { return target.GetSize(); }; info.AddMemberFunction("SIZE", fun_size, "Return the size of the population."); From 928269eb5f4054594be7e7c3e8c47f7ca464385b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:16:25 -0400 Subject: [PATCH 278/445] Added a CopyValue() to Symbol_Scope. --- source/Emplode/Symbol_Scope.hpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index fd24f261..c4214256 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -68,6 +68,27 @@ namespace emplode { /// Set this symbol to be a correctly-typed scope pointer. emp::Ptr AsScopePtr() override { return this; } + emp::Ptr AsScopePtr() const override { return this; } + + bool CopyValue(const Symbol & in) override { + if (in.IsScope() == false) return false; // Mis-matched types; failed to copy. + + const Symbol_Scope & in_scope = in.AsScope(); + + // Assignment to an existing Struct cannot create new variables; all must already exist. + // Do not delete other existing entries. + for (const auto & [name, ptr] : in_scope.symbol_table) { + // If entry does not exist fail the copy. + if (emp::Has(symbol_table, name)) return false; + + bool success = symbol_table[name]->CopyValue(*ptr); + if (!success) return false; // Stop immediately on failure. + } + + // If we made it this far, it must have worked! + return true; + } + /// Get a symbol out of this scope; symbol_ptr_t GetSymbol(std::string name) { return emp::Find(symbol_table, name, nullptr); } @@ -181,7 +202,7 @@ namespace emplode { /// Write out this scope AND it's contents to the provided stream. const Symbol & Write(std::ostream & os=std::cout, const std::string & prefix="", - size_t comment_offset=32) const override + size_t comment_offset=32) const override { // If this is a built-in scope, don't print it. if (IsBuiltin()) return *this; From 61970993877878b2faaff2446ac8e2524d32c8f6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:22:48 -0400 Subject: [PATCH 279/445] Condensed identifier information and lookup into a SymbolTable object. --- source/Emplode/Emplode.hpp | 287 ++++++++++++++++++++++--------------- 1 file changed, 169 insertions(+), 118 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 636bc8c5..e5b8a116 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -87,6 +87,150 @@ namespace emplode { + class SymbolTable { + protected: + Symbol_Scope root_scope; ///< All variables from the root level. + std::map events_map; ///< A map of names to event groups. + std::unordered_map> type_map; ///< All types available in the script. + emp::StreamManager file_map; ///< Track all file streams by name. + + public: + SymbolTable(const std::string & name) + : root_scope(name, "Outer-most, global scope.", nullptr) { + // Initialize the type map. + type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); + + file_map.SetOutputDefaultFile(); // Stream manager should default to 'file' output. + } + + ~SymbolTable() { + // Clean up type information. + for (auto [name, ptr] : type_map) ptr.Delete(); + } + + Symbol_Scope & GetRootScope() { return root_scope; } + const Symbol_Scope & GetRootScope() const { return root_scope; } + emp::StreamManager & GetFileManager() { return file_map; } + + bool HasEvent(const std::string & name) const { return emp::Has(events_map, name); } + bool HasType(const std::string & name) const { return emp::Has(type_map, name); } + + /// To add a built-in function (at the root level) provide it with a name and description. + /// As long as the function only requires types known to the config system, it should be + /// converted properly. For a variadic function, the provided function must take a + /// vector of ASTNode pointers, but may return any known type. + template + void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { + root_scope.AddBuiltinFunction(name, fun, desc); + } + + /// To add a type, provide the type name (that can be referred to in a script) and a function + /// that should be called (with the variable name) when an instance of that type is created. + /// The function must return a reference to the newly created instance. + template + TypeInfo & AddType( + const std::string & type_name, + const std::string & desc, + FUN_T init_fun, + emp::TypeID type_id, + bool is_config_owned=false + ) { + emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); + size_t index = type_map.size(); + auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); + info_ptr->LinkType(type_id); + type_map[type_name] = info_ptr; + return *type_map[type_name]; + } + + /// If the linked type can be provided as a template parameter, we can also double check that + /// it is derived from EmplodeType (as it needs to be...) + template + TypeInfo & AddType( + const std::string & type_name, + const std::string & desc, + FUN_T init_fun, + bool is_config_owned=false + ) { + static_assert(std::is_base_of(), + "Only EmplodeType objects can be used as a custom config type."); + TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); + OBJECT_T::InitType(info); + return info; + } + + /// If init_fun is not specified in add type, build our own and assume that we own the object. + template + TypeInfo & AddType(const std::string & type_name, const std::string & desc) { + return AddType(type_name, desc, + [](const std::string & /*name*/){ return emp::NewPtr(); }, + true); + } + + Symbol_Object & MakeObjSymbol( + const std::string & type_name, + const std::string & var_name, + Symbol_Scope & scope + ) { + // Retrieve the information about the requested type. + TypeInfo & type_info = *type_map[type_name]; + const std::string & type_desc = type_info.GetDesc(); + const bool is_config_owned = type_info.GetOwned(); + + // Use the TypeInfo associated with the provided type name to build an instance. + emp::Ptr new_obj = type_info.MakeObj(var_name); + + // Setup a scope for this new type, linking the object to it. + Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); + + // Let the new object know about its scope. + new_obj->Setup(new_obj_symbol, type_info); + + return new_obj_symbol; + } + + + /// Create a new type of event that can be used in the scripting language. + Events & AddEventType(const std::string & name) { + emp_assert(!HasEvent(name), "Event type already exists!", name); + return events_map[name]; + } + + /// Add an instance of an event with an action that should be triggered. + void AddEvent(const std::string & name, emp::Ptr action, + double first=0.0, double repeat=0.0, double max=-1.0) { + emp_assert(HasEvent(name), name); + // Debug ("Adding event instance for '", name, "' (", first, ":", repeat, ":", max, ")"); + events_map[name].AddEvent(action, first, repeat, max); + } + + /// Indicate the an event trigger value has been updated; trigger associated events. + void UpdateEventValue(const std::string & name, double new_value) { + emp_assert(HasEvent(name), name); + // Debug("Uppdating event value '", name, "' to ", new_value); + events_map[name].UpdateValue(new_value); + } + + /// Trigger all events of a type (ignoring trigger values) + void TriggerEvents(const std::string & name) { + emp_assert(HasEvent(name), name); + events_map[name].TriggerAll(); + } + + /// Print all of the events to the provided stream. + void PrintEvents(std::ostream & os) const { + for (const auto & x : events_map) { + x.second.Write(x.first, os); + } + } + + }; + + class Emplode { public: using pos_t = emp::TokenStream::Iterator; @@ -94,19 +238,10 @@ namespace emplode { protected: std::string filename; ///< Source for for code to generate. Lexer lexer; ///< Lexer to process input code. - Symbol_Scope root_scope; ///< All variables from the root level. + SymbolTable symbol_table; ///< Management of identifiers. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. bool debug = false; ///< Should we print full debug information? - /// A map of names to event groups. - std::map events_map; - - /// A map of all types available in the script. - std::unordered_map> type_map; - - /// Management of built-in types. - emp::StreamManager files; ///< Track all file streams. - /// A list of precedence levels for symbols. std::unordered_map precedence_map; @@ -117,7 +252,7 @@ namespace emplode { bool IsString(pos_t pos) const { return pos.IsValid() && lexer.IsString(*pos); } bool IsDots(pos_t pos) const { return pos.IsValid() && lexer.IsDots(*pos); } - bool IsType(pos_t pos) const { return pos.IsValid() && emp::Has(type_map, pos->lexeme); } + bool IsType(pos_t pos) const { return pos.IsValid() && symbol_table.HasType(pos->lexeme); } char AsChar(pos_t pos) const { return (pos.IsValid() && lexer.IsSymbol(*pos)) ? pos->lexeme[0] : 0; @@ -225,18 +360,11 @@ namespace emplode { public: Emplode(std::string in_filename="") : filename(in_filename) - , root_scope("Emplode", "Outer-most, global scope.", nullptr) - , ast_root(root_scope) + , symbol_table("Emplode") + , ast_root(symbol_table.GetRootScope()) { if (filename != "") Load(filename); - // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); - // Setup operator precedence. size_t cur_prec = 0; precedence_map["("] = cur_prec++; @@ -309,8 +437,9 @@ namespace emplode { AddFunction("FROM_SCALE", math3_from_scale, "Scale arg1 from arg2-arg3 as unit distance" ); // Setup default DataFile type. - files.SetOutputDefaultFile(); // Stream manager should default to files for output. - auto df_init = [this](const std::string & name) { return emp::NewPtr(name, files); }; + auto df_init = [this](const std::string & name) { + return emp::NewPtr(name, symbol_table.GetFileManager()); + }; auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); df_type.AddMemberFunction( "ADD_COLUMN", @@ -331,91 +460,27 @@ namespace emplode { Emplode & operator=(const Emplode &) = delete; Emplode & operator=(Emplode &&) = delete; - ~Emplode() { - // Clean up type information. - for (auto [name, ptr] : type_map) ptr.Delete(); - } - /// Create a new type of event that can be used in the scripting language. - Events & AddEventType(const std::string & name) { - emp_assert(!emp::Has(events_map, name)); - Debug ("Adding event type '", name, "'"); - return events_map[name]; - } + Events & AddEventType(const std::string & name) { return symbol_table.AddEventType(name); } /// Add an instance of an event with an action that should be triggered. - void AddEvent(const std::string & name, emp::Ptr action, - double first=0.0, double repeat=0.0, double max=-1.0) { - emp_assert(emp::Has(events_map, name), name); - Debug ("Adding event instance for '", name, "' (", first, ":", repeat, ":", max, ")"); - events_map[name].AddEvent(action, first, repeat, max); - } + template + void AddEvent(Ts &&... args) { symbol_table.AddEvent(std::forward(args)...); } /// Indicate the an event trigger value has been updated; trigger associated events. void UpdateEventValue(const std::string & name, double new_value) { - emp_assert(emp::Has(events_map, name), name); - Debug("Uppdating event value '", name, "' to ", new_value); - events_map[name].UpdateValue(new_value); + symbol_table.UpdateEventValue(name, new_value); } /// Trigger all events of a type (ignoring trigger values) - void TriggerEvents(const std::string & name) { - emp_assert(emp::Has(events_map, name), name); - events_map[name].TriggerAll(); - } + void TriggerEvents(const std::string & name) { symbol_table.TriggerEvents(name); } - /// Print all of the events to the provided stream. - void PrintEvents(std::ostream & os) const { - for (const auto & x : events_map) { - x.second.Write(x.first, os); - } - } - /// To add a type, provide the type name (that can be referred to in a script) and a function - /// that should be called (with the variable name) when an instance of that type is created. - /// The function must return a reference to the newly created instance. - template - TypeInfo & AddType( - const std::string & type_name, - const std::string & desc, - FUN_T init_fun, - emp::TypeID type_id, - bool is_config_owned=false - ) { - emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); - size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); - info_ptr->LinkType(type_id); - type_map[type_name] = info_ptr; - return *type_map[type_name]; + template + TypeInfo & AddType(ARG_Ts &&... args) { + return symbol_table.AddType( std::forward(args)... ); } - /// If the linked type can be provided as a template parameter, we can also double check that - /// it is derived from EmplodeType (as it needs to be...) - template - TypeInfo & AddType( - const std::string & type_name, - const std::string & desc, - FUN_T init_fun, - bool is_config_owned=false - ) { - static_assert(std::is_base_of(), - "Only EmplodeType objects can be used as a custom config type."); - TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); - OBJECT_T::InitType(*this, info); - return info; - } - - /// If init_fun is not specified in add type, build our own and assume that we own the object. - template - TypeInfo & AddType(const std::string & type_name, const std::string & desc) { - return AddType(type_name, desc, - [](const std::string & /*name*/){ return emp::NewPtr(); }, - true); - } - - /// Also allow direct file management. - emp::StreamManager & GetFileManager() { return files; } /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be @@ -423,11 +488,11 @@ namespace emplode { /// vector of ASTNode pointers, but may return any known type. template void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { - root_scope.AddBuiltinFunction(name, fun, desc); + symbol_table.AddFunction(name, fun, desc); } - Symbol_Scope & GetRootScope() { return root_scope; } - const Symbol_Scope & GetRootScope() const { return root_scope; } + SymbolTable & GetSymbolTable() { return symbol_table; } + const SymbolTable & GetSymbolTable() const { return symbol_table; } // Load a single, specified configuration file. void Load(const std::string & filename) { @@ -438,7 +503,7 @@ namespace emplode { pos_t pos = tokens.begin(); // Start at the beginning of the file. // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, root_scope); + auto cur_block = ParseStatementList(pos, symbol_table.GetRootScope()); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -459,7 +524,7 @@ namespace emplode { pos_t pos = tokens.begin(); // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, root_scope); + auto cur_block = ParseStatementList(pos, symbol_table.GetRootScope()); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -469,11 +534,11 @@ namespace emplode { // Load the provided statement and run it. std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { Debug("Running Execute()"); - if (!scope) scope = &root_scope; // Default scope to root level. + if (!scope) scope = &symbol_table.GetRootScope(); // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. - auto cur_block = ParseStatement(pos, root_scope); // Convert tokens to AST + auto cur_block = ParseStatement(pos, symbol_table.GetRootScope()); // Convert tokens to AST auto result_ptr = cur_block->Process(); // Process AST to get result symbol. std::string result = ""; // Default result to an empty string. if (result_ptr) { @@ -486,9 +551,9 @@ namespace emplode { Emplode & Write(std::ostream & os=std::cout) { - root_scope.WriteContents(os); + symbol_table.GetRootScope().WriteContents(os); os << '\n'; - PrintEvents(os); + symbol_table.PrintEvents(os); return *this; } @@ -751,21 +816,7 @@ namespace emplode { // Otherwise we have an object of a custom type to add. Debug("Building var '", var_name, "' of type '", type_name, "'"); - // Retrieve the information about the requested type. - TypeInfo & type_info = *type_map[type_name]; - const std::string & type_desc = type_map[type_name]->GetDesc(); - const bool is_config_owned = type_info.GetOwned(); - - // Use the TypeInfo associated with the provided type name to build an instance. - emp::Ptr new_obj = type_info.MakeObj(var_name); - - // Setup a scope for this new type, linking the object to it. - Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); - - // Let the new object know about its scope. - new_obj->Setup(new_obj_symbol, type_info); - - return new_obj_symbol; + return symbol_table.MakeObjSymbol(type_name, var_name, scope); } // Parse an event description. From c9eb0c53bc60a0b2637f1d91650f146345fe4102 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:23:44 -0400 Subject: [PATCH 280/445] Updated MABE to account for the symbol table object. --- source/core/MABE.hpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index a64a698a..209c7e15 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -372,7 +372,7 @@ namespace mabe { EmplodeScope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } /// Return to the root scope. - EmplodeScope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } + EmplodeScope & ResetScope() { return *(cur_scope = &(config.GetSymbolTable().GetRootScope())); } /// Setup the configuration options for MABE, including for each module. void SetupConfig(); @@ -595,13 +595,10 @@ namespace mabe { [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) , trait_man(error_man) , args(emp::cl::args_to_strings(argc, argv)) - , cur_scope(&(config.GetRootScope())) + , cur_scope(&(config.GetSymbolTable().GetRootScope())) { // Setup "Population" as a type in the config file. - auto pop_init_fun = - [this](const std::string & name) -> emp::Ptr { - return &AddPopulation(name); - }; + auto pop_init_fun = [this](const std::string & name) { return &AddPopulation(name); }; auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun); // 'INJECT' allows a user to add an organism to a population. @@ -614,6 +611,10 @@ namespace mabe { "Inject organisms into population (args: org_name, org_count)."); + // Setup "Collection" as another config type. + auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); + + // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { @@ -642,7 +643,7 @@ namespace mabe { // 'WRITE' will collect data and write it to a file. - auto & files = config.GetFileManager(); + auto & files = config.GetSymbolTable().GetFileManager(); std::function write_fun = [this,&files](const std::string & filename, const std::string & collection, std::string format) { const bool file_exists = files.Has(filename); // Is file is already setup? @@ -1055,9 +1056,9 @@ namespace mabe { void MABE::SetupConfig() { emp_assert(cur_scope); - emp_assert(cur_scope.Raw() == &(config.GetRootScope()), + emp_assert(cur_scope.Raw() == &(config.GetSymbolTable().GetRootScope()), cur_scope->GetName(), - config.GetRootScope().GetName()); // Scope should start at root level. + config.GetSymbolTable().GetRootScope().GetName()); // Scope should start at root level. // Setup main MABE variables. cur_scope->LinkFuns("random_seed", From 2e16c7d0bb4e8e5cbd98ae49e054a2fe7895ca81 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:46:25 -0400 Subject: [PATCH 281/445] Added an OK() member function to modules for debugging. --- source/core/Module.hpp | 2 ++ source/core/ModuleBase.hpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index c7afad35..f4dc1901 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -408,6 +408,8 @@ namespace mabe { bool DoPlaceBirth_IsTriggered() override { return control.DoPlaceBirth_IsTriggered(this); }; bool DoPlaceInject_IsTriggered() override { return control.DoPlaceInject_IsTriggered(this); }; bool DoFindNeighbor_IsTriggered() override { return control.DoFindNeighbor_IsTriggered(this); }; + + bool OK() const override { return true; } }; /// Build a class that will automatically register modules when created (globally) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index d26af41f..925bad1e 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -297,6 +297,8 @@ namespace mabe { virtual bool DoPlaceInject_IsTriggered() = 0; virtual bool DoFindNeighbor_IsTriggered() = 0; + virtual bool OK() const = 0; // For debugging purposes only. + // ---=== Specialty Functions for Organism Managers ===--- virtual emp::TypeID GetObjType() const { emp_assert(false, "GetObjType() must be overridden for ManagerModule."); From 36531f1d7237b42df30e8e9b7df99ddc993ffe49 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 12:47:28 -0400 Subject: [PATCH 282/445] General cleanup of unneeded code form MABE.hpp; added OK() check on Modules. --- source/core/MABE.hpp | 46 +++++++------------------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 209c7e15..6db963dd 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -53,10 +53,6 @@ namespace mabe { /// Note that this class is derived from MABEBase, which handles all population /// manipulation and signal management. - using emplode::Emplode; - using emplode::EmplodeType; - using EmplodeScope = emplode::Symbol_Scope; - class MABE : public MABEBase { private: const std::string VERSION = "0.0.1"; @@ -73,7 +69,7 @@ namespace mabe { using trait_summary_t = std::function; using symbol_ptr_t = emp::Ptr; - // Setup a cache for functions used to collect data for files. + // Setup a cache for functions used to collect data for files. @CAO: Move to module! std::unordered_map> file_fun_cache; /// Populations used; generated in the configuration file. @@ -116,8 +112,7 @@ namespace mabe { emp::vector config_filenames; ///< Names of configuration files to load. emp::vector config_settings; ///< Additional config commands to run. std::string gen_filename; ///< Name of output file to generate. - Emplode config; ///< Configuration information for this run. - emp::Ptr cur_scope; ///< Which config scope are we currently using? + emplode::Emplode config; ///< Configuration information for this run. // ----------- Helper Functions ----------- @@ -359,21 +354,6 @@ namespace mabe { // --- Manage configuration scope --- - /// Access to the current configuration scope. - EmplodeScope & GetCurScope() { return *cur_scope; } - - /// Add a new scope under the current one. - EmplodeScope & AddScope(const std::string & name, const std::string & desc) { - cur_scope = &(cur_scope->AddScope(name, desc)); - return *cur_scope; - } - - /// Move up one level of scope. - EmplodeScope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } - - /// Return to the root scope. - EmplodeScope & ResetScope() { return *(cur_scope = &(config.GetSymbolTable().GetRootScope())); } - /// Setup the configuration options for MABE, including for each module. void SetupConfig(); @@ -595,7 +575,6 @@ namespace mabe { [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) , trait_man(error_man) , args(emp::cl::args_to_strings(argc, argv)) - , cur_scope(&(config.GetSymbolTable().GetRootScope())) { // Setup "Population" as a type in the config file. auto pop_init_fun = [this](const std::string & name) { return &AddPopulation(name); }; @@ -617,7 +596,7 @@ namespace mabe { // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { - auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { + auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { return mod.init_fun(*this,name); }; config.AddType(mod.name, mod.desc, mod_init_fun, mod.type_id); @@ -916,7 +895,6 @@ namespace mabe { /// Return a ramdom position from a desginated population with a living organism in it. OrgPosition MABE::GetRandomOrgPos(Population & pop) { emp_assert(pop.GetNumOrgs() > 0, "GetRandomOrgPos cannot be called if there are no orgs."); - // @CAO: Something better to do in a sparse population? OrgPosition pos = GetRandomPos(pop); while (pos.IsEmpty()) pos = GetRandomPos(pop); return pos; @@ -1055,13 +1033,9 @@ namespace mabe { } void MABE::SetupConfig() { - emp_assert(cur_scope); - emp_assert(cur_scope.Raw() == &(config.GetSymbolTable().GetRootScope()), - cur_scope->GetName(), - config.GetSymbolTable().GetRootScope().GetName()); // Scope should start at root level. - // Setup main MABE variables. - cur_scope->LinkFuns("random_seed", + auto & root_scope = config.GetSymbolTable().GetRootScope(); + root_scope.LinkFuns("random_seed", [this](){ return random.GetSeed(); }, [this](int seed){ random.ResetSeed(seed); }, "Seed for random number generator; use 0 to base on time."); @@ -1070,14 +1044,8 @@ namespace mabe { bool MABE::OK() { bool result = true; - - // Make sure the populations are all OK. - for (size_t pop_id = 0; pop_id < pops.size(); pop_id++) { - result &= pops[pop_id]->OK(); - } - - // @CAO: Should check to make sure modules are okay too. - + for (auto mod_ptr : modules) result &= mod_ptr->OK(); // Ensure modules are okay. + for (auto pop_ptr : pops) result &= pop_ptr->OK(); // Ensure populations are okay. return result; } From ff316dc6f19f1df12573e2c612eb13efcaf609ba Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 14:03:45 -0400 Subject: [PATCH 283/445] Added a (currently unused) ParseState class --- source/Emplode/Emplode.hpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index e5b8a116..0b8573e3 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -230,6 +230,35 @@ namespace emplode { }; + class ParseState { + private: + emp::TokenStream::Iterator pos; + emp::Ptr symbol_table; + emp::Ptr scope; + emp::Ptr lexer; + + public: + ParseState(emp::TokenStream::Iterator _pos, SymbolTable & _table, + Symbol_Scope & _scope, Lexer & _lexer) + : pos(_pos), symbol_table(&_table), scope(&_scope), lexer(&_lexer) {} + ParseState(ParseState &) = default; + ~ParseState() { } + + ParseState & operator=(const ParseState &) = default; + + bool IsID() const { return pos && lexer->IsID(*pos); } + bool IsNumber() const { return pos && lexer->IsNumber(*pos); } + bool IsChar() const { return pos && lexer->IsChar(*pos); } + bool IsString() const { return pos && lexer->IsString(*pos); } + bool IsDots() const { return pos && lexer->IsDots(*pos); } + + bool IsEvent() const { return symbol_table->HasEvent(AsLexeme()); } + bool IsType() const { return symbol_table->HasType(AsLexeme()); } + + char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } + const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } + }; + class Emplode { public: From 2ea6b27a8d414f5e335537ded359c3708a3d4e81 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 23:24:52 -0400 Subject: [PATCH 284/445] Moved MakeTempLeaf() into AST.hpp --- source/Emplode/AST.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 9904d95f..cc5c0aaf 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -121,6 +121,19 @@ namespace emplode { } }; + // Helper functions for making temporary leaves. + emp::Ptr MakeTempLeaf(double val) { + auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); + out_ptr->SetTemporary(); + return emp::NewPtr(out_ptr); + } + + emp::Ptr MakeTempLeaf(const std::string & val) { + auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); + out_ptr->SetTemporary(); + return emp::NewPtr(out_ptr); + } + class ASTNode_Block : public ASTNode_Internal { protected: emp::Ptr scope_ptr; @@ -224,6 +237,7 @@ namespace emplode { emp_assert(children.size() == 2); symbol_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. symbol_ptr_t rhs = children[1]->Process(); // Determine the right-hand-side value. + // @CAO Should make sure that lhs is properly assignable. lhs->CopyValue(*rhs); if (rhs->IsTemporary()) rhs.Delete(); From 1e84ab910bc08139dafd38eda8ce90c5027be658 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 2 Nov 2021 23:26:03 -0400 Subject: [PATCH 285/445] Fleshed out ParseState class -- now used in all parsing --- source/Emplode/Emplode.hpp | 434 +++++++++++++++++++++---------------- 1 file changed, 245 insertions(+), 189 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 0b8573e3..7262c37e 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -96,7 +96,7 @@ namespace emplode { public: SymbolTable(const std::string & name) - : root_scope(name, "Outer-most, global scope.", nullptr) { + : root_scope(name, "Global scope", nullptr) { // Initialize the type map. type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); @@ -230,22 +230,59 @@ namespace emplode { }; + + + class ParseState { private: emp::TokenStream::Iterator pos; emp::Ptr symbol_table; - emp::Ptr scope; + emp::vector< emp::Ptr > scope_stack; emp::Ptr lexer; public: ParseState(emp::TokenStream::Iterator _pos, SymbolTable & _table, Symbol_Scope & _scope, Lexer & _lexer) - : pos(_pos), symbol_table(&_table), scope(&_scope), lexer(&_lexer) {} + : pos(_pos), symbol_table(&_table), lexer(&_lexer) { scope_stack.push_back(&_scope); } ParseState(ParseState &) = default; ~ParseState() { } ParseState & operator=(const ParseState &) = default; + bool operator==(const ParseState & in) const { return pos == in.pos; } + bool operator!=(const ParseState & in) const { return pos != in.pos; } + bool operator< (const ParseState & in) const { return pos < in.pos; } + bool operator<=(const ParseState & in) const { return pos <= in.pos; } + bool operator> (const ParseState & in) const { return pos > in.pos; } + bool operator>=(const ParseState & in) const { return pos >= in.pos; } + + ParseState & operator++() { ++pos; return *this; } + ParseState operator++(int) { ParseState old(*this); ++pos; return old; } + ParseState & operator--() { --pos; return *this; } + ParseState operator--(int) { ParseState old(*this); --pos; return old; } + + bool IsValid() const { return pos.IsValid(); } + bool AtEnd() const { return pos.AtEnd(); } + + size_t GetIndex() const { return pos.GetIndex(); } ///< Return index in token stream. + size_t GetTokenSize() const { return pos.IsValid() ? pos->lexeme.size() : 0; } + Symbol_Scope & GetScope() { + emp_assert(scope_stack.size() && scope_stack.back() != nullptr); + return *scope_stack.back(); + } + const Symbol_Scope & GetScope() const { + emp_assert(scope_stack.size() && scope_stack.back() != nullptr); + return *scope_stack.back(); + } + const std::string & GetScopeName() const { return GetScope().GetName(); } + + std::string AsString() { + return emp::to_string("[pos=", pos.GetIndex(), + ",lex='", AsLexeme(), + "',scope='", GetScope().GetName(), + "']"); + } + bool IsID() const { return pos && lexer->IsID(*pos); } bool IsNumber() const { return pos && lexer->IsNumber(*pos); } bool IsChar() const { return pos && lexer->IsChar(*pos); } @@ -257,6 +294,70 @@ namespace emplode { char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } + const std::string & UseLexeme() { + const std::string & out = AsLexeme(); + pos++; + return out; + } + + template + void Error(Ts &&... args) const { + std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); + std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " + << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; + exit(1); + } + + template + void Require(bool test, Ts &&... args) const { + if (!test) Error(std::forward(args)...); + } + template + void RequireID(Ts &&... args) const { + if (!IsID()) Error(std::forward(args)...); + } + template + void RequireNumber(Ts &&... args) const { + if (!IsNumber()) Error(std::forward(args)...); + } + template + void RequireString(Ts &&... args) const { + if (!IsString()) Error(std::forward(args)...); + } + template + void RequireChar(char req_char, Ts &&... args) const { + if (AsChar() != req_char) Error(std::forward(args)...); + } + template + void RequireLexeme(const std::string & lex, Ts &&... args) const { + if (AsLexeme() != lex) Error(std::forward(args)...); + } + + void PushScope(Symbol_Scope & _scope) { scope_stack.push_back(&_scope); } + void PopScope() { scope_stack.pop_back(); } + + Symbol & LookupSymbol(const std::string & var_name, bool scan_scopes) { + emp::Ptr out_symbol = GetScope().LookupSymbol(var_name, scan_scopes); + // If we can't find this identifier, throw an error. + if (out_symbol.IsNull()) { + Error("'", var_name, "' does not exist as a parameter, variable, or type.", + " Current scope is '", GetScope().GetName(), "'"); + } + return *out_symbol; + } + + Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { + return GetScope().AddStringVar(name, desc); + } + Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { + return GetScope().AddValueVar(name, desc); + } + Symbol_Scope & AddScope(const std::string & name, const std::string & desc) { + return GetScope().AddScope(name, desc); + } + Symbol_Object & AddObject(const std::string & type_name, const std::string & var_name) { + return symbol_table->MakeObjSymbol(type_name, var_name, GetScope()); + } }; @@ -269,27 +370,8 @@ namespace emplode { Lexer lexer; ///< Lexer to process input code. SymbolTable symbol_table; ///< Management of identifiers. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = false; ///< Should we print full debug information? - - /// A list of precedence levels for symbols. - std::unordered_map precedence_map; - - // -- Helper functions -- - bool IsID(pos_t pos) const { return pos.IsValid() && lexer.IsID(*pos); } - bool IsNumber(pos_t pos) const { return pos.IsValid() && lexer.IsNumber(*pos); } - bool IsChar(pos_t pos) const { return pos.IsValid() && lexer.IsChar(*pos); } - bool IsString(pos_t pos) const { return pos.IsValid() && lexer.IsString(*pos); } - bool IsDots(pos_t pos) const { return pos.IsValid() && lexer.IsDots(*pos); } - - bool IsType(pos_t pos) const { return pos.IsValid() && symbol_table.HasType(pos->lexeme); } - - char AsChar(pos_t pos) const { - return (pos.IsValid() && lexer.IsSymbol(*pos)) ? pos->lexeme[0] : 0; - } - const std::string & AsLexeme(pos_t pos) const { - return pos.IsValid() ? pos->lexeme : emp::empty_string(); - } - size_t GetSize(pos_t pos) const { return pos.IsValid() ? pos->lexeme.size() : 0; } + bool debug = true; ///< Should we print full debug information? + std::unordered_map precedence_map; ///< Precedence levels for symbols. std::string ConcatLexemes(pos_t start_pos, pos_t end_pos) const { emp_assert(start_pos <= end_pos); @@ -304,54 +386,21 @@ namespace emplode { return ss.str(); } - template - void Error(pos_t pos, Ts... args) const { - std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); - std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " - << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; - exit(1); - } - template void Debug(Ts... args) const { if (debug) std::cout << "DEBUG: " << emp::to_string(std::forward(args)...) << std::endl; } - template - void Require(bool result, pos_t pos, Ts... args) const { - if (!result) { Error(pos, std::forward(args)...); } - } - template - void RequireID(pos_t pos, Ts... args) const { - if (!IsID(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireNumber(pos_t pos, Ts... args) const { - if (!IsNumber(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireString(pos_t pos, Ts... args) const { - if (!IsString(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireChar(char req_char, pos_t pos, Ts... args) const { - if (AsChar(pos) != req_char) { Error(pos, std::forward(args)...); } - } - template - void RequireLexeme(const std::string & req_str, pos_t pos, Ts... args) const { - if (AsLexeme(pos) != req_str) { Error(pos, std::forward(args)...); } - } /// Load a variable name from the provided scope. /// If create_ok is true, create any variables that we don't find. Otherwise continue the /// search for them in successively outer (lower) scopes. - [[nodiscard]] emp::Ptr ParseVar(pos_t & pos, - Symbol_Scope & cur_scope, + [[nodiscard]] emp::Ptr ParseVar(ParseState & state, bool create_ok=false, bool scan_scopes=true); /// Load a value from the provided scope, which can come from a variable or a literal. - [[nodiscard]] emp::Ptr ParseValue(pos_t & pos, Symbol_Scope & cur_scope); + [[nodiscard]] emp::Ptr ParseValue(ParseState & state); /// Calculate the result of the provided operation on two computed entries. [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, @@ -359,26 +408,25 @@ namespace emplode { emp::Ptr value2); /// Calculate a full expression found in a token sequence, using the provided scope. - [[nodiscard]] emp::Ptr - ParseExpression(pos_t & pos, Symbol_Scope & cur_scope, size_t prec_limit=1000); + [[nodiscard]] emp::Ptr ParseExpression(ParseState & state, size_t prec_limit=1000); /// Parse the declaration of a variable and return the newly created Symbol - Symbol & ParseDeclaration(pos_t & pos, Symbol_Scope & scope); + Symbol & ParseDeclaration(ParseState & state); /// Parse an event description. - emp::Ptr ParseEvent(pos_t & pos, Symbol_Scope & scope); + emp::Ptr ParseEvent(ParseState & state); /// Parse the next input in the specified Struct. A statement can be a variable declaration, /// an expression, or an event. - [[nodiscard]] emp::Ptr ParseStatement(pos_t & pos, Symbol_Scope & scope); + [[nodiscard]] emp::Ptr ParseStatement(ParseState & state); /// Keep parsing statements until there aren't any more or we leave this scope. - [[nodiscard]] emp::Ptr ParseStatementList(pos_t & pos, Symbol_Scope & scope) { - Debug("Running ParseStatementList(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); - auto cur_block = emp::NewPtr(scope); - while (pos.IsValid() && AsChar(pos) != '}') { + [[nodiscard]] emp::Ptr ParseStatementList(ParseState & state) { + Debug("Running ParseStatementList(", state.AsString(), ")"); + auto cur_block = emp::NewPtr(state.GetScope()); + while (state.IsValid() && state.AsChar() != '}') { // Parse each statement in the file. - emp::Ptr statement_node = ParseStatement(pos, scope); + emp::Ptr statement_node = ParseStatement(state); // If the current statement is real, add it to the current block. if (!statement_node.IsNull()) cur_block->AddChild( statement_node ); @@ -532,7 +580,8 @@ namespace emplode { pos_t pos = tokens.begin(); // Start at the beginning of the file. // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, symbol_table.GetRootScope()); + ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; + auto cur_block = ParseStatementList(state); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -553,7 +602,8 @@ namespace emplode { pos_t pos = tokens.begin(); // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, symbol_table.GetRootScope()); + ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; + auto cur_block = ParseStatementList(state); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -567,7 +617,8 @@ namespace emplode { auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. - auto cur_block = ParseStatement(pos, symbol_table.GetRootScope()); // Convert tokens to AST + ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; + auto cur_block = ParseStatement(state); // Convert tokens to AST auto result_ptr = cur_block->Process(); // Process AST to get result symbol. std::string result = ""; // Default result to an empty string. if (result_ptr) { @@ -601,97 +652,92 @@ namespace emplode { // Load a variable name from the provided scope. - emp::Ptr Emplode::ParseVar(pos_t & pos, - Symbol_Scope & cur_scope, - bool create_ok, bool scan_scopes) + emp::Ptr Emplode::ParseVar(ParseState & state, bool create_ok, bool scan_scopes) { - Debug("Running ParseVar(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); + Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, ")"); // First, check for leading dots. - if (IsDots(pos)) { + if (state.IsDots()) { + Debug("...found dots: ", state.AsLexeme()); scan_scopes = false; // One or more initial dots specify scope; don't scan! - size_t num_dots = GetSize(pos); // Extra dots shift scope. - emp::Ptr scope_ptr = &cur_scope; + size_t num_dots = state.GetTokenSize(); // Extra dots shift scope. + emp::Ptr cur_scope = &state.GetScope(); while (num_dots-- > 1) { - scope_ptr = scope_ptr->GetScope(); - if (scope_ptr.IsNull()) Error(pos, "Too many dots; goes beyond global scope."); + cur_scope = cur_scope->GetScope(); + if (cur_scope.IsNull()) state.Error("Too many dots; goes beyond global scope."); } - ++pos; + ++state; // Recursively call in the found scope if needed; given leading dot, do not scan scopes. - if (scope_ptr.Raw() != &cur_scope) return ParseVar(pos, *scope_ptr, create_ok, false); + if (cur_scope.Raw() != &state.GetScope()) { + state.PushScope(*cur_scope); + auto result = ParseVar(state, create_ok, false); + state.PopScope(); + return result; + } } // Next, we must have a variable name. // @CAO: Or a : ? E.g., technically "..:size" could give you the parent scope size. - RequireID(pos, "Must provide a variable identifier!"); - std::string var_name = AsLexeme(pos++); + state.RequireID("Must provide a variable identifier!"); + std::string var_name = state.UseLexeme(); // Lookup this variable. - emp::Ptr cur_symbol = cur_scope.LookupSymbol(var_name, scan_scopes); - - // If we can't find this variable, throw an error. - if (cur_symbol.IsNull()) { - Error(pos, "'", var_name, "' does not exist as a parameter, variable, or type.", - " Current scope is '", cur_scope.GetName(), "'"); - } + Debug("...looking up symbol '", var_name, + "' starting at scope '", state.GetScopeName(), + "'; scanning=", scan_scopes); + Symbol & cur_symbol = state.LookupSymbol(var_name, scan_scopes); // If this variable just provided a scope, keep going. - if (IsDots(pos)) return ParseVar(pos, cur_symbol->AsScope(), create_ok, false); + if (state.IsDots()) { + state.PushScope(cur_symbol.AsScope()); + auto result = ParseVar(state, create_ok, false); + state.PopScope(); + return result; + } // Otherwise return the variable as a leaf! - return emp::NewPtr(cur_symbol); - } - - emp::Ptr MakeTempLeaf(double val) { - auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); - out_ptr->SetTemporary(); - return emp::NewPtr(out_ptr); - } - - emp::Ptr MakeTempLeaf(const std::string & val) { - auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); - out_ptr->SetTemporary(); - return emp::NewPtr(out_ptr); + return emp::NewPtr(&cur_symbol); } // Load a value from the provided scope, which can come from a variable or a literal. - emp::Ptr Emplode::ParseValue(pos_t & pos, Symbol_Scope & cur_scope) { - Debug("Running ParseValue(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ")"); + emp::Ptr Emplode::ParseValue(ParseState & state) { + Debug("Running ParseValue(", state.AsString(), ")"); // Anything that begins with an identifier or dots must represent a variable. Refer! - if (IsID(pos) || IsDots(pos)) return ParseVar(pos, cur_scope, false, true); + if (state.IsID() || state.IsDots()) return ParseVar(state, false, true); // A literal number should have a temporary created with its value. - if (IsNumber(pos)) { - Debug("...value is a number: ", AsLexeme(pos)); - double value = emp::from_string(AsLexeme(pos++)); // Calculate value. - return MakeTempLeaf(value); // Return temporary Symbol. + if (state.IsNumber()) { + Debug("...value is a number: ", state.AsLexeme()); + double value = emp::from_string(state.UseLexeme()); // Calculate value. + return MakeTempLeaf(value); // Return temporary Symbol. } // A literal char should be converted to its ASCII value. - if (IsChar(pos)) { - Debug("...value is a char: ", AsLexeme(pos)); - char lit_char = emp::from_literal_char(AsLexeme(pos++)); // Convert the literal char. - return MakeTempLeaf((double) lit_char); // Return temporary Symbol. + if (state.IsChar()) { + Debug("...value is a char: ", state.AsLexeme()); + char lit_char = emp::from_literal_char(state.UseLexeme()); // Convert the literal char. + return MakeTempLeaf((double) lit_char); // Return temporary Symbol. } // A literal string should be converted to a regular string and used. - if (IsString(pos)) { - Debug("...value is a string: ", AsLexeme(pos)); - std::string str = emp::from_literal_string(AsLexeme(pos++)); // Convert the literal string. - return MakeTempLeaf(str); // Return temporary Symbol. + if (state.IsString()) { + Debug("...value is a string: ", state.AsLexeme()); + std::string str = emp::from_literal_string(state.UseLexeme()); // Convert the literal string. + return MakeTempLeaf(str); // Return temporary Symbol. } // If we have an open parenthesis, process everything inside into a single value... - if (AsChar(pos) == '(') { - pos++; - emp::Ptr out_ast = ParseExpression(pos, cur_scope); - RequireChar(')', pos++, "Expected a close parenthesis in expression."); + if (state.AsChar() == '(') { + ++state; + emp::Ptr out_ast = ParseExpression(state); + state.RequireChar(')', "Expected a close parenthesis in expression."); + ++state; return out_ast; } - Error(pos, "Expected a value, found: ", AsLexeme(pos)); + state.Error("Expected a value, found: ", state.AsLexeme()); return nullptr; } @@ -781,31 +827,31 @@ namespace emplode { // Calculate an expression in the provided scope. - emp::Ptr Emplode::ParseExpression( - pos_t & pos, - Symbol_Scope & scope, - size_t prec_limit - ) { - Debug("Running ParseExpression(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); + emp::Ptr Emplode::ParseExpression(ParseState & state, size_t prec_limit) { + Debug("Running ParseExpression(", state.AsString(), ", limit=", prec_limit, ")"); // @CAO Should test for unary operators at the beginning of an expression. /// Process a value (and possibly more!) - emp::Ptr cur_node = ParseValue(pos, scope); - std::string symbol = AsLexeme(pos); + emp::Ptr cur_node = ParseValue(state); + std::string symbol = state.AsLexeme(); + + Debug("...back in ParseExpression; symbol=", symbol, "; state=", state.AsString()); + while ( emp::Has(precedence_map, symbol) && precedence_map[symbol] < prec_limit ) { - ++pos; + ++state; // Do we have a function call? if (symbol == "(") { // Collect arguments. emp::vector< emp::Ptr > args; - while (AsChar(pos) != ')') { - emp::Ptr next_arg = ParseExpression(pos, scope); - args.push_back(next_arg); // Save this argument. - if (AsChar(pos) != ',') break; // If we don't have a comma, no more args! - ++pos; // Move on to the next argument. + while (state.AsChar() != ')') { + emp::Ptr next_arg = ParseExpression(state); + args.push_back(next_arg); // Save this argument. + if (state.AsChar() != ',') break; // If we don't have a comma, no more args! + ++state; // Move on to the next argument. } - RequireChar(')', pos++, "Expected a ')' to end function call."); + state.RequireChar(')', "Expected a ')' to end function call."); + ++state; // cur_node should have evaluated itself to a function; a Call node will link that // function with its arguments, run it, and return the result. @@ -814,12 +860,12 @@ namespace emplode { // Otherwise we must have a binary math operation. else { - emp::Ptr node2 = ParseExpression(pos, scope, precedence_map[symbol]); + emp::Ptr node2 = ParseExpression(state, precedence_map[symbol]); cur_node = ProcessOperation(symbol, cur_node, node2); } // Move the current value over to cur_node and check if we have a new symbol... - symbol = AsLexeme(pos); + symbol = state.AsLexeme(); } emp_assert(!cur_node.IsNull()); @@ -827,113 +873,123 @@ namespace emplode { } // Parse an the declaration of a variable. - Symbol & Emplode::ParseDeclaration(pos_t & pos, Symbol_Scope & scope) { - std::string type_name = AsLexeme(pos++); - RequireID(pos, "Type name '", type_name, "' must be followed by variable to declare."); - std::string var_name = AsLexeme(pos++); + Symbol & Emplode::ParseDeclaration(ParseState & state) { + std::string type_name = state.UseLexeme(); + state.RequireID("Type name '", type_name, "' must be followed by variable to declare."); + std::string var_name = state.UseLexeme(); if (type_name == "String") { - return scope.AddStringVar(var_name, "Local string variable."); + return state.AddStringVar(var_name, "Local string variable."); } else if (type_name == "Value") { - return scope.AddValueVar(var_name, "Local value variable."); + return state.AddValueVar(var_name, "Local value variable."); } else if (type_name == "Struct") { - return scope.AddScope(var_name, "Local struct"); + return state.AddScope(var_name, "Local struct"); } // Otherwise we have an object of a custom type to add. - Debug("Building var '", var_name, "' of type '", type_name, "'"); - - return symbol_table.MakeObjSymbol(type_name, var_name, scope); + Debug("Building object '", var_name, "' of type '", type_name, "'"); + return state.AddObject(type_name, var_name); } // Parse an event description. - emp::Ptr Emplode::ParseEvent(pos_t & pos, Symbol_Scope & scope) { - RequireChar('@', pos++, "All event declarations must being with an '@'."); - RequireID(pos, "Events must start by specifying event name."); - const std::string & event_name = AsLexeme(pos++); - RequireChar('(', pos++, "Expected parentheses after '", event_name, "' for args."); + emp::Ptr Emplode::ParseEvent(ParseState & state) { + state.RequireChar('@', "All event declarations must being with an '@'."); + ++state; + state.RequireID("Events must start by specifying event name."); + const std::string & event_name = state.UseLexeme(); + state.RequireChar('(', "Expected parentheses after '", event_name, "' for args."); + ++state; emp::vector> args; - while (AsChar(pos) != ')') { - args.push_back( ParseExpression(pos, scope) ); - if (AsChar(pos) == ',') pos++; + while (state.AsChar() != ')') { + args.push_back( ParseExpression(state) ); + if (state.AsChar() == ',') ++state; } - RequireChar(')', pos++, "Event args must end in a ')'"); + state.RequireChar(')', "Event args must end in a ')'"); + ++state; - emp::Ptr action = ParseStatement(pos, scope); + emp::Ptr action = ParseStatement(state); Debug("Building event '", event_name, "' with args ", args); auto setup_event = [this, event_name](emp::Ptr action, - const emp::vector> & args) { - AddEvent(event_name, action, - (args.size() > 0) ? args[0]->AsDouble() : 0.0, - (args.size() > 1) ? args[1]->AsDouble() : 0.0, - (args.size() > 2) ? args[2]->AsDouble() : -1.0); + const emp::vector> & args) { + AddEvent( + event_name, action, + (args.size() > 0) ? args[0]->AsDouble() : 0.0, + (args.size() > 1) ? args[1]->AsDouble() : 0.0, + (args.size() > 2) ? args[2]->AsDouble() : -1.0 + ); }; return emp::NewPtr(event_name, action, args, setup_event); } // Process the next input in the specified Struct. - emp::Ptr Emplode::ParseStatement(pos_t & pos, Symbol_Scope & scope) { - Debug("Running ParseStatement(", pos.GetIndex(), ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); + emp::Ptr Emplode::ParseStatement(ParseState & state) { + Debug("Running ParseStatement(", state.AsString(), ")"); // Allow a statement with an empty line. - if (AsChar(pos) == ';') { pos++; return nullptr; } + if (state.AsChar() == ';') { ++state; return nullptr; } // Allow a statement to be a new scope. - if (AsChar(pos) == '{') { - pos++; + if (state.AsChar() == '{') { + state++; // @CAO Need to add an anonymous scope (that gets written properly) - emp::Ptr out_node = ParseStatementList(pos, scope); - RequireChar('}', pos++, "Expected '}' to close scope."); + emp::Ptr out_node = ParseStatementList(state); + state.RequireChar('}', "Expected '}' to close scope."); + ++state; return out_node; } // Allow event definitions if a statement begins with an '@' - if (AsChar(pos) == '@') return ParseEvent(pos, scope); + if (state.AsChar() == '@') return ParseEvent(state); // Allow this statement to be a declaration if it begins with a type. - if (IsType(pos)) { - Symbol & new_symbol = ParseDeclaration(pos, scope); + if (state.IsType()) { + Symbol & new_symbol = ParseDeclaration(state); // If the next symbol is a ';' this is a declaration without an assignment. - if (AsChar(pos) == ';') { - pos++; // Skip the semi-colon. + if (state.AsChar() == ';') { + state++; // Skip the semi-colon. return nullptr; // We are done! } // If this symbol is a new scope, it can be populated now either directly (with in braces) // or indirectly (with and assignment) if (new_symbol.IsScope()) { - if (AsChar(pos) == '{') { - pos++; - emp::Ptr out_node = ParseStatementList(pos, new_symbol.AsScope()); - RequireChar('}', pos++, "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); + if (state.AsChar() == '{') { + ++state; + state.PushScope(new_symbol.AsScope()); + emp::Ptr out_node = ParseStatementList(state); + state.PopScope(); + state.RequireChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); + ++state; return out_node; } - RequireChar('=', pos, "Expected scope '", new_symbol.GetName(), - "' definition to start with a '{' or '='; found ''", AsLexeme(pos), "'."); + state.RequireChar('=', "Expected scope '", new_symbol.GetName(), + "' definition to start with a '{' or '='; found ''", state.AsLexeme(), "'."); } // Otherwise rewind so that the new variable can be used to start an expression. - --pos; + --state; } // If we made it here, remainder should be an expression. - emp::Ptr out_node = ParseExpression(pos, scope); + emp::Ptr out_node = ParseExpression(state); // Expressions must end in a semi-colon. - RequireChar(';', pos++, "Expected ';' at the end of a statement."); + state.RequireChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); + ++state; return out_node; } } + #endif From a142f7488dc4b04bf4aafc83defc411f6dfd7898 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 09:45:57 -0400 Subject: [PATCH 286/445] Commenting, streamlining, and minor simplifications in parsing. --- source/Emplode/Emplode.hpp | 74 +++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 7262c37e..7f4df84e 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -292,14 +292,27 @@ namespace emplode { bool IsEvent() const { return symbol_table->HasEvent(AsLexeme()); } bool IsType() const { return symbol_table->HasType(AsLexeme()); } + /// Convert the current state to a character; use \0 if cur token is not a symbol. char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } + + /// Return the lexeme associate with the current state. const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } + + /// Return the lexeme associate with the current state AND advance the token stream. const std::string & UseLexeme() { const std::string & out = AsLexeme(); pos++; return out; } + /// Return whether the current token is the specified char; if so also advance token stream. + bool UseIfChar(char test_char) { + if (AsChar() != test_char) return false; + ++pos; + return true; + } + + /// Report an error in parsing this file and exit. template void Error(Ts &&... args) const { std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); @@ -333,6 +346,12 @@ namespace emplode { if (AsLexeme() != lex) Error(std::forward(args)...); } + template + void UseRequiredChar(char req_char, Ts &&... args) { + if (AsChar() != req_char) Error(std::forward(args)...); + ++pos; + } + void PushScope(Symbol_Scope & _scope) { scope_stack.push_back(&_scope); } void PopScope() { scope_stack.pop_back(); } @@ -729,11 +748,9 @@ namespace emplode { } // If we have an open parenthesis, process everything inside into a single value... - if (state.AsChar() == '(') { - ++state; + if (state.UseIfChar('(')) { emp::Ptr out_ast = ParseExpression(state); - state.RequireChar(')', "Expected a close parenthesis in expression."); - ++state; + state.UseRequiredChar(')', "Expected a close parenthesis in expression."); return out_ast; } @@ -834,14 +851,14 @@ namespace emplode { /// Process a value (and possibly more!) emp::Ptr cur_node = ParseValue(state); - std::string symbol = state.AsLexeme(); + std::string op = state.AsLexeme(); - Debug("...back in ParseExpression; symbol=", symbol, "; state=", state.AsString()); + Debug("...back in ParseExpression; op=`", op, "`; state=", state.AsString()); - while ( emp::Has(precedence_map, symbol) && precedence_map[symbol] < prec_limit ) { - ++state; + while ( emp::Has(precedence_map, op) && precedence_map[op] < prec_limit ) { + ++state; // Move past the current operator // Do we have a function call? - if (symbol == "(") { + if (op == "(") { // Collect arguments. emp::vector< emp::Ptr > args; while (state.AsChar() != ')') { @@ -850,8 +867,7 @@ namespace emplode { if (state.AsChar() != ',') break; // If we don't have a comma, no more args! ++state; // Move on to the next argument. } - state.RequireChar(')', "Expected a ')' to end function call."); - ++state; + state.UseRequiredChar(')', "Expected a ')' to end function call."); // cur_node should have evaluated itself to a function; a Call node will link that // function with its arguments, run it, and return the result. @@ -860,12 +876,12 @@ namespace emplode { // Otherwise we must have a binary math operation. else { - emp::Ptr node2 = ParseExpression(state, precedence_map[symbol]); - cur_node = ProcessOperation(symbol, cur_node, node2); + emp::Ptr node2 = ParseExpression(state, precedence_map[op]); + cur_node = ProcessOperation(op, cur_node, node2); } - // Move the current value over to cur_node and check if we have a new symbol... - symbol = state.AsLexeme(); + // Move the current value over to cur_node and check if we have a new operator... + op = state.AsLexeme(); } emp_assert(!cur_node.IsNull()); @@ -895,20 +911,17 @@ namespace emplode { // Parse an event description. emp::Ptr Emplode::ParseEvent(ParseState & state) { - state.RequireChar('@', "All event declarations must being with an '@'."); - ++state; + state.UseRequiredChar('@', "All event declarations must being with an '@'."); state.RequireID("Events must start by specifying event name."); const std::string & event_name = state.UseLexeme(); - state.RequireChar('(', "Expected parentheses after '", event_name, "' for args."); - ++state; + state.UseRequiredChar('(', "Expected parentheses after '", event_name, "' for args."); emp::vector> args; while (state.AsChar() != ')') { args.push_back( ParseExpression(state) ); - if (state.AsChar() == ',') ++state; + state.UseIfChar(','); // Skip comma if next (does allow trailing comma) } - state.RequireChar(')', "Event args must end in a ')'"); - ++state; + state.UseRequiredChar(')', "Event args must end in a ')'"); emp::Ptr action = ParseStatement(state); @@ -932,15 +945,13 @@ namespace emplode { Debug("Running ParseStatement(", state.AsString(), ")"); // Allow a statement with an empty line. - if (state.AsChar() == ';') { ++state; return nullptr; } + if (state.UseIfChar(';')) { return nullptr; } // Allow a statement to be a new scope. - if (state.AsChar() == '{') { - state++; + if (state.UseIfChar('{')) { // @CAO Need to add an anonymous scope (that gets written properly) emp::Ptr out_node = ParseStatementList(state); - state.RequireChar('}', "Expected '}' to close scope."); - ++state; + state.UseRequiredChar('}', "Expected '}' to close scope."); return out_node; } @@ -960,13 +971,11 @@ namespace emplode { // If this symbol is a new scope, it can be populated now either directly (with in braces) // or indirectly (with and assignment) if (new_symbol.IsScope()) { - if (state.AsChar() == '{') { - ++state; + if (state.UseIfChar('{')) { state.PushScope(new_symbol.AsScope()); emp::Ptr out_node = ParseStatementList(state); state.PopScope(); - state.RequireChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); - ++state; + state.UseRequiredChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); return out_node; } @@ -984,8 +993,7 @@ namespace emplode { emp::Ptr out_node = ParseExpression(state); // Expressions must end in a semi-colon. - state.RequireChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); - ++state; + state.UseRequiredChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); return out_node; } From 14310fb410a661b17726b7883053890c996e1d0a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 10:17:28 -0400 Subject: [PATCH 287/445] Placed SymbolTable into its own file. --- source/Emplode/SymbolTable.hpp | 174 +++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 source/Emplode/SymbolTable.hpp diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp new file mode 100644 index 00000000..37997d2c --- /dev/null +++ b/source/Emplode/SymbolTable.hpp @@ -0,0 +1,174 @@ +/** + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file SymbolTable.hpp + * @brief Manages linking names to associated data in the Emplode language. + * @note Status: BETA + * + */ + +#include +#include +#include + +#include "emp/base/Ptr.hpp" +#include "emp/datastructs/map_utils.hpp" +#include "emp/io/StreamManager.hpp" +#include "emp/meta/TypeID.hpp" + +#include "Events.hpp" +#include "Symbol_Scope.hpp" + + +#ifndef EMPLODE_SYMBOL_TABLE_HPP +#define EMPLODE_SYMBOL_TABLE_HPP + +namespace emplode { + + class SymbolTable { + protected: + Symbol_Scope root_scope; ///< All variables from the root level. + std::map events_map; ///< A map of names to event groups. + std::unordered_map> type_map; ///< All types available in the script. + emp::StreamManager file_map; ///< Track all file streams by name. + + public: + SymbolTable(const std::string & name) + : root_scope(name, "Global scope", nullptr) { + // Initialize the type map. + type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); + + file_map.SetOutputDefaultFile(); // Stream manager should default to 'file' output. + } + + ~SymbolTable() { + // Clean up type information. + for (auto [name, ptr] : type_map) ptr.Delete(); + } + + Symbol_Scope & GetRootScope() { return root_scope; } + const Symbol_Scope & GetRootScope() const { return root_scope; } + emp::StreamManager & GetFileManager() { return file_map; } + + bool HasEvent(const std::string & name) const { return emp::Has(events_map, name); } + bool HasType(const std::string & name) const { return emp::Has(type_map, name); } + + /// To add a built-in function (at the root level) provide it with a name and description. + /// As long as the function only requires types known to the config system, it should be + /// converted properly. For a variadic function, the provided function must take a + /// vector of ASTNode pointers, but may return any known type. + template + void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { + root_scope.AddBuiltinFunction(name, fun, desc); + } + + /// To add a type, provide the type name (that can be referred to in a script) and a function + /// that should be called (with the variable name) when an instance of that type is created. + /// The function must return a reference to the newly created instance. + template + TypeInfo & AddType( + const std::string & type_name, + const std::string & desc, + FUN_T init_fun, + emp::TypeID type_id, + bool is_config_owned=false + ) { + emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); + size_t index = type_map.size(); + auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); + info_ptr->LinkType(type_id); + type_map[type_name] = info_ptr; + return *type_map[type_name]; + } + + /// If the linked type can be provided as a template parameter, we can also double check that + /// it is derived from EmplodeType (as it needs to be...) + template + TypeInfo & AddType( + const std::string & type_name, + const std::string & desc, + FUN_T init_fun, + bool is_config_owned=false + ) { + static_assert(std::is_base_of(), + "Only EmplodeType objects can be used as a custom config type."); + TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); + OBJECT_T::InitType(info); + return info; + } + + /// If init_fun is not specified in add type, build our own and assume that we own the object. + template + TypeInfo & AddType(const std::string & type_name, const std::string & desc) { + return AddType(type_name, desc, + [](const std::string & /*name*/){ return emp::NewPtr(); }, + true); + } + + Symbol_Object & MakeObjSymbol( + const std::string & type_name, + const std::string & var_name, + Symbol_Scope & scope + ) { + // Retrieve the information about the requested type. + TypeInfo & type_info = *type_map[type_name]; + const std::string & type_desc = type_info.GetDesc(); + const bool is_config_owned = type_info.GetOwned(); + + // Use the TypeInfo associated with the provided type name to build an instance. + emp::Ptr new_obj = type_info.MakeObj(var_name); + + // Setup a scope for this new type, linking the object to it. + Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); + + // Let the new object know about its scope. + new_obj->Setup(new_obj_symbol, type_info); + + return new_obj_symbol; + } + + + /// Create a new type of event that can be used in the scripting language. + Events & AddEventType(const std::string & name) { + emp_assert(!HasEvent(name), "Event type already exists!", name); + return events_map[name]; + } + + /// Add an instance of an event with an action that should be triggered. + void AddEvent(const std::string & name, emp::Ptr action, + double first=0.0, double repeat=0.0, double max=-1.0) { + emp_assert(HasEvent(name), name); + // Debug ("Adding event instance for '", name, "' (", first, ":", repeat, ":", max, ")"); + events_map[name].AddEvent(action, first, repeat, max); + } + + /// Indicate the an event trigger value has been updated; trigger associated events. + void UpdateEventValue(const std::string & name, double new_value) { + emp_assert(HasEvent(name), name); + // Debug("Uppdating event value '", name, "' to ", new_value); + events_map[name].UpdateValue(new_value); + } + + /// Trigger all events of a type (ignoring trigger values) + void TriggerEvents(const std::string & name) { + emp_assert(HasEvent(name), name); + events_map[name].TriggerAll(); + } + + /// Print all of the events to the provided stream. + void PrintEvents(std::ostream & os) const { + for (const auto & x : events_map) { + x.second.Write(x.first, os); + } + } + + }; + +} +#endif From 8a9f049f2dd4f88a3b06aca957322ec24509ed9d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 10:17:58 -0400 Subject: [PATCH 288/445] Moved SymbolTable out of main Emplode file. --- source/Emplode/Emplode.hpp | 154 ++----------------------------------- 1 file changed, 5 insertions(+), 149 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 7f4df84e..a79fd067 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -69,10 +69,12 @@ #define EMPLODE_HPP #include +#include +#include +#include #include "emp/base/assert.hpp" #include "emp/base/map.hpp" -#include "emp/io/StreamManager.hpp" #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" @@ -80,159 +82,13 @@ #include "DataFile.hpp" #include "EmplodeType.hpp" #include "Symbol_Function.hpp" -#include "Symbol_Scope.hpp" +#include "SymbolTable.hpp" #include "Events.hpp" #include "Lexer.hpp" #include "TypeInfo.hpp" namespace emplode { - class SymbolTable { - protected: - Symbol_Scope root_scope; ///< All variables from the root level. - std::map events_map; ///< A map of names to event groups. - std::unordered_map> type_map; ///< All types available in the script. - emp::StreamManager file_map; ///< Track all file streams by name. - - public: - SymbolTable(const std::string & name) - : root_scope(name, "Global scope", nullptr) { - // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); - - file_map.SetOutputDefaultFile(); // Stream manager should default to 'file' output. - } - - ~SymbolTable() { - // Clean up type information. - for (auto [name, ptr] : type_map) ptr.Delete(); - } - - Symbol_Scope & GetRootScope() { return root_scope; } - const Symbol_Scope & GetRootScope() const { return root_scope; } - emp::StreamManager & GetFileManager() { return file_map; } - - bool HasEvent(const std::string & name) const { return emp::Has(events_map, name); } - bool HasType(const std::string & name) const { return emp::Has(type_map, name); } - - /// To add a built-in function (at the root level) provide it with a name and description. - /// As long as the function only requires types known to the config system, it should be - /// converted properly. For a variadic function, the provided function must take a - /// vector of ASTNode pointers, but may return any known type. - template - void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { - root_scope.AddBuiltinFunction(name, fun, desc); - } - - /// To add a type, provide the type name (that can be referred to in a script) and a function - /// that should be called (with the variable name) when an instance of that type is created. - /// The function must return a reference to the newly created instance. - template - TypeInfo & AddType( - const std::string & type_name, - const std::string & desc, - FUN_T init_fun, - emp::TypeID type_id, - bool is_config_owned=false - ) { - emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); - size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); - info_ptr->LinkType(type_id); - type_map[type_name] = info_ptr; - return *type_map[type_name]; - } - - /// If the linked type can be provided as a template parameter, we can also double check that - /// it is derived from EmplodeType (as it needs to be...) - template - TypeInfo & AddType( - const std::string & type_name, - const std::string & desc, - FUN_T init_fun, - bool is_config_owned=false - ) { - static_assert(std::is_base_of(), - "Only EmplodeType objects can be used as a custom config type."); - TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); - OBJECT_T::InitType(info); - return info; - } - - /// If init_fun is not specified in add type, build our own and assume that we own the object. - template - TypeInfo & AddType(const std::string & type_name, const std::string & desc) { - return AddType(type_name, desc, - [](const std::string & /*name*/){ return emp::NewPtr(); }, - true); - } - - Symbol_Object & MakeObjSymbol( - const std::string & type_name, - const std::string & var_name, - Symbol_Scope & scope - ) { - // Retrieve the information about the requested type. - TypeInfo & type_info = *type_map[type_name]; - const std::string & type_desc = type_info.GetDesc(); - const bool is_config_owned = type_info.GetOwned(); - - // Use the TypeInfo associated with the provided type name to build an instance. - emp::Ptr new_obj = type_info.MakeObj(var_name); - - // Setup a scope for this new type, linking the object to it. - Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); - - // Let the new object know about its scope. - new_obj->Setup(new_obj_symbol, type_info); - - return new_obj_symbol; - } - - - /// Create a new type of event that can be used in the scripting language. - Events & AddEventType(const std::string & name) { - emp_assert(!HasEvent(name), "Event type already exists!", name); - return events_map[name]; - } - - /// Add an instance of an event with an action that should be triggered. - void AddEvent(const std::string & name, emp::Ptr action, - double first=0.0, double repeat=0.0, double max=-1.0) { - emp_assert(HasEvent(name), name); - // Debug ("Adding event instance for '", name, "' (", first, ":", repeat, ":", max, ")"); - events_map[name].AddEvent(action, first, repeat, max); - } - - /// Indicate the an event trigger value has been updated; trigger associated events. - void UpdateEventValue(const std::string & name, double new_value) { - emp_assert(HasEvent(name), name); - // Debug("Uppdating event value '", name, "' to ", new_value); - events_map[name].UpdateValue(new_value); - } - - /// Trigger all events of a type (ignoring trigger values) - void TriggerEvents(const std::string & name) { - emp_assert(HasEvent(name), name); - events_map[name].TriggerAll(); - } - - /// Print all of the events to the provided stream. - void PrintEvents(std::ostream & os) const { - for (const auto & x : events_map) { - x.second.Write(x.first, os); - } - } - - }; - - - - class ParseState { private: emp::TokenStream::Iterator pos; @@ -389,7 +245,7 @@ namespace emplode { Lexer lexer; ///< Lexer to process input code. SymbolTable symbol_table; ///< Management of identifiers. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = true; ///< Should we print full debug information? + bool debug = false; ///< Should we print full debug information? std::unordered_map precedence_map; ///< Precedence levels for symbols. std::string ConcatLexemes(pos_t start_pos, pos_t end_pos) const { From c4ef299b9351cff6a7b1c391fca4264401ffe7d4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 10:26:46 -0400 Subject: [PATCH 289/445] Moved ParseState over to Parser.hpp; proper Parser class to follow. --- source/Emplode/Emplode.hpp | 152 +------------------------------- source/Emplode/Parser.hpp | 176 +++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 149 deletions(-) create mode 100644 source/Emplode/Parser.hpp diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index a79fd067..03914078 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -81,161 +81,15 @@ #include "AST.hpp" #include "DataFile.hpp" #include "EmplodeType.hpp" -#include "Symbol_Function.hpp" -#include "SymbolTable.hpp" #include "Events.hpp" #include "Lexer.hpp" +#include "Parser.hpp" +#include "Symbol_Function.hpp" +#include "SymbolTable.hpp" #include "TypeInfo.hpp" namespace emplode { - class ParseState { - private: - emp::TokenStream::Iterator pos; - emp::Ptr symbol_table; - emp::vector< emp::Ptr > scope_stack; - emp::Ptr lexer; - - public: - ParseState(emp::TokenStream::Iterator _pos, SymbolTable & _table, - Symbol_Scope & _scope, Lexer & _lexer) - : pos(_pos), symbol_table(&_table), lexer(&_lexer) { scope_stack.push_back(&_scope); } - ParseState(ParseState &) = default; - ~ParseState() { } - - ParseState & operator=(const ParseState &) = default; - - bool operator==(const ParseState & in) const { return pos == in.pos; } - bool operator!=(const ParseState & in) const { return pos != in.pos; } - bool operator< (const ParseState & in) const { return pos < in.pos; } - bool operator<=(const ParseState & in) const { return pos <= in.pos; } - bool operator> (const ParseState & in) const { return pos > in.pos; } - bool operator>=(const ParseState & in) const { return pos >= in.pos; } - - ParseState & operator++() { ++pos; return *this; } - ParseState operator++(int) { ParseState old(*this); ++pos; return old; } - ParseState & operator--() { --pos; return *this; } - ParseState operator--(int) { ParseState old(*this); --pos; return old; } - - bool IsValid() const { return pos.IsValid(); } - bool AtEnd() const { return pos.AtEnd(); } - - size_t GetIndex() const { return pos.GetIndex(); } ///< Return index in token stream. - size_t GetTokenSize() const { return pos.IsValid() ? pos->lexeme.size() : 0; } - Symbol_Scope & GetScope() { - emp_assert(scope_stack.size() && scope_stack.back() != nullptr); - return *scope_stack.back(); - } - const Symbol_Scope & GetScope() const { - emp_assert(scope_stack.size() && scope_stack.back() != nullptr); - return *scope_stack.back(); - } - const std::string & GetScopeName() const { return GetScope().GetName(); } - - std::string AsString() { - return emp::to_string("[pos=", pos.GetIndex(), - ",lex='", AsLexeme(), - "',scope='", GetScope().GetName(), - "']"); - } - - bool IsID() const { return pos && lexer->IsID(*pos); } - bool IsNumber() const { return pos && lexer->IsNumber(*pos); } - bool IsChar() const { return pos && lexer->IsChar(*pos); } - bool IsString() const { return pos && lexer->IsString(*pos); } - bool IsDots() const { return pos && lexer->IsDots(*pos); } - - bool IsEvent() const { return symbol_table->HasEvent(AsLexeme()); } - bool IsType() const { return symbol_table->HasType(AsLexeme()); } - - /// Convert the current state to a character; use \0 if cur token is not a symbol. - char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } - - /// Return the lexeme associate with the current state. - const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } - - /// Return the lexeme associate with the current state AND advance the token stream. - const std::string & UseLexeme() { - const std::string & out = AsLexeme(); - pos++; - return out; - } - - /// Return whether the current token is the specified char; if so also advance token stream. - bool UseIfChar(char test_char) { - if (AsChar() != test_char) return false; - ++pos; - return true; - } - - /// Report an error in parsing this file and exit. - template - void Error(Ts &&... args) const { - std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); - std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " - << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; - exit(1); - } - - template - void Require(bool test, Ts &&... args) const { - if (!test) Error(std::forward(args)...); - } - template - void RequireID(Ts &&... args) const { - if (!IsID()) Error(std::forward(args)...); - } - template - void RequireNumber(Ts &&... args) const { - if (!IsNumber()) Error(std::forward(args)...); - } - template - void RequireString(Ts &&... args) const { - if (!IsString()) Error(std::forward(args)...); - } - template - void RequireChar(char req_char, Ts &&... args) const { - if (AsChar() != req_char) Error(std::forward(args)...); - } - template - void RequireLexeme(const std::string & lex, Ts &&... args) const { - if (AsLexeme() != lex) Error(std::forward(args)...); - } - - template - void UseRequiredChar(char req_char, Ts &&... args) { - if (AsChar() != req_char) Error(std::forward(args)...); - ++pos; - } - - void PushScope(Symbol_Scope & _scope) { scope_stack.push_back(&_scope); } - void PopScope() { scope_stack.pop_back(); } - - Symbol & LookupSymbol(const std::string & var_name, bool scan_scopes) { - emp::Ptr out_symbol = GetScope().LookupSymbol(var_name, scan_scopes); - // If we can't find this identifier, throw an error. - if (out_symbol.IsNull()) { - Error("'", var_name, "' does not exist as a parameter, variable, or type.", - " Current scope is '", GetScope().GetName(), "'"); - } - return *out_symbol; - } - - Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { - return GetScope().AddStringVar(name, desc); - } - Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { - return GetScope().AddValueVar(name, desc); - } - Symbol_Scope & AddScope(const std::string & name, const std::string & desc) { - return GetScope().AddScope(name, desc); - } - Symbol_Object & AddObject(const std::string & type_name, const std::string & var_name) { - return symbol_table->MakeObjSymbol(type_name, var_name, GetScope()); - } - }; - - class Emplode { public: using pos_t = emp::TokenStream::Iterator; diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp new file mode 100644 index 00000000..4431edc8 --- /dev/null +++ b/source/Emplode/Parser.hpp @@ -0,0 +1,176 @@ +/** + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file Parser.hpp + * @brief Manages parsing of Emplode language input streams. + * @note Status: BETA + * + */ + +#ifndef EMPLODE_PARSER_HPP +#define EMPLODE_PARSER_HPP + +#include +#include + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/tools/string_utils.hpp" + +#include "Lexer.hpp" +#include "Symbol_Scope.hpp" +#include "SymbolTable.hpp" + +namespace emplode { + + class ParseState { + private: + emp::TokenStream::Iterator pos; + emp::Ptr symbol_table; + emp::vector< emp::Ptr > scope_stack; + emp::Ptr lexer; + + public: + ParseState(emp::TokenStream::Iterator _pos, SymbolTable & _table, + Symbol_Scope & _scope, Lexer & _lexer) + : pos(_pos), symbol_table(&_table), lexer(&_lexer) { scope_stack.push_back(&_scope); } + ParseState(ParseState &) = default; + ~ParseState() { } + + ParseState & operator=(const ParseState &) = default; + + bool operator==(const ParseState & in) const { return pos == in.pos; } + bool operator!=(const ParseState & in) const { return pos != in.pos; } + bool operator< (const ParseState & in) const { return pos < in.pos; } + bool operator<=(const ParseState & in) const { return pos <= in.pos; } + bool operator> (const ParseState & in) const { return pos > in.pos; } + bool operator>=(const ParseState & in) const { return pos >= in.pos; } + + ParseState & operator++() { ++pos; return *this; } + ParseState operator++(int) { ParseState old(*this); ++pos; return old; } + ParseState & operator--() { --pos; return *this; } + ParseState operator--(int) { ParseState old(*this); --pos; return old; } + + bool IsValid() const { return pos.IsValid(); } + bool AtEnd() const { return pos.AtEnd(); } + + size_t GetIndex() const { return pos.GetIndex(); } ///< Return index in token stream. + size_t GetTokenSize() const { return pos.IsValid() ? pos->lexeme.size() : 0; } + Symbol_Scope & GetScope() { + emp_assert(scope_stack.size() && scope_stack.back() != nullptr); + return *scope_stack.back(); + } + const Symbol_Scope & GetScope() const { + emp_assert(scope_stack.size() && scope_stack.back() != nullptr); + return *scope_stack.back(); + } + const std::string & GetScopeName() const { return GetScope().GetName(); } + + std::string AsString() { + return emp::to_string("[pos=", pos.GetIndex(), + ",lex='", AsLexeme(), + "',scope='", GetScope().GetName(), + "']"); + } + + bool IsID() const { return pos && lexer->IsID(*pos); } + bool IsNumber() const { return pos && lexer->IsNumber(*pos); } + bool IsChar() const { return pos && lexer->IsChar(*pos); } + bool IsString() const { return pos && lexer->IsString(*pos); } + bool IsDots() const { return pos && lexer->IsDots(*pos); } + + bool IsEvent() const { return symbol_table->HasEvent(AsLexeme()); } + bool IsType() const { return symbol_table->HasType(AsLexeme()); } + + /// Convert the current state to a character; use \0 if cur token is not a symbol. + char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } + + /// Return the lexeme associate with the current state. + const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } + + /// Return the lexeme associate with the current state AND advance the token stream. + const std::string & UseLexeme() { + const std::string & out = AsLexeme(); + pos++; + return out; + } + + /// Return whether the current token is the specified char; if so also advance token stream. + bool UseIfChar(char test_char) { + if (AsChar() != test_char) return false; + ++pos; + return true; + } + + /// Report an error in parsing this file and exit. + template + void Error(Ts &&... args) const { + std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); + std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " + << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; + exit(1); + } + + template + void Require(bool test, Ts &&... args) const { + if (!test) Error(std::forward(args)...); + } + template + void RequireID(Ts &&... args) const { + if (!IsID()) Error(std::forward(args)...); + } + template + void RequireNumber(Ts &&... args) const { + if (!IsNumber()) Error(std::forward(args)...); + } + template + void RequireString(Ts &&... args) const { + if (!IsString()) Error(std::forward(args)...); + } + template + void RequireChar(char req_char, Ts &&... args) const { + if (AsChar() != req_char) Error(std::forward(args)...); + } + template + void RequireLexeme(const std::string & lex, Ts &&... args) const { + if (AsLexeme() != lex) Error(std::forward(args)...); + } + + template + void UseRequiredChar(char req_char, Ts &&... args) { + if (AsChar() != req_char) Error(std::forward(args)...); + ++pos; + } + + void PushScope(Symbol_Scope & _scope) { scope_stack.push_back(&_scope); } + void PopScope() { scope_stack.pop_back(); } + + Symbol & LookupSymbol(const std::string & var_name, bool scan_scopes) { + emp::Ptr out_symbol = GetScope().LookupSymbol(var_name, scan_scopes); + // If we can't find this identifier, throw an error. + if (out_symbol.IsNull()) { + Error("'", var_name, "' does not exist as a parameter, variable, or type.", + " Current scope is '", GetScope().GetName(), "'"); + } + return *out_symbol; + } + + Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { + return GetScope().AddStringVar(name, desc); + } + Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { + return GetScope().AddValueVar(name, desc); + } + Symbol_Scope & AddScope(const std::string & name, const std::string & desc) { + return GetScope().AddScope(name, desc); + } + Symbol_Object & AddObject(const std::string & type_name, const std::string & var_name) { + return symbol_table->MakeObjSymbol(type_name, var_name, GetScope()); + } + }; + + +} +#endif From a6d90dc40bbb457c77a5dc4df34c98a6dcf6d7eb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 12:02:04 -0400 Subject: [PATCH 290/445] Moved Parser functionality into a Parser class. --- source/Emplode/Emplode.hpp | 430 ++----------------------------------- source/Emplode/Parser.hpp | 404 +++++++++++++++++++++++++++++++++- 2 files changed, 417 insertions(+), 417 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 03914078..5d0b10f0 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -96,11 +96,10 @@ namespace emplode { protected: std::string filename; ///< Source for for code to generate. - Lexer lexer; ///< Lexer to process input code. SymbolTable symbol_table; ///< Management of identifiers. + Lexer lexer; ///< Lexer to process input code. + Parser parser; ///< Parser to transform token stream into an abstract syntax tree. ASTNode_Block ast_root; ///< Abstract syntax tree version of input file. - bool debug = false; ///< Should we print full debug information? - std::unordered_map precedence_map; ///< Precedence levels for symbols. std::string ConcatLexemes(pos_t start_pos, pos_t end_pos) const { emp_assert(start_pos <= end_pos); @@ -115,54 +114,6 @@ namespace emplode { return ss.str(); } - template - void Debug(Ts... args) const { - if (debug) std::cout << "DEBUG: " << emp::to_string(std::forward(args)...) << std::endl; - } - - - /// Load a variable name from the provided scope. - /// If create_ok is true, create any variables that we don't find. Otherwise continue the - /// search for them in successively outer (lower) scopes. - [[nodiscard]] emp::Ptr ParseVar(ParseState & state, - bool create_ok=false, - bool scan_scopes=true); - - /// Load a value from the provided scope, which can come from a variable or a literal. - [[nodiscard]] emp::Ptr ParseValue(ParseState & state); - - /// Calculate the result of the provided operation on two computed entries. - [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, - emp::Ptr value1, - emp::Ptr value2); - - /// Calculate a full expression found in a token sequence, using the provided scope. - [[nodiscard]] emp::Ptr ParseExpression(ParseState & state, size_t prec_limit=1000); - - /// Parse the declaration of a variable and return the newly created Symbol - Symbol & ParseDeclaration(ParseState & state); - - /// Parse an event description. - emp::Ptr ParseEvent(ParseState & state); - - /// Parse the next input in the specified Struct. A statement can be a variable declaration, - /// an expression, or an event. - [[nodiscard]] emp::Ptr ParseStatement(ParseState & state); - - /// Keep parsing statements until there aren't any more or we leave this scope. - [[nodiscard]] emp::Ptr ParseStatementList(ParseState & state) { - Debug("Running ParseStatementList(", state.AsString(), ")"); - auto cur_block = emp::NewPtr(state.GetScope()); - while (state.IsValid() && state.AsChar() != '}') { - // Parse each statement in the file. - emp::Ptr statement_node = ParseStatement(state); - - // If the current statement is real, add it to the current block. - if (!statement_node.IsNull()) cur_block->AddChild( statement_node ); - } - return cur_block; - } - public: Emplode(std::string in_filename="") : filename(in_filename) @@ -171,18 +122,6 @@ namespace emplode { { if (filename != "") Load(filename); - // Setup operator precedence. - size_t cur_prec = 0; - precedence_map["("] = cur_prec++; - precedence_map["**"] = cur_prec++; - precedence_map["*"] = precedence_map["/"] = precedence_map["%"] = cur_prec++; - precedence_map["+"] = precedence_map["-"] = cur_prec++; - precedence_map["<"] = precedence_map["<="] = precedence_map[">"] = precedence_map[">="] = cur_prec++; - precedence_map["=="] = precedence_map["!="] = cur_prec++; - precedence_map["&&"] = cur_prec++; - precedence_map["||"] = cur_prec++; - precedence_map["="] = cur_prec++; - // Setup default functions. // 'EXEC' dynamically executes the contents of a string. @@ -233,14 +172,14 @@ namespace emplode { AddFunction("POW", [](double x, double y){ return emp::Pow(x,y); }, "Take arg1 to the arg2 power" ); // Default 3-input math functions - auto math3_if = [](double x, double y, double z){ return (x!=0.0) ? y : z; }; - AddFunction("IF", math3_if, "If arg1 is true, return arg2, else arg3" ); - auto math3_clamp = [](double x, double y, double z){ return (xz) ? z : x; }; - AddFunction("CLAMP", math3_clamp, "Return arg1, forced into range [arg2,arg3]" ); - auto math3_to_scale = [](double x, double y, double z){ return (z-y)*x+y; }; - AddFunction("TO_SCALE", math3_to_scale, "Scale arg1 to arg2-arg3 as unit distance" ); - auto math3_from_scale = [](double x, double y, double z){ return (x-y) / (z-y); }; - AddFunction("FROM_SCALE", math3_from_scale, "Scale arg1 from arg2-arg3 as unit distance" ); + AddFunction("IF", [](double x, double y, double z){ return (x!=0.0) ? y : z; }, + "If arg1 is true, return arg2, else arg3" ); + AddFunction("CLAMP", [](double x, double y, double z){ return (xz) ? z : x; }, + "Return arg1, forced into range [arg2,arg3]" ); + AddFunction("TO_SCALE", [](double x, double y, double z){ return (z-y)*x+y; }, + "Scale arg1 to arg2-arg3 as unit distance" ); + AddFunction("FROM_SCALE", [](double x, double y, double z){ return (x-y) / (z-y); }, + "Scale arg1 from arg2-arg3 as unit distance" ); // Setup default DataFile type. auto df_init = [this](const std::string & name) { @@ -269,10 +208,6 @@ namespace emplode { /// Create a new type of event that can be used in the scripting language. Events & AddEventType(const std::string & name) { return symbol_table.AddEventType(name); } - /// Add an instance of an event with an action that should be triggered. - template - void AddEvent(Ts &&... args) { symbol_table.AddEvent(std::forward(args)...); } - /// Indicate the an event trigger value has been updated; trigger associated events. void UpdateEventValue(const std::string & name, double new_value) { symbol_table.UpdateEventValue(name, new_value); @@ -281,13 +216,11 @@ namespace emplode { /// Trigger all events of a type (ignoring trigger values) void TriggerEvents(const std::string & name) { symbol_table.TriggerEvents(name); } - template TypeInfo & AddType(ARG_Ts &&... args) { return symbol_table.AddType( std::forward(args)... ); } - /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided function must take a @@ -302,7 +235,6 @@ namespace emplode { // Load a single, specified configuration file. void Load(const std::string & filename) { - Debug("Running Load(", filename, ")"); std::ifstream file(filename); // Load the provided file. emp::TokenStream tokens = lexer.Tokenize(file, filename); // Convert to more-usable tokens. file.close(); // Close the file (now that it's converted) @@ -310,7 +242,7 @@ namespace emplode { // Parse and run the program, starting from the outer scope. ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; - auto cur_block = ParseStatementList(state); + auto cur_block = parser.ParseStatementList(state); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -326,13 +258,12 @@ namespace emplode { // @param statements List is statements to be parsed. // @param name Name of statement group (for error messages) void LoadStatements(const emp::vector & statements, const std::string & name) { - Debug("Running LoadStatements()"); emp::TokenStream tokens = lexer.Tokenize(statements, name); // Convert to tokens. pos_t pos = tokens.begin(); // Parse and run the program, starting from the outer scope. ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; - auto cur_block = ParseStatementList(state); + auto cur_block = parser.ParseStatementList(state); cur_block->Process(); // Store this AST onto the full set we're working with. @@ -341,20 +272,19 @@ namespace emplode { // Load the provided statement and run it. std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { - Debug("Running Execute()"); if (!scope) scope = &symbol_table.GetRootScope(); // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; - auto cur_block = ParseStatement(state); // Convert tokens to AST + auto cur_block = parser.ParseStatement(state); // Convert tokens to AST auto result_ptr = cur_block->Process(); // Process AST to get result symbol. std::string result = ""; // Default result to an empty string. if (result_ptr) { result = result_ptr->AsString(); // Convert result to output string. if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete the result symbol if done. } - cur_block.Delete(); // Delete the AST. + cur_block.Delete(); // Delete the temporary AST. return result; // Return the result string. } @@ -376,338 +306,6 @@ namespace emplode { } }; - ////////////////////////////////////////////////////////// - // --== Emplode member function Implementations! ==-- - - - // Load a variable name from the provided scope. - emp::Ptr Emplode::ParseVar(ParseState & state, bool create_ok, bool scan_scopes) - { - Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, ")"); - - // First, check for leading dots. - if (state.IsDots()) { - Debug("...found dots: ", state.AsLexeme()); - scan_scopes = false; // One or more initial dots specify scope; don't scan! - size_t num_dots = state.GetTokenSize(); // Extra dots shift scope. - emp::Ptr cur_scope = &state.GetScope(); - while (num_dots-- > 1) { - cur_scope = cur_scope->GetScope(); - if (cur_scope.IsNull()) state.Error("Too many dots; goes beyond global scope."); - } - ++state; - - // Recursively call in the found scope if needed; given leading dot, do not scan scopes. - if (cur_scope.Raw() != &state.GetScope()) { - state.PushScope(*cur_scope); - auto result = ParseVar(state, create_ok, false); - state.PopScope(); - return result; - } - } - - // Next, we must have a variable name. - // @CAO: Or a : ? E.g., technically "..:size" could give you the parent scope size. - state.RequireID("Must provide a variable identifier!"); - std::string var_name = state.UseLexeme(); - - // Lookup this variable. - Debug("...looking up symbol '", var_name, - "' starting at scope '", state.GetScopeName(), - "'; scanning=", scan_scopes); - Symbol & cur_symbol = state.LookupSymbol(var_name, scan_scopes); - - // If this variable just provided a scope, keep going. - if (state.IsDots()) { - state.PushScope(cur_symbol.AsScope()); - auto result = ParseVar(state, create_ok, false); - state.PopScope(); - return result; - } - - // Otherwise return the variable as a leaf! - return emp::NewPtr(&cur_symbol); - } - - // Load a value from the provided scope, which can come from a variable or a literal. - emp::Ptr Emplode::ParseValue(ParseState & state) { - Debug("Running ParseValue(", state.AsString(), ")"); - - // Anything that begins with an identifier or dots must represent a variable. Refer! - if (state.IsID() || state.IsDots()) return ParseVar(state, false, true); - - // A literal number should have a temporary created with its value. - if (state.IsNumber()) { - Debug("...value is a number: ", state.AsLexeme()); - double value = emp::from_string(state.UseLexeme()); // Calculate value. - return MakeTempLeaf(value); // Return temporary Symbol. - } - - // A literal char should be converted to its ASCII value. - if (state.IsChar()) { - Debug("...value is a char: ", state.AsLexeme()); - char lit_char = emp::from_literal_char(state.UseLexeme()); // Convert the literal char. - return MakeTempLeaf((double) lit_char); // Return temporary Symbol. - } - - // A literal string should be converted to a regular string and used. - if (state.IsString()) { - Debug("...value is a string: ", state.AsLexeme()); - std::string str = emp::from_literal_string(state.UseLexeme()); // Convert the literal string. - return MakeTempLeaf(str); // Return temporary Symbol. - } - - // If we have an open parenthesis, process everything inside into a single value... - if (state.UseIfChar('(')) { - emp::Ptr out_ast = ParseExpression(state); - state.UseRequiredChar(')', "Expected a close parenthesis in expression."); - return out_ast; - } - - state.Error("Expected a value, found: ", state.AsLexeme()); - - return nullptr; - } - - // Process a single provided operation on two Symbol objects. - emp::Ptr Emplode::ProcessOperation(const std::string & symbol, - emp::Ptr in_node1, - emp::Ptr in_node2) - { - emp_assert(!in_node1.IsNull()); - emp_assert(!in_node2.IsNull()); - - // If this operation is assignment, do so! - if (symbol == "=") return emp::NewPtr(in_node1, in_node2); - - // If the first argument is numeric, assume we are using a math operator. - if (in_node1->IsNumeric()) { - - // Determine the output value and put it in a temporary node. - emp::Ptr out_val = emp::NewPtr(symbol); - - if (symbol == "+") out_val->SetFun( [](double v1, double v2){ return v1 + v2; } ); - else if (symbol == "-") out_val->SetFun( [](double v1, double v2){ return v1 - v2; } ); - else if (symbol == "**") out_val->SetFun( [](double v1, double v2){ return emp::Pow(v1, v2); } ); - else if (symbol == "*") out_val->SetFun( [](double v1, double v2){ return v1 * v2; } ); - else if (symbol == "/") out_val->SetFun( [](double v1, double v2){ return v1 / v2; } ); - else if (symbol == "%") out_val->SetFun( [](double v1, double v2){ return emp::Mod(v1, v2); } ); - else if (symbol == "==") out_val->SetFun( [](double v1, double v2){ return v1 == v2; } ); - else if (symbol == "!=") out_val->SetFun( [](double v1, double v2){ return v1 != v2; } ); - else if (symbol == "<") out_val->SetFun( [](double v1, double v2){ return v1 < v2; } ); - else if (symbol == "<=") out_val->SetFun( [](double v1, double v2){ return v1 <= v2; } ); - else if (symbol == ">") out_val->SetFun( [](double v1, double v2){ return v1 > v2; } ); - else if (symbol == ">=") out_val->SetFun( [](double v1, double v2){ return v1 >= v2; } ); - - // @CAO: Need to still handle these last two differently for short-circuiting. - else if (symbol == "&&") out_val->SetFun( [](double v1, double v2){ return v1 && v2; } ); - else if (symbol == "||") out_val->SetFun( [](double v1, double v2){ return v1 || v2; } ); - - out_val->AddChild(in_node1); - out_val->AddChild(in_node2); - - return out_val; - } - - // Otherwise assume that we are dealing with strings. - if (symbol == "+") { - auto out_val = emp::NewPtr>(symbol); - out_val->SetFun([](std::string val1, std::string val2){ return val1 + val2; }); - out_val->AddChild(in_node1); - out_val->AddChild(in_node2); - - return out_val; - } - else if (symbol == "*") { - auto fun = [](std::string val1, double val2) { - std::string out_string; - out_string.reserve(val1.size() * (size_t) val2); - for (size_t i = 0; i < (size_t) val2; i++) out_string += val1; - return out_string; - }; - - auto out_val = emp::NewPtr>(symbol); - out_val->SetFun(fun); - out_val->AddChild(in_node1); - out_val->AddChild(in_node2); - - return out_val; - } - else { - auto out_val = emp::NewPtr>(symbol); - - if (symbol == "==") out_val->SetFun([](std::string v1, std::string v2){ return v1 == v2; }); - else if (symbol == "!=") out_val->SetFun([](std::string v1, std::string v2){ return v1 != v2; }); - else if (symbol == "<") out_val->SetFun([](std::string v1, std::string v2){ return v1 < v2; }); - else if (symbol == "<=") out_val->SetFun([](std::string v1, std::string v2){ return v1 <= v2; }); - else if (symbol == ">") out_val->SetFun([](std::string v1, std::string v2){ return v1 > v2; }); - else if (symbol == ">=") out_val->SetFun([](std::string v1, std::string v2){ return v1 >= v2; }); - - out_val->AddChild(in_node1); - out_val->AddChild(in_node2); - - return out_val; - } - - return nullptr; - } - - - // Calculate an expression in the provided scope. - emp::Ptr Emplode::ParseExpression(ParseState & state, size_t prec_limit) { - Debug("Running ParseExpression(", state.AsString(), ", limit=", prec_limit, ")"); - - // @CAO Should test for unary operators at the beginning of an expression. - - /// Process a value (and possibly more!) - emp::Ptr cur_node = ParseValue(state); - std::string op = state.AsLexeme(); - - Debug("...back in ParseExpression; op=`", op, "`; state=", state.AsString()); - - while ( emp::Has(precedence_map, op) && precedence_map[op] < prec_limit ) { - ++state; // Move past the current operator - // Do we have a function call? - if (op == "(") { - // Collect arguments. - emp::vector< emp::Ptr > args; - while (state.AsChar() != ')') { - emp::Ptr next_arg = ParseExpression(state); - args.push_back(next_arg); // Save this argument. - if (state.AsChar() != ',') break; // If we don't have a comma, no more args! - ++state; // Move on to the next argument. - } - state.UseRequiredChar(')', "Expected a ')' to end function call."); - - // cur_node should have evaluated itself to a function; a Call node will link that - // function with its arguments, run it, and return the result. - cur_node = emp::NewPtr(cur_node, args); - } - - // Otherwise we must have a binary math operation. - else { - emp::Ptr node2 = ParseExpression(state, precedence_map[op]); - cur_node = ProcessOperation(op, cur_node, node2); - } - - // Move the current value over to cur_node and check if we have a new operator... - op = state.AsLexeme(); - } - - emp_assert(!cur_node.IsNull()); - return cur_node; - } - - // Parse an the declaration of a variable. - Symbol & Emplode::ParseDeclaration(ParseState & state) { - std::string type_name = state.UseLexeme(); - state.RequireID("Type name '", type_name, "' must be followed by variable to declare."); - std::string var_name = state.UseLexeme(); - - if (type_name == "String") { - return state.AddStringVar(var_name, "Local string variable."); - } - else if (type_name == "Value") { - return state.AddValueVar(var_name, "Local value variable."); - } - else if (type_name == "Struct") { - return state.AddScope(var_name, "Local struct"); - } - - // Otherwise we have an object of a custom type to add. - Debug("Building object '", var_name, "' of type '", type_name, "'"); - return state.AddObject(type_name, var_name); - } - - // Parse an event description. - emp::Ptr Emplode::ParseEvent(ParseState & state) { - state.UseRequiredChar('@', "All event declarations must being with an '@'."); - state.RequireID("Events must start by specifying event name."); - const std::string & event_name = state.UseLexeme(); - state.UseRequiredChar('(', "Expected parentheses after '", event_name, "' for args."); - - emp::vector> args; - while (state.AsChar() != ')') { - args.push_back( ParseExpression(state) ); - state.UseIfChar(','); // Skip comma if next (does allow trailing comma) - } - state.UseRequiredChar(')', "Event args must end in a ')'"); - - emp::Ptr action = ParseStatement(state); - - Debug("Building event '", event_name, "' with args ", args); - - auto setup_event = [this, event_name](emp::Ptr action, - const emp::vector> & args) { - AddEvent( - event_name, action, - (args.size() > 0) ? args[0]->AsDouble() : 0.0, - (args.size() > 1) ? args[1]->AsDouble() : 0.0, - (args.size() > 2) ? args[2]->AsDouble() : -1.0 - ); - }; - - return emp::NewPtr(event_name, action, args, setup_event); - } - - // Process the next input in the specified Struct. - emp::Ptr Emplode::ParseStatement(ParseState & state) { - Debug("Running ParseStatement(", state.AsString(), ")"); - - // Allow a statement with an empty line. - if (state.UseIfChar(';')) { return nullptr; } - - // Allow a statement to be a new scope. - if (state.UseIfChar('{')) { - // @CAO Need to add an anonymous scope (that gets written properly) - emp::Ptr out_node = ParseStatementList(state); - state.UseRequiredChar('}', "Expected '}' to close scope."); - return out_node; - } - - // Allow event definitions if a statement begins with an '@' - if (state.AsChar() == '@') return ParseEvent(state); - - // Allow this statement to be a declaration if it begins with a type. - if (state.IsType()) { - Symbol & new_symbol = ParseDeclaration(state); - - // If the next symbol is a ';' this is a declaration without an assignment. - if (state.AsChar() == ';') { - state++; // Skip the semi-colon. - return nullptr; // We are done! - } - - // If this symbol is a new scope, it can be populated now either directly (with in braces) - // or indirectly (with and assignment) - if (new_symbol.IsScope()) { - if (state.UseIfChar('{')) { - state.PushScope(new_symbol.AsScope()); - emp::Ptr out_node = ParseStatementList(state); - state.PopScope(); - state.UseRequiredChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); - return out_node; - } - - state.RequireChar('=', "Expected scope '", new_symbol.GetName(), - "' definition to start with a '{' or '='; found ''", state.AsLexeme(), "'."); - - } - - // Otherwise rewind so that the new variable can be used to start an expression. - --state; - } - - - // If we made it here, remainder should be an expression. - emp::Ptr out_node = ParseExpression(state); - - // Expressions must end in a semi-colon. - state.UseRequiredChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); - - return out_node; - } - } #endif diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 4431edc8..caa8d437 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -36,7 +36,7 @@ namespace emplode { ParseState(emp::TokenStream::Iterator _pos, SymbolTable & _table, Symbol_Scope & _scope, Lexer & _lexer) : pos(_pos), symbol_table(&_table), lexer(&_lexer) { scope_stack.push_back(&_scope); } - ParseState(ParseState &) = default; + ParseState(const ParseState &) = default; ~ParseState() { } ParseState & operator=(const ParseState &) = default; @@ -169,8 +169,410 @@ namespace emplode { Symbol_Object & AddObject(const std::string & type_name, const std::string & var_name) { return symbol_table->MakeObjSymbol(type_name, var_name, GetScope()); } + + /// Add an instance of an event with an action that should be triggered. + template + void AddEvent(Ts &&... args) { symbol_table->AddEvent(std::forward(args)...); } + }; + + + + class Parser { + private: + std::unordered_map precedence_map; ///< Precedence levels for symbols. + bool debug = false; ///< Print full debug information? + + template + void Debug(Ts... args) const { + if (debug) std::cout << "DEBUG: " << emp::to_string(std::forward(args)...) << std::endl; + } + + public: + Parser() { + // Setup operator precedence. + size_t cur_prec = 0; + precedence_map["("] = cur_prec++; + precedence_map["**"] = cur_prec++; + precedence_map["*"] = precedence_map["/"] = precedence_map["%"] = cur_prec++; + precedence_map["+"] = precedence_map["-"] = cur_prec++; + precedence_map["<"] = precedence_map["<="] = precedence_map[">"] = precedence_map[">="] = cur_prec++; + precedence_map["=="] = precedence_map["!="] = cur_prec++; + precedence_map["&&"] = cur_prec++; + precedence_map["||"] = cur_prec++; + precedence_map["="] = cur_prec++; + } + ~Parser() {} + + /// Load a variable name from the provided scope. + /// If create_ok is true, create any variables that we don't find. Otherwise continue the + /// search for them in successively outer (lower) scopes. + [[nodiscard]] emp::Ptr ParseVar(ParseState & state, + bool create_ok=false, + bool scan_scopes=true); + + /// Load a value from the provided scope, which can come from a variable or a literal. + [[nodiscard]] emp::Ptr ParseValue(ParseState & state); + + /// Calculate the result of the provided operation on two computed entries. + [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, + emp::Ptr value1, + emp::Ptr value2); + + /// Calculate a full expression found in a token sequence, using the provided scope. + [[nodiscard]] emp::Ptr ParseExpression(ParseState & state, size_t prec_limit=1000); + + /// Parse the declaration of a variable and return the newly created Symbol + Symbol & ParseDeclaration(ParseState & state); + + /// Parse an event description. + emp::Ptr ParseEvent(ParseState & state); + + /// Parse the next input in the specified Struct. A statement can be a variable declaration, + /// an expression, or an event. + [[nodiscard]] emp::Ptr ParseStatement(ParseState & state); + + /// Keep parsing statements until there aren't any more or we leave this scope. + [[nodiscard]] emp::Ptr ParseStatementList(ParseState & state) { + Debug("Running ParseStatementList(", state.AsString(), ")"); + auto cur_block = emp::NewPtr(state.GetScope()); + while (state.IsValid() && state.AsChar() != '}') { + // Parse each statement in the file. + emp::Ptr statement_node = ParseStatement(state); + + // If the current statement is real, add it to the current block. + if (!statement_node.IsNull()) cur_block->AddChild( statement_node ); + } + return cur_block; + } }; + // Load a variable name from the provided scope. + emp::Ptr Parser::ParseVar(ParseState & state, bool create_ok, bool scan_scopes) + { + Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, ")"); + + // First, check for leading dots. + if (state.IsDots()) { + Debug("...found dots: ", state.AsLexeme()); + scan_scopes = false; // One or more initial dots specify scope; don't scan! + size_t num_dots = state.GetTokenSize(); // Extra dots shift scope. + emp::Ptr cur_scope = &state.GetScope(); + while (num_dots-- > 1) { + cur_scope = cur_scope->GetScope(); + if (cur_scope.IsNull()) state.Error("Too many dots; goes beyond global scope."); + } + ++state; + + // Recursively call in the found scope if needed; given leading dot, do not scan scopes. + if (cur_scope.Raw() != &state.GetScope()) { + state.PushScope(*cur_scope); + auto result = ParseVar(state, create_ok, false); + state.PopScope(); + return result; + } + } + + // Next, we must have a variable name. + // @CAO: Or a : ? E.g., technically "..:size" could give you the parent scope size. + state.RequireID("Must provide a variable identifier!"); + std::string var_name = state.UseLexeme(); + + // Lookup this variable. + Debug("...looking up symbol '", var_name, + "' starting at scope '", state.GetScopeName(), + "'; scanning=", scan_scopes); + Symbol & cur_symbol = state.LookupSymbol(var_name, scan_scopes); + + // If this variable just provided a scope, keep going. + if (state.IsDots()) { + state.PushScope(cur_symbol.AsScope()); + auto result = ParseVar(state, create_ok, false); + state.PopScope(); + return result; + } + + // Otherwise return the variable as a leaf! + return emp::NewPtr(&cur_symbol); + } + + // Load a value from the provided scope, which can come from a variable or a literal. + emp::Ptr Parser::ParseValue(ParseState & state) { + Debug("Running ParseValue(", state.AsString(), ")"); + + // Anything that begins with an identifier or dots must represent a variable. Refer! + if (state.IsID() || state.IsDots()) return ParseVar(state, false, true); + + // A literal number should have a temporary created with its value. + if (state.IsNumber()) { + Debug("...value is a number: ", state.AsLexeme()); + double value = emp::from_string(state.UseLexeme()); // Calculate value. + return MakeTempLeaf(value); // Return temporary Symbol. + } + + // A literal char should be converted to its ASCII value. + if (state.IsChar()) { + Debug("...value is a char: ", state.AsLexeme()); + char lit_char = emp::from_literal_char(state.UseLexeme()); // Convert the literal char. + return MakeTempLeaf((double) lit_char); // Return temporary Symbol. + } + + // A literal string should be converted to a regular string and used. + if (state.IsString()) { + Debug("...value is a string: ", state.AsLexeme()); + std::string str = emp::from_literal_string(state.UseLexeme()); // Convert the literal string. + return MakeTempLeaf(str); // Return temporary Symbol. + } + + // If we have an open parenthesis, process everything inside into a single value... + if (state.UseIfChar('(')) { + emp::Ptr out_ast = ParseExpression(state); + state.UseRequiredChar(')', "Expected a close parenthesis in expression."); + return out_ast; + } + + state.Error("Expected a value, found: ", state.AsLexeme()); + + return nullptr; + } + + // Process a single provided operation on two Symbol objects. + emp::Ptr Parser::ProcessOperation(const std::string & symbol, + emp::Ptr in_node1, + emp::Ptr in_node2) + { + emp_assert(!in_node1.IsNull()); + emp_assert(!in_node2.IsNull()); + + // If this operation is assignment, do so! + if (symbol == "=") return emp::NewPtr(in_node1, in_node2); + + // If the first argument is numeric, assume we are using a math operator. + if (in_node1->IsNumeric()) { + + // Determine the output value and put it in a temporary node. + emp::Ptr out_val = emp::NewPtr(symbol); + + if (symbol == "+") out_val->SetFun( [](double v1, double v2){ return v1 + v2; } ); + else if (symbol == "-") out_val->SetFun( [](double v1, double v2){ return v1 - v2; } ); + else if (symbol == "**") out_val->SetFun( [](double v1, double v2){ return emp::Pow(v1, v2); } ); + else if (symbol == "*") out_val->SetFun( [](double v1, double v2){ return v1 * v2; } ); + else if (symbol == "/") out_val->SetFun( [](double v1, double v2){ return v1 / v2; } ); + else if (symbol == "%") out_val->SetFun( [](double v1, double v2){ return emp::Mod(v1, v2); } ); + else if (symbol == "==") out_val->SetFun( [](double v1, double v2){ return v1 == v2; } ); + else if (symbol == "!=") out_val->SetFun( [](double v1, double v2){ return v1 != v2; } ); + else if (symbol == "<") out_val->SetFun( [](double v1, double v2){ return v1 < v2; } ); + else if (symbol == "<=") out_val->SetFun( [](double v1, double v2){ return v1 <= v2; } ); + else if (symbol == ">") out_val->SetFun( [](double v1, double v2){ return v1 > v2; } ); + else if (symbol == ">=") out_val->SetFun( [](double v1, double v2){ return v1 >= v2; } ); + + // @CAO: Need to still handle these last two differently for short-circuiting. + else if (symbol == "&&") out_val->SetFun( [](double v1, double v2){ return v1 && v2; } ); + else if (symbol == "||") out_val->SetFun( [](double v1, double v2){ return v1 || v2; } ); + + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; + } + + // Otherwise assume that we are dealing with strings. + if (symbol == "+") { + auto out_val = emp::NewPtr>(symbol); + out_val->SetFun([](std::string val1, std::string val2){ return val1 + val2; }); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; + } + else if (symbol == "*") { + auto fun = [](std::string val1, double val2) { + std::string out_string; + out_string.reserve(val1.size() * (size_t) val2); + for (size_t i = 0; i < (size_t) val2; i++) out_string += val1; + return out_string; + }; + + auto out_val = emp::NewPtr>(symbol); + out_val->SetFun(fun); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; + } + else { + auto out_val = emp::NewPtr>(symbol); + + if (symbol == "==") out_val->SetFun([](std::string v1, std::string v2){ return v1 == v2; }); + else if (symbol == "!=") out_val->SetFun([](std::string v1, std::string v2){ return v1 != v2; }); + else if (symbol == "<") out_val->SetFun([](std::string v1, std::string v2){ return v1 < v2; }); + else if (symbol == "<=") out_val->SetFun([](std::string v1, std::string v2){ return v1 <= v2; }); + else if (symbol == ">") out_val->SetFun([](std::string v1, std::string v2){ return v1 > v2; }); + else if (symbol == ">=") out_val->SetFun([](std::string v1, std::string v2){ return v1 >= v2; }); + + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; + } + + return nullptr; + } + + + // Calculate an expression in the provided scope. + emp::Ptr Parser::ParseExpression(ParseState & state, size_t prec_limit) { + Debug("Running ParseExpression(", state.AsString(), ", limit=", prec_limit, ")"); + + // @CAO Should test for unary operators at the beginning of an expression. + + /// Process a value (and possibly more!) + emp::Ptr cur_node = ParseValue(state); + std::string op = state.AsLexeme(); + + Debug("...back in ParseExpression; op=`", op, "`; state=", state.AsString()); + + while ( emp::Has(precedence_map, op) && precedence_map[op] < prec_limit ) { + ++state; // Move past the current operator + // Do we have a function call? + if (op == "(") { + // Collect arguments. + emp::vector< emp::Ptr > args; + while (state.AsChar() != ')') { + emp::Ptr next_arg = ParseExpression(state); + args.push_back(next_arg); // Save this argument. + if (state.AsChar() != ',') break; // If we don't have a comma, no more args! + ++state; // Move on to the next argument. + } + state.UseRequiredChar(')', "Expected a ')' to end function call."); + + // cur_node should have evaluated itself to a function; a Call node will link that + // function with its arguments, run it, and return the result. + cur_node = emp::NewPtr(cur_node, args); + } + + // Otherwise we must have a binary math operation. + else { + emp::Ptr node2 = ParseExpression(state, precedence_map[op]); + cur_node = ProcessOperation(op, cur_node, node2); + } + + // Move the current value over to cur_node and check if we have a new operator... + op = state.AsLexeme(); + } + + emp_assert(!cur_node.IsNull()); + return cur_node; + } + + // Parse an the declaration of a variable. + Symbol & Parser::ParseDeclaration(ParseState & state) { + std::string type_name = state.UseLexeme(); + state.RequireID("Type name '", type_name, "' must be followed by variable to declare."); + std::string var_name = state.UseLexeme(); + + if (type_name == "String") { + return state.AddStringVar(var_name, "Local string variable."); + } + else if (type_name == "Value") { + return state.AddValueVar(var_name, "Local value variable."); + } + else if (type_name == "Struct") { + return state.AddScope(var_name, "Local struct"); + } + + // Otherwise we have an object of a custom type to add. + Debug("Building object '", var_name, "' of type '", type_name, "'"); + return state.AddObject(type_name, var_name); + } + + // Parse an event description. + emp::Ptr Parser::ParseEvent(ParseState & state) { + state.UseRequiredChar('@', "All event declarations must being with an '@'."); + state.RequireID("Events must start by specifying event name."); + const std::string & event_name = state.UseLexeme(); + state.UseRequiredChar('(', "Expected parentheses after '", event_name, "' for args."); + + emp::vector> args; + while (state.AsChar() != ')') { + args.push_back( ParseExpression(state) ); + state.UseIfChar(','); // Skip comma if next (does allow trailing comma) + } + state.UseRequiredChar(')', "Event args must end in a ')'"); + + emp::Ptr action = ParseStatement(state); + + Debug("Building event '", event_name, "' with args ", args); + + auto setup_event = + [state, event_name](emp::Ptr action, const emp::vector> & args) mutable + { + state.AddEvent( + event_name, action, + (args.size() > 0) ? args[0]->AsDouble() : 0.0, + (args.size() > 1) ? args[1]->AsDouble() : 0.0, + (args.size() > 2) ? args[2]->AsDouble() : -1.0 + ); + }; + + return emp::NewPtr(event_name, action, args, setup_event); + } + + // Process the next input in the specified Struct. + emp::Ptr Parser::ParseStatement(ParseState & state) { + Debug("Running ParseStatement(", state.AsString(), ")"); + + // Allow a statement with an empty line. + if (state.UseIfChar(';')) { return nullptr; } + + // Allow a statement to be a new scope. + if (state.UseIfChar('{')) { + // @CAO Need to add an anonymous scope (that gets written properly) + emp::Ptr out_node = ParseStatementList(state); + state.UseRequiredChar('}', "Expected '}' to close scope."); + return out_node; + } + + // Allow event definitions if a statement begins with an '@' + if (state.AsChar() == '@') return ParseEvent(state); + + // Allow this statement to be a declaration if it begins with a type. + if (state.IsType()) { + Symbol & new_symbol = ParseDeclaration(state); + + // If the next symbol is a ';' this is a declaration without an assignment. + if (state.AsChar() == ';') { + state++; // Skip the semi-colon. + return nullptr; // We are done! + } + + // If this symbol is a new scope, it can be populated now either directly (with in braces) + // or indirectly (with and assignment) + if (new_symbol.IsScope()) { + if (state.UseIfChar('{')) { + state.PushScope(new_symbol.AsScope()); + emp::Ptr out_node = ParseStatementList(state); + state.PopScope(); + state.UseRequiredChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); + return out_node; + } + + state.RequireChar('=', "Expected scope '", new_symbol.GetName(), + "' definition to start with a '{' or '='; found ''", state.AsLexeme(), "'."); + + } + + // Otherwise rewind so that the new variable can be used to start an expression. + --state; + } + + + // If we made it here, remainder should be an expression. + emp::Ptr out_node = ParseExpression(state); + + // Expressions must end in a semi-colon. + state.UseRequiredChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); + return out_node; + } } #endif From b4f4cfa99744213c707704843b2b989f9edff23c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 3 Nov 2021 12:20:49 -0400 Subject: [PATCH 291/445] Added unary negation to the parser. --- source/Emplode/Parser.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index caa8d437..9fe566fc 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -251,7 +251,7 @@ namespace emplode { { Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, ")"); - // First, check for leading dots. + // Check for leading dots to require this scope (one dot) or indicate a lower-level scope. if (state.IsDots()) { Debug("...found dots: ", state.AsLexeme()); scan_scopes = false; // One or more initial dots specify scope; don't scan! @@ -299,6 +299,14 @@ namespace emplode { emp::Ptr Parser::ParseValue(ParseState & state) { Debug("Running ParseValue(", state.AsString(), ")"); + // First check for a unary negation at the start of the value. + if (state.UseIfChar('-')) { + auto out_val = emp::NewPtr("unary negation"); + out_val->SetFun( [](double val){ return -val; } ); + out_val->AddChild(ParseValue(state)); + return out_val; + } + // Anything that begins with an identifier or dots must represent a variable. Refer! if (state.IsID() || state.IsDots()) return ParseVar(state, false, true); From 880b62147b7c819cf1128e3488cfc5dc592c3706 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 4 Nov 2021 12:55:04 -0400 Subject: [PATCH 292/445] Added AsFunction and AsObject into the base class of Symbol (plus other support) --- source/Emplode/Symbol.hpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index 7cc6609f..f52b38a0 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -35,8 +35,10 @@ namespace emplode { - class Symbol_Scope; class EmplodeType; + class Symbol_Function; + class Symbol_Object; + class Symbol_Scope; class Symbol { protected: @@ -100,15 +102,16 @@ namespace emplode { virtual bool IsNumeric() const { return false; } ///< Is symbol any kind of number? virtual bool IsBool() const { return false; } ///< Is symbol a Boolean value? - virtual bool IsInt() const { return false; } ///< Is symbol a integer value? virtual bool IsDouble() const { return false; } ///< Is symbol a floting point value? + virtual bool IsInt() const { return false; } ///< Is symbol a integer value? virtual bool IsString() const { return false; } ///< Is symbol a string? - virtual bool IsLocal() const { return false; } ///< Was symbol defined in config file? + virtual bool IsError() const { return false; } ///< Does symbol flag an error? virtual bool IsFunction() const { return false; } ///< Is symbol a function? - virtual bool IsScope() const { return false; } ///< Is symbol a full scope? virtual bool IsObject() const { return false; } ///< Is symbol associated with C++ object? - virtual bool IsError() const { return false; } ///< Does symbol flag an error? + virtual bool IsScope() const { return false; } ///< Is symbol a full scope? + + virtual bool IsLocal() const { return false; } ///< Was symbol defined in config file? virtual bool HasNumericReturn() const { return false; } ///< Is symbol a function that returns a number? virtual bool HasStringReturn() const { return false; } ///< Is symbol a function that returns a string? @@ -124,16 +127,19 @@ namespace emplode { virtual Symbol & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } + virtual emp::Ptr AsFunctionPtr() { return nullptr; } + virtual emp::Ptr AsFunctionPtr() const { return nullptr; } + virtual emp::Ptr AsObjectPtr() { return nullptr; } + virtual emp::Ptr AsObjectPtr() const { return nullptr; } virtual emp::Ptr AsScopePtr() { return nullptr; } virtual emp::Ptr AsScopePtr() const { return nullptr; } - Symbol_Scope & AsScope() { - emp_assert(AsScopePtr()); - return *(AsScopePtr()); - } - const Symbol_Scope & AsScope() const { - emp_assert(AsScopePtr()); - return *(AsScopePtr()); - } + + Symbol_Function & AsFunction() { emp_assert(AsFunctionPtr()); return *(AsFunctionPtr()); } + const Symbol_Function & AsFunction() const { emp_assert(AsFunctionPtr()); return *(AsFunctionPtr()); } + Symbol_Object & AsObject() { emp_assert(AsObjectPtr()); return *(AsObjectPtr()); } + const Symbol_Object & AsObject() const { emp_assert(AsObjectPtr()); return *(AsObjectPtr()); } + Symbol_Scope & AsScope() { emp_assert(AsScopePtr()); return *(AsScopePtr()); } + const Symbol_Scope & AsScope() const { emp_assert(AsScopePtr()); return *(AsScopePtr()); } virtual emp::Ptr GetObjectPtr() { return nullptr; } virtual emp::Ptr GetObjectPtr() const { return nullptr; } From 7e9708f0c7dcefc64a6edbc8515e7e24c0703a72 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 4 Nov 2021 12:55:40 -0400 Subject: [PATCH 293/445] Added a generator for DefaultCopyFun() --- source/Emplode/EmplodeTools.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/source/Emplode/EmplodeTools.hpp b/source/Emplode/EmplodeTools.hpp index 56ca47d7..5f79c418 100644 --- a/source/Emplode/EmplodeTools.hpp +++ b/source/Emplode/EmplodeTools.hpp @@ -37,6 +37,17 @@ namespace EmplodeTools { return out_symbol; } + template + static auto DefaultCopyFun() { + return [](const EmplodeType & from, EmplodeType & to) { + emp::Ptr from_ptr = dynamic_cast(&from); + emp::Ptr to_ptr = dynamic_cast(&to); + if (!from_ptr || !to_ptr) return false; + *to_ptr = *from_ptr; + return true; + }; + } + template static auto ConvertReturn( RETURN_T && return_value ) { // If a return value is already a symbol pointer, just pass it through. @@ -180,7 +191,9 @@ namespace EmplodeTools { // Wrap a provided MEMBER function to make it take a reference to the object it is a member of // and a vector of Ptr and return a single Ptr representing the result. template - static auto WrapMemberFunction(emp::TypeID class_type, const std::string & name, FUN_T fun) { + static auto WrapMemberFunction([[maybe_unused]] emp::TypeID class_type, + const std::string & name, FUN_T fun) + { // Do some checks that will produce reasonable errors. using info_t = emp::FunInfo; using index_t = emp::ValPackCount; From 7455e3ea678a337e553e3442e230d3a2a4dc4bb9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:17:16 -0400 Subject: [PATCH 294/445] Added object copying functionality to TypeInfo. --- source/Emplode/TypeInfo.hpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/source/Emplode/TypeInfo.hpp b/source/Emplode/TypeInfo.hpp index 51a7847d..fbb0c605 100644 --- a/source/Emplode/TypeInfo.hpp +++ b/source/Emplode/TypeInfo.hpp @@ -39,6 +39,7 @@ namespace emplode { class TypeInfo { private: using init_fun_t = std::function (const std::string &)>; + using copy_fun_t = std::function; size_t index; std::string type_name; @@ -46,6 +47,7 @@ namespace emplode { emp::TypeID type_id; init_fun_t init_fun; + copy_fun_t copy_fun; bool config_owned = false; // Should objects of this type be managed by Emplode? emp::vector< MemberFunInfo > member_funs; @@ -57,18 +59,26 @@ namespace emplode { // Constructor to allow a new configuration type whose objects require initialization. TypeInfo(size_t _id, const std::string & _name, const std::string & _desc, - init_fun_t _init, bool _config_owned=false) - : index(_id), type_name(_name), desc(_desc), init_fun(_init), config_owned(_config_owned) + init_fun_t _init, copy_fun_t _copy, bool _config_owned=false) + : index(_id), type_name(_name), desc(_desc), + init_fun(_init), copy_fun(_copy), config_owned(_config_owned) { } size_t GetIndex() const { return index; } const std::string & GetTypeName() const { return type_name; } const std::string & GetDesc() const { return desc; } - emp::TypeID GetType() const { return type_id; } + emp::TypeID GetTypeID() const { return type_id; } bool GetOwned() const { return config_owned; } const emp::vector & GetMemberFunctions() const { return member_funs; } - emp::Ptr MakeObj(const std::string & name) const { return init_fun(name); } + emp::Ptr MakeObj(const std::string & name) const { + emp_assert(init_fun, "No initialization function exists for type.", type_name); + return init_fun(name); + } + bool CopyObj(const EmplodeType & from, EmplodeType & to) const { + emp_assert(copy_fun, "No copy function exists for type.", type_name); + return copy_fun(from, to); + } // Link this TypeInfo object to a real C++ type. // @CAO It would be nice to test to make sure this is an EmplodeType, but not possible with a TypeID. From 407dea8e2ae208ca9b08434fb142c991078e93c9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:18:14 -0400 Subject: [PATCH 295/445] Added functionality to copy Function symbols; not sure if needed? --- source/Emplode/Symbol_Function.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/Emplode/Symbol_Function.hpp b/source/Emplode/Symbol_Function.hpp index f1b931da..77237506 100644 --- a/source/Emplode/Symbol_Function.hpp +++ b/source/Emplode/Symbol_Function.hpp @@ -53,6 +53,26 @@ namespace emplode { bool HasNumericReturn() const override { return numeric_return; } bool HasStringReturn() const override { return string_return; } + /// Set this symbol to be a correctly-typed scope pointer. + emp::Ptr AsFunctionPtr() override { return this; } + emp::Ptr AsFunctionPtr() const override { return this; } + + bool CopyValue(const Symbol & in) override { + if (in.IsFunction() == false) { + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but " << in.GetName() << " is not a Function." << std::endl; + return false; // Mis-matched types; failed to copy. + } + + const Symbol_Function & in_fun = in.AsFunction(); + fun = in_fun.fun; + numeric_return = in_fun.numeric_return; + string_return = in_fun.string_return; + + return true; + } + + symbol_ptr_t Call( const emp::vector & args ) override { return fun(args); } }; From 6c5ecf4be6f01fc1affbb409624646205146e93d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:19:46 -0400 Subject: [PATCH 296/445] Added error messages to Symbol_Scope; prevent copying of functions. --- source/Emplode/Symbol_Scope.hpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index c4214256..7b4312c0 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -71,7 +71,11 @@ namespace emplode { emp::Ptr AsScopePtr() const override { return this; } bool CopyValue(const Symbol & in) override { - if (in.IsScope() == false) return false; // Mis-matched types; failed to copy. + if (in.IsScope() == false) { + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but " << in.GetName() << " is not a Scope." << std::endl; + return false; // Mis-matched types; failed to copy. + } const Symbol_Scope & in_scope = in.AsScope(); @@ -79,10 +83,20 @@ namespace emplode { // Do not delete other existing entries. for (const auto & [name, ptr] : in_scope.symbol_table) { // If entry does not exist fail the copy. - if (emp::Has(symbol_table, name)) return false; + if (!emp::Has(symbol_table, name)) { + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but " << GetName() << "." << name << " does not exist." << std::endl; + return false; + } + + if (ptr->IsFunction()) continue; // Don't copy functions. bool success = symbol_table[name]->CopyValue(*ptr); - if (!success) return false; // Stop immediately on failure. + if (!success) { + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but failed on `" << GetName() << "." << name << "`." << std::endl; + return false; // Stop immediately on failure. + } } // If we made it this far, it must have worked! From 24134d9235a09a34035194b1b8139589780413f1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:21:06 -0400 Subject: [PATCH 297/445] Changed EmplodeType::GetScope() to AsScope(); removed dependency on Symbol_Object. --- source/Emplode/EmplodeType.hpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 53abb3c6..463aeee3 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -13,17 +13,18 @@ #include "emp/base/assert.hpp" -#include "Symbol_Object.hpp" +#include "Symbol_Scope.hpp" #include "TypeInfo.hpp" namespace emplode { class Emplode; + class Symbol_Object; // Base class for types that we want to be used for scripting. class EmplodeType { protected: - emp::Ptr symbol_ptr; + emp::Ptr symbol_ptr; emp::Ptr type_info_ptr; // Some special, internal variables associated with each object. @@ -44,8 +45,14 @@ namespace emplode { // Optional function to override to add configuration options associated with an object. virtual void SetupConfig() { }; - Symbol_Object & GetScope() { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } - const Symbol_Object & GetScope() const { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr; } + Symbol_Scope & AsScope() { + emp_assert(!symbol_ptr.IsNull()); + return *symbol_ptr.DynamicCast(); + } + const Symbol_Scope & AsScope() const { + emp_assert(!symbol_ptr.IsNull()); + return *symbol_ptr.DynamicCast(); + } const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } @@ -91,7 +98,7 @@ namespace emplode { const std::string & name, const std::string & desc, bool is_builtin = false) { - return GetScope().LinkVar(name, var, desc, is_builtin); + return AsScope().LinkVar(name, var, desc, is_builtin); } /// Link a configuration entry to a pair of functions - it automatically calls the set @@ -102,7 +109,7 @@ namespace emplode { const std::string & name, const std::string & desc, bool is_builtin = false) { - return GetScope().LinkFuns(name, get_fun, set_fun, desc, is_builtin); + return AsScope().LinkFuns(name, get_fun, set_fun, desc, is_builtin); } // Helper functions and info. @@ -153,7 +160,7 @@ namespace emplode { new_desc << "\n " << entry.name << ": " << entry.desc; } - return GetScope().LinkFuns(name, get_fun, set_fun, new_desc.str()); + return AsScope().LinkFuns(name, get_fun, set_fun, new_desc.str()); } }; } From 677b12dab8a8cd9c3c12fc220ac18fec3dcd8994 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:22:04 -0400 Subject: [PATCH 298/445] Setup proper dependency on EmplodeType. --- source/Emplode/Symbol_Object.hpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index 8a2ef7e0..98c71718 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -13,12 +13,11 @@ #include "emp/base/map.hpp" +#include "EmplodeType.hpp" #include "Symbol_Scope.hpp" namespace emplode { - class EmplodeType; - // Set of multiple config entries. class Symbol_Object : public Symbol_Scope { protected: @@ -34,9 +33,12 @@ namespace emplode { bool _owned) : Symbol_Scope(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } - Symbol_Object(const Symbol_Object & in) : Symbol_Scope(in) { + Symbol_Object(const Symbol_Object & in) : Symbol_Scope(in) { // Copy the internal object. // @CAO MUST DO THIS!!!!!!!!!!!!!!!!!!!!! + + // Copy all defined variables/scopes/functions + for (auto [name, ptr] : symbol_table) { symbol_table[name] = ptr->Clone(); } } Symbol_Object(Symbol_Object && in) : Symbol_Scope(std::move(in)), obj_ptr(in.obj_ptr), obj_owned(in.obj_owned) @@ -51,10 +53,18 @@ namespace emplode { if (obj_owned) obj_ptr.Delete(); } - emp::Ptr GetObjectPtr() override { return obj_ptr; } + emp::Ptr GetObjectPtr() override { return obj_ptr; } emp::Ptr GetObjectPtr() const override { return obj_ptr; } bool IsObject() const override { return true; } + emp::TypeID GetObjectType() { + if (obj_ptr.IsNull()) return emp::GetTypeID(); + return obj_ptr->GetTypeInfo().GetTypeID(); + } + + /// Set this symbol to be a correctly-typed scope pointer. + emp::Ptr AsObjectPtr() override { return this; } + emp::Ptr AsObjectPtr() const override { return this; } /// Make a copy of this scope and all of the entries inside it. emp::Ptr Clone() const override { return emp::NewPtr(*this); } From 40dbd8e72dcdcd6b72b872d50c47c630a3dde49a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 13:22:54 -0400 Subject: [PATCH 299/445] Track line numbers in AST for better error reporting (should also use filenames...) --- source/Emplode/AST.hpp | 48 +++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index cc5c0aaf..99f351db 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -17,6 +17,7 @@ #include "Symbol.hpp" #include "Symbol_Scope.hpp" +#include "Symbol_Object.hpp" #include "EmplodeTools.hpp" namespace emplode { @@ -31,11 +32,15 @@ namespace emplode { using node_vector_t = emp::vector; node_ptr_t parent = nullptr; + int line_id = -1; // Line number of input file with error. public: ASTNode() { ; } virtual ~ASTNode() { ; } + int GetLine() const { return line_id; } + void SetLine(int in_line) { line_id = in_line; } + virtual const std::string & GetName() const = 0; virtual bool IsNumeric() const { return false; } // Can node be represented as a number? @@ -88,8 +93,11 @@ namespace emplode { bool own_symbol; ///< Should this node be in charge of deleting the symbol? public: - ASTNode_Leaf(symbol_ptr_t _ptr) : symbol_ptr(_ptr), own_symbol(_ptr->IsTemporary()) { + ASTNode_Leaf(symbol_ptr_t _ptr, int _line=-1) + : symbol_ptr(_ptr), own_symbol(_ptr->IsTemporary()) + { symbol_ptr->SetTemporary(false); // If this symbol was temporary, it is now owned. + line_id = _line; } ~ASTNode_Leaf() { if (own_symbol) symbol_ptr.Delete(); } @@ -139,7 +147,9 @@ namespace emplode { emp::Ptr scope_ptr; public: - ASTNode_Block(Symbol_Scope & in_scope) : scope_ptr(&in_scope) { } + ASTNode_Block(Symbol_Scope & in_scope, int in_line=-1) : scope_ptr(&in_scope) { + line_id = in_line; + } emp::Ptr GetScope() override { return scope_ptr; } @@ -165,7 +175,9 @@ namespace emplode { // A unary operator take in a double and returns another one. std::function< double(double) > fun; public: - ASTNode_Math1(const std::string & name) : ASTNode_Internal(name) { } + ASTNode_Math1(const std::string & name, int _line=-1) : ASTNode_Internal(name) { + line_id = _line; + } bool IsNumeric() const override { return true; } bool HasValue() const override { return true; } @@ -192,7 +204,9 @@ namespace emplode { protected: std::function< RETURN_T(ARG1_T, ARG2_T) > fun; public: - ASTNode_Op2(const std::string & name) : ASTNode_Internal(name) { } + ASTNode_Op2(const std::string & name, int _line=-1) : ASTNode_Internal(name) { + line_id = _line; + } bool IsNumeric() const override { return std::is_same(); } bool IsString() const override { return std::is_same(); } @@ -222,9 +236,10 @@ namespace emplode { class ASTNode_Assign : public ASTNode_Internal { public: - ASTNode_Assign(node_ptr_t lhs, node_ptr_t rhs) { + ASTNode_Assign(node_ptr_t lhs, node_ptr_t rhs, int _line=-1) { AddChild(lhs); AddChild(rhs); + line_id = _line; } bool IsNumeric() const override { return children[0]->IsNumeric(); } @@ -238,8 +253,17 @@ namespace emplode { symbol_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. symbol_ptr_t rhs = children[1]->Process(); // Determine the right-hand-side value. + if (lhs->IsObject() && lhs->AsObject().GetObjectType().GetName() == "mabe::Population") { + std::cout << "BREAKPOINT!" << std::endl; + //emp_error("BREAKPOINT! line=", line_id); + } + // @CAO Should make sure that lhs is properly assignable. - lhs->CopyValue(*rhs); + bool success = lhs->CopyValue(*rhs); + if (!success) { + std::cerr << "Error: copy to '" << lhs->GetName() << "' failed" << std::endl; + exit(1); + } if (rhs->IsTemporary()) rhs.Delete(); return lhs; } @@ -253,9 +277,10 @@ namespace emplode { class ASTNode_Call : public ASTNode_Internal { public: - ASTNode_Call(node_ptr_t fun, const node_vector_t & args) { + ASTNode_Call(node_ptr_t fun, const node_vector_t & args, int _line=-1) { AddChild(fun); for (auto arg : args) AddChild(arg); + line_id = _line; } bool IsNumeric() const override { return children[0]->HasNumericReturn(); } @@ -297,11 +322,18 @@ namespace emplode { setup_fun_t setup_event; public: - ASTNode_Event(const std::string & event_name, node_ptr_t action, const node_vector_t & args, setup_fun_t in_fun) + ASTNode_Event( + const std::string & event_name, + node_ptr_t action, + const node_vector_t & args, + setup_fun_t in_fun, + int _line=-1 + ) : ASTNode_Internal(event_name), setup_event(in_fun) { AddChild(action); for (auto arg : args) AddChild(arg); + line_id = _line; } symbol_ptr_t Process() override { From 5f19d7ebe2ca02f51e249ab27c1e3b52db28f9a2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 14:51:44 -0400 Subject: [PATCH 300/445] Change DataFile to use a pointer to StreamManager to facilitate copying. --- source/Emplode/DataFile.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/source/Emplode/DataFile.hpp b/source/Emplode/DataFile.hpp index 0b4fe113..349d312e 100644 --- a/source/Emplode/DataFile.hpp +++ b/source/Emplode/DataFile.hpp @@ -16,6 +16,7 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" +#include "emp/io/StreamManager.hpp" #include "EmplodeType.hpp" @@ -31,18 +32,21 @@ namespace emplode { fun_t fun; }; - std::string name=""; ///< Unique name for this object. - emp::StreamManager & files; ///< Global file manager. + std::string name=""; ///< Unique name for this object. + emp::Ptr files; ///< Global file manager. - std::string filename; ///< Name of output file. - emp::vector cols; ///< Data about columns maintainted. + std::string filename; ///< Name of output file. + emp::vector cols; ///< Data about columns maintainted. public: DataFile() = delete; DataFile(const std::string & in_name, emp::StreamManager & _files) - : name(in_name), files(_files) { } + : name(in_name), files(&_files) { } + DataFile(const DataFile &) = default; ~DataFile() { } + DataFile & operator=(const DataFile &) = default; + std::string GetName() const { return name; } // Setup member functions associated with population. @@ -64,8 +68,8 @@ namespace emplode { } size_t Write() { - const bool file_exists = files.Has(filename); // Is file is already setup? - std::ostream & file = files.GetOutputStream(filename); // File to write to. + const bool file_exists = files->Has(filename); // Is file is already setup? + std::ostream & file = files->GetOutputStream(filename); // File to write to. // If we need headers, set them up! if (!file_exists) { From beb9d12505196fa44cec25d5d93cf86a3283335b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 14:52:34 -0400 Subject: [PATCH 301/445] Make symbol tables handle functions for copying of objects. --- source/Emplode/SymbolTable.hpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index 37997d2c..e0a31f30 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -9,6 +9,9 @@ * */ +#ifndef EMPLODE_SYMBOL_TABLE_HPP +#define EMPLODE_SYMBOL_TABLE_HPP + #include #include #include @@ -22,9 +25,6 @@ #include "Symbol_Scope.hpp" -#ifndef EMPLODE_SYMBOL_TABLE_HPP -#define EMPLODE_SYMBOL_TABLE_HPP - namespace emplode { class SymbolTable { @@ -71,17 +71,19 @@ namespace emplode { /// To add a type, provide the type name (that can be referred to in a script) and a function /// that should be called (with the variable name) when an instance of that type is created. /// The function must return a reference to the newly created instance. - template + template TypeInfo & AddType( const std::string & type_name, const std::string & desc, - FUN_T init_fun, + INIT_FUN_T init_fun, + COPY_FUN_T copy_fun, emp::TypeID type_id, bool is_config_owned=false ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, init_fun, is_config_owned ); + auto info_ptr = emp::NewPtr( index, type_name, desc, + init_fun, copy_fun, is_config_owned ); info_ptr->LinkType(type_id); type_map[type_name] = info_ptr; return *type_map[type_name]; @@ -89,26 +91,29 @@ namespace emplode { /// If the linked type can be provided as a template parameter, we can also double check that /// it is derived from EmplodeType (as it needs to be...) - template + template TypeInfo & AddType( const std::string & type_name, const std::string & desc, - FUN_T init_fun, + INIT_FUN_T init_fun, + COPY_FUN_T copy_fun, bool is_config_owned=false ) { static_assert(std::is_base_of(), "Only EmplodeType objects can be used as a custom config type."); - TypeInfo & info = AddType(type_name, desc, init_fun, emp::GetTypeID(), is_config_owned); + TypeInfo & info = AddType(type_name, desc, init_fun, copy_fun, + emp::GetTypeID(), is_config_owned); OBJECT_T::InitType(info); return info; } - /// If init_fun is not specified in add type, build our own and assume that we own the object. + /// If init_fun and copy_fun are not specified in add type, build our own and assume that we + /// own the object. template TypeInfo & AddType(const std::string & type_name, const std::string & desc) { - return AddType(type_name, desc, - [](const std::string & /*name*/){ return emp::NewPtr(); }, - true); + auto init_fun = [](const std::string & /*name*/){ return emp::NewPtr(); }; + auto copy_fun = EmplodeTools::DefaultCopyFun(); + return AddType(type_name, desc, init_fun, copy_fun, true); } Symbol_Object & MakeObjSymbol( From 8dddea8e5e0b4d4924995fcf74fe9b2189e1915e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 14:53:23 -0400 Subject: [PATCH 302/445] Setup parser to provide AST with file line information for error reporting. --- source/Emplode/Parser.hpp | 45 +++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 9fe566fc..11866012 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -19,6 +19,7 @@ #include "emp/base/vector.hpp" #include "emp/tools/string_utils.hpp" +#include "AST.hpp" #include "Lexer.hpp" #include "Symbol_Scope.hpp" #include "SymbolTable.hpp" @@ -57,6 +58,7 @@ namespace emplode { bool AtEnd() const { return pos.AtEnd(); } size_t GetIndex() const { return pos.GetIndex(); } ///< Return index in token stream. + int GetLine() const { return (int) pos->line_id; } size_t GetTokenSize() const { return pos.IsValid() ? pos->lexeme.size() : 0; } Symbol_Scope & GetScope() { emp_assert(scope_stack.size() && scope_stack.back() != nullptr); @@ -87,6 +89,9 @@ namespace emplode { /// Convert the current state to a character; use \0 if cur token is not a symbol. char AsChar() const { return (pos && lexer->IsSymbol(*pos)) ? pos->lexeme[0] : 0; } + /// Return the token associate with the current state. + emp::Token AsToken() const { return *pos; } + /// Return the lexeme associate with the current state. const std::string & AsLexeme() const { return pos ? pos->lexeme : emp::empty_string(); } @@ -214,7 +219,7 @@ namespace emplode { [[nodiscard]] emp::Ptr ParseValue(ParseState & state); /// Calculate the result of the provided operation on two computed entries. - [[nodiscard]] emp::Ptr ProcessOperation(const std::string & symbol, + [[nodiscard]] emp::Ptr ProcessOperation(const emp::Token & op_token, emp::Ptr value1, emp::Ptr value2); @@ -234,7 +239,7 @@ namespace emplode { /// Keep parsing statements until there aren't any more or we leave this scope. [[nodiscard]] emp::Ptr ParseStatementList(ParseState & state) { Debug("Running ParseStatementList(", state.AsString(), ")"); - auto cur_block = emp::NewPtr(state.GetScope()); + auto cur_block = emp::NewPtr(state.GetScope(), state.GetLine()); while (state.IsValid() && state.AsChar() != '}') { // Parse each statement in the file. emp::Ptr statement_node = ParseStatement(state); @@ -249,7 +254,9 @@ namespace emplode { // Load a variable name from the provided scope. emp::Ptr Parser::ParseVar(ParseState & state, bool create_ok, bool scan_scopes) { - Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, ")"); + int start_line = state.GetLine(); + Debug("Running ParseVar(", state.AsString(), ",", create_ok, ",", scan_scopes, + ") at line ", start_line); // Check for leading dots to require this scope (one dot) or indicate a lower-level scope. if (state.IsDots()) { @@ -292,7 +299,7 @@ namespace emplode { } // Otherwise return the variable as a leaf! - return emp::NewPtr(&cur_symbol); + return emp::NewPtr(&cur_symbol, start_line); } // Load a value from the provided scope, which can come from a variable or a literal. @@ -301,7 +308,7 @@ namespace emplode { // First check for a unary negation at the start of the value. if (state.UseIfChar('-')) { - auto out_val = emp::NewPtr("unary negation"); + auto out_val = emp::NewPtr("unary negation", state.GetLine()); out_val->SetFun( [](double val){ return -val; } ); out_val->AddChild(ParseValue(state)); return out_val; @@ -344,21 +351,22 @@ namespace emplode { } // Process a single provided operation on two Symbol objects. - emp::Ptr Parser::ProcessOperation(const std::string & symbol, + emp::Ptr Parser::ProcessOperation(const emp::Token & op_token, emp::Ptr in_node1, emp::Ptr in_node2) { + const std::string symbol = op_token.lexeme; emp_assert(!in_node1.IsNull()); emp_assert(!in_node2.IsNull()); // If this operation is assignment, do so! - if (symbol == "=") return emp::NewPtr(in_node1, in_node2); + if (symbol == "=") return emp::NewPtr(in_node1, in_node2, op_token.line_id); // If the first argument is numeric, assume we are using a math operator. if (in_node1->IsNumeric()) { // Determine the output value and put it in a temporary node. - emp::Ptr out_val = emp::NewPtr(symbol); + emp::Ptr out_val = emp::NewPtr(symbol, op_token.line_id); if (symbol == "+") out_val->SetFun( [](double v1, double v2){ return v1 + v2; } ); else if (symbol == "-") out_val->SetFun( [](double v1, double v2){ return v1 - v2; } ); @@ -385,7 +393,8 @@ namespace emplode { // Otherwise assume that we are dealing with strings. if (symbol == "+") { - auto out_val = emp::NewPtr>(symbol); + auto out_val = + emp::NewPtr>(symbol, op_token.line_id); out_val->SetFun([](std::string val1, std::string val2){ return val1 + val2; }); out_val->AddChild(in_node1); out_val->AddChild(in_node2); @@ -400,7 +409,7 @@ namespace emplode { return out_string; }; - auto out_val = emp::NewPtr>(symbol); + auto out_val = emp::NewPtr>(symbol, op_token.line_id); out_val->SetFun(fun); out_val->AddChild(in_node1); out_val->AddChild(in_node2); @@ -408,7 +417,7 @@ namespace emplode { return out_val; } else { - auto out_val = emp::NewPtr>(symbol); + auto out_val = emp::NewPtr>(symbol, op_token.line_id); if (symbol == "==") out_val->SetFun([](std::string v1, std::string v2){ return v1 == v2; }); else if (symbol == "!=") out_val->SetFun([](std::string v1, std::string v2){ return v1 != v2; }); @@ -435,6 +444,7 @@ namespace emplode { /// Process a value (and possibly more!) emp::Ptr cur_node = ParseValue(state); + emp::Token op_token = state.AsToken(); std::string op = state.AsLexeme(); Debug("...back in ParseExpression; op=`", op, "`; state=", state.AsString()); @@ -455,17 +465,18 @@ namespace emplode { // cur_node should have evaluated itself to a function; a Call node will link that // function with its arguments, run it, and return the result. - cur_node = emp::NewPtr(cur_node, args); + cur_node = emp::NewPtr(cur_node, args, op_token.line_id); } // Otherwise we must have a binary math operation. else { emp::Ptr node2 = ParseExpression(state, precedence_map[op]); - cur_node = ProcessOperation(op, cur_node, node2); + cur_node = ProcessOperation(op_token, cur_node, node2); } // Move the current value over to cur_node and check if we have a new operator... op = state.AsLexeme(); + op_token = state.AsToken(); } emp_assert(!cur_node.IsNull()); @@ -495,6 +506,7 @@ namespace emplode { // Parse an event description. emp::Ptr Parser::ParseEvent(ParseState & state) { + emp::Token start_token = state.AsToken(); state.UseRequiredChar('@', "All event declarations must being with an '@'."); state.RequireID("Events must start by specifying event name."); const std::string & event_name = state.UseLexeme(); @@ -522,7 +534,7 @@ namespace emplode { ); }; - return emp::NewPtr(event_name, action, args, setup_event); + return emp::NewPtr(event_name, action, args, setup_event, start_token.line_id); } // Process the next input in the specified Struct. @@ -548,10 +560,7 @@ namespace emplode { Symbol & new_symbol = ParseDeclaration(state); // If the next symbol is a ';' this is a declaration without an assignment. - if (state.AsChar() == ';') { - state++; // Skip the semi-colon. - return nullptr; // We are done! - } + if (state.UseIfChar(';')) return nullptr; // We are done! // If this symbol is a new scope, it can be populated now either directly (with in braces) // or indirectly (with and assignment) From 208a6dbb4d1338cccd1b55118ad958b05313e12c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 5 Nov 2021 14:53:53 -0400 Subject: [PATCH 303/445] Updated DeveloperNotes to reflect other changes in Emplode. --- source/Emplode/DeveloperNotes.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/source/Emplode/DeveloperNotes.md b/source/Emplode/DeveloperNotes.md index b9bf5506..9485cae4 100644 --- a/source/Emplode/DeveloperNotes.md +++ b/source/Emplode/DeveloperNotes.md @@ -15,22 +15,27 @@ Symbol_Linked - [Symbol] Symbol_Scope - [Symbol,Symbol_Function,Symbol_Linked] -Symbol_Object - [Symbol_Scope] +EmplodeType - [Symbol_Scope,TypeInfo] + +Symbol_Object - [Symbol_Scope,EmplodeType] AST - [Symbol_Object,Symbol,EmplodeTools] -EmplodeType - [Symbol_Object,TypeInfo] Events - [AST] DataFile - [EmplodeType] -Emplode - [ALL] Main parser +SymbolTable - [Events,Symbol_Scope] + +Parser - [AST,Lexer,SymbolTable] + +Emplode - [ALL] TODO: * Emplode as a whole should move from MABE to Empirical -* We need a consistent and functional error system. +* We need a consistent and functional ERROR SYSTEM. Right now we either output direct to the command line OR create a Symbol_Error with a meaningful error included, but it's then ignored and never printed. What should happen is that an error message is raised and the appropriate interface From 51e2edbd96fdb2e42d8864fd5d6236906152946d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:20:07 -0500 Subject: [PATCH 304/445] Setup a CopyValue() member function for EmplodeType --- source/Emplode/EmplodeType.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 463aeee3..1ac9595a 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -45,6 +45,10 @@ namespace emplode { // Optional function to override to add configuration options associated with an object. virtual void SetupConfig() { }; + // Normally when an EmplodeType is copied, just the scope variables are copied over. + // Override CopyValue() if more needs to happen. + virtual bool CopyValue(const EmplodeType &) { return false; } + Symbol_Scope & AsScope() { emp_assert(!symbol_ptr.IsNull()); return *symbol_ptr.DynamicCast(); From e69dbe52c00c689a27be21f01462cc98ac1bd501 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:21:13 -0500 Subject: [PATCH 305/445] Setup TypeInfo::CopyObj() to copy an object value if it's been told how to. --- source/Emplode/TypeInfo.hpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/source/Emplode/TypeInfo.hpp b/source/Emplode/TypeInfo.hpp index fbb0c605..86c12700 100644 --- a/source/Emplode/TypeInfo.hpp +++ b/source/Emplode/TypeInfo.hpp @@ -55,14 +55,19 @@ namespace emplode { public: // Constructor to allow a simple new configuration type TypeInfo(size_t _id, const std::string & _name, const std::string & _desc) - : index(_id), type_name(_name), desc(_desc) { } + : index(_id), type_name(_name), desc(_desc) + { + emp_assert(type_name != ""); + } // Constructor to allow a new configuration type whose objects require initialization. TypeInfo(size_t _id, const std::string & _name, const std::string & _desc, init_fun_t _init, copy_fun_t _copy, bool _config_owned=false) : index(_id), type_name(_name), desc(_desc), init_fun(_init), copy_fun(_copy), config_owned(_config_owned) - { } + { + emp_assert(type_name != ""); + } size_t GetIndex() const { return index; } const std::string & GetTypeName() const { return type_name; } @@ -76,8 +81,8 @@ namespace emplode { return init_fun(name); } bool CopyObj(const EmplodeType & from, EmplodeType & to) const { - emp_assert(copy_fun, "No copy function exists for type.", type_name); - return copy_fun(from, to); + if (copy_fun) return copy_fun(from, to); + return false; } // Link this TypeInfo object to a real C++ type. From 5a25ca28e66802fc3a8b7e798b5b6542a4449393 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:22:09 -0500 Subject: [PATCH 306/445] Added a CopyValue() override to Symbol_Object that copies the scope then runs any special copying. --- source/Emplode/Symbol_Object.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index 98c71718..f74fa24b 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -66,6 +66,26 @@ namespace emplode { emp::Ptr AsObjectPtr() override { return this; } emp::Ptr AsObjectPtr() const override { return this; } + bool CopyValue(const Symbol & in) override { + if (in.IsObject() == false) { + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but " << in.GetName() << " is not an Object." << std::endl; + return false; // Mis-matched types; failed to copy. + } + + // Copy the underlying scope... + Symbol_Scope::CopyValue(in); + + // Now copy special details for the object. + const Symbol_Object & in_object = in.AsObject(); + + // If typeinfo knows how to make this copy, let it. + if (obj_ptr->GetTypeInfo().CopyObj(*in_object.obj_ptr, *obj_ptr)) return true; + + // Otherwise use the default copy method for the object. + return obj_ptr->CopyValue(*in_object.obj_ptr); + } + /// Make a copy of this scope and all of the entries inside it. emp::Ptr Clone() const override { return emp::NewPtr(*this); } }; From cf164b2b299a9d4fac92acd2d7ee8cd42f034070 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:22:58 -0500 Subject: [PATCH 307/445] Added a copy function for DataFile. --- source/Emplode/Emplode.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 5d0b10f0..befaae0b 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -131,6 +131,7 @@ namespace emplode { // 'PRINT' is a simple debugging command to output the value of a variable. auto print_fun = [](const emp::vector> & args) { for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); + std::cout << std::endl; return 0; }; AddFunction("PRINT", print_fun, "Print out the provided variables."); @@ -185,7 +186,9 @@ namespace emplode { auto df_init = [this](const std::string & name) { return emp::NewPtr(name, symbol_table.GetFileManager()); }; - auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, true); + auto df_copy = EmplodeTools::DefaultCopyFun(); + auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", + df_init, df_copy, true); df_type.AddMemberFunction( "ADD_COLUMN", [exec_fun](DataFile & file, const std::string & title, const std::string & expression){ From 2595edbd5b5967d9ce1020bffc0b62e1fd77eb5c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:24:31 -0500 Subject: [PATCH 308/445] Added a base CopyValue() for modules so that they are not required to override it. --- source/core/ModuleBase.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 925bad1e..7bbd9409 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -201,6 +201,12 @@ namespace mabe { for (auto & x : trait_map) x.second.Delete(); } + /// By DEFAULT modules do not do anything extra when copying themselves. + bool CopyValue(const EmplodeType &) override { return true; } + + /// By DEFAULT modules do not do anything to setup configurations. + void SetupConfig() override { } + const std::string & GetName() const noexcept { return name; } const std::string & GetDesc() const noexcept { return desc; } @@ -320,8 +326,6 @@ namespace mabe { emp::Ptr Make(emp::Random & random) { return Make_impl(random).template DynamicCast(); } - - virtual void SetupConfig() { } }; struct ModuleInfo { From 3a9b62ad31675df79e9decbaf804a0c63a0b0493 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:28:14 -0500 Subject: [PATCH 309/445] Make populations and modules copy-function aware. --- source/core/MABE.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 6db963dd..0ae8bedd 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -44,6 +44,8 @@ namespace mabe { + using namespace emplode::EmplodeTools; + /// @brief The main MABE controller class /// /// The MABE controller class manages interactions between all modules, @@ -578,7 +580,9 @@ namespace mabe { { // Setup "Population" as a type in the config file. auto pop_init_fun = [this](const std::string & name) { return &AddPopulation(name); }; - auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun); + auto pop_copy_fun = DefaultCopyFun(); + auto & pop_type = config.AddType("Population", "Collection of organisms", + pop_init_fun, pop_copy_fun); // 'INJECT' allows a user to add an organism to a population. std::function inject_fun = @@ -599,7 +603,7 @@ namespace mabe { auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { return mod.init_fun(*this,name); }; - config.AddType(mod.name, mod.desc, mod_init_fun, mod.type_id); + config.AddType(mod.name, mod.desc, mod_init_fun, nullptr, mod.type_id); } From e4a6deccc5884a0f4cfbb2db7f67c1f2729c1cd9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 7 Nov 2021 12:28:37 -0500 Subject: [PATCH 310/445] Change module base from using GetScope() to more acurate AsScope() --- source/core/Module.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index f4dc1901..6201f6a2 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -63,7 +63,7 @@ namespace mabe { if (var == -1) AddError("Trying to access population '", name, "'; does not exist."); }; - return GetScope().LinkFuns(name, get_fun, set_fun, desc); + return AsScope().LinkFuns(name, get_fun, set_fun, desc); } /// Link one or more populations (or portions of a population) to a parameter. @@ -80,7 +80,7 @@ namespace mabe { var = control.ToCollection(load_str); }; - return GetScope().LinkFuns(name, get_fun, set_fun, desc); + return AsScope().LinkFuns(name, get_fun, set_fun, desc); } /// Link another module to this one, by name (track using int ID) @@ -98,7 +98,7 @@ namespace mabe { if (var == -1) AddError("Trying to access module '", name, "'; does not exist."); }; - return GetScope().LinkFuns(name, get_fun, set_fun, desc); + return AsScope().LinkFuns(name, get_fun, set_fun, desc); } /// Link a range of values with a start, stop, and step. @@ -123,7 +123,7 @@ namespace mabe { stop_var = name.size() ? emp::from_string(name) : -1; // -1 indicates no stop. }; - return GetScope().LinkFuns(name, get_fun, set_fun, desc); + return AsScope().LinkFuns(name, get_fun, set_fun, desc); } public: From a6d0eb2605bcd6bb008434683d66a0040cd154ac Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 11:46:46 -0500 Subject: [PATCH 311/445] Setup organisms to track the population that they are part of. --- source/core/Organism.hpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 01c00b87..24b2f55c 100644 --- a/source/core/Organism.hpp +++ b/source/core/Organism.hpp @@ -36,14 +36,28 @@ namespace mabe { + class Population; + class Organism : public OrgType, public emp::AnnotatedType { + private: + emp::Ptr pop_ptr = nullptr; public: Organism(ModuleBase & _man) : OrgType(_man) { ; } - virtual ~Organism() {} + virtual ~Organism() { + emp_assert( + pop_ptr.IsNull(), + "Organisms must be removed from populations before deletion; use MABE::ClearOrgAt()." + ); + } /// Test if this organism represents an empty cell. virtual bool IsEmpty() const noexcept { return false; } + emp::Ptr GetPopPtr() const { return pop_ptr; } + Population & GetPopulation() { return *pop_ptr; } + void SetPopulation(Population & in) { pop_ptr = ∈ } + void ClearPopulation() { pop_ptr = nullptr; } + /// Specialty version of Clone to return an Organism type. [[nodiscard]] emp::Ptr CloneOrganism() const { return OrgType::Clone().DynamicCast(); From b925b76bdcacd877099685aa1f1ada7cde72c2a6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 11:49:09 -0500 Subject: [PATCH 312/445] Added new Population functions; support org population tracking; improve memory management. --- source/core/Population.hpp | 43 ++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index d587b7c6..698a7b84 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -98,7 +98,6 @@ namespace mabe { using iterator_t = PopIterator; using const_iterator_t = ConstPopIterator; - public: Population() { emp_assert(false, "Do not use default constructor on Population!"); } Population(const std::string & in_name, size_t in_id, @@ -125,11 +124,14 @@ namespace mabe { emp_assert(OK()); } + // All organism copying must be tracked! + Population & operator=(const Population & in_pop) = delete; + // Populations can be copied, but should not be moved to maintain correct empty orgs. Population(Population &&) = delete; Population & operator=(Population &&) = delete; - ~Population() { for (auto x : orgs) if (!x->IsEmpty()) x.Delete(); } + ~Population() { emp_assert(num_orgs==0, "Population should be cleaned up before deletion."); } std::string GetName() const override { return name; } int GetID() const noexcept override { return pop_id; } @@ -140,6 +142,7 @@ namespace mabe { bool IsEmpty(size_t pos) const { return IsValid(pos) && orgs[pos]->IsEmpty(); } bool IsOccupied(size_t pos) const { return IsValid(pos) && !orgs[pos]->IsEmpty(); } + void SetName(const std::string & in_name) { name = in_name; } void SetID(int in_id) noexcept { pop_id = in_id; } Organism & operator[](size_t org_id) { return *(orgs[org_id]); } @@ -160,8 +163,9 @@ namespace mabe { void SetOrg(size_t pos, emp::Ptr org_ptr) { emp_assert(pos < orgs.size()); emp_assert(IsEmpty(pos)); // Must be valid and should not overwrite a living cell. - emp_assert(!org_ptr->IsEmpty()); // Use ClearOrg if you want to empty a cell. + emp_assert(!org_ptr->IsEmpty()); // Use ExtractOrg if you want to make a cell empty. orgs[pos] = org_ptr; + org_ptr->SetPopulation(*this); num_orgs++; } @@ -171,7 +175,10 @@ namespace mabe { emp_assert(!empty_org.IsNull(), "Empty org must be provided before extraction."); emp::Ptr out_org = orgs[pos]; orgs[pos] = empty_org; - if (!out_org->IsEmpty()) num_orgs--; + if (!out_org->IsEmpty()) { + num_orgs--; + out_org->ClearPopulation(); // Alert organism that it is no longer part of this population. + } return out_org; } @@ -203,9 +210,16 @@ namespace mabe { public: // Setup member functions associated with population. static void InitType(emplode::TypeInfo & info) { - std::function fun_size = - [](Population & target) { return target.GetSize(); }; - info.AddMemberFunction("SIZE", fun_size, "Return the size of the population."); + info.AddMemberFunction("ID", [](Population & target) { return target.GetID(); }, + "Return the ID number for the population."); + info.AddMemberFunction("NAME", [](Population & target) { return target.GetName(); }, + "Return the name of the population."); + info.AddMemberFunction("NUM_ORGS", [](Population & target) { return target.GetNumOrgs(); }, + "Return the number of organisms in the population."); + info.AddMemberFunction("SIZE", [](Population & target) { return target.GetSize(); }, + "Return the capacity of the population."); + info.AddMemberFunction("PTR", [](Population & target) { return (size_t) ⌖ }, + "DEBUG: Give memory location of target."); } @@ -213,13 +227,13 @@ namespace mabe { bool OK() const { // We may have a handful of populations, but assume error if we have more than a million. if (pop_id > 1000000) { - std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; + std::cerr << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; return false; } // We should never have more living organisms than slots in the population. if (num_orgs > orgs.size()) { - std::cout << "ERROR: Population " << pop_id << " size is " << orgs.size() + std::cerr << "ERROR: Population " << pop_id << " size is " << orgs.size() << " but num_orgs = " << num_orgs << std::endl; return false; } @@ -229,18 +243,25 @@ namespace mabe { for (size_t pos = 0; pos < orgs.size(); pos++) { // No vector positions should be NULL (use EmptyOrganism instead) if (orgs[pos].IsNull()) { - std::cout << "ERROR: Population " << pop_id << " as position " << pos + std::cerr << "ERROR: Population " << pop_id << " as position " << pos << " has null pointer instead of an organism." << std::endl; return false; } + // Organisms should point back at this population. + if (orgs[pos]->GetPopPtr() != this) { + std::cerr << "ERROR: Population " << pop_id << " org# " << pos + << " does not point back at the correct population." << std::endl; + return false; + } + // Count the number of living (non-empty) organisms as we go. if (!orgs[pos]->IsEmpty()) org_count++; } // Make sure we counted the correct number of organisms in the population. if (num_orgs != org_count) { - std::cout << "ERROR: Population " << pop_id << " has num_orgs = " << num_orgs + std::cerr << "ERROR: Population " << pop_id << " has num_orgs = " << num_orgs << ", but audit counts " << org_count << " orgs." << std::endl; return false; } From da12441472c367c2129092deb8b18e08e38c67f5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 11:50:01 -0500 Subject: [PATCH 313/445] Cleanup in MABEBase.hpp --- source/core/MABEBase.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 364ae9af..342b7a54 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -124,22 +124,23 @@ namespace mabe { /// @param[in] pos is the position to perform the insertion. /// @param[in] ppos is the parent position (required if it exists; not used with inject). void AddOrgAt(emp::Ptr org_ptr, OrgPosition pos, OrgPosition ppos=OrgPosition()) { - emp_assert(org_ptr); // Must have a non-null organism to insert. - before_placement_sig.Trigger(*org_ptr, pos, ppos); - ClearOrgAt(pos); // Clear out any organism already in this position. - pos.PopPtr()->SetOrg(pos.Pos(), org_ptr); // Put the new organism in place. - on_placement_sig.Trigger(pos); + emp_assert(org_ptr); // Must have a non-null organism to insert. + before_placement_sig.Trigger(*org_ptr, pos, ppos); // Notify listerners org is about to be placed. + ClearOrgAt(pos); // Clear any organism already in this position. + pos.PopPtr()->SetOrg(pos.Pos(), org_ptr); // Put the new organism in place. + on_placement_sig.Trigger(pos); // Notify listeners org has been placed. } /// All permanent deletion of organisms from a population should come through here. /// If the relevant position is already empty, nothing happens. + /// After the position is cleared, caller must replace (possibly with an empty org) or resize away. /// @param[in] pos is the position to perform the deletion. void ClearOrgAt(OrgPosition pos) { emp_assert(pos.IsValid()); - if (pos.IsEmpty()) return; // Nothing to remove! + if (pos.IsEmpty()) return; // Already empty? Nothing to remove! - before_death_sig.Trigger(pos); - pos.PopPtr()->ExtractOrg(pos.Pos()).Delete(); + before_death_sig.Trigger(pos); // Send signal of current organism dying. + pos.Pop().ExtractOrg(pos.Pos()).Delete(); // Delete current organism. } /// All movement of organisms from one population position to another should come through here. From ed8b230ed2c7f24d9ce0c5fc209df045e81f8f8b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 11:51:20 -0500 Subject: [PATCH 314/445] Setup copy functon for populations; added MABE::CopyPop() and ClearPop(); improved mem management. --- source/core/MABE.hpp | 47 +++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 0ae8bedd..59c7fa0c 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -153,9 +153,13 @@ namespace mabe { MABE(const MABE &) = delete; MABE(MABE &&) = delete; ~MABE() { - if (empty_org) empty_org.Delete(); // Delete empty_org ptr. - for (auto x : modules) x.Delete(); // Delete all modules. - for (auto x : pops) x.Delete(); // Delete all populations. + for (auto mod_ptr : modules) mod_ptr.Delete(); // Delete all modules. + for (auto pop_ptr : pops) { // Delete all populations. + ClearPop(*pop_ptr); + pop_ptr.Delete(); + } + // Delete the empty organism AFTER clearing the populations, so it's not still used. + if (empty_org) empty_org.Delete(); } // --- Basic accessors --- @@ -265,8 +269,24 @@ namespace mabe { return DoBirth(*ppos, ppos, target_pop, birth_count, do_mutations); } + /// Remove all organisms from a population. + void ClearPop(Population & pop) { + for (PopIterator pos = pop.begin(); pos != pop.end(); ++pos) ClearOrgAt(pos); + } + /// Resize a population while clearing all of the organisms in it. - void EmptyPop(Population & pop, size_t new_size); + void EmptyPop(Population & pop, size_t new_size=0) { + ClearPop(pop); + MABEBase::ResizePop(pop, new_size); + } + + void CopyPop(const Population & from_pop, Population & to_pop) { + EmptyPop(to_pop, from_pop.GetSize()); // Clear all current orgs in the to_pop and resize. + for (size_t pos=0; pos < from_pop.GetSize(); ++pos) { + if (from_pop.IsEmpty(pos)) continue; + InjectAt(from_pop[pos], to_pop.IteratorAt(pos)); + } + } /// Return a ramdom position from a desginated population. OrgPosition GetRandomPos(Population & pop) { @@ -580,7 +600,13 @@ namespace mabe { { // Setup "Population" as a type in the config file. auto pop_init_fun = [this](const std::string & name) { return &AddPopulation(name); }; - auto pop_copy_fun = DefaultCopyFun(); + auto pop_copy_fun = [this](const EmplodeType & from, EmplodeType & to) { + emp::Ptr from_pop = dynamic_cast(&from); + emp::Ptr to_pop = dynamic_cast(&to); + if (!from_pop || !to_pop) return false; // Wrong type! + CopyPop(*from_pop, *to_pop); // Do the actual copy. + return true; + }; auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun, pop_copy_fun); @@ -885,17 +911,6 @@ namespace mabe { } - /// Resize a population while clearing all of the organisms in it. - void MABE::EmptyPop(Population & pop, size_t new_size) { - // Clean up any organisms in the population. - for (PopIterator pos = pop.begin(); pos != pop.end(); ++pos) { - ClearOrgAt(pos); - } - - MABEBase::ResizePop(pop, new_size); - } - - /// Return a ramdom position from a desginated population with a living organism in it. OrgPosition MABE::GetRandomOrgPos(Population & pop) { emp_assert(pop.GetNumOrgs() > 0, "GetRandomOrgPos cannot be called if there are no orgs."); From 4633fa042f4d5fcb05ef322bfed4fe5a486bb095 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 17:42:42 -0500 Subject: [PATCH 315/445] Removed copy constructor and max_orgs field from Population. --- source/core/Population.hpp | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 698a7b84..3c5353c8 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -90,7 +90,6 @@ namespace mabe { size_t pop_id = (size_t) -1; ///< Position in world of this population. emp::vector> orgs; ///< Info on all organisms in this population. size_t num_orgs = 0; ///< How many living organisms are in this population? - size_t max_orgs = (size_t) -1; ///< Maximum number of orgs allowed in population. emp::Ptr empty_org = nullptr; ///< Organism to fill in empty cells (does have data map!) @@ -107,28 +106,11 @@ namespace mabe { { orgs.resize(pop_size, empty_org); } - Population(const Population & in_pop) - : name(in_pop.name), pop_id(in_pop.pop_id), orgs(in_pop.orgs.size()) - , num_orgs(in_pop.num_orgs), max_orgs(in_pop.max_orgs) - , empty_org(in_pop.empty_org) - { - emp_assert(in_pop.OK()); - for (size_t i = 0; i < orgs.size(); i++) { - if (in_pop.orgs[i]->IsEmpty()) { // Make sure we always use local empty organism. - emp_assert(!empty_org.IsNull(), "Empty organisms must be set before they can be used!"); - orgs[i] = empty_org; - } else { // Otherwise clone the organism. - orgs[i] = in_pop.orgs[i]->CloneOrganism(); - } - } - emp_assert(OK()); - } - // All organism copying must be tracked! - Population & operator=(const Population & in_pop) = delete; - - // Populations can be copied, but should not be moved to maintain correct empty orgs. + // All organism moving/copying must be tracked and done through MABE object. + Population(const Population & in_pop) = delete; Population(Population &&) = delete; + Population & operator=(const Population & in_pop) = delete; Population & operator=(Population &&) = delete; ~Population() { emp_assert(num_orgs==0, "Population should be cleaned up before deletion."); } From 065be8b699906dc202d2d968ea5324353148fea7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 17:43:24 -0500 Subject: [PATCH 316/445] Setup Emplode functions to be able to return objects. --- source/Emplode/EmplodeTools.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Emplode/EmplodeTools.hpp b/source/Emplode/EmplodeTools.hpp index 5f79c418..700dd22e 100644 --- a/source/Emplode/EmplodeTools.hpp +++ b/source/Emplode/EmplodeTools.hpp @@ -50,6 +50,9 @@ namespace EmplodeTools { template static auto ConvertReturn( RETURN_T && return_value ) { + constexpr bool is_ref = std::is_lvalue_reference(); + using base_t = std::remove_reference_t; + // If a return value is already a symbol pointer, just pass it through. if constexpr (std::is_same()) { return return_value; @@ -61,6 +64,11 @@ namespace EmplodeTools { return MakeTempSymbol(return_value); } + // If a return value is a Emplode type, return its Symbol_Object reference. + else if constexpr (is_ref && std::is_base_of()) { + return return_value.AsScope().AsObject(); + } + // For now these are the only legal return type; raise error otherwise! else { emp::ShowType{}; From 51ca4ea60d9aec98d882327e5a2f121884f4c3b8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 17:44:04 -0500 Subject: [PATCH 317/445] Added Clear() and Set() to Collection; made HasPosition() const. --- source/core/Collection.hpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 77e1721a..6ebfa277 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -290,8 +290,8 @@ namespace mabe { return emp::Has(pos_map, (Population *) &pop); } - bool HasPosition(OrgPosition & pos) const { - auto info_it = pos_map.find(pos.PopPtr()); + bool HasPosition(const OrgPosition & pos) const { + auto info_it = pos_map.find(pos.PopPtr().ConstCast()); return info_it != pos_map.end() && (info_it->second.full_pop || info_it->second.pos_set.Has(pos.Pos())); } @@ -368,6 +368,9 @@ namespace mabe { ConstCollectionIterator begin() const { return ConstCollectionIterator(this); } ConstCollectionIterator end() const { return ConstCollectionIterator(this, nullptr); } + /// Remove all entries from a collection. + Collection & Clear() { pos_map.clear(); return *this; } + /// Add a Population to this collection. template Collection & Insert(Population & pop, Ts &&... extras) { @@ -416,6 +419,13 @@ namespace mabe { /// Base case... Collection & Insert() { return *this; } + /// Set this collection to be exactly the provided items. + template + Collection & Set(Ts &&...args) { + Clear(); + return Insert( std::forward(args)... ); + } + // @CAO: Add: // * Remove() - works with position or population (or another collection?) // * Has() - position From 8b7d729f653d86c51b64ccf35cbce54de4ac8f91 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 8 Nov 2021 17:44:26 -0500 Subject: [PATCH 318/445] Added a long series of member functions for Collection. --- source/core/MABE.hpp | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 59c7fa0c..d8512f5c 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -622,7 +622,46 @@ namespace mabe { // Setup "Collection" as another config type. auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); - + collect_type.AddMemberFunction("ADD_COLLECT", + [](Collection & collect, Collection & in) -> Collection& + { return collect.Insert(in); }, + "Merge another collection into this one." + ); + collect_type.AddMemberFunction("ADD_ORG", + [](Collection & collect, Population & pop, size_t id) -> Collection& + { return collect.Insert(pop.IteratorAt(id)); }, + "Add a single position to this collection." + ); + collect_type.AddMemberFunction("ADD_POP", + [](Collection & collect, Population & pop) -> Collection& { return collect.Insert(pop); }, + "Add a whole population to this collection." + ); + collect_type.AddMemberFunction("CLEAR", + [](Collection & collect) -> Collection& { return collect.Clear(); }, + "Remove all entries from this collection." + ); + collect_type.AddMemberFunction("HAS_ORG", + [](Collection & collect, Population & pop, size_t id) + { return collect.HasPosition(pop.IteratorAt(id)); }, + "Is the specified org position in this collection?" + ); + collect_type.AddMemberFunction("HAS_POP", + [](Collection & collect, Population & pop) { return collect.HasPopulation(pop); }, + "Is the specified population in this collection?" + ); + collect_type.AddMemberFunction("SET_ORG", + [](Collection & collect, Population & pop, size_t id) -> Collection& + { return collect.Set(pop.IteratorAt(id)); }, + "Set this collection to be a single position." + ); + collect_type.AddMemberFunction("SET_POP", + [](Collection & collect, Population & pop) -> Collection& { return collect.Set(pop); }, + "Set this collection to be a whole population." + ); + collect_type.AddMemberFunction("SIZE", + [](Collection & collect) { return collect.GetSize(); }, + "Identify how many positions are in this collection." + ); // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { From c4b7214cd85422f2769cb7f244813dcf98ffb4fc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 9 Nov 2021 11:16:58 -0500 Subject: [PATCH 319/445] Added virtual GetTypeInfoPtr() to emplode::Symbol --- source/Emplode/Symbol.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index f52b38a0..d35ce5c2 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -39,6 +39,7 @@ namespace emplode { class Symbol_Function; class Symbol_Object; class Symbol_Scope; + class TypeInfo; class Symbol { protected: @@ -143,6 +144,7 @@ namespace emplode { virtual emp::Ptr GetObjectPtr() { return nullptr; } virtual emp::Ptr GetObjectPtr() const { return nullptr; } + virtual emp::Ptr GetTypeInfoPtr() const { return nullptr; } /// A generic As() function that will call the appropriate converter. template From eb9ef980eeff850ccfc5e855b37c703f56663fa8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 9 Nov 2021 11:18:02 -0500 Subject: [PATCH 320/445] Setup AddObject to also take the associated TypeInfo as a parameter. --- source/Emplode/Symbol_Scope.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index 7b4312c0..1fe6c72b 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -19,6 +19,7 @@ #include "Symbol.hpp" #include "Symbol_Function.hpp" #include "Symbol_Linked.hpp" +#include "TypeInfo.hpp" namespace emplode { @@ -181,7 +182,8 @@ namespace emplode { Symbol_Object & AddObject( const std::string & name, const std::string & desc, - emp::Ptr obj_ptr=nullptr, + emp::Ptr obj_ptr, + TypeInfo & type_info, bool obj_owned=false ); From 37e0cd18ce9fa6dab1a876b88be161129d71a3a6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 9 Nov 2021 11:19:02 -0500 Subject: [PATCH 321/445] Setup Symbol_Object to track and use its own TypeInfo. --- source/Emplode/Symbol_Object.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index f74fa24b..61951731 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -23,6 +23,7 @@ namespace emplode { protected: ///< Point to associated object and track ownership emp::Ptr obj_ptr = nullptr; + emp::Ptr type_info_ptr = nullptr; bool obj_owned = false; public: @@ -30,8 +31,10 @@ namespace emplode { const std::string & _desc, emp::Ptr _scope, emp::Ptr _obj, + TypeInfo & _type_info, bool _owned) - : Symbol_Scope(_name, _desc, _scope), obj_ptr(_obj), obj_owned(_owned) { } + : Symbol_Scope(_name, _desc, _scope) + , obj_ptr(_obj), type_info_ptr(&_type_info), obj_owned(_owned) { } Symbol_Object(const Symbol_Object & in) : Symbol_Scope(in) { // Copy the internal object. @@ -55,11 +58,12 @@ namespace emplode { emp::Ptr GetObjectPtr() override { return obj_ptr; } emp::Ptr GetObjectPtr() const override { return obj_ptr; } + emp::Ptr GetTypeInfoPtr() const override { return type_info_ptr; } bool IsObject() const override { return true; } emp::TypeID GetObjectType() { - if (obj_ptr.IsNull()) return emp::GetTypeID(); - return obj_ptr->GetTypeInfo().GetTypeID(); + if (type_info_ptr.IsNull()) return emp::GetTypeID(); + return type_info_ptr->GetTypeID(); } /// Set this symbol to be a correctly-typed scope pointer. @@ -80,7 +84,7 @@ namespace emplode { const Symbol_Object & in_object = in.AsObject(); // If typeinfo knows how to make this copy, let it. - if (obj_ptr->GetTypeInfo().CopyObj(*in_object.obj_ptr, *obj_ptr)) return true; + if (type_info_ptr->CopyObj(*in_object.obj_ptr, *obj_ptr)) return true; // Otherwise use the default copy method for the object. return obj_ptr->CopyValue(*in_object.obj_ptr); @@ -95,9 +99,10 @@ namespace emplode { const std::string & name, const std::string & desc, emp::Ptr obj_ptr, + TypeInfo & type_info, bool obj_owned ) { - return Add(name, desc, this, obj_ptr, obj_owned); + return Add(name, desc, this, obj_ptr, type_info, obj_owned); } } From 7637ec490d36767df9c938538b2bde3e3b355f5a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 9 Nov 2021 11:20:08 -0500 Subject: [PATCH 322/445] Remove _active and _desc from EmplodeType (not longer used) and move type info to Symbol_Object. --- source/Emplode/EmplodeType.hpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 1ac9595a..3851c21a 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -24,12 +24,7 @@ namespace emplode { // Base class for types that we want to be used for scripting. class EmplodeType { protected: - emp::Ptr symbol_ptr; - emp::Ptr type_info_ptr; - - // Some special, internal variables associated with each object. - bool _active=true; ///< Should this object be used in the current run? - std::string _desc=""; ///< Special description for this object. + emp::Ptr symbol_ptr = nullptr; public: /// Setup the TYPE of object in the config. This is a stub class, but any new class derived from @@ -58,16 +53,9 @@ namespace emplode { return *symbol_ptr.DynamicCast(); } - const TypeInfo & GetTypeInfo() const { return *type_info_ptr; } - /// Setup an instance of a new EmplodeType object; provide it with its symbol and type information. - void Setup(Symbol_Object & in_symbol, TypeInfo & _info) { + void Setup(Symbol_Object & in_symbol) { symbol_ptr = &in_symbol; - type_info_ptr = &_info; - - // Link standard internal variables for this object. - LinkVar(_active, "_active", "Should we activate this module? (0=off, 1=on)", true); - LinkVar(_desc, "_desc", "Special description for those object.", true); // Link specialized variable for the derived type. SetupConfig(); @@ -75,7 +63,7 @@ namespace emplode { // Load in any member function for this object into the object. using symbol_ptr_t = emp::Ptr; using member_fun_t = std::function &)>; - const auto & member_map = type_info_ptr->GetMemberFunctions(); + const auto & member_map = symbol_ptr->GetTypeInfoPtr()->GetMemberFunctions(); // std::cout << "Loading member functions for '" << in_symbol.GetName() << "'; " // << member_map.size() << " found." From 43c8ea0dcf704674f5eb2b5b6fca730695d670b3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 9 Nov 2021 11:20:56 -0500 Subject: [PATCH 323/445] When creating a new object, provide type information to Scope_Object, not the object itself. --- source/Emplode/SymbolTable.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index e0a31f30..77a32f26 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -130,10 +130,11 @@ namespace emplode { emp::Ptr new_obj = type_info.MakeObj(var_name); // Setup a scope for this new type, linking the object to it. - Symbol_Object & new_obj_symbol = scope.AddObject(var_name, type_desc, new_obj, is_config_owned); + Symbol_Object & new_obj_symbol = + scope.AddObject(var_name, type_desc, new_obj, type_info, is_config_owned); // Let the new object know about its scope. - new_obj->Setup(new_obj_symbol, type_info); + new_obj->Setup(new_obj_symbol); return new_obj_symbol; } From 5ec1b3bd34b653524942e4357c3c4fc9a2b708da Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:50:53 -0500 Subject: [PATCH 324/445] Restructued EmplodeTools into SymbolTableBase to allow virtual functions and levelization. --- source/Emplode/EmplodeTools.hpp | 371 +++++++++++++++++--------------- 1 file changed, 194 insertions(+), 177 deletions(-) diff --git a/source/Emplode/EmplodeTools.hpp b/source/Emplode/EmplodeTools.hpp index 700dd22e..7c113235 100644 --- a/source/Emplode/EmplodeTools.hpp +++ b/source/Emplode/EmplodeTools.hpp @@ -3,13 +3,13 @@ * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md * @date 2021. * - * @file EmplodeTools.hpp + * @file SymbolTableBase.hpp * @brief Tools for working with Symbol objects, especially for wrapping functions. * @note Status: BETA */ -#ifndef EMPLODE_TOOLS_HPP -#define EMPLODE_TOOLS_HPP +#ifndef EMPLODE_SYMBOL_TABLE_BASE_HPP +#define EMPLODE_SYMBOL_TABLE_BASE_HPP #include @@ -22,204 +22,221 @@ #include "Symbol.hpp" namespace emplode { -namespace EmplodeTools { - - using symbol_ptr_t = emp::Ptr; - using symbol_vector_t = emp::vector; - using target_t = symbol_ptr_t( const symbol_vector_t & ); - - // Use EmplodeTools::MakeTempSymbol(value) to quickly allocate a temporary symbol with a - // given value. NOTE: Caller is responsible for deleting the created symbol! - template - static emp::Ptr> MakeTempSymbol(VALUE_T value) { - auto out_symbol = emp::NewPtr>("__Temp", value, "", nullptr); - out_symbol->SetTemporary(); - return out_symbol; - } - - template - static auto DefaultCopyFun() { - return [](const EmplodeType & from, EmplodeType & to) { - emp::Ptr from_ptr = dynamic_cast(&from); - emp::Ptr to_ptr = dynamic_cast(&to); - if (!from_ptr || !to_ptr) return false; - *to_ptr = *from_ptr; - return true; - }; - } - - template - static auto ConvertReturn( RETURN_T && return_value ) { - constexpr bool is_ref = std::is_lvalue_reference(); - using base_t = std::remove_reference_t; - // If a return value is already a symbol pointer, just pass it through. - if constexpr (std::is_same()) { - return return_value; + // A base class for symbol table to provide low-level access to functions. + class SymbolTableBase { + public: + virtual ~SymbolTableBase() { } + + using symbol_ptr_t = emp::Ptr; + using symbol_vector_t = emp::vector; + using target_t = symbol_ptr_t( const symbol_vector_t & ); + + // Use EmplodeTools::MakeTempSymbol(value) to quickly allocate a temporary symbol with a + // given value. NOTE: Caller is responsible for deleting the created symbol! + virtual emp::Ptr MakeTempObjSymbol(emp::TypeID type_id) = 0; + + template + auto MakeTempSymbol(T value) { + if constexpr (std::is_base_of()) { + return MakeTempObjSymbol(emp::GetTypeID()); + } else { + auto out_symbol = emp::NewPtr>("__Temp", value, "", nullptr); + out_symbol->SetTemporary(); + return out_symbol; + } } - // If a return value is a basic type, wrap it in a temporary symbol - else if constexpr (std::is_same() || - std::is_arithmetic()) { - return MakeTempSymbol(return_value); + template + static auto DefaultCopyFun() { + return [](const EmplodeType & from, EmplodeType & to) { + emp::Ptr from_ptr = dynamic_cast(&from); + emp::Ptr to_ptr = dynamic_cast(&to); + if (!from_ptr || !to_ptr) return false; + *to_ptr = *from_ptr; + return true; + }; } - // If a return value is a Emplode type, return its Symbol_Object reference. - else if constexpr (is_ref && std::is_base_of()) { - return return_value.AsScope().AsObject(); + template + decltype(auto) ConvertReturn( RETURN_T && return_value ) { + constexpr bool is_ref = std::is_lvalue_reference(); + using base_t = std::remove_reference_t; + + // If a return value is already a symbol pointer, just pass it through. + if constexpr (std::is_same()) { + return return_value; + } + + // If a return value is a basic type, wrap it in a temporary symbol + else if constexpr (std::is_same() || + std::is_arithmetic()) { + return MakeTempSymbol(return_value); + } + + // If a return value is a REFERENCE to an Emplode type, return its Symbol_Object. + else if constexpr (is_ref && std::is_base_of()) { + return return_value.AsScope().AsObject(); + } + + // If a return value is an Emplode type VALUE, build a temporary symbol for it. + else if constexpr (!is_ref && std::is_base_of()) { + return MakeTempSymbol(return_value); + } + + // For now these are the only legal return type; raise error otherwise! + else { + emp::ShowType{}; + static_assert(emp::dependent_false(), + "Invalid return value in Symbol_Function::SetFunction()"); + } } - // For now these are the only legal return type; raise error otherwise! - else { - emp::ShowType{}; - static_assert(emp::dependent_false(), - "Invalid return value in Symbol_Function::SetFunction()"); - } - } + template struct WrapFunction_impl; - template struct WrapFunction_impl; + // Specialization for functions with NO arguments + template + struct WrapFunction_impl> { - // Specialization for functions with NO arguments - template - struct WrapFunction_impl> { + template + static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun, SymbolTableBase & st) { + return [name=name,fun=fun,&st]([[maybe_unused]] const symbol_vector_t & args) { + emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); + return st.ConvertReturn( fun() ); + }; + } - template - static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun) { - return [name=name,fun=fun]([[maybe_unused]] const symbol_vector_t & args) { - emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); - return ConvertReturn( fun() ); - }; - } + }; - }; + // Specialization for functions with AT LEAST ONE argument. + template + struct WrapFunction_impl> { + using this_fun_t = RETURN_T(PARAM1_T, PARAM_Ts...); + static_assert( sizeof...(PARAM_Ts) == sizeof...(INDEX_VALS), + "Need one index for each parameter." ); + + template + static auto ConvertFun(const std::string & name, FUN_T fun, SymbolTableBase & st) { + return [name=name,fun=fun,&st](const symbol_vector_t & args) { + // If this function already takes a const symbol_vector_t & as its only parameter, + // just pass it along. + if constexpr (sizeof...(PARAM_Ts) == 0 && + std::is_same_v) { + return st.ConvertReturn( fun(args) ); + } - // Specialization for functions with AT LEAST ONE argument. - template - struct WrapFunction_impl> { - using this_fun_t = RETURN_T(PARAM1_T, PARAM_Ts...); - static_assert( sizeof...(PARAM_Ts) == sizeof...(INDEX_VALS), - "Need one index for each parameter." ); + // Otherwise make sure we have the correct arguments. + else { + constexpr size_t NUM_PARAMS = 1 + sizeof...(PARAM_Ts); + if (args.size() != NUM_PARAMS) { + std::cerr << "Error in call to function '" << name + << "'; expected " << NUM_PARAMS + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + return st.ConvertReturn( fun(args[0]->As(), + args[INDEX_VALS+1]->template As()...) ); + } + }; + } + + template + static auto ConvertMemberFun(const std::string & name, FUN_T fun, SymbolTableBase & st) { + using info_t = emp::FunInfo; + + static_assert(std::is_reference_v, + "First parameter for supplied member functions must be reference to object"); + static_assert(std::is_base_of_v>, + "First parameter for supplied member functions must derived from EmplodeType"); + static_assert(info_t::num_args == sizeof...(PARAM_Ts) + 1, + "PARAM_Ts must match the extra arguments in member function."); + + return [name=name,fun=fun,&st](EmplodeType & obj, const symbol_vector_t & args) -> decltype(auto) { + // Make sure the correct object type is used for first argument. + emp::Ptr obj_ptr(&obj); + auto typed_ptr = obj_ptr.DynamicCast>(); + emp_assert(typed_ptr, "Internal error: member function call on wrong object type!", name); + + // If this member function takes no additional arguments just call it without any! + if constexpr (sizeof...(PARAM_Ts) == 0) { + if (args.size() != 0) { + std::cerr << "Error in call to function '" << name + << "'; expected ZERO arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + return st.ConvertReturn( fun(*typed_ptr) ); + } - template - static auto ConvertFun(const std::string & name, FUN_T fun) { - return [name=name,fun=fun](const symbol_vector_t & args) { - // If this function already takes a const symbol_vector_t & as its only parameter, - // just pass it along. - if constexpr (sizeof...(PARAM_Ts) == 0 && - std::is_same_v) { - return ConvertReturn( fun(args) ); - } - - // Otherwise make sure we have the correct arguments. - else { - constexpr size_t NUM_PARAMS = 1 + sizeof...(PARAM_Ts); - if (args.size() != NUM_PARAMS) { - std::cerr << "Error in call to function '" << name - << "'; expected " << NUM_PARAMS - << " arguments, but received " << args.size() << "." - << std::endl; + // If this function already takes a const symbol_vector_t & as its only extra parameter, + // just pass it along. + else if constexpr (sizeof...(PARAM_Ts) == 1 && + std::is_same_v, const symbol_vector_t &>) { + return st.ConvertReturn( fun(*typed_ptr, args) ); } - //@CAO should collect file position information for the above errors. - return ConvertReturn( fun(args[0]->As(), - args[INDEX_VALS+1]->template As()...) ); - } - }; - } + // Otherwise make sure we have the correct arguments. + else { + constexpr size_t NUM_PARAMS = sizeof...(PARAM_Ts); + if (args.size() != NUM_PARAMS) { + std::cerr << "Error in call to function '" << name + << "'; expected " << NUM_PARAMS + << " arguments, but received " << args.size() << "." + << std::endl; + } + //@CAO should collect file position information for the above errors. + + return st.ConvertReturn( fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); + } + }; + } + }; + + // Wrap a provided function to make it take a vector of Ptr and return a + // single Ptr representing the result. template - static auto ConvertMemberFun(const std::string & name, FUN_T fun) { + auto WrapFunction(const std::string & name, FUN_T fun) { using info_t = emp::FunInfo; - - static_assert(std::is_reference_v, - "First parameter for supplied member functions must be reference to object"); - static_assert(std::is_base_of_v>, - "First parameter for supplied member functions must derived from EmplodeType"); - static_assert(info_t::num_args == sizeof...(PARAM_Ts) + 1, - "PARAM_Ts must match the extra arguments in member function."); - - return [name=name,fun=fun](EmplodeType & obj, const symbol_vector_t & args) { - // Make sure the correct object type is used for first argument. - emp::Ptr obj_ptr(&obj); - auto typed_ptr = obj_ptr.DynamicCast>(); - emp_assert(typed_ptr, "Internal error: member function call on wrong object type!", name); - - // If this member function takes no additional arguments just call it without any! - if constexpr (sizeof...(PARAM_Ts) == 0) { - if (args.size() != 0) { - std::cerr << "Error in call to function '" << name - << "'; expected ZERO arguments, but received " << args.size() << "." - << std::endl; - } - //@CAO should collect file position information for the above errors. - - return ConvertReturn( fun(*typed_ptr) ); - } - - // If this function already takes a const symbol_vector_t & as its only extra parameter, - // just pass it along. - else if constexpr (sizeof...(PARAM_Ts) == 1 && - std::is_same_v, const symbol_vector_t &>) { - return ConvertReturn( fun(*typed_ptr, args) ); - } - - // Otherwise make sure we have the correct arguments. - else { - constexpr size_t NUM_PARAMS = sizeof...(PARAM_Ts); - if (args.size() != NUM_PARAMS) { - std::cerr << "Error in call to function '" << name - << "'; expected " << NUM_PARAMS - << " arguments, but received " << args.size() << "." - << std::endl; - } - //@CAO should collect file position information for the above errors. - - return ConvertReturn( fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); - } - }; + using fun_t = typename info_t::fun_t; + if constexpr (info_t::num_args == 0) { + return WrapFunction_impl>::ConvertFun(name, fun, *this); + } else { + using index_t = emp::ValPackCount; + return WrapFunction_impl::ConvertFun(name, fun, *this); + } } - }; - - // Wrap a provided function to make it take a vector of Ptr and return a - // single Ptr representing the result. - template - static auto WrapFunction(const std::string & name, FUN_T fun) { - using info_t = emp::FunInfo; - using fun_t = typename info_t::fun_t; - if constexpr (info_t::num_args == 0) { - return WrapFunction_impl>::ConvertFun(name, fun); - } else { + // Wrap a provided MEMBER function to make it take a reference to the object it is a member of + // and a vector of Ptr and return a single Ptr representing the result. + template + auto WrapMemberFunction([[maybe_unused]] emp::TypeID class_type, + const std::string & name, FUN_T fun) + { + // Do some checks that will produce reasonable errors. + using info_t = emp::FunInfo; using index_t = emp::ValPackCount; - return WrapFunction_impl::ConvertFun(name, fun); + static_assert(info_t::num_args >= 1, "Member function add must always begin with an object reference."); + + // Is the first parameter the correct type? + using object_t = typename info_t::template arg_t<0>; + using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; + static_assert(std::is_base_of(), + "Member functions must take a reference to the associated EmplodeType"); + emp_assert( class_type.IsType(), + "First parameter must match class type of member function being created!", + emp::GetTypeID(), class_type ); + + using helper_t = WrapFunction_impl::fun_t, index_t>; + return helper_t::ConvertMemberFun(name, fun, *this); } - } - - // Wrap a provided MEMBER function to make it take a reference to the object it is a member of - // and a vector of Ptr and return a single Ptr representing the result. - template - static auto WrapMemberFunction([[maybe_unused]] emp::TypeID class_type, - const std::string & name, FUN_T fun) - { - // Do some checks that will produce reasonable errors. - using info_t = emp::FunInfo; - using index_t = emp::ValPackCount; - static_assert(info_t::num_args >= 1, "Member function add must always begin with an object reference."); - - // Is the first parameter the correct type? - using object_t = typename info_t::template arg_t<0>; - using base_object_t = typename std::remove_cv_t< std::remove_reference_t >; - static_assert(std::is_base_of(), - "Member functions must take a reference to the associated EmplodeType"); - emp_assert( class_type.IsType(), - "First parameter must match class type of member function being created!", - emp::GetTypeID(), class_type ); - - return WrapFunction_impl::fun_t, index_t>::ConvertMemberFun(name, fun); - } -} + }; + } #endif From 7a29b4580043289f8aa978467a8ed4a4175fd17e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:51:27 -0500 Subject: [PATCH 325/445] Renamed EmplodeTools.hpp to SymbolTableBase.hpp --- source/Emplode/{EmplodeTools.hpp => SymbolTableBase.hpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename source/Emplode/{EmplodeTools.hpp => SymbolTableBase.hpp} (100%) diff --git a/source/Emplode/EmplodeTools.hpp b/source/Emplode/SymbolTableBase.hpp similarity index 100% rename from source/Emplode/EmplodeTools.hpp rename to source/Emplode/SymbolTableBase.hpp From a0534391192c60a589b02cbd5b39112c3ee890a1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:55:35 -0500 Subject: [PATCH 326/445] TypeInfo now tracks member function return types; use symbol tables instead of EmplodeTools. --- source/Emplode/TypeInfo.hpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/source/Emplode/TypeInfo.hpp b/source/Emplode/TypeInfo.hpp index 86c12700..c5d19d06 100644 --- a/source/Emplode/TypeInfo.hpp +++ b/source/Emplode/TypeInfo.hpp @@ -18,7 +18,7 @@ #include "emp/tools/string_utils.hpp" #include "Symbol.hpp" -#include "EmplodeTools.hpp" +#include "SymbolTableBase.hpp" namespace emplode { @@ -30,9 +30,11 @@ namespace emplode { std::string name; std::string desc; fun_t fun; + emp::TypeID return_type; - MemberFunInfo(const std::string & in_name, const std::string & in_desc, fun_t in_fun) - : name(in_name), desc(in_desc), fun(in_fun) {} + MemberFunInfo(const std::string & in_name, const std::string & in_desc, + fun_t in_fun, emp::TypeID in_rtype) + : name(in_name), desc(in_desc), fun(in_fun), return_type(in_rtype) {} }; // TypeInfo tracks a particular type to be used in the configuration langauge. @@ -41,6 +43,8 @@ namespace emplode { using init_fun_t = std::function (const std::string &)>; using copy_fun_t = std::function; + SymbolTableBase & symbol_table; // Which symbol table are we part of? + size_t index; std::string type_name; std::string desc; @@ -54,16 +58,16 @@ namespace emplode { public: // Constructor to allow a simple new configuration type - TypeInfo(size_t _id, const std::string & _name, const std::string & _desc) - : index(_id), type_name(_name), desc(_desc) + TypeInfo(SymbolTableBase & _st, size_t _id, const std::string & _name, const std::string & _desc) + : symbol_table(_st), index(_id), type_name(_name), desc(_desc) { emp_assert(type_name != ""); } // Constructor to allow a new configuration type whose objects require initialization. - TypeInfo(size_t _id, const std::string & _name, const std::string & _desc, + TypeInfo(SymbolTableBase & _st, size_t _id, const std::string & _name, const std::string & _desc, init_fun_t _init, copy_fun_t _copy, bool _config_owned=false) - : index(_id), type_name(_name), desc(_desc), + : symbol_table(_st), index(_id), type_name(_name), desc(_desc), init_fun(_init), copy_fun(_copy), config_owned(_config_owned) { emp_assert(type_name != ""); @@ -76,7 +80,7 @@ namespace emplode { bool GetOwned() const { return config_owned; } const emp::vector & GetMemberFunctions() const { return member_funs; } - emp::Ptr MakeObj(const std::string & name) const { + emp::Ptr MakeObj(const std::string & name="__temp__") const { emp_assert(init_fun, "No initialization function exists for type.", type_name); return init_fun(name); } @@ -102,10 +106,11 @@ namespace emplode { // << std::endl; // ----- Transform this function into one that TypeInfo can make use of ---- - MemberFunInfo::fun_t member_fun = EmplodeTools::WrapMemberFunction(type_id, name, fun); + MemberFunInfo::fun_t member_fun = symbol_table.WrapMemberFunction(type_id, name, fun); // Add this member function to the library we are building. - member_funs.emplace_back(name, desc, member_fun); + using return_t = typename emp::FunInfo::return_t; + member_funs.emplace_back(name, desc, member_fun, emp::GetTypeID()); } }; From aac35923e5851e01e4cdb18be5a6fd3c6c609b7f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:57:14 -0500 Subject: [PATCH 327/445] Cleanup handling of return types in Symbol_Function; use SymbolTableBase. --- source/Emplode/Symbol_Function.hpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/source/Emplode/Symbol_Function.hpp b/source/Emplode/Symbol_Function.hpp index 77237506..bcee00a4 100644 --- a/source/Emplode/Symbol_Function.hpp +++ b/source/Emplode/Symbol_Function.hpp @@ -19,7 +19,7 @@ #include "emp/meta/ValPack.hpp" #include "Symbol.hpp" -#include "EmplodeTools.hpp" +#include "SymbolTableBase.hpp" namespace emplode { @@ -27,31 +27,29 @@ namespace emplode { private: using this_t = Symbol_Function; using symbol_ptr_t = emp::Ptr; - using fun_t = std::function< symbol_ptr_t( const emp::vector & ) >; - fun_t fun; - bool numeric_return = false; - bool string_return = false; + using fun_t = symbol_ptr_t( const emp::vector & ); + using std_fun_t = std::function< fun_t >; + + std_fun_t fun; // Unified-form function. + emp::TypeID return_type; // Native return type for original function. // size_t arg_count; public: - template Symbol_Function(const std::string & _name, - FUN_T _fun, + std_fun_t _fun, const std::string & _desc, - emp::Ptr _scope) - : Symbol(_name, _desc, _scope), fun(EmplodeTools::WrapFunction(_name, _fun)) + emp::Ptr _scope, + emp::TypeID _ret_type) + : Symbol(_name, _desc, _scope), fun(_fun), return_type(_ret_type) { - using return_t = typename emp::FunInfo::return_t; - numeric_return = std::is_scalar_v; - string_return = std::is_same(); } Symbol_Function(const Symbol_Function &) = default; emp::Ptr Clone() const override { return emp::NewPtr(*this); } bool IsFunction() const override { return true; } - bool HasNumericReturn() const override { return numeric_return; } - bool HasStringReturn() const override { return string_return; } + bool HasNumericReturn() const override { return return_type.IsArithmetic(); } + bool HasStringReturn() const override { return return_type.IsType(); } /// Set this symbol to be a correctly-typed scope pointer. emp::Ptr AsFunctionPtr() override { return this; } @@ -66,8 +64,7 @@ namespace emplode { const Symbol_Function & in_fun = in.AsFunction(); fun = in_fun.fun; - numeric_return = in_fun.numeric_return; - string_return = in_fun.string_return; + return_type = in_fun.return_type; return true; } From 724a071b628a34a4ed8328cc7c9e2938ce3f4271 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:58:02 -0500 Subject: [PATCH 328/445] Symbol_Scope now needs base return type info when adding functions. --- source/Emplode/Symbol_Scope.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index 1fe6c72b..a34af2b7 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -189,18 +189,16 @@ namespace emplode { /// Add a new user-defined function. template - Symbol_Function & AddFunction(const std::string & name, - FUN_T fun, - const std::string & desc) { - return Add(name, fun, desc, this); + Symbol_Function & AddFunction(const std::string & name, FUN_T fun, + const std::string & desc, emp::TypeID return_type) { + return Add(name, fun, desc, this, return_type); } /// Add a new function that is a standard part of the scripting language. template - Symbol_Function & AddBuiltinFunction(const std::string & name, - FUN_T fun, - const std::string & desc) { - return AddBuiltin(name, fun, desc, this); + Symbol_Function & AddBuiltinFunction(const std::string & name, FUN_T fun, + const std::string & desc, emp::TypeID return_type) { + return AddBuiltin(name, fun, desc, this, return_type); } /// Write out all of the parameters contained in this scope to the provided stream. From 2d1eb8167ddd30da62cfe7f865c0a423a2ddc720 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 09:58:44 -0500 Subject: [PATCH 329/445] Include return type info when converting member function to class instance. --- source/Emplode/EmplodeType.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index 3851c21a..e745c711 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -73,7 +73,8 @@ namespace emplode { member_fun_t linked_fun = [this, &member_info](const emp::vector & args){ return member_info.fun(*this, args); }; - symbol_ptr->AddFunction(member_info.name, linked_fun, member_info.desc).SetBuiltin(); + symbol_ptr->AddFunction(member_info.name, linked_fun, + member_info.desc, member_info.return_type).SetBuiltin(); // std::cout << "Adding member function '" << member_info.name << "' to object '" // << symbol_ptr->GetName() << "'." << std::endl; From 70c35eeadb93063449a4819c72c65c9aa325c503 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:19:32 -0500 Subject: [PATCH 330/445] Removed copy constructor for Symbol_Object in favor of more explicit Clone() --- source/Emplode/Symbol_Object.hpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index 61951731..401db5f1 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -35,14 +35,8 @@ namespace emplode { bool _owned) : Symbol_Scope(_name, _desc, _scope) , obj_ptr(_obj), type_info_ptr(&_type_info), obj_owned(_owned) { } - - Symbol_Object(const Symbol_Object & in) : Symbol_Scope(in) { - // Copy the internal object. - // @CAO MUST DO THIS!!!!!!!!!!!!!!!!!!!!! - - // Copy all defined variables/scopes/functions - for (auto [name, ptr] : symbol_table) { symbol_table[name] = ptr->Clone(); } - } + + Symbol_Object(const Symbol_Object & in) = delete; Symbol_Object(Symbol_Object && in) : Symbol_Scope(std::move(in)), obj_ptr(in.obj_ptr), obj_owned(in.obj_owned) { @@ -91,7 +85,17 @@ namespace emplode { } /// Make a copy of this scope and all of the entries inside it. - emp::Ptr Clone() const override { return emp::NewPtr(*this); } + emp::Ptr Clone() const override { + emp::Ptr out_obj = type_info_ptr->MakeObj(); // Create an initial object. + type_info_ptr->CopyObj(*obj_ptr, *out_obj); // Copy this object. + emp::Ptr out_scope = nullptr; // Unknown scope? + + // Construct a unique name for the new object. + std::string out_name = emp::to_string(GetName(), "__", (size_t) out_obj); + + return emp::NewPtr(out_name, GetDesc(), out_scope, out_obj, + *type_info_ptr, obj_owned); + } }; // Definition needed to add an object to an existing scope. From 02ed7eb0f22ecbe94f631fdbe8c7c44c8c74b7cd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:26:11 -0500 Subject: [PATCH 331/445] Fixed Symbol_Object::Clone() to also copy internal symbols. --- source/Emplode/Symbol_Object.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index 401db5f1..ce1b067b 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -93,8 +93,15 @@ namespace emplode { // Construct a unique name for the new object. std::string out_name = emp::to_string(GetName(), "__", (size_t) out_obj); - return emp::NewPtr(out_name, GetDesc(), out_scope, out_obj, - *type_info_ptr, obj_owned); + // Build the new Symbol_Object. + auto out = emp::NewPtr(out_name, GetDesc(), out_scope, out_obj, + *type_info_ptr, obj_owned); + + // Copy over all of the internal symbols. + for (auto [name, ptr] : symbol_table) { out->symbol_table[name] = ptr->Clone(); } + // @CAO: Will linkages be in place? + + return out; } }; From bd6d2e2af0c873c0d305a5cabf49932c2a42b31b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:27:18 -0500 Subject: [PATCH 332/445] Fixed AST parent tracking; provide symbol-table access. --- source/Emplode/AST.hpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 99f351db..43465710 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -18,7 +18,7 @@ #include "Symbol.hpp" #include "Symbol_Scope.hpp" #include "Symbol_Object.hpp" -#include "EmplodeTools.hpp" +#include "SymbolTableBase.hpp" namespace emplode { @@ -57,6 +57,7 @@ namespace emplode { node_ptr_t GetParent() { return parent; } void SetParent(node_ptr_t in_parent) { parent = in_parent; } virtual emp::Ptr GetScope() { return parent ? parent->GetScope() : nullptr; } + virtual SymbolTableBase & GetSymbolTable() { return parent->GetSymbolTable(); } virtual symbol_ptr_t Process() = 0; @@ -83,7 +84,10 @@ namespace emplode { size_t GetNumChildren() const override { return children.size(); } node_ptr_t GetChild(size_t id) override { return children[id]; } - void AddChild(node_ptr_t child) { children.push_back(child); } + void AddChild(node_ptr_t child) { + children.push_back(child); + child->SetParent(this); + } }; /// An ASTNode representing a leaf in the tree (i.e., a variable or literal) @@ -145,6 +149,7 @@ namespace emplode { class ASTNode_Block : public ASTNode_Internal { protected: emp::Ptr scope_ptr; + emp::Ptr symbol_table = nullptr; public: ASTNode_Block(Symbol_Scope & in_scope, int in_line=-1) : scope_ptr(&in_scope) { @@ -153,6 +158,12 @@ namespace emplode { emp::Ptr GetScope() override { return scope_ptr; } + SymbolTableBase & GetSymbolTable() override { + if (symbol_table) return *symbol_table; + return parent->GetSymbolTable(); + } + void SetSymbolTable(SymbolTableBase & _st) { symbol_table = &_st; } + symbol_ptr_t Process() override { for (auto node : children) { symbol_ptr_t out = node->Process(); @@ -189,7 +200,7 @@ namespace emplode { symbol_ptr_t input_symbol = children[0]->Process(); // Process child to get input symbol double output_value = fun(input_symbol->AsDouble()); // Run the function to get ouput value if (input_symbol->IsTemporary()) input_symbol.Delete(); // If we are done with input; delete! - return EmplodeTools::MakeTempSymbol(output_value); + return GetSymbolTable().MakeTempSymbol(output_value); } void Write(std::ostream & os, const std::string & offset) const override { @@ -221,7 +232,7 @@ namespace emplode { auto out_val = fun(in1->As(), in2->As()); // Run function; get ouput if (in1->IsTemporary()) in1.Delete(); // If we are done with in1; delete! if (in2->IsTemporary()) in2.Delete(); // If we are done with in2; delete! - return EmplodeTools::MakeTempSymbol(out_val); + return GetSymbolTable().MakeTempSymbol(out_val); } void Write(std::ostream & os, const std::string & offset) const override { From d9f45542b413b31b4a2b0da11db84f8828abad75 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:29:01 -0500 Subject: [PATCH 333/445] Cleanup on SymbolTable to help typeinfo track table and lookups by TypeID. --- source/Emplode/SymbolTable.hpp | 74 ++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index 77a32f26..0517f5e7 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -23,26 +23,33 @@ #include "Events.hpp" #include "Symbol_Scope.hpp" +#include "SymbolTableBase.hpp" namespace emplode { - class SymbolTable { + class SymbolTable : public SymbolTableBase { protected: - Symbol_Scope root_scope; ///< All variables from the root level. - std::map events_map; ///< A map of names to event groups. - std::unordered_map> type_map; ///< All types available in the script. - emp::StreamManager file_map; ///< Track all file streams by name. + Symbol_Scope root_scope; ///< Outermost (global) scope. + std::map events_map; ///< Events, lookup by name. + std::unordered_map> type_map; ///< Types, lookup by name. + std::unordered_map> typeid_map; ///< Types, lookup by TypeID. + emp::StreamManager file_map; ///< File streams by name. public: SymbolTable(const std::string & name) : root_scope(name, "Global scope", nullptr) { // Initialize the type map. - type_map["INVALID"] = emp::NewPtr( 0, "/*ERROR*/", "Error, Invalid type!" ); - type_map["Void"] = emp::NewPtr( 1, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( 2, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( 3, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( 4, "Struct", "User-made structure" ); + type_map["INVALID"] = emp::NewPtr( *this, 0, "/*ERROR*/", "Error, Invalid type!" ); + type_map["Void"] = emp::NewPtr( *this, 1, "Void", "Non-type variable; no value" ); + type_map["Value"] = emp::NewPtr( *this, 2, "Value", "Numeric variable" ); + type_map["String"] = emp::NewPtr( *this, 3, "String", "String variable" ); + type_map["Struct"] = emp::NewPtr( *this, 4, "Struct", "User-made structure" ); + + // Those types + typeid_map[emp::GetTypeID()] = type_map["Void"]; + typeid_map[emp::GetTypeID()] = type_map["Value"]; + typeid_map[emp::GetTypeID()] = type_map["String"]; file_map.SetOutputDefaultFile(); // Stream manager should default to 'file' output. } @@ -58,6 +65,7 @@ namespace emplode { bool HasEvent(const std::string & name) const { return emp::Has(events_map, name); } bool HasType(const std::string & name) const { return emp::Has(type_map, name); } + bool HasTypeID(emp::TypeID id) const { return emp::Has(typeid_map, id); } /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be @@ -65,7 +73,10 @@ namespace emplode { /// vector of ASTNode pointers, but may return any known type. template void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { - root_scope.AddBuiltinFunction(name, fun, desc); + auto emplode_fun = WrapFunction(name, fun); + using return_t = typename emp::FunInfo::return_t; + emp::TypeID return_id = emp::GetTypeID(); + root_scope.AddBuiltinFunction(name, emplode_fun, desc, return_id); } /// To add a type, provide the type name (that can be referred to in a script) and a function @@ -82,10 +93,12 @@ namespace emplode { ) { emp_assert(!emp::Has(type_map, type_name), type_name, "Type already exists!"); size_t index = type_map.size(); - auto info_ptr = emp::NewPtr( index, type_name, desc, + auto info_ptr = emp::NewPtr( *this, index, type_name, desc, init_fun, copy_fun, is_config_owned ); info_ptr->LinkType(type_id); type_map[type_name] = info_ptr; + typeid_map[type_id] = info_ptr; + return *type_map[type_name]; } @@ -112,17 +125,17 @@ namespace emplode { template TypeInfo & AddType(const std::string & type_name, const std::string & desc) { auto init_fun = [](const std::string & /*name*/){ return emp::NewPtr(); }; - auto copy_fun = EmplodeTools::DefaultCopyFun(); + auto copy_fun = DefaultCopyFun(); return AddType(type_name, desc, init_fun, copy_fun, true); } - Symbol_Object & MakeObjSymbol( - const std::string & type_name, + /// Make a new Symbol_Object using the provided TypeInfo, variable name, and scope. + Symbol_Object & MakeObjSymbol( + TypeInfo & type_info, const std::string & var_name, Symbol_Scope & scope ) { // Retrieve the information about the requested type. - TypeInfo & type_info = *type_map[type_name]; const std::string & type_desc = type_info.GetDesc(); const bool is_config_owned = type_info.GetOwned(); @@ -139,6 +152,35 @@ namespace emplode { return new_obj_symbol; } + /// Make a new Symbol_Object using the provided type name, variable name, and scope. + Symbol_Object & MakeObjSymbol(const std::string & type_name, const std::string & var_name, + Symbol_Scope & scope) { + return MakeObjSymbol(*type_map[type_name], var_name, scope); + } + + /// Make a new Symbol_Object using the provided TypeID, variable name, and scope. + Symbol_Object & MakeObjSymbol(emp::TypeID type_id, const std::string & var_name, + Symbol_Scope & scope) { + return MakeObjSymbol(*typeid_map[type_id], var_name, scope); + } + + + emp::Ptr MakeTempObjSymbol(emp::TypeID type_id) override { + TypeInfo & type_info = *typeid_map[type_id]; + emp_assert(type_info.GetOwned(), + "Only symbol-owned types can be temporary since they are deleted dynamically.", + type_info.GetTypeName()); + + // Use the TypeInfo associated with the provided type name to build an instance. + emp::Ptr new_obj = type_info.MakeObj("__Temp"); + auto new_symbol = emp::NewPtr("__Temp", "", nullptr, new_obj, type_info, true); + new_symbol->SetTemporary(); + + // Setup the new object with its symbol. + new_obj->Setup(*new_symbol); + + return new_symbol; + } /// Create a new type of event that can be used in the scripting language. Events & AddEventType(const std::string & name) { From 92986c315dfb24bf3d1d7f04c69a549d19eff945 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:29:57 -0500 Subject: [PATCH 334/445] ParserState gives access to symbol table; Parser sets symbol table in AST. --- source/Emplode/Parser.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 11866012..cf0def7d 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -60,6 +60,7 @@ namespace emplode { size_t GetIndex() const { return pos.GetIndex(); } ///< Return index in token stream. int GetLine() const { return (int) pos->line_id; } size_t GetTokenSize() const { return pos.IsValid() ? pos->lexeme.size() : 0; } + SymbolTable & GetSymbolTable() { return *symbol_table; } Symbol_Scope & GetScope() { emp_assert(scope_stack.size() && scope_stack.back() != nullptr); return *scope_stack.back(); @@ -240,6 +241,7 @@ namespace emplode { [[nodiscard]] emp::Ptr ParseStatementList(ParseState & state) { Debug("Running ParseStatementList(", state.AsString(), ")"); auto cur_block = emp::NewPtr(state.GetScope(), state.GetLine()); + cur_block->SetSymbolTable(state.GetSymbolTable()); while (state.IsValid() && state.AsChar() != '}') { // Parse each statement in the file. emp::Ptr statement_node = ParseStatement(state); From bef3396fb43ae624b2bf4dfeb7863802a174a347 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:31:02 -0500 Subject: [PATCH 335/445] Provide symbol table info to stand-alone expression evaluation. --- source/Emplode/Emplode.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index befaae0b..cda99c92 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -186,7 +186,7 @@ namespace emplode { auto df_init = [this](const std::string & name) { return emp::NewPtr(name, symbol_table.GetFileManager()); }; - auto df_copy = EmplodeTools::DefaultCopyFun(); + auto df_copy = symbol_table.DefaultCopyFun(); auto & df_type = AddType("DataFile", "Manage CSV-style date file output.", df_init, df_copy, true); df_type.AddMemberFunction( @@ -280,8 +280,15 @@ namespace emplode { tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. pos_t pos = tokens.begin(); // Start are beginning of stream. ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; - auto cur_block = parser.ParseStatement(state); // Convert tokens to AST - auto result_ptr = cur_block->Process(); // Process AST to get result symbol. + auto cur_expr = parser.ParseStatement(state); // Convert tokens to AST + + // Now place the expression in a temporary block. + auto cur_block = emp::NewPtr(symbol_table.GetRootScope(), 0); + cur_block->SetSymbolTable(state.GetSymbolTable()); + cur_block->AddChild(cur_expr); + + // Process just the expressions so that we can get a result from it. + auto result_ptr = cur_expr->Process(); // Process AST to get result symbol. std::string result = ""; // Default result to an empty string. if (result_ptr) { result = result_ptr->AsString(); // Convert result to output string. From a3adfe95aa5fa0113de46e98b8a4de7cbc4c2aa8 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 10:32:02 -0500 Subject: [PATCH 336/445] Moved Collection member functions from MABE.hpp to Collection.hpp --- source/core/Collection.hpp | 43 ++++++++++++++++++++++++++++++++ source/core/MABE.hpp | 51 +++----------------------------------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 6ebfa277..7ccd7bc4 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -246,6 +246,49 @@ namespace mabe { using iterator_t = CollectionIterator; using const_iterator_t = ConstCollectionIterator; + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("ADD_COLLECT", + [](Collection & collect, Collection & in) -> Collection& + { return collect.Insert(in); }, + "Merge another collection into this one." + ); + info.AddMemberFunction("ADD_ORG", + [](Collection & collect, Population & pop, size_t id) -> Collection& + { return collect.Insert(pop.IteratorAt(id)); }, + "Add a single position to this collection." + ); + info.AddMemberFunction("ADD_POP", + [](Collection & collect, Population & pop) -> Collection& { return collect.Insert(pop); }, + "Add a whole population to this collection." + ); + info.AddMemberFunction("CLEAR", + [](Collection & collect) -> Collection& { return collect.Clear(); }, + "Remove all entries from this collection." + ); + info.AddMemberFunction("HAS_ORG", + [](Collection & collect, Population & pop, size_t id) + { return collect.HasPosition(pop.IteratorAt(id)); }, + "Is the specified org position in this collection?" + ); + info.AddMemberFunction("HAS_POP", + [](Collection & collect, Population & pop) { return collect.HasPopulation(pop); }, + "Is the specified population in this collection?" + ); + info.AddMemberFunction("SET_ORG", + [](Collection & collect, Population & pop, size_t id) -> Collection& + { return collect.Set(pop.IteratorAt(id)); }, + "Set this collection to be a single position." + ); + info.AddMemberFunction("SET_POP", + [](Collection & collect, Population & pop) -> Collection& { return collect.Set(pop); }, + "Set this collection to be a whole population." + ); + info.AddMemberFunction("SIZE", + [](Collection & collect) { return collect.GetSize(); }, + "Identify how many positions are in this collection." + ); + } + /// Calculate the total number of positions represented in this collection. size_t GetSize() const noexcept override { size_t count = 0; diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index d8512f5c..b56d89e1 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -44,8 +44,6 @@ namespace mabe { - using namespace emplode::EmplodeTools; - /// @brief The main MABE controller class /// /// The MABE controller class manages interactions between all modules, @@ -538,9 +536,6 @@ namespace mabe { // Loop through each module to update its signals. for (emp::Ptr mod_ptr : modules) { - // If a module is deactivated, don't use it's signals. - if (mod_ptr->_active == false) continue; - // For the current module, loop through all of the signals. for (size_t sig_id = 0; sig_id < sig_ptrs.size(); sig_id++) { if (mod_ptr->has_signal[sig_id]) sig_ptrs[sig_id]->push_back(mod_ptr); @@ -610,6 +605,9 @@ namespace mabe { auto & pop_type = config.AddType("Population", "Collection of organisms", pop_init_fun, pop_copy_fun); + // Setup "Collection" as another config type. + auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); + // 'INJECT' allows a user to add an organism to a population. std::function inject_fun = [this](Population & pop, const std::string & org_type_name, size_t count) { @@ -620,49 +618,6 @@ namespace mabe { "Inject organisms into population (args: org_name, org_count)."); - // Setup "Collection" as another config type. - auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); - collect_type.AddMemberFunction("ADD_COLLECT", - [](Collection & collect, Collection & in) -> Collection& - { return collect.Insert(in); }, - "Merge another collection into this one." - ); - collect_type.AddMemberFunction("ADD_ORG", - [](Collection & collect, Population & pop, size_t id) -> Collection& - { return collect.Insert(pop.IteratorAt(id)); }, - "Add a single position to this collection." - ); - collect_type.AddMemberFunction("ADD_POP", - [](Collection & collect, Population & pop) -> Collection& { return collect.Insert(pop); }, - "Add a whole population to this collection." - ); - collect_type.AddMemberFunction("CLEAR", - [](Collection & collect) -> Collection& { return collect.Clear(); }, - "Remove all entries from this collection." - ); - collect_type.AddMemberFunction("HAS_ORG", - [](Collection & collect, Population & pop, size_t id) - { return collect.HasPosition(pop.IteratorAt(id)); }, - "Is the specified org position in this collection?" - ); - collect_type.AddMemberFunction("HAS_POP", - [](Collection & collect, Population & pop) { return collect.HasPopulation(pop); }, - "Is the specified population in this collection?" - ); - collect_type.AddMemberFunction("SET_ORG", - [](Collection & collect, Population & pop, size_t id) -> Collection& - { return collect.Set(pop.IteratorAt(id)); }, - "Set this collection to be a single position." - ); - collect_type.AddMemberFunction("SET_POP", - [](Collection & collect, Population & pop) -> Collection& { return collect.Set(pop); }, - "Set this collection to be a whole population." - ); - collect_type.AddMemberFunction("SIZE", - [](Collection & collect) { return collect.GetSize(); }, - "Identify how many positions are in this collection." - ); - // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { From 7673a96811aed0dcc41e195c385b1369dfd3e46f Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 10 Nov 2021 16:37:55 -0500 Subject: [PATCH 337/445] Fixed adding functions that return temporary objects. --- source/Emplode/SymbolTable.hpp | 10 +++++---- source/Emplode/SymbolTableBase.hpp | 34 ++++++++++++++++++------------ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index 0517f5e7..71c5bc06 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -165,7 +165,8 @@ namespace emplode { } - emp::Ptr MakeTempObjSymbol(emp::TypeID type_id) override { + emp::Ptr MakeTempObjSymbol(emp::TypeID type_id, + emp::Ptr value_ptr=nullptr) override { TypeInfo & type_info = *typeid_map[type_id]; emp_assert(type_info.GetOwned(), "Only symbol-owned types can be temporary since they are deleted dynamically.", @@ -174,14 +175,15 @@ namespace emplode { // Use the TypeInfo associated with the provided type name to build an instance. emp::Ptr new_obj = type_info.MakeObj("__Temp"); auto new_symbol = emp::NewPtr("__Temp", "", nullptr, new_obj, type_info, true); - new_symbol->SetTemporary(); - // Setup the new object with its symbol. - new_obj->Setup(*new_symbol); + new_symbol->SetTemporary(); // Mark new symbol to be deleted. + new_obj->Setup(*new_symbol); // Setup new object with its symbol. + if (value_ptr) type_info.CopyObj(*value_ptr, *new_obj); // Copy value in, if we have one. return new_symbol; } + /// Create a new type of event that can be used in the scripting language. Events & AddEventType(const std::string & name) { emp_assert(!HasEvent(name), "Event type already exists!", name); diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index 7c113235..09f69c98 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -16,6 +16,7 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" #include "emp/datastructs/tuple_utils.hpp" +#include "emp/debug/debug.hpp" #include "emp/meta/FunInfo.hpp" #include "emp/meta/ValPack.hpp" @@ -32,14 +33,15 @@ namespace emplode { using symbol_vector_t = emp::vector; using target_t = symbol_ptr_t( const symbol_vector_t & ); - // Use EmplodeTools::MakeTempSymbol(value) to quickly allocate a temporary symbol with a - // given value. NOTE: Caller is responsible for deleting the created symbol! - virtual emp::Ptr MakeTempObjSymbol(emp::TypeID type_id) = 0; + // Quickly allocate a temporary symbol with a given value. + // NOTE: Caller is responsible for deleting the created symbol! + virtual emp::Ptr + MakeTempObjSymbol(emp::TypeID type_id, emp::Ptr value_ptr=nullptr) = 0; template auto MakeTempSymbol(T value) { if constexpr (std::is_base_of()) { - return MakeTempObjSymbol(emp::GetTypeID()); + return MakeTempObjSymbol(emp::GetTypeID(), &value); } else { auto out_symbol = emp::NewPtr>("__Temp", value, "", nullptr); out_symbol->SetTemporary(); @@ -59,10 +61,14 @@ namespace emplode { } template - decltype(auto) ConvertReturn( RETURN_T && return_value ) { + decltype(auto) ConvertReturn( const std::string & fun_name, RETURN_T && return_value ) { constexpr bool is_ref = std::is_lvalue_reference(); using base_t = std::remove_reference_t; + // if (fun_name == "INJECT") { + // emp_debug("INJECT! Return type=", emp::GetTypeID()); + // } + // If a return value is already a symbol pointer, just pass it through. if constexpr (std::is_same()) { return return_value; @@ -86,7 +92,7 @@ namespace emplode { // For now these are the only legal return type; raise error otherwise! else { - emp::ShowType{}; + std::cerr << "Failed to convert return type for function " << fun_name << std::endl; static_assert(emp::dependent_false(), "Invalid return value in Symbol_Function::SetFunction()"); } @@ -103,7 +109,7 @@ namespace emplode { static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun, SymbolTableBase & st) { return [name=name,fun=fun,&st]([[maybe_unused]] const symbol_vector_t & args) { emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); - return st.ConvertReturn( fun() ); + return st.ConvertReturn( name, fun() ); }; } @@ -123,7 +129,7 @@ namespace emplode { // just pass it along. if constexpr (sizeof...(PARAM_Ts) == 0 && std::is_same_v) { - return st.ConvertReturn( fun(args) ); + return st.ConvertReturn( name, fun(args) ); } // Otherwise make sure we have the correct arguments. @@ -137,8 +143,10 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( fun(args[0]->As(), - args[INDEX_VALS+1]->template As()...) ); + return st.ConvertReturn( + name, + fun(args[0]->As(), args[INDEX_VALS+1]->template As()...) + ); } }; } @@ -169,14 +177,14 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( fun(*typed_ptr) ); + return st.ConvertReturn( name, fun(*typed_ptr) ); } // If this function already takes a const symbol_vector_t & as its only extra parameter, // just pass it along. else if constexpr (sizeof...(PARAM_Ts) == 1 && std::is_same_v, const symbol_vector_t &>) { - return st.ConvertReturn( fun(*typed_ptr, args) ); + return st.ConvertReturn( name, fun(*typed_ptr, args) ); } // Otherwise make sure we have the correct arguments. @@ -190,7 +198,7 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); + return st.ConvertReturn( name, fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); } }; } From fc2a139fae5f913e24fc6009708ce68e795249cb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 15:15:21 -0500 Subject: [PATCH 338/445] Updated Collection to properly handle the const-ness of Populations. --- source/core/Collection.hpp | 78 ++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 7ccd7bc4..fde14fc9 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -124,6 +124,7 @@ namespace mabe { /// Information about a single population in this collection. struct PopInfo { bool full_pop = false; ///< Should we use the full population? + bool is_mutable = false; ///< Are we allowed to change this population? emp::BitVector pos_set; ///< Which positions are we using for this population? /// Identify how many positions we have. @@ -167,10 +168,10 @@ namespace mabe { /// Shift this population to using the pos_set. void RemoveFull(pop_ptr_t pop_ptr) { - if (!full_pop) return; - pos_set.Resize(pop_ptr->GetSize()); - pos_set.SetAll(); - full_pop = false; + if (!full_pop) return; // Already not a full population. + pos_set.Resize(pop_ptr->GetSize()); // Resize position set to have room for all positions. + pos_set.SetAll(); // Initially include all orgs. + full_pop = false; // Record that pop is no longer officially full. } }; @@ -235,6 +236,9 @@ namespace mabe { template Collection(Population & pop, Ts &&... extras) { Insert( pop, std::forward(extras)... ); } + template + Collection(const Population & pop, Ts &&... extras) { Insert( pop, std::forward(extras)... ); } + template Collection(OrgPosition pos, Ts &&... extras) { Insert( pos, std::forward(extras)... ); } @@ -302,6 +306,8 @@ namespace mabe { for (auto [pop_ptr, pop_info] : pos_map) { if (org_id < pop_info.GetSize(pop_ptr)) { size_t pos = pop_info.GetPos(org_id); + emp_assert(pop_info.is_mutable == true, + "Cannot use At() for const population in Collection; try ConstAt() or use const iterator."); return pop_ptr->At(pos); } org_id -= pop_info.GetSize(pop_ptr); @@ -326,6 +332,9 @@ namespace mabe { return pos_map.begin()->first->At(0); // Return the first organism since out of range. } + // Always return a constant organism. + const Organism & ConstAt(size_t org_id) const { return At(org_id); } + Organism & operator[](size_t org_id) { return At(org_id); } const Organism & operator[](size_t org_id) const { return At(org_id); } @@ -364,7 +373,9 @@ namespace mabe { pop_ptr_t GetFirstPop() { if (pos_map.size() == 0) return nullptr; - else return pos_map.begin()->first; + emp_assert(pos_map.begin()->second.is_mutable == true, + "Cannot use GetFirstPop() for const Population in Collection; try ConstGetFirstPop()."); + return pos_map.begin()->first; } const_pop_ptr_t GetFirstPop() const { @@ -372,6 +383,8 @@ namespace mabe { else return pos_map.begin()->first; } + const_pop_ptr_t ConstGetFirstPop() const { return GetFirstPop(); } + template void IncPosition(T & it) const { const_pop_ptr_t cur_pop = it.PopPtr(); @@ -410,6 +423,8 @@ namespace mabe { CollectionIterator end() { return CollectionIterator(this, nullptr); } ConstCollectionIterator begin() const { return ConstCollectionIterator(this); } ConstCollectionIterator end() const { return ConstCollectionIterator(this, nullptr); } + ConstCollectionIterator cbegin() const { return ConstCollectionIterator(this); } + ConstCollectionIterator cend() const { return ConstCollectionIterator(this, nullptr); } /// Remove all entries from a collection. Collection & Clear() { pos_map.clear(); return *this; } @@ -417,15 +432,44 @@ namespace mabe { /// Add a Population to this collection. template Collection & Insert(Population & pop, Ts &&... extras) { + PopInfo & pop_info = pos_map[&pop]; + pop_info.full_pop = true; + pop_info.is_mutable = true; + return Insert( std::forward(extras)... ); // Insert anything else provided. + } + + /// Add a const Population to this collection. + template + Collection & Insert(const Population & pop, Ts &&... extras) { pos_map[&pop].full_pop = true; - return Insert( std::forward(extras)... ); + return Insert( std::forward(extras)... ); // Insert anything else provided. } /// Add an organism (by position!) template Collection & Insert(OrgPosition pos, Ts &&... extras) { - pos_map[pos.PopPtr()].InsertPos(pos.Pos()); - return Insert( std::forward(extras)... ); + PopInfo & pop_info = pos_map[pos.PopPtr()]; + pop_info.InsertPos(pos.Pos()); + pop_info.is_mutable = true; + return Insert( std::forward(extras)... ); // Insert anything else provided. + } + + /// Add a const organism (by position!) + template + Collection & Insert(ConstOrgPosition pos, Ts &&... extras) { + PopInfo & pop_info = pos_map[pos.PopPtr()]; + pop_info.InsertPos(pos.Pos()); + return Insert( std::forward(extras)... ); // Insert anything else provided. + } + + template + Collection & Insert(PopIterator pi, Ts &&... extras) { + return Insert( pi.AsPosition(), std::forward(extras)... ); + } + + template + Collection & Insert(ConstPopIterator pi, Ts &&... extras) { + return Insert( pi.AsPosition(), std::forward(extras)... ); } /// Add a whole other collection. @@ -434,17 +478,20 @@ namespace mabe { for (auto & [pop_ptr, in_pop_info] : in_collection.pos_map) { PopInfo & pop_info = pos_map[pop_ptr]; - if (pop_info.full_pop) continue; // This population is already full. + // If the incoming collection has mutable access to a population, this one should too. + if (in_pop_info.is_mutable) pop_info.is_mutable = true; + + // If we already have a full population, we are done!. + if (pop_info.full_pop) continue; // If we're adding a full population, do so. if (in_pop_info.full_pop) { pop_info.full_pop = true; continue; } // Otherwise add just the entries we need to. - emp::BitVector & pos_set = pop_info.pos_set; emp::BitVector in_pos_set = in_pop_info.pos_set; - // First, make sure both position sets are the same size. + // Make sure both position sets are the size of the larger one. if (in_pos_set.GetSize() < pos_set.GetSize()) { in_pos_set.Resize(pos_set.GetSize()); } @@ -452,14 +499,14 @@ namespace mabe { pos_set.Resize(in_pos_set.GetSize()); } - // Use 'OR' to join the sets. + // Use 'OR' to find the union of the sets. pos_set |= in_pos_set; } - return Insert( std::forward(extras)... ); + return Insert( std::forward(extras)... ); // Insert anything else provided. } - /// Base case... + /// Base case... nothing left to insert. Collection & Insert() { return *this; } /// Set this collection to be exactly the provided items. @@ -520,7 +567,7 @@ namespace mabe { // If the current iterator is smaller, delete the current population (not in intersection) if (cur_it->first < in_it->first) { cur_it = pos_map.erase(cur_it); continue; } - // Otherwise populations must be the same! If second pop is full, keep first as is! + // Otherwise populations must be the same! If 'in' pop is full, keep this one as is! if (!in_it->second.full_pop) { cur_it->second.RemoveFull(cur_it->first); // Shift first pop to individuals cur_it->second.pos_set &= in_it->second.pos_set; // Now pick out the intersection. @@ -537,6 +584,7 @@ namespace mabe { return *this; } + static std::string EMPGetTypeName() { return "mabe::Collection"; } }; // ------------------------------------------------------- From 6006c8e733dcc769f59dc3a28c5605c2148e06cd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 15:15:58 -0500 Subject: [PATCH 339/445] Cleaned up the automation of trait equations. --- source/core/MABE.hpp | 154 ++++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 60 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index b56d89e1..97272462 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -65,7 +65,7 @@ namespace mabe { ErrorManager error_man; ///< Object to manage warnings and errors. // Setup helper types. - using trait_equation_t = std::function; + using trait_equation_t = std::function; using trait_summary_t = std::function; using symbol_ptr_t = emp::Ptr; @@ -218,23 +218,21 @@ namespace mabe { SwapOrgs(from_pos, to_pos); } - /// Inject a copy of the provided organism and return the position it was placed in; - /// if more than one is added, return the position of the final injection. - OrgPosition Inject(Population & pop, const Organism & org, size_t copy_count=1); + /// Inject one or more copies of an organism and return the positions they were placed in. + Collection Inject(Population & pop, const Organism & org, size_t copy_count=1); /// Inject this specific instance of an organism and turn over the pointer to be managed - /// by MABE. Teturn the position the organism was placed in. + /// by MABE. Return the position the organism was placed in. OrgPosition InjectInstance(Population & pop, emp::Ptr org_ptr); - /// Add an organsim of a specified type to the world (provide the type name and the - /// MABE controller will create instances of it.) Returns the position of the last - /// organism placed. - OrgPosition Inject(Population & pop, const std::string & type_name, size_t copy_count=1); + /// Add one or more organisms of a specified type (provide the type name; the MABE controller + /// will create instances of it.) Returns the positions the organisms were placed. + Collection Inject(Population & pop, const std::string & type_name, size_t copy_count=1); /// Add an organism of a specified type and population (provide names of both and they /// will be properly setup.) - OrgPosition InjectByName(const std::string & pop_name, + Collection InjectByName(const std::string & pop_name, const std::string & type_name, size_t copy_count=1); @@ -351,7 +349,8 @@ namespace mabe { /// enties in there, returning the result. trait_equation_t BuildTraitEquation(const std::string & equation) { - return dm_parser.BuildMathFunction(org_data_map, equation); + auto dm_fun = dm_parser.BuildMathFunction(org_data_map, equation); + return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; } /// Scan a provided equation and return the names of all traits used in that equation. @@ -364,7 +363,9 @@ namespace mabe { /// trait_name from each, aggregating those values based on the trait_filter and returning /// the result as a string. - trait_summary_t BuildTraitSummary(const std::string & trait_name, std::string trait_filter); + template + std::function + BuildTraitSummary(const std::string & trait_fun, std::string trait_filter); // Handler for printing trait data void OutputTraitData(std::ostream & os, @@ -608,14 +609,21 @@ namespace mabe { // Setup "Collection" as another config type. auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); - // 'INJECT' allows a user to add an organism to a population. - std::function inject_fun = + // 'INJECT' allows a user to add an organism to a population; returns collection of added orgs. + std::function inject_fun = [this](Population & pop, const std::string & org_type_name, size_t count) { - Inject(pop, org_type_name, count); - return 0; + return Inject(pop, org_type_name, count); }; pop_type.AddMemberFunction("INJECT", inject_fun, - "Inject organisms into population (args: org_name, org_count)."); + "Inject organisms into population. Args: org_name, org_count. Return: OrgList of injected orgs."); + + std::function ave_trait_fun = + [this](Population & pop, const std::string & trait_equation) { + auto trait_fun = BuildTraitSummary(trait_equation, "mean"); + return emp::from_string(trait_fun( Collection(pop) )); + }; + pop_type.AddMemberFunction("CALC_MEAN", ave_trait_fun, + "Determine the average value of a trait (or equation) in the Population."); // Setup all known modules as available types in the config file. @@ -797,21 +805,22 @@ namespace mabe { /// Inject a copy of the provided organism and return the position it was placed in; /// if more than one is added, return the position of the final injection. - OrgPosition MABE::Inject(Population & pop, const Organism & org, size_t copy_count) { + Collection MABE::Inject(Population & pop, const Organism & org, size_t copy_count) { emp_assert(org.GetDataMap().SameLayout(org_data_map)); - OrgPosition pos; + Collection placement_set; for (size_t i = 0; i < copy_count; i++) { emp::Ptr inject_org = org.CloneOrganism(); on_inject_ready_sig.Trigger(*inject_org, pop); - pos = FindInjectPosition(pop, *inject_org); + OrgPosition pos = FindInjectPosition(pop, *inject_org); if (pos.IsValid()) { AddOrgAt( inject_org, pos); + placement_set.Insert(pos); } else { inject_org.Delete(); error_man.AddError("Invalid position; failed to inject organism ", i, "!"); } } - return pos; + return placement_set; } /// Inject this specific instance of an organism and turn over the pointer to be managed @@ -832,24 +841,26 @@ namespace mabe { /// Add an organsim of a specified type to the world (provide the type name and the /// MABE controller will create instances of it.) Returns the position of the last /// organism placed. - OrgPosition MABE::Inject(Population & pop, const std::string & type_name, size_t copy_count) { + Collection MABE::Inject(Population & pop, const std::string & type_name, size_t copy_count) { Verbose("Injecting ", copy_count, " orgs of type '", type_name, "' into population ", pop.GetID()); - auto & org_manager = GetModule(type_name); // Look up type of organism. - OrgPosition pos; // Place to save injection position. - for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. + auto & org_manager = GetModule(type_name); // Look up type of organism. + Collection placement_set; // Track set of positions placed. + for (size_t i = 0; i < copy_count; i++) { // Loop through, injecting each instance. auto org_ptr = org_manager.Make(random); // ...Build an org of this type. - pos = InjectInstance(pop, org_ptr); // ...Inject it into the population. + OrgPosition pos = InjectInstance(pop, org_ptr); // ...Inject it into the population. + placement_set.Insert(pos); // ...Record the position. } - return pos; // Return last position injected. + + return placement_set; // Return last position injected. } /// Add an organism of a specified type and population (provide names of both and they /// will be properly setup.) - OrgPosition MABE::InjectByName(const std::string & pop_name, - const std::string & type_name, - size_t copy_count) { + Collection MABE::InjectByName(const std::string & pop_name, + const std::string & type_name, + size_t copy_count) { int pop_id = GetPopID(pop_name); if (pop_id == -1) { error_man.AddError("Invalid population name used in inject: ", @@ -858,8 +869,7 @@ namespace mabe { "copy_count=", copy_count); } Population & pop = GetPopulation(pop_id); - OrgPosition pos = Inject(pop, type_name, copy_count); // Inject a copy of the organism. - return pos; // Return last position injected. + return Inject(pop, type_name, copy_count); // Inject the organisms. } /// Give birth to one or more offspring; return position of last placed. @@ -953,44 +963,68 @@ namespace mabe { /// entropy : Return the Shannon entropy of this value. /// :trait : Return the mutual information with another provided trait. - MABE::trait_summary_t MABE::BuildTraitSummary( - const std::string & trait_name, + template + std::function MABE::BuildTraitSummary( + const std::string & trait_fun, std::string trait_filter ) { - // The trait input has two components: - // (1) the trait NAME and - // (2) (optionally) how to calculate the trait SUMMARY, such as min, max, ave, etc. - - // Everything before the first colon is the trait name. - size_t trait_id = org_data_map.GetID(trait_name); - emp::TypeID trait_type = org_data_map.GetType(trait_id); - const bool is_numeric = trait_type.IsArithmetic(); + static_assert( std::is_same() || std::is_same(), + "BuildTraitSummary FROM_T must be Collection or Population." ); + static_assert( std::is_same() || std::is_same(), + "BuildTraitSummary TO_T must be double or std::string." ); - auto get_double_fun = [trait_id, trait_type](const Organism & org) { - return org.GetTraitAsDouble(trait_id, trait_type); - }; - auto get_string_fun = [trait_id, trait_type](const Organism & org) { - return emp::to_literal( org.GetTraitAsString(trait_id, trait_type) ); - }; + // The trait input has two components: + // (1) the trait (or trait function) and + // (2) how to calculate the trait SUMMARY, such as min, max, ave, etc. + + // If we have a single trait, we may want to use a string type. + if (emp::is_identifier(trait_fun) // If we have a single trait... + && org_data_map.HasName(trait_fun) // ...and it's in the data map... + && !org_data_map.IsNumeric(trait_fun) // ...and it's not numeric... + ) { + size_t trait_id = org_data_map.GetID(trait_fun); + emp::TypeID result_type = org_data_map.GetType(trait_id); + + auto get_fun = [trait_id, result_type](const Organism & org) { + return emp::to_literal( org.GetTraitAsString(trait_id, result_type) ); + }; + auto fun = emp::BuildCollectFun(trait_filter, get_fun); - // Return the number of times a specific value was found. - if (trait_filter[0] == '=') { - // @CAO: DO THIS! - trait_filter.erase(0,1); // Erase the '=' and we are left with the string to match. + // Go through all combinations of TO/FROM to return the correct types. + if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & p){ return emp::from_string(fun( Collection(p) )); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & c){ return emp::from_string(fun(c)); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & p){ return fun( Collection(p) ); }; + } + else return fun; } - // Otherwise pass along to the BuildCollectFun with the correct type... - auto result = is_numeric - ? emp::BuildCollectFun(trait_filter, get_double_fun) - : emp::BuildCollectFun(trait_filter, get_string_fun); + // If we made it here, we are numeric. + auto get_fun = BuildTraitEquation(trait_fun); + auto fun = emp::BuildCollectFun(trait_filter, get_fun); - // If we made it past the 'if' statements, we don't know this aggregation type. - if (!result) { - error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_name, "'."); + // If we don't have a fun, we weren't able to build an aggregation function. + if (!fun) { + error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_fun, "'."); return [](const Collection &){ return std::string("Error! Unknown trait function"); }; } - return result; + // Go through all combinations of TO/FROM to return the correct types. + // @CAO need to adjust BuildCollectFun so that it returns correct type; not always string. + if constexpr (std::is_same() && std::is_same()) { + return [fun](const Population & p){ return emp::from_string(fun( Collection(p) )); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const Collection & c){ return emp::from_string(fun(c)); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const Population & p){ return fun( Collection(p) ); }; + } + else return fun; } // Handler for printing trait data From 00063c61f45a8049a6ff629bc5443f3c3215b155 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 15:17:02 -0500 Subject: [PATCH 340/445] Fixed SelectTournament to assume generated functions take Organisms not DataMaps. --- source/select/SelectTournament.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index 3c55130d..fe88d7b0 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -72,13 +72,13 @@ namespace mabe { // Find a random organism in the population and call it "best" size_t best_id = random.GetUInt(N); while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); - double best_fit = fit_fun(select_pop[best_id].GetDataMap()); + double best_fit = fit_fun(select_pop[best_id]); // Loop through other organisms for the rest of the tournament size, and pick best. for (size_t test=1; test < tourny_size; test++) { size_t test_id = random.GetUInt(N); while (select_pop[test_id].IsEmpty()) test_id = random.GetUInt(N); - double test_fit = fit_fun(select_pop[test_id].GetDataMap()); + double test_fit = fit_fun(select_pop[test_id]); if (test_fit > best_fit) { best_id = test_id; best_fit = test_fit; From c5a761c484185fd778eb757b7c3d2f9bf1e81ee6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 20:37:59 -0500 Subject: [PATCH 341/445] Added Symbol::DebugString() to provide a string of information describing a symbol. --- source/Emplode/Symbol.hpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index d35ce5c2..b27bc387 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -192,7 +192,7 @@ namespace emplode { } Symbol & SetMin(double min) { range.SetLower(min); return *this; } - Symbol & SetMax(double max) { range.SetLower(max); return *this; } + Symbol & SetMax(double max) { range.SetUpper(max); return *this; } // Try to copy another config symbol into this one; return true if successful. virtual bool CopyValue(const Symbol & ) { return false; } @@ -243,6 +243,29 @@ namespace emplode { return *this; } + // Generate a string with information about this symbol. + std::string DebugString() const { + std::string out = emp::to_string( + "Symbol '", GetName(), + "' type=", GetTypename(), + " scope=", scope ? scope.Cast()->GetName() : "[none]"); + + if (IsTemporary()) out += " TEMPORARY"; + if (IsBuiltin()) out += " BUILTIN"; + if (IsError()) out += " ERROR"; + if (IsNumeric()) out += " Numeric"; + if (IsString()) out += " String"; + if (IsFunction()) out += " Function"; + if (IsObject()) out += " Object"; + if (IsScope()) out += " Scope"; + if (IsLocal()) out += " Local"; + if (IsFunction()) out += " Function"; + if (HasNumericReturn()) out += " (numeric return)"; + if (HasStringReturn()) out += " (string return)"; + + return out; + } + }; /// A generic version of a symbol for an internally maintained variable. From 880d0beee2626a1a18ab098d6f9d1a89fbadbd21 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 20:39:35 -0500 Subject: [PATCH 342/445] Fixed a const cast on Population in Collection. --- source/core/Collection.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index fde14fc9..0c3fda9c 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -441,7 +441,8 @@ namespace mabe { /// Add a const Population to this collection. template Collection & Insert(const Population & pop, Ts &&... extras) { - pos_map[&pop].full_pop = true; + emp::Ptr pop_ptr = &pop; + pos_map[pop_ptr.ConstCast()].full_pop = true; return Insert( std::forward(extras)... ); // Insert anything else provided. } From 5ca148504c7b354cb7816af7ce268620c169fac6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 20:40:08 -0500 Subject: [PATCH 343/445] Analysis functions on Population and Collection are fixed and included. --- source/core/MABE.hpp | 79 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 97272462..c39b2b99 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -360,12 +360,22 @@ namespace mabe { } /// Build a function to scan a collection of organisms, reading the value for the given - /// trait_name from each, aggregating those values based on the trait_filter and returning - /// the result as a string. + /// trait function from each, aggregating those values based on the trait_filter and returning + /// the result as a string. Output is a function in the form: TO_T(const FROM_T &) + template + std::function + BuildTraitSummary(const std::string & trait_fun, std::string trait_filter); + + /// Build a function that takes a trait equation, builds it, and runs it on a container. + /// Output is a function in the form: TO_T(const FROM_T &, trait_equation) + template + auto BuildTraitFunction(const std::string & fun_type) { + return [this,fun_type](FROM_T & pop, const std::string & trait_equation) { + auto trait_fun = BuildTraitSummary(trait_equation, fun_type); + return trait_fun(pop); + }; + } - template - std::function - BuildTraitSummary(const std::string & trait_fun, std::string trait_filter); // Handler for printing trait data void OutputTraitData(std::ostream & os, @@ -617,14 +627,55 @@ namespace mabe { pop_type.AddMemberFunction("INJECT", inject_fun, "Inject organisms into population. Args: org_name, org_count. Return: OrgList of injected orgs."); - std::function ave_trait_fun = - [this](Population & pop, const std::string & trait_equation) { - auto trait_fun = BuildTraitSummary(trait_equation, "mean"); - return emp::from_string(trait_fun( Collection(pop) )); - }; - pop_type.AddMemberFunction("CALC_MEAN", ave_trait_fun, - "Determine the average value of a trait (or equation) in the Population."); - + pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + "Count the number of distinct values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + "Identify the most common value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + "Calculate the average value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + "Find the smallest value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + "Find the largest value of a trait (or equation)."); + pop_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + "Find the index of the smallest value of a trait (or equation)."); + pop_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + "Find the index of the largest value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + "Find the 50-percentile value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + "Find the variance of the distribution of values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + "Find the variance of the distribution of values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + "Add up the total value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + "Determine the entropy of values for a trait (or equation)."); + + collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + "Count the number of distinct values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + "Identify the most common value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + "Calculate the average value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + "Find the smallest value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + "Find the largest value of a trait (or equation)."); + collect_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + "Find the index of the smallest value of a trait (or equation)."); + collect_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + "Find the index of the largest value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + "Find the 50-percentile value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + "Find the variance of the distribution of values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + "Find the variance of the distribution of values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + "Add up the total value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + "Determine the entropy of values for a trait (or equation)."); // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { @@ -1010,7 +1061,7 @@ namespace mabe { // If we don't have a fun, we weren't able to build an aggregation function. if (!fun) { error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_fun, "'."); - return [](const Collection &){ return std::string("Error! Unknown trait function"); }; + return [](const FROM_T &){ return TO_T(); }; } // Go through all combinations of TO/FROM to return the correct types. From ba8dfd0f44ddfd9c9bea2c094c85167edfba9517 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 11 Nov 2021 20:40:46 -0500 Subject: [PATCH 344/445] Updated DevelNotes with recent changes to Emplode. --- source/Emplode/DeveloperNotes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Emplode/DeveloperNotes.md b/source/Emplode/DeveloperNotes.md index 9485cae4..326d023f 100644 --- a/source/Emplode/DeveloperNotes.md +++ b/source/Emplode/DeveloperNotes.md @@ -7,19 +7,19 @@ LEVEL MAP: Symbol - [] Lexer - [] -EmplodeTools - [Symbol] +SymbolTableBase - [Symbol] -TypeInfo - [Symbol,EmplodeTools] Basic information for a user-defined type. +TypeInfo - [Symbol,SymbolTableBase] Basic information for a user-defined type. Symbol_Function - [Symbol] Symbol_Linked - [Symbol] -Symbol_Scope - [Symbol,Symbol_Function,Symbol_Linked] +Symbol_Scope - [Symbol,Symbol_Function,Symbol_Linked,TypeInfo] EmplodeType - [Symbol_Scope,TypeInfo] Symbol_Object - [Symbol_Scope,EmplodeType] -AST - [Symbol_Object,Symbol,EmplodeTools] +AST - [Symbol_Object,Symbol,SymbolTableBase] Events - [AST] DataFile - [EmplodeType] From 989aa532ff2aefd3a0aa82fa2e0293cbdc549327 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 12 Nov 2021 14:52:07 -0500 Subject: [PATCH 345/445] Added better error information to Symbol_Object when a copy fails. --- source/Emplode/Symbol_Object.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index ce1b067b..c6bb8b91 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -66,8 +66,10 @@ namespace emplode { bool CopyValue(const Symbol & in) override { if (in.IsObject() == false) { - std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() - << "', but " << in.GetName() << " is not an Object." << std::endl; + std::cerr << "Trying to assign `" << in.GetName() << "' to '" << GetName() + << "', but " << in.GetName() << " is not an Object." << std::endl; + std::cerr << "Target: " << in.DebugString() << std::endl; + emp_error("Assignment failed."); return false; // Mis-matched types; failed to copy. } From 19aa409e137ee555b920d09c82dc962db1a3e940 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 13 Nov 2021 15:35:58 -0500 Subject: [PATCH 346/445] Removed newly deprecated modules. --- source/{interface => OLD}/FileOutput.hpp | 0 source/{schema => OLD}/MovePopulation.hpp | 0 source/{schema => OLD}/Mutate.hpp | 0 source/modules.hpp | 5 ----- 4 files changed, 5 deletions(-) rename source/{interface => OLD}/FileOutput.hpp (100%) rename source/{schema => OLD}/MovePopulation.hpp (100%) rename source/{schema => OLD}/Mutate.hpp (100%) diff --git a/source/interface/FileOutput.hpp b/source/OLD/FileOutput.hpp similarity index 100% rename from source/interface/FileOutput.hpp rename to source/OLD/FileOutput.hpp diff --git a/source/schema/MovePopulation.hpp b/source/OLD/MovePopulation.hpp similarity index 100% rename from source/schema/MovePopulation.hpp rename to source/OLD/MovePopulation.hpp diff --git a/source/schema/Mutate.hpp b/source/OLD/Mutate.hpp similarity index 100% rename from source/schema/Mutate.hpp rename to source/OLD/Mutate.hpp diff --git a/source/modules.hpp b/source/modules.hpp index b46d5625..bbbaae05 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -17,7 +17,6 @@ // Interface Modules #include "interface/CommandLine.hpp" -#include "interface/FileOutput.hpp" // Placement Modules #include "placement/GrowthPlacement.hpp" @@ -28,10 +27,6 @@ #include "select/SelectRoulette.hpp" #include "select/SelectTournament.hpp" -// Other schema -#include "schema/MovePopulation.hpp" -#include "schema/Mutate.hpp" - // Organism Types #include "orgs/AvidaGPOrg.hpp" #include "orgs/BitsOrg.hpp" From 5004dac27faf3768652f719be527171a72339aab Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:26:04 -0500 Subject: [PATCH 347/445] Added DataFile::AddSetup() AND config cleaned up member functions NUM_COLS and WRITE --- source/Emplode/DataFile.hpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/source/Emplode/DataFile.hpp b/source/Emplode/DataFile.hpp index 349d312e..77c16fee 100644 --- a/source/Emplode/DataFile.hpp +++ b/source/Emplode/DataFile.hpp @@ -26,10 +26,11 @@ namespace emplode { /// dynamically. class DataFile : public EmplodeType { private: - using fun_t = std::function; + using data_fun_t = std::function; + using setup_fun_t = std::function; struct ColumnInfo { std::string header; - fun_t fun; + data_fun_t fun; }; std::string name=""; ///< Unique name for this object. @@ -37,6 +38,7 @@ namespace emplode { std::string filename; ///< Name of output file. emp::vector cols; ///< Data about columns maintainted. + emp::vector setup; ///< Commands to run before writing columns. public: DataFile() = delete; @@ -51,22 +53,30 @@ namespace emplode { // Setup member functions associated with population. static void InitType(TypeInfo & info) { - auto fun_num_cols = [](DataFile & target) { return target.cols.size(); }; - info.AddMemberFunction("NUM_COLS", fun_num_cols, "Return the number of columns in this file."); - info.AddMemberFunction("WRITE", [](DataFile & target) { return target.Write(); }, - "Add on the next line of data."); + info.AddMemberFunction("NUM_COLS", + [](DataFile & df) { return df.cols.size(); }, + "Return the number of columns in this file."); + info.AddMemberFunction("WRITE", + [](DataFile & df) { return df.Write(); }, + "Add on the next line of data."); } void SetupConfig() override { LinkVar(filename, "filename", "Name to use for this file."); } - size_t AddColumn(const std::string & header, fun_t fun) { + size_t AddColumn(const std::string & header, data_fun_t fun) { size_t col_id = cols.size(); cols.push_back(ColumnInfo{header,fun}); return col_id; } + size_t AddSetup(setup_fun_t fun) { + size_t setup_id = setup.size(); + setup.push_back(fun); + return setup_id; + } + size_t Write() { const bool file_exists = files->Has(filename); // Is file is already setup? std::ostream & file = files->GetOutputStream(filename); // File to write to. @@ -80,6 +90,9 @@ namespace emplode { file << '\n'; } + // Do any setup for the columns. + for (auto fun : setup) fun(); + // Now print out each entry. for (size_t i = 0; i < cols.size(); ++i) { if (i) file << ","; From 29b64dd040b18783a916a029fee803914826b7b9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:27:13 -0500 Subject: [PATCH 348/445] Setup default Symbol::AsDouble() and AsString() so results indicate problem, but not throw error. --- source/Emplode/Symbol.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index b27bc387..78c7bb55 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -122,8 +122,8 @@ namespace emplode { Symbol & SetTemporary(bool in=true) { is_temporary = in; return *this; } Symbol & SetBuiltin(bool in=true) { is_builtin = in; return *this; } - virtual double AsDouble() const { emp_assert(false); return 0.0; } - virtual std::string AsString() const { emp_assert(false); return ""; } + virtual double AsDouble() const { return std::nan("NaN"); } + virtual std::string AsString() const { return "[[__INVALID SYMBOL CONVERSION__]]"; } virtual Symbol & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } From e2e99029c30ae827e2dca6247c4c17efe66e2cd4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:27:45 -0500 Subject: [PATCH 349/445] Added DataFile config member function for ADD_SETUP --- source/Emplode/Emplode.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index cda99c92..4ae784cb 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -200,6 +200,13 @@ namespace emplode { }, "Add a column to the associated DataFile. Args: title, string to execute for result" ); + df_type.AddMemberFunction( + "ADD_SETUP", + [exec_fun](DataFile & file, std::string cmd){ + return file.AddSetup( [exec_fun,cmd](){ exec_fun(cmd); }); + }, + "Add a command to be run each time before columns are output." + ); } // Prevent copy or move since we are using lambdas that capture 'this' From e39206423443a8bccd316f2fbb900902ad0b4863 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:29:58 -0500 Subject: [PATCH 350/445] Clean up in Collection and added missing constructor implementation. --- source/core/Collection.hpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 0c3fda9c..58253d52 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -302,6 +302,10 @@ namespace mabe { return count; } + iterator_t IteratorAt(size_t org_id) { return iterator_t(*this, org_id); } + const_iterator_t IteratorAt(size_t org_id) const { return const_iterator_t(*this, org_id); } + const_iterator_t ConstIteratorAt(size_t org_id) const { return IteratorAt(org_id); } + Organism & At(size_t org_id) override { for (auto [pop_ptr, pop_info] : pos_map) { if (org_id < pop_info.GetSize(pop_ptr)) { @@ -544,19 +548,20 @@ namespace mabe { } /// Produce a new collection limited to living organisms. - Collection GetAlive() { + Collection GetAlive() const { Collection out(*this); out.RemoveEmpty(); return out; } /// Merge this collection with another collection. - Collection & operator |= (const Collection & collection2) { - return Insert(collection2); - } + Collection & operator|= (const Collection & in) { return Insert(in); } + + /// Shortcut to insert anything into this collection. + template Collection & operator+= (const T & in) { return Insert(in); } /// Reduce to the intersection with another collection. - Collection & operator &= (const Collection & in_collection) { + Collection & operator&= (const Collection & in_collection) { auto cur_it = pos_map.begin(); auto in_it = in_collection.pos_map.begin(); @@ -618,7 +623,7 @@ namespace mabe { *this = collection_ptr->end(); } - /// Constructor where you can optionally supply population pointer and position. + /// Constructor where you can optionally supply collection pointer and position. template CollectionIterator_Interface ::CollectionIterator_Interface(emp::Ptr _col, size_t _pos) @@ -628,13 +633,24 @@ namespace mabe { if (base_t::IsValid() == false) IncPosition(); } - /// Constructor where you can optionally supply population pointer and position. + /// Constructor where you can optionally supply collection pointer and position. template CollectionIterator_Interface ::CollectionIterator_Interface(emp::Ptr _col, emp::Ptr pop, size_t _pos) : base_t(pop, _pos), collection_ptr(_col) { } + + /// Constructor where you supply a collection reference and optional position. + template + CollectionIterator_Interface + ::CollectionIterator_Interface(COLLECTION_T & _col, size_t _pos) + : base_t(_col.GetFirstPop(), _pos), collection_ptr(&_col) + { + // Make sure that this iterator is actually valid. If not, move to next position. + if (base_t::IsValid() == false) IncPosition(); + } + } #endif From b0e8e80424c1c18a305c3011210d70357422e43a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:32:07 -0500 Subject: [PATCH 351/445] Setup type initialization functionality for modules. --- source/core/ManagerModule.hpp | 3 ++- source/core/Module.hpp | 3 ++- source/core/ModuleBase.hpp | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index 0a93fe8e..ff486381 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -117,9 +117,10 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + new_info.obj_init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { return &control.AddModule(name, desc); }; + new_info.type_init_fun = [](emplode::TypeInfo & info){ MODULE_T::InitType(info); }; GetModuleInfo().insert(new_info); } }; diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 6201f6a2..c58b1f38 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -419,9 +419,10 @@ namespace mabe { ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + new_info.obj_init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { return &control.AddModule(name, desc); }; + new_info.type_init_fun = [](emplode::TypeInfo & info){ T::InitType(info); }; new_info.type_id = emp::GetTypeID(); GetModuleInfo().insert(new_info); } diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index 7bbd9409..c384f3ca 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -331,7 +331,8 @@ namespace mabe { struct ModuleInfo { std::string name; std::string desc; - std::function(MABE &, const std::string &)> init_fun; + std::function(MABE &, const std::string &)> obj_init_fun; + std::function type_init_fun; emp::TypeID type_id; bool operator<(const ModuleInfo & in) const { return name < in.name; } }; From 2b4d5ed6678e13b93d81b6df2ead8277835a35f9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 00:36:01 -0500 Subject: [PATCH 352/445] Added MABE::MoveOrgs; member funs for Collection/Population; births return collection of children. --- source/core/MABE.hpp | 114 ++++++++++++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index c39b2b99..d5a72094 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -247,20 +247,21 @@ namespace mabe { /// Give birth to one or more offspring; return position of last placed. /// Triggers 'before repro' signal on parent (once) and 'offspring ready' on each offspring. /// Regular signal triggers occur in AddOrgAt. - OrgPosition DoBirth(const Organism & org, - OrgPosition ppos, - Population & target_pop, - size_t birth_count=1, - bool do_mutations=true); + Collection DoBirth(const Organism & org, + OrgPosition ppos, + Population & target_pop, + size_t birth_count=1, + bool do_mutations=true); - OrgPosition DoBirth(const Organism & org, - OrgPosition ppos, - OrgPosition target_pos, - bool do_mutations=true); + Collection DoBirth(const Organism & org, + OrgPosition ppos, + OrgPosition target_pos, + bool do_mutations=true); - /// A shortcut to DoBirth where only the parent position needs to be supplied. - OrgPosition Replicate(OrgPosition ppos, Population & target_pop, + /// A shortcut to DoBirth where only the parent position needs to be supplied; + /// Return all offspring placed. + Collection Replicate(OrgPosition ppos, Population & target_pop, size_t birth_count=1, bool do_mutations=true) { return DoBirth(*ppos, ppos, target_pop, birth_count, do_mutations); } @@ -284,6 +285,9 @@ namespace mabe { } } + /// Move all organisms from one population to another. + void MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to); + /// Return a ramdom position from a desginated population. OrgPosition GetRandomPos(Population & pop) { emp_assert(pop.GetSize() > 0); @@ -626,7 +630,12 @@ namespace mabe { }; pop_type.AddMemberFunction("INJECT", inject_fun, "Inject organisms into population. Args: org_name, org_count. Return: OrgList of injected orgs."); + pop_type.AddMemberFunction("REPLACE_WITH", + [this](Population & to_pop, Population & from_pop){ MoveOrgs(from_pop, to_pop, true); return 0; }, + "Move all organisms organisms from another population, removing current orgs." ); + pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + "Return the value of the provided trait for the first organism"); pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), "Count the number of distinct values of a trait (or equation)."); pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), @@ -651,7 +660,21 @@ namespace mabe { "Add up the total value of a trait (or equation)."); pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), "Determine the entropy of values for a trait (or equation)."); - + pop_type.AddMemberFunction("FIND_MIN", + [this](Population & pop, const std::string & trait_equation) -> Collection { + auto trait_fun = BuildTraitSummary(trait_equation, "min_id"); + return pop.IteratorAt(trait_fun(pop)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + pop_type.AddMemberFunction("FIND_MAX", + [this](Population & pop, const std::string & trait_equation) -> Collection { + auto trait_fun = BuildTraitSummary(trait_equation, "max_id"); + return pop.IteratorAt(trait_fun(pop)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + + collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + "Return the value of the provided trait for the first organism"); collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), "Count the number of distinct values of a trait (or equation)."); collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), @@ -676,13 +699,26 @@ namespace mabe { "Add up the total value of a trait (or equation)."); collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), "Determine the entropy of values for a trait (or equation)."); + collect_type.AddMemberFunction("FIND_MIN", + [this](Collection & collect, const std::string & trait_equation) -> Collection { + auto trait_fun = BuildTraitSummary(trait_equation, "min_id"); + return collect.IteratorAt(trait_fun(collect)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + collect_type.AddMemberFunction("FIND_MAX", + [this](Collection & collect, const std::string & trait_equation) -> Collection { + auto trait_fun = BuildTraitSummary(trait_equation, "max_id"); + return collect.IteratorAt(trait_fun(collect)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); // Setup all known modules as available types in the config file. for (auto & mod : GetModuleInfo()) { auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { - return mod.init_fun(*this,name); + return mod.obj_init_fun(*this,name); }; - config.AddType(mod.name, mod.desc, mod_init_fun, nullptr, mod.type_id); + auto & type_info = config.AddType(mod.name, mod.desc, mod_init_fun, nullptr, mod.type_id); + mod.type_init_fun(type_info); // Setup functions for this module. } @@ -926,16 +962,17 @@ namespace mabe { /// Give birth to one or more offspring; return position of last placed. /// Triggers 'before repro' signal on parent (once) and 'offspring ready' on each offspring. /// Regular signal triggers occur in AddOrgAt. - OrgPosition MABE::DoBirth(const Organism & org, - OrgPosition ppos, - Population & target_pop, - size_t birth_count, - bool do_mutations) { - emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. - before_repro_sig.Trigger(ppos); - OrgPosition pos; // Position of each offspring placed. + Collection MABE::DoBirth(const Organism & org, + OrgPosition ppos, + Population & target_pop, + size_t birth_count, + bool do_mutations) { + emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. + before_repro_sig.Trigger(ppos); // Signal reproduction event. + OrgPosition pos; // Position of each offspring placed. emp::Ptr new_org; - for (size_t i = 0; i < birth_count; i++) { // Loop through offspring, adding each + Collection birth_list; // Track positions of all offspring. + for (size_t i = 0; i < birth_count; i++) { // Loop through offspring, adding each new_org = do_mutations ? org.MakeOffspringOrganism(random) : org.CloneOrganism(); // Alert modules that offspring is ready, then find its birth position. @@ -943,16 +980,19 @@ namespace mabe { pos = FindBirthPosition(target_pop, *new_org, ppos); // If this placement is valid, do so. Otherwise delete the organism. - if (pos.IsValid()) AddOrgAt(new_org, pos, ppos); + if (pos.IsValid()) { + AddOrgAt(new_org, pos, ppos); + birth_list.Insert(pos); + } else new_org.Delete(); } - return pos; + return birth_list; } - OrgPosition MABE::DoBirth(const Organism & org, - OrgPosition ppos, - OrgPosition target_pos, - bool do_mutations) { + Collection MABE::DoBirth(const Organism & org, + OrgPosition ppos, + OrgPosition target_pos, + bool do_mutations) { emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. emp_assert(target_pos.IsValid()); // Target positions must already be valid. @@ -965,6 +1005,22 @@ namespace mabe { return target_pos; } + void MABE::MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to) { + // Get the starting point for the new organisms to ove to. + Population::iterator_t it_to = reset_to ? to_pop.begin() : to_pop.end(); + + // Prepare the "to" population before moving the new organisms in. + if (reset_to) EmptyPop(to_pop, from_pop.GetSize()); // Clear out the population. + else ResizePop(to_pop, to_pop.GetSize() + from_pop.GetSize()); + + // Move the organisms over + for (auto it_from = from_pop.begin(); it_from != from_pop.end(); ++it_from, ++it_to) { + if (it_from.IsOccupied()) MoveOrg(it_from, it_to); + } + + // Clear out the from population now that we're done with it. + EmptyPop(from_pop, 0); + } /// Return a ramdom position from a desginated population with a living organism in it. OrgPosition MABE::GetRandomOrgPos(Population & pop) { From adbc2c6581230ea48486257752536b3e094ae3ed Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 14 Nov 2021 15:45:41 -0500 Subject: [PATCH 353/445] Removed GrowthPlacement module; now default behavior. --- source/{placement => OLD}/GrowthPlacement.hpp | 0 source/modules.hpp | 1 - 2 files changed, 1 deletion(-) rename source/{placement => OLD}/GrowthPlacement.hpp (100%) diff --git a/source/placement/GrowthPlacement.hpp b/source/OLD/GrowthPlacement.hpp similarity index 100% rename from source/placement/GrowthPlacement.hpp rename to source/OLD/GrowthPlacement.hpp diff --git a/source/modules.hpp b/source/modules.hpp index bbbaae05..c7c1fc4d 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -19,7 +19,6 @@ #include "interface/CommandLine.hpp" // Placement Modules -#include "placement/GrowthPlacement.hpp" // Selection Modules #include "select/SelectElite.hpp" From 917e16278a36e546b8a84f370bba58313e01fc8e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:24:05 -0500 Subject: [PATCH 354/445] Setup Emplode lexer to accept strings with any types of quotes. --- source/Emplode/Lexer.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/Emplode/Lexer.hpp b/source/Emplode/Lexer.hpp index c0a253cc..aff54767 100644 --- a/source/Emplode/Lexer.hpp +++ b/source/Emplode/Lexer.hpp @@ -20,7 +20,6 @@ namespace emplode { int token_identifier = -1; ///< Token id for identifiers int token_number = -1; ///< Token id for literal numbers int token_string = -1; ///< Token id for literal strings - int token_char = -1; ///< Token id for literal characters int token_dots = -1; ///< Token id for a series of dots (...) int token_symbol = -1; ///< Token id for other symbols @@ -34,8 +33,7 @@ namespace emplode { // Meaningful tokens have next priority. token_identifier = AddToken("Identifier", "[a-zA-Z_][a-zA-Z0-9_]*"); token_number = AddToken("Literal Number", "[0-9]+(\\.[0-9]+)?"); - token_string = AddToken("Literal String", "\\\"([^\"\\\\]|\\\\.)*\\\""); - token_char = AddToken("Literal Character", "'([^'\n\\\\]|\\\\.)+'"); + token_string = AddToken("Literal String", "(\\\"([^\"\\\\]|\\\\.)*\\\")|('([^'\\\\]|\\\\.)*')|(`([^`\\\\]|\\\\.)*`)"); token_dots = AddToken("Dots", "\".\"+"); /// Symbol tokens should have least priority. They include any solitary character not listed @@ -46,7 +44,6 @@ namespace emplode { bool IsID(const emp::Token token) const noexcept { return token.token_id == token_identifier; } bool IsNumber(const emp::Token token) const noexcept { return token.token_id == token_number; } bool IsString(const emp::Token token) const noexcept { return token.token_id == token_string; } - bool IsChar(const emp::Token token) const noexcept { return token.token_id == token_char; } bool IsDots(const emp::Token token) const noexcept { return token.token_id == token_dots; } bool IsSymbol(const emp::Token token) const noexcept { return token.token_id == token_symbol; } }; From 943e90f5fd315c4e46592a9e0762a91f0e71d8c4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:24:55 -0500 Subject: [PATCH 355/445] Removed Emplode Parser from allowing literal chars; all quotes are now strings. --- source/Emplode/Parser.hpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index cf0def7d..96235a16 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -80,7 +80,6 @@ namespace emplode { bool IsID() const { return pos && lexer->IsID(*pos); } bool IsNumber() const { return pos && lexer->IsNumber(*pos); } - bool IsChar() const { return pos && lexer->IsChar(*pos); } bool IsString() const { return pos && lexer->IsString(*pos); } bool IsDots() const { return pos && lexer->IsDots(*pos); } @@ -326,18 +325,11 @@ namespace emplode { return MakeTempLeaf(value); // Return temporary Symbol. } - // A literal char should be converted to its ASCII value. - if (state.IsChar()) { - Debug("...value is a char: ", state.AsLexeme()); - char lit_char = emp::from_literal_char(state.UseLexeme()); // Convert the literal char. - return MakeTempLeaf((double) lit_char); // Return temporary Symbol. - } - // A literal string should be converted to a regular string and used. if (state.IsString()) { Debug("...value is a string: ", state.AsLexeme()); - std::string str = emp::from_literal_string(state.UseLexeme()); // Convert the literal string. - return MakeTempLeaf(str); // Return temporary Symbol. + std::string str = emp::from_literal_string(state.UseLexeme(), "\"'`"); // Convert literal string. + return MakeTempLeaf(str); // Return temporary Symbol. } // If we have an open parenthesis, process everything inside into a single value... From e1fb00d5ccb14ddc48d382815db672fd4f405b9a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:26:33 -0500 Subject: [PATCH 356/445] Added Population::PlaceBirth(), PlaceInject(), and FindNeighbor() as configurable functions. --- source/core/Population.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 3c5353c8..4bce1485 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -93,6 +93,10 @@ namespace mabe { emp::Ptr empty_org = nullptr; ///< Organism to fill in empty cells (does have data map!) + std::function place_birth_fun; + std::function place_inject_fun; + std::function find_neighbor_fun; + public: using iterator_t = PopIterator; using const_iterator_t = ConstPopIterator; @@ -127,6 +131,10 @@ namespace mabe { void SetName(const std::string & in_name) { name = in_name; } void SetID(int in_id) noexcept { pop_id = in_id; } + template void SetPlaceBirthFun(FUN_T fun) { place_birth_fun = fun; } + template void SetPlaceInjectFun(FUN_T fun) { place_inject_fun = fun; } + template void SetFindNeighborFun(FUN_T fun) { find_neighbor_fun = fun; } + Organism & operator[](size_t org_id) { return *(orgs[org_id]); } const Organism & operator[](size_t org_id) const { return *(orgs[org_id]); } Organism & At(size_t org_id) override { return *(orgs[org_id]); } @@ -140,6 +148,10 @@ namespace mabe { iterator_t IteratorAt(size_t pos) { return iterator_t(this, pos); } const_iterator_t ConstIteratorAt(size_t pos) const { return const_iterator_t(this, pos); } + OrgPosition PlaceBirth(Organism & org, OrgPosition ppos) { return place_birth_fun(org, ppos); } + OrgPosition PlaceInject(Organism & org) { return place_inject_fun(org); } + OrgPosition FindNeighbor(OrgPosition pos) { return find_neighbor_fun(pos); } + private: // ---== To be used by friend class MABEBase only! ==--- void SetOrg(size_t pos, emp::Ptr org_ptr) { From 6a6996866ac9bc85a9237a5cf772f0af6c889c29 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:27:16 -0500 Subject: [PATCH 357/445] Move placement functionality over to Population. --- source/core/MABE.hpp | 48 +++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index d5a72094..ece3029f 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -183,19 +183,6 @@ namespace mabe { /// Update MABE a specified number of time steps. void DoRun(size_t num_updates); - // -- World Structure -- - - OrgPosition FindBirthPosition(Population & pop, Organism & offspring, OrgPosition ppos) { - return do_place_birth_sig.FindPosition(pop, offspring, ppos); - } - OrgPosition FindInjectPosition(Population & pop, Organism & new_org) { - return do_place_inject_sig.FindPosition(pop, new_org); - } - OrgPosition FindNeighbor(OrgPosition pos) { - return do_find_neighbor_sig.FindPosition(pos); - } - - // --- Population Management --- size_t GetNumPopulations() const { return pops.size(); } @@ -417,9 +404,6 @@ namespace mabe { bool BeforeExit_IsTriggered(mod_ptr_t mod) { return before_exit_sig.cur_mod == mod; }; bool TraceEval_IsTriggered(mod_ptr_t mod) { return trace_eval_sig.cur_mod == mod; }; bool OnHelp_IsTriggered(mod_ptr_t mod) { return on_help_sig.cur_mod == mod; }; - bool DoPlaceBirth_IsTriggered(mod_ptr_t mod) { return do_place_birth_sig.cur_mod == mod; }; - bool DoPlaceInject_IsTriggered(mod_ptr_t mod) { return do_place_inject_sig.cur_mod == mod; }; - bool DoFindNeighbor_IsTriggered(mod_ptr_t mod) { return do_find_neighbor_sig.cur_mod == mod; }; }; @@ -633,6 +617,9 @@ namespace mabe { pop_type.AddMemberFunction("REPLACE_WITH", [this](Population & to_pop, Population & from_pop){ MoveOrgs(from_pop, to_pop, true); return 0; }, "Move all organisms organisms from another population, removing current orgs." ); + pop_type.AddMemberFunction("APPEND", + [this](Population & to_pop, Population & from_pop){ MoveOrgs(from_pop, to_pop, false); return 0; }, + "Move all organisms organisms from another population, adding after current orgs." ); pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), "Return the value of the provided trait for the first organism"); @@ -729,11 +716,8 @@ namespace mabe { Deprecate("print", "PRINT"); // Add other built-in functions to the config file. - - // 'EXIT' terminates a run gracefully. - std::function exit_fun = [this](){ exit_now = true; return 0; }; - config.AddFunction("EXIT", exit_fun, "Exit from this MABE run."); - + config.AddFunction("EXIT", [this](){ exit_now = true; return 0; }, "Exit from this MABE run."); + config.AddFunction("GET_UPDATE", [this](){ return GetUpdate(); }, "Get current update."); std::function preprocess_fun = [this](const std::string & str) { return Preprocess(str); }; @@ -878,7 +862,21 @@ namespace mabe { emp::Ptr new_pop = emp::NewPtr(name, cur_pop_id, pop_size, empty_org); // Create new population. pops.push_back(new_pop); // Record new population. - return *new_pop; // Return new population. + + // Setup default functions for the new population. + new_pop->SetPlaceBirthFun( [this,new_pop](Organism & /*org*/, OrgPosition /*ppos*/) { + return PushEmpty(*new_pop); + }); + new_pop->SetPlaceInjectFun( [this,new_pop](Organism & /*org*/) { + return PushEmpty(*new_pop); + }); + new_pop->SetFindNeighborFun( [this,new_pop](OrgPosition pos) { + if (pos.IsInPop(*new_pop)) return OrgPosition(); // Wrong pop! No neighbor. + // Return a random org since no structure to population. + return OrgPosition(new_pop, GetRandom().GetUInt(new_pop->GetSize())); + }); + + return *new_pop; } /// If GetPopulation() is called without an ID, return the current population or create one. @@ -898,7 +896,7 @@ namespace mabe { for (size_t i = 0; i < copy_count; i++) { emp::Ptr inject_org = org.CloneOrganism(); on_inject_ready_sig.Trigger(*inject_org, pop); - OrgPosition pos = FindInjectPosition(pop, *inject_org); + OrgPosition pos = pop.PlaceInject(*inject_org); if (pos.IsValid()) { AddOrgAt( inject_org, pos); placement_set.Insert(pos); @@ -915,7 +913,7 @@ namespace mabe { OrgPosition MABE::InjectInstance(Population & pop, emp::Ptr org_ptr) { emp_assert(org_ptr->GetDataMap().SameLayout(org_data_map)); on_inject_ready_sig.Trigger(*org_ptr, pop); - OrgPosition pos = FindInjectPosition(pop, *org_ptr); + OrgPosition pos = pop.PlaceInject(*org_ptr); if (pos.IsValid()) AddOrgAt( org_ptr, pos); else { org_ptr.Delete(); @@ -977,7 +975,7 @@ namespace mabe { // Alert modules that offspring is ready, then find its birth position. on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); - pos = FindBirthPosition(target_pop, *new_org, ppos); + pos = target_pop.PlaceBirth(*new_org, ppos); // If this placement is valid, do so. Otherwise delete the organism. if (pos.IsValid()) { From 9c9d51d9c8687625c13afa1579b7177670dae8f5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:27:39 -0500 Subject: [PATCH 358/445] Removed Do-functions; now unneeded. --- source/core/MABEBase.hpp | 10 ---------- source/core/Module.hpp | 36 ------------------------------------ source/core/ModuleBase.hpp | 19 ------------------- 3 files changed, 65 deletions(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 342b7a54..50ec5657 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -77,13 +77,6 @@ namespace mabe { // TraceEval() SigListener trace_eval_sig; - // OrgPosition DoPlaceBirth(Population & target_pop, Organism & offspring, OrgPosition parent_position); - SigListener do_place_birth_sig; - // OrgPosition DoPlaceInject(Population & target_pop, Organism & new_organism) - SigListener do_place_inject_sig; - // OrgPosition DoFindNeighbor(OrgPosition target_organism) { - SigListener do_find_neighbor_sig; - /// If a module fails to use a signal, we never check it again UNLESS we are explicitly /// told to rescan the signals (perhaps because new functionality was enabled.) bool rescan_signals = true; @@ -109,9 +102,6 @@ namespace mabe { , before_exit_sig("before_exit", ModuleBase::SIG_BeforeExit, &ModuleBase::BeforeExit, sig_ptrs) , on_help_sig("on_help", ModuleBase::SIG_OnHelp, &ModuleBase::OnHelp, sig_ptrs) , trace_eval_sig("trace_eval", ModuleBase::SIG_TraceEval, &ModuleBase::TraceEval, sig_ptrs) - , do_place_birth_sig("do_place_birth", ModuleBase::SIG_DoPlaceBirth, &ModuleBase::DoPlaceBirth, sig_ptrs) - , do_place_inject_sig("do_place_inject", ModuleBase::SIG_DoPlaceInject, &ModuleBase::DoPlaceInject, sig_ptrs) - , do_find_neighbor_sig("do_find_neighbor", ModuleBase::SIG_DoFindNeighbor, &ModuleBase::DoFindNeighbor, sig_ptrs) { ; } public: diff --git a/source/core/Module.hpp b/source/core/Module.hpp index c58b1f38..c1dda4af 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -341,39 +341,6 @@ namespace mabe { } - // Functions to be called based on actions that need to happen. Each of these returns a - // viable result or an invalid object if need to pass on to the next module. Modules will - // be queried in order until one of them returns a valid result. - - // Function: Place a new organism about to be born. - // Args: Population to place in, Organism to place, position of parent - // Return: Position to place offspring or an invalid position if failed. - - OrgPosition DoPlaceBirth(Population &, Organism &, OrgPosition) override { - has_signal[SIG_DoPlaceBirth] = false; - control.RescanSignals(); - return OrgPosition(); - } - - // Function: Place a new organism about to be injected. - // Args: Population to place in, Organism that will be placed. - // Return: Position to place injected organism, or an invalid position if failed. - - OrgPosition DoPlaceInject(Population &, Organism &) override { - has_signal[SIG_DoPlaceInject] = false; - control.RescanSignals(); - return OrgPosition(); - } - - // Function: Find a random neighbor to a designated position. - // Args: Position to find neighbor of, position found. - - OrgPosition DoFindNeighbor(OrgPosition) override { - has_signal[SIG_DoFindNeighbor] = false; - control.RescanSignals(); - return OrgPosition(); - } - /// Turn off all signals in this function. void Deactivate() override { has_signal.Clear(); @@ -405,9 +372,6 @@ namespace mabe { bool BeforeExit_IsTriggered() override { return control.BeforeExit_IsTriggered(this); }; bool OnHelp_IsTriggered() override { return control.OnHelp_IsTriggered(this); }; bool TraceEval_IsTriggered() override { return control.TraceEval_IsTriggered(this); }; - bool DoPlaceBirth_IsTriggered() override { return control.DoPlaceBirth_IsTriggered(this); }; - bool DoPlaceInject_IsTriggered() override { return control.DoPlaceInject_IsTriggered(this); }; - bool DoFindNeighbor_IsTriggered() override { return control.DoFindNeighbor_IsTriggered(this); }; bool OK() const override { return true; } }; diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index c384f3ca..cd5be291 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -59,14 +59,6 @@ * TraceEval(Organism & org, ostream & out_stream) * : Print a trace of the evaluation of an organism. * ... - * - * - Various Do* functions run in modules until one of them returns a valid answer. - * DoPlaceBirth(Population & target_pop, Organism & offspring, OrgPosition parent_pos) - * : Place a new offspring about to be born. - * DoPlaceInject(Population & pop, Organism & new_org) - * : Place a new offspring about to be injected. - * DoFindNeighbor(OrgPosition target_pos) - * : Find a random neighbor to a designated position. */ #ifndef MABE_MODULE_BASE_H @@ -150,9 +142,6 @@ namespace mabe { SIG_BeforeExit, SIG_OnHelp, SIG_TraceEval, - SIG_DoPlaceBirth, - SIG_DoPlaceInject, - SIG_DoFindNeighbor, NUM_SIGNALS, SIG_UNKNOWN }; @@ -272,10 +261,6 @@ namespace mabe { virtual void OnHelp() = 0; virtual void TraceEval(Organism &, std::ostream &) = 0; - virtual OrgPosition DoPlaceBirth(Population &, Organism &, OrgPosition) = 0; - virtual OrgPosition DoPlaceInject(Population &, Organism &) = 0; - virtual OrgPosition DoFindNeighbor(OrgPosition) = 0; - virtual void Deactivate() = 0; ///< Turn off all signals in this function. virtual void Activate() = 0; ///< Turn on all signals in this function. @@ -299,10 +284,6 @@ namespace mabe { virtual bool OnHelp_IsTriggered() = 0; virtual bool TraceEval_IsTriggered() = 0; - virtual bool DoPlaceBirth_IsTriggered() = 0; - virtual bool DoPlaceInject_IsTriggered() = 0; - virtual bool DoFindNeighbor_IsTriggered() = 0; - virtual bool OK() const = 0; // For debugging purposes only. // ---=== Specialty Functions for Organism Managers ===--- From 0ff5d1a389ff87cb2c2aaf455b3fe6d3db4eb436 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 15 Nov 2021 10:28:42 -0500 Subject: [PATCH 359/445] Moved EvalNK from auto-evaluate on update to providing an EVALUATE function. --- source/evaluate/static/EvalNK.hpp | 72 ++++++++++++++++++------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/source/evaluate/static/EvalNK.hpp b/source/evaluate/static/EvalNK.hpp index ca6d609b..ed8e9794 100644 --- a/source/evaluate/static/EvalNK.hpp +++ b/source/evaluate/static/EvalNK.hpp @@ -23,11 +23,41 @@ namespace mabe { size_t N; size_t K; NKLandscape landscape; - mabe::Collection target_collect; std::string bits_trait; std::string fitness_trait; + double Evaluate(const Collection & orgs) { + // Loop through the population and evaluate each organism. + double max_fitness = 0.0; + emp::Ptr max_org = nullptr; + mabe::Collection alive_orgs( orgs.GetAlive() ); + for (Organism & org : alive_orgs) { + org.GenerateOutput(); + const auto & bits = org.GetTrait(bits_trait); + if (bits.size() != N) { + AddError("Org returns ", bits.size(), " bits, but ", + N, " bits needed for NK landscape.", + "\nOrg: ", org.ToString()); + } + double fitness = landscape.GetFitness(bits); + org.SetTrait(fitness_trait, fitness); + + if (fitness > max_fitness || !max_org) { + max_fitness = fitness; + max_org = &org; + } + } + + return max_fitness; + } + + // If a population is provided to Evaluate, first convert it to a Collection. + double Evaluate(Population & pop) { return Evaluate( Collection(pop) ); } + + // If a string is provided to Evaluate, convert it to a Collection. + double Evaluate(const std::string & in) { return Evaluate( control.ToCollection(in) ); } + public: EvalNK(mabe::MABE & control, const std::string & name="EvalNK", @@ -35,7 +65,6 @@ namespace mabe { size_t _N=100, size_t _K=3, const std::string & _btrait="bits", const std::string & _ftrait="fitness") : Module(control, name, desc) , N(_N), K(_K) - , target_collect(control.GetPopulation(0)) , bits_trait(_btrait) , fitness_trait(_ftrait) { @@ -43,8 +72,19 @@ namespace mabe { } ~EvalNK() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("EVAL", [](EvalNK & mod, const std::string & in) { return mod.Evaluate(in); }, + "Use NK landscape to evaluate all orgs in a provided collection."); + info.AddMemberFunction("EVAL_POP", [](EvalNK & mod, Population & pop) { return mod.Evaluate(pop); }, + "Use NK landscape to evaluate all orgs in a Population."); + info.AddMemberFunction("EVAL_ORGS", [](EvalNK & mod, Collection & list) { return mod.Evaluate(list); }, + "Use NK landscape to evaluate all orgs in an OrgList."); + info.AddMemberFunction("RESET", [](EvalNK & mod) { mod.landscape.Config(mod.N, mod.K, mod.control.GetRandom()); return 0; }, + "Regenerate the NK landscape with current N and K."); + } + void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(N, "N", "Number of bits required in output"); LinkVar(K, "K", "Number of bits used in each gene"); LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); @@ -60,32 +100,6 @@ namespace mabe { landscape.Config(N, K, control.GetRandom()); // Setup the fitness landscape. } - void OnUpdate(size_t /* update */) override { - emp_assert(control.GetNumPopulations() >= 1); - - // Loop through the population and evaluate each organism. - double max_fitness = 0.0; - emp::Ptr max_org = nullptr; - mabe::Collection alive_collect( target_collect.GetAlive() ); - for (Organism & org : alive_collect) { - org.GenerateOutput(); - const auto & bits = org.GetTrait(bits_trait); - if (bits.size() != N) { - AddError("Org returns ", bits.size(), " bits, but ", - N, " bits needed for NK landscape.", - "\nOrg: ", org.ToString()); - } - double fitness = landscape.GetFitness(bits); - org.SetTrait(fitness_trait, fitness); - - if (fitness > max_fitness || !max_org) { - max_fitness = fitness; - max_org = &org; - } - } - - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; - } }; MABE_REGISTER_MODULE(EvalNK, "Evaluate bitstrings on an NK fitness lanscape."); From 0069a6ebf6fef2b12cfb40c730ff33ea7975320c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 10:27:29 -0500 Subject: [PATCH 360/445] Added EmplodeType::MakeRValueFrom() to allow for dynamic type conversions in function calls. --- source/Emplode/EmplodeType.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp index e745c711..c893150f 100644 --- a/source/Emplode/EmplodeType.hpp +++ b/source/Emplode/EmplodeType.hpp @@ -35,6 +35,14 @@ namespace emplode { // in your own class; you are NOT overriding a virtual function. } + /// If you want this type to be made from another EmplodeType on the fly, build a new version + /// of this static member function to be called. + template static OUT_T MakeRValueFrom(IN_T &&) { + emp_error("Cannot convert provided input to requested RValue", emp::GetTypeID()); + return *((OUT_T *) nullptr); + } + + virtual ~EmplodeType() { } // Optional function to override to add configuration options associated with an object. From 0b3257149b9b9c1d3bd576d47607c04239a33e99 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 10:28:58 -0500 Subject: [PATCH 361/445] Allow dynamic type conversions in Symbol::As() --- source/Emplode/Symbol.hpp | 55 ++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index 78c7bb55..df719fe3 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -99,7 +99,7 @@ namespace emplode { bool IsBuiltin() const noexcept { return is_builtin; } Format GetFormat() const noexcept { return format; } - virtual std::string GetTypename() const { return "Unknown"; } + virtual std::string GetTypename() const = 0; ///< Derived classes must provide type info. virtual bool IsNumeric() const { return false; } ///< Is symbol any kind of number? virtual bool IsBool() const { return false; } ///< Is symbol a Boolean value? @@ -145,16 +145,26 @@ namespace emplode { virtual emp::Ptr GetObjectPtr() { return nullptr; } virtual emp::Ptr GetObjectPtr() const { return nullptr; } virtual emp::Ptr GetTypeInfoPtr() const { return nullptr; } + virtual emp::TypeID GetObjectType() const { return emp::GetTypeID(); } + virtual bool HasObjectType(emp::TypeID in_type) const { return in_type == GetObjectType(); } + template bool HasObjectType() const { return HasObjectType(emp::GetTypeID()); } + + /// Helper struct to determine the return type for As. + template struct AsRT_impl { using type = T; }; // Default: T requested + template struct AsRT_impl { using type = T; }; // const ref -> value /// A generic As() function that will call the appropriate converter. template - decltype(auto) As() { + auto As() -> typename AsRT_impl::type { // If a const type is requested, non-const can be converted, so work with that. using decay_T = std::decay_t; - constexpr bool is_nonconst_ref = !std::is_const_v && std::is_reference_v; + // constexpr bool is_const = std::is_const_v; + constexpr bool is_ref = std::is_reference_v; + // constexpr bool is_substitutable = is_const || !is_ref; + constexpr bool is_substitutable = !is_ref; // If we have a numeric or string request, run the appropriate conversion. - if constexpr (std::is_arithmetic() && !is_nonconst_ref) { + if constexpr (std::is_arithmetic() && is_substitutable) { return static_cast(AsDouble()); } else if constexpr (std::is_same() || @@ -162,7 +172,7 @@ namespace emplode { return AsString(); } - // If we want either a pointer or reference to the current object, return it. + // If we want either a pointer or reference to a Symbol object, return it. else if constexpr (std::is_same>()) { return this; } else if constexpr (std::is_same()) { return *this; } @@ -176,10 +186,35 @@ namespace emplode { // If we want a user-defined type, it must be derived from EmplodeType. else if constexpr (std::is_base_of()) { emp::Ptr obj_ptr = GetObjectPtr(); - emp_assert(obj_ptr); // @CAO: Should provide a user error. - emp::Ptr out_ptr = obj_ptr.DynamicCast(); - emp_assert(out_ptr); // @CAO: Should provide a user error. - return *out_ptr; + // If have an object, see what we can convert it to. + if (obj_ptr){ + // First, check if this is already the correct type. + emp::Ptr typed_obj_ptr = obj_ptr.DynamicCast(); + + // If not, check if we can substitute it for another type. + if (!typed_obj_ptr) { + // If not (and we can use an r-value) build a temporary value! + if constexpr (is_substitutable) { + return decay_T::template MakeRValueFrom(*obj_ptr); + } + + // If both options fail, report an error. + emp_error("Cannot convert symbol to target object type. Symbol: ", + DebugString(), " Target: ", emp::GetTypeID()); + } + + return *typed_obj_ptr; + } + + // We must be trying to return an object from something other than another object. + if constexpr (is_substitutable) { + if (IsNumeric()) return decay_T::template MakeRValueFrom(AsDouble()); + return decay_T::template MakeRValueFrom(AsString()); + } + + emp_error("Cannot convert symbol to target object type.", DebugString(), emp::GetTypeID()); + auto out = emp::NewPtr>(); + return (T) *out; } // Oh no! We don't know this type... @@ -288,7 +323,7 @@ namespace emplode { std::string GetTypename() const override { if constexpr (std::is_scalar_v) return "Value"; - else return "Unknown"; + else return "Illegal type as Symbol_Var"; } symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } From 9ec7032d2ff7511a7ada7a8b512ed1bfb40e8aaa Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 10:30:40 -0500 Subject: [PATCH 362/445] Added GetTypename() to all types derived from Symbol. --- source/Emplode/Symbol_Function.hpp | 2 ++ source/Emplode/Symbol_Linked.hpp | 8 ++++---- source/Emplode/Symbol_Object.hpp | 6 +++++- source/Emplode/Symbol_Scope.hpp | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/source/Emplode/Symbol_Function.hpp b/source/Emplode/Symbol_Function.hpp index bcee00a4..be36dfda 100644 --- a/source/Emplode/Symbol_Function.hpp +++ b/source/Emplode/Symbol_Function.hpp @@ -47,6 +47,8 @@ namespace emplode { Symbol_Function(const Symbol_Function &) = default; emp::Ptr Clone() const override { return emp::NewPtr(*this); } + std::string GetTypename() const override { return "[Symbol_Function]"; } + bool IsFunction() const override { return true; } bool HasNumericReturn() const override { return return_type.IsArithmetic(); } bool HasStringReturn() const override { return return_type.IsType(); } diff --git a/source/Emplode/Symbol_Linked.hpp b/source/Emplode/Symbol_Linked.hpp index eca96e4e..d561364a 100644 --- a/source/Emplode/Symbol_Linked.hpp +++ b/source/Emplode/Symbol_Linked.hpp @@ -31,8 +31,8 @@ namespace emplode { Symbol_Linked(const this_t &) = default; std::string GetTypename() const override { - if constexpr (std::is_scalar_v) return "Value"; - else return "Unknown"; + if constexpr (std::is_scalar_v) return "[LinkedValue]"; + else return "[Error:InvalidLinkedType]"; } emp::Ptr Clone() const override { return emp::NewPtr(*this); } @@ -66,7 +66,7 @@ namespace emplode { : Symbol(in_name, std::forward(args)...), var(in_var) { ; } Symbol_Linked(const this_t &) = default; - std::string GetTypename() const override { return "String"; } + std::string GetTypename() const override { return "[LinkedString]"; } emp::Ptr Clone() const override { return emp::NewPtr(*this); } @@ -101,7 +101,7 @@ namespace emplode { { ; } Symbol_LinkedFunctions(const this_t &) = default; - std::string GetTypename() const override { return "[[Function]]"; } + std::string GetTypename() const override { return "[Symbol_LinkedFunctions]"; } emp::Ptr Clone() const override { return emp::NewPtr(*this); } diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index c6bb8b91..7101c776 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -54,8 +54,12 @@ namespace emplode { emp::Ptr GetObjectPtr() const override { return obj_ptr; } emp::Ptr GetTypeInfoPtr() const override { return type_info_ptr; } + std::string GetTypename() const override { + return emp::to_string("[Symbol_Object:", GetObjectType(), "]"); + } + bool IsObject() const override { return true; } - emp::TypeID GetObjectType() { + emp::TypeID GetObjectType() const override { if (type_info_ptr.IsNull()) return emp::GetTypeID(); return type_info_ptr->GetTypeID(); } diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index a34af2b7..17fe6cec 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -64,6 +64,8 @@ namespace emplode { for (auto [name, ptr] : symbol_table) { ptr.Delete(); } } + std::string GetTypename() const override { return "[Symbol_Scope]"; } + bool IsScope() const override { return true; } bool IsLocal() const override { return true; } // @CAO, for now assuming all scopes are local! From adfb42d1dbab81ce8cde1276fe419de2e3870f18 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 10:31:43 -0500 Subject: [PATCH 363/445] Added MakeRValueFrom to Collection to allow dynamic conversions from Population. --- source/core/Collection.hpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 58253d52..45939229 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -293,6 +293,28 @@ namespace mabe { ); } + template static OUT_T MakeRValueFrom(IN_T && in) { + static_assert(std::is_same(), + "Internal error: type mis-match for MakeRValueFrom()"); + // using decay_T = std::decay_t; + if constexpr (std::is_same()) { + // Test if we are converting from a population! + emp::Ptr in_ptr = ∈ + auto pop_ptr = in_ptr.DynamicCast(); + if (pop_ptr) return Collection(*pop_ptr); + + // Currently, no other EmplodeTypes to convert from... + } + // Conversion from string requires MABE controller... + // else if constexpr (std::is_same()) { + // } + // Cannot convert from double. + // else if constexpr (std::is_same()) { + // } + emp_error("Cannot convert provided input to requested RValue", emp::GetTypeID()); + return *((OUT_T *) &in); + } + /// Calculate the total number of positions represented in this collection. size_t GetSize() const noexcept override { size_t count = 0; From 57eae56005b612dd609602b9524e9b71c09b79dd Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 10:34:15 -0500 Subject: [PATCH 364/445] Updated EvalNK to use a single EVAL member function for both Population and OrgList --- source/evaluate/static/EvalNK.hpp | 71 +++++++++++++++---------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/source/evaluate/static/EvalNK.hpp b/source/evaluate/static/EvalNK.hpp index ed8e9794..b61d2123 100644 --- a/source/evaluate/static/EvalNK.hpp +++ b/source/evaluate/static/EvalNK.hpp @@ -27,37 +27,6 @@ namespace mabe { std::string bits_trait; std::string fitness_trait; - double Evaluate(const Collection & orgs) { - // Loop through the population and evaluate each organism. - double max_fitness = 0.0; - emp::Ptr max_org = nullptr; - mabe::Collection alive_orgs( orgs.GetAlive() ); - for (Organism & org : alive_orgs) { - org.GenerateOutput(); - const auto & bits = org.GetTrait(bits_trait); - if (bits.size() != N) { - AddError("Org returns ", bits.size(), " bits, but ", - N, " bits needed for NK landscape.", - "\nOrg: ", org.ToString()); - } - double fitness = landscape.GetFitness(bits); - org.SetTrait(fitness_trait, fitness); - - if (fitness > max_fitness || !max_org) { - max_fitness = fitness; - max_org = &org; - } - } - - return max_fitness; - } - - // If a population is provided to Evaluate, first convert it to a Collection. - double Evaluate(Population & pop) { return Evaluate( Collection(pop) ); } - - // If a string is provided to Evaluate, convert it to a Collection. - double Evaluate(const std::string & in) { return Evaluate( control.ToCollection(in) ); } - public: EvalNK(mabe::MABE & control, const std::string & name="EvalNK", @@ -74,13 +43,11 @@ namespace mabe { // Setup member functions associated with this class. static void InitType(emplode::TypeInfo & info) { - info.AddMemberFunction("EVAL", [](EvalNK & mod, const std::string & in) { return mod.Evaluate(in); }, - "Use NK landscape to evaluate all orgs in a provided collection."); - info.AddMemberFunction("EVAL_POP", [](EvalNK & mod, Population & pop) { return mod.Evaluate(pop); }, - "Use NK landscape to evaluate all orgs in a Population."); - info.AddMemberFunction("EVAL_ORGS", [](EvalNK & mod, Collection & list) { return mod.Evaluate(list); }, + info.AddMemberFunction("EVAL", + [](EvalNK & mod, Collection list) { return mod.Evaluate(list); }, "Use NK landscape to evaluate all orgs in an OrgList."); - info.AddMemberFunction("RESET", [](EvalNK & mod) { mod.landscape.Config(mod.N, mod.K, mod.control.GetRandom()); return 0; }, + info.AddMemberFunction("RESET", + [](EvalNK & mod) { mod.landscape.Config(mod.N, mod.K, mod.control.GetRandom()); return 0; }, "Regenerate the NK landscape with current N and K."); } @@ -100,6 +67,36 @@ namespace mabe { landscape.Config(N, K, control.GetRandom()); // Setup the fitness landscape. } + double Evaluate(const Collection & orgs) { + // Loop through the population and evaluate each organism. + double max_fitness = 0.0; + emp::Ptr max_org = nullptr; + mabe::Collection alive_orgs( orgs.GetAlive() ); + for (Organism & org : alive_orgs) { + org.GenerateOutput(); + const auto & bits = org.GetTrait(bits_trait); + if (bits.size() != N) { + AddError("Org returns ", bits.size(), " bits, but ", + N, " bits needed for NK landscape.", + "\nOrg: ", org.ToString()); + } + double fitness = landscape.GetFitness(bits); + org.SetTrait(fitness_trait, fitness); + + if (fitness > max_fitness || !max_org) { + max_fitness = fitness; + max_org = &org; + } + } + + return max_fitness; + } + + // If a population is provided to Evaluate, first convert it to a Collection. + double Evaluate(Population & pop) { return Evaluate( Collection(pop) ); } + + // If a string is provided to Evaluate, convert it to a Collection. + double Evaluate(const std::string & in) { return Evaluate( control.ToCollection(in) ); } }; MABE_REGISTER_MODULE(EvalNK, "Evaluate bitstrings on an NK fitness lanscape."); From 794bc86f7ca96a534a9afb7f061adf871b35efd0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 17:01:26 -0500 Subject: [PATCH 365/445] Updated EvalCountBits to new module setup. --- source/evaluate/static/EvalCountBits.hpp | 42 +++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/source/evaluate/static/EvalCountBits.hpp b/source/evaluate/static/EvalCountBits.hpp index cf4f275f..8bd7eac1 100644 --- a/source/evaluate/static/EvalCountBits.hpp +++ b/source/evaluate/static/EvalCountBits.hpp @@ -19,10 +19,8 @@ namespace mabe { class EvalCountBits : public Module { private: - Collection target_collect; - std::string bits_trait; - std::string fitness_trait; + std::string score_trait; bool count_type; // =0 for counts zeros, or =1 for count ones. public: @@ -30,58 +28,64 @@ namespace mabe { const std::string & name="EvalCountBits", const std::string & desc="Evaluate bitstrings by counting ones (or zeros).", const std::string & _btrait="bits", - const std::string & _ftrait="fitness", + const std::string & _ftrait="score", bool _ctype=1) : Module(control, name, desc) - , target_collect(control.GetPopulation(0)) , bits_trait(_btrait) - , fitness_trait(_ftrait) + , score_trait(_ftrait) , count_type(_ctype) { SetEvaluateMod(true); } ~EvalCountBits() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("EVAL", + [](EvalCountBits & mod, Collection list) { return mod.Evaluate(list); }, + "Count the ones in all orgs in an OrgList."); + } + void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); - LinkVar(fitness_trait, "fitness_trait", "Which trait should we store NK fitness in?"); + LinkVar(score_trait, "score_trait", "Which trait should we store NK score in?"); LinkVar(count_type, "count_type", "Which type of bit should we count? (0 or 1)"); } void SetupModule() override { AddRequiredTrait(bits_trait); - AddOwnedTrait(fitness_trait, "All-ones fitness value", 0.0); + AddOwnedTrait(score_trait, "All-ones score value", 0.0); } - void OnUpdate(size_t /* update */) override { + double Evaluate(Collection orgs) { emp_assert(control.GetNumPopulations() >= 1); // Loop through the population and evaluate each organism. - double max_fitness = 0.0; + double max_score = 0.0; emp::Ptr max_org = nullptr; - mabe::Collection alive_collect( target_collect.GetAlive() ); + mabe::Collection alive_collect( orgs.GetAlive() ); for (Organism & org : alive_collect) { // Make sure this organism has its bit sequence ready for us to access. org.GenerateOutput(); // Count the number of ones in the bit sequence. const emp::BitVector & bits = org.GetTrait(bits_trait); - double fitness = (double) bits.CountOnes(); + double score = (double) bits.CountOnes(); // If we were supposed to count zeros, subtract ones count from total number of bits. - if (count_type == 0) fitness = bits.size() - fitness; + if (count_type == 0) score = bits.size() - score; - // Store the count on the organism in the fitness trait. - org.SetTrait(fitness_trait, fitness); + // Store the count on the organism in the score trait. + org.SetTrait(score_trait, score); - if (fitness > max_fitness || !max_org) { - max_fitness = fitness; + if (score > max_score || !max_org) { + max_score = score; max_org = &org; } } - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; + std::cout << "Max " << score_trait << " = " << max_score << std::endl; + return max_score; } }; From e1bfba8cdffa7c0bf23c8d2419c3f18ee5fb5329 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 17:02:17 -0500 Subject: [PATCH 366/445] Updated EvalDiagnostic to new module setup. --- source/evaluate/static/EvalDiagnostic.hpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/source/evaluate/static/EvalDiagnostic.hpp b/source/evaluate/static/EvalDiagnostic.hpp index 67141855..d2382592 100644 --- a/source/evaluate/static/EvalDiagnostic.hpp +++ b/source/evaluate/static/EvalDiagnostic.hpp @@ -17,8 +17,6 @@ namespace mabe { class EvalDiagnostic : public Module { private: - Collection target_collect; // Which organisms should we evaluate? - std::string vals_trait; // Set of values to evaluate std::string scores_trait; // Vector of scores for each value std::string total_trait; // A single value totalling all of the scores. @@ -43,7 +41,6 @@ namespace mabe { const std::string & _strait="scores", const std::string & _ttrait="total") : Module(control, name, desc) - , target_collect(control.GetPopulation(0)) , vals_trait(_vtrait) , scores_trait(_strait) , total_trait(_ttrait) @@ -52,8 +49,16 @@ namespace mabe { } ~EvalDiagnostic() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction( + "EVAL", + [](EvalDiagnostic & mod, Collection orgs) { return mod.Evaluate(orgs); }, + "Evaluate organisms using the specified diagnostic." + ); + } + void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(vals_trait, "vals_trait", "Which trait stores the values to evaluate?"); LinkVar(scores_trait, "scores_trait", "Which trait should we store revised scores in?"); LinkVar(total_trait, "total_trait", "Which trait should we store the total score in?"); @@ -72,15 +77,13 @@ namespace mabe { AddOwnedTrait(total_trait, "Combined score for current diagnostic.", 0.0); } - void OnUpdate(size_t /* update */) override { - emp_assert(control.GetNumPopulations() >= 1); - + double Evaluate(Collection orgs) { // Track the organism with the highest total score. double max_total = 0.0; emp::Ptr max_org = nullptr; // Loop through the living organisms in the target collection to evaluate each. - mabe::Collection alive_collect( target_collect.GetAlive() ); + mabe::Collection alive_collect( orgs.GetAlive() ); for (Organism & org : alive_collect) { // Make sure this organism has its values ready for us to access. org.GenerateOutput(); @@ -162,6 +165,7 @@ namespace mabe { max_org = &org; } } + return max_total; } }; From d2efc2fbc4edafb93317fff37a69c5c0a990b92a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 17:02:39 -0500 Subject: [PATCH 367/445] Updated EvalRoyalRoad to new module setup. --- source/evaluate/static/EvalRoyalRoad.hpp | 39 +++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp index 69233b70..1685d591 100644 --- a/source/evaluate/static/EvalRoyalRoad.hpp +++ b/source/evaluate/static/EvalRoyalRoad.hpp @@ -22,10 +22,8 @@ namespace mabe { class EvalRoyalRoad : public Module { private: - Collection target_collect; - std::string bits_trait; - std::string fitness_trait; + std::string score_trait; size_t brick_size = 8; double extra_bit_cost = 0.5; @@ -35,31 +33,36 @@ namespace mabe { const std::string & name="EvalRoyalRoad", const std::string & desc="Evaluate bitstrings by counting ones (or zeros).") : Module(control, name, desc) - , target_collect(control.GetPopulation(0)) , bits_trait("bits") - , fitness_trait("fitness") + , score_trait("score") { SetEvaluateMod(true); } ~EvalRoyalRoad() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("EVAL", + [](EvalRoyalRoad & mod, Collection list) { return mod.Evaluate(list); }, + "Evaluate RoyalRoad on all orgs in an OrgList."); + } + void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(bits_trait, "bits_trait", "Which trait stores the bit sequence to evaluate?"); - LinkVar(fitness_trait, "fitness_trait", "Which trait should we store Royal Road fitness in?"); + LinkVar(score_trait, "score_trait", "Which trait should we store Royal Road score in?"); LinkVar(brick_size, "brick_size", "Number of ones to have a whole brick in the road."); LinkVar(extra_bit_cost, "extra_bit_cost", "Penalty per-bit for extra-long roads."); } void SetupModule() override { AddRequiredTrait(bits_trait); - AddOwnedTrait(fitness_trait, "Royal Road fitness value", 0.0); + AddOwnedTrait(score_trait, "Royal Road score value", 0.0); } - void OnUpdate(size_t /* update */) override { + double Evaluate(Collection orgs) { // Loop through the population and evaluate each organism. - double max_fitness = 0.0; - mabe::Collection alive_collect = target_collect.GetAlive(); + double max_score = 0.0; + mabe::Collection alive_collect = orgs.GetAlive(); for (Organism & org : alive_collect) { // Make sure this organism has its bit sequence ready for us to access. org.GenerateOutput(); @@ -74,20 +77,20 @@ namespace mabe { const int overage = road_length % brick_size; - // Store the count on the organism in the fitness trait. - double fitness = road_length - overage * (extra_bit_cost + 1.0); - org.SetTrait(fitness_trait, fitness); + // Store the count on the organism in the score trait. + double score = road_length - overage * (extra_bit_cost + 1.0); + org.SetTrait(score_trait, score); - if (fitness > max_fitness) { - max_fitness = fitness; + if (score > max_score) { + max_score = score; } } - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; + return max_score; } }; - MABE_REGISTER_MODULE(EvalRoyalRoad, "Evaluate bitstrings by counting ones (or zeros)."); + MABE_REGISTER_MODULE(EvalRoyalRoad, "Evaluate bitstrings by counting groups of ones (bricks) from the beginning."); } #endif From f02f1d5902f083fa471201d641c2d49e013bfe99 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 17:03:11 -0500 Subject: [PATCH 368/445] Updated and expanded the functionality of EvalMatchBits; easy to add new match metrics. --- source/evaluate/static/EvalMatchBits.hpp | 130 +++++++++++++---------- 1 file changed, 73 insertions(+), 57 deletions(-) diff --git a/source/evaluate/static/EvalMatchBits.hpp b/source/evaluate/static/EvalMatchBits.hpp index 949e97a1..ba9e6ce6 100644 --- a/source/evaluate/static/EvalMatchBits.hpp +++ b/source/evaluate/static/EvalMatchBits.hpp @@ -23,93 +23,109 @@ namespace mabe { class EvalMatchBits : public Module { private: - int eval_pop1 = 0; - int eval_pop2 = 0; + enum Type { + MATCH_COUNT, + MISMATCH_COUNT, + UNKNOWN + }; std::string bits_trait = "bits"; - std::string fitness_trait = "bit_matches"; - bool count_matches; // =0 counts MISmatches, or =1 for count matches. + std::string score_trait = "bit_matches"; + Type match_type = Type::MATCH_COUNT; + bool record_both = false; // Save result on both organisms? (vs. first only) + double empty_score = 0.0; // Score to give orgs matched with empty positions. public: EvalMatchBits(mabe::MABE & control, const std::string & name="EvalMatchBits", - const std::string & desc="Evaluate bitstrings by counting ones (or zeros).") + const std::string & desc="Evaluate org bitstring by counting matches with another org's bisstring.") : Module(control, name, desc) { SetEvaluateMod(true); } ~EvalMatchBits() { } - void SetupConfig() override { - LinkPop(eval_pop1, "eval_pop1", "Population to evaluate."); - LinkPop(eval_pop2, "eval_pop2", "Population to compare to."); + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("EVAL", + [](EvalMatchBits & mod, Collection list1, Collection list2) { + return mod.Evaluate(list1, list2); + }, + "Evaluate Bit Matching by comparing orgs in the two OrgLists."); + } + void SetupConfig() override { LinkVar(bits_trait, "bits_trait", "Trait storing bit sequence to evaluate."); - LinkVar(fitness_trait, "fitness_trait", "Trait to store fitness result."); - LinkVar(count_matches, "count_matches", "=0 counts MISmatches, or =1 for count matches."); + LinkVar(score_trait, "score_trait", "Trait to store match score result."); + LinkMenu(match_type, "match_type", "How should the bit sequences be compared?", + Type::MATCH_COUNT, "match_count", "Count bit positions with the same value.", + Type::MISMATCH_COUNT, "mismatch_count", "Count bit positions with the different values."); + LinkVar(record_both, "record_both", "Save result on both organisms? (0 -> first only)"); + LinkVar(empty_score, "empty_score", "Score to give orgs matched again an empty position?"); } void SetupModule() override { AddRequiredTrait(bits_trait); - AddOwnedTrait(fitness_trait, "All-ones fitness value", 0.0); + AddOwnedTrait(score_trait, "Match score value", 0.0); } - void OnUpdate(size_t /* update */) override { - emp_assert(control.GetNumPopulations() >= 1); - - // Loop through the populations and evaluate each organism pair. - double best_match = 0.0; - Population & pop1 = control.GetPopulation(eval_pop1); - Population & pop2 = control.GetPopulation(eval_pop2); - - // Loop through all organisms in the first population, matching them with the second. - for (size_t pos = 0; pos < pop1.GetSize(); pos++) { - // If the first population is empty, still check for organism in the second to score. - if (pop1.IsEmpty(pos)) { - if (pop2.IsOccupied(pos)) pop2[pos].SetTrait(fitness_trait, 0.0); - continue; // Skip over empty cell in first population. + double EvaluateMatch(Organism & org1, Organism & org2) { + double match_score = empty_score; + + // Only calculate a real score if both organisms are non-empty. + if (!org1.IsEmpty() && !org2.IsEmpty()) { + // Make sure both organisms have bit sequences ready for us to access. + org1.GenerateOutput(); + org2.GenerateOutput(); + + const emp::BitVector & bits1 = org1.GetTrait(bits_trait); + const emp::BitVector & bits2 = org2.GetTrait(bits_trait); + org1.SetTrait(score_trait, match_score); + + // Count the number of matches in the bit sequences. + switch (match_type) { + case Type::MATCH_COUNT: + match_score = (double) (bits1 ^ bits2).CountZeros(); + break; + case Type::MISMATCH_COUNT: + match_score = (double) (bits1 ^ bits2).CountOnes(); + break; + default: + emp_error("Unknown match type for EvalMatchBits!"); + match_score = -1.0; } + } - Organism & org = pop1[pos]; - - // If there is NO corresponding organisms in pop2, return a zero match. - double fitness = 0.0; - if (pop2.IsOccupied(pos)) { - // Find the corresponding organism in the compare population. - Organism & org2 = pop2[pos]; - - // Make sure both organisms have bit sequences ready for us to access. - org.GenerateOutput(); - org2.GenerateOutput(); - - // Count the number of matches in the bit sequences. - const emp::BitVector & bits1 = org.GetTrait(bits_trait); - const emp::BitVector & bits2 = org2.GetTrait(bits_trait); - - if (count_matches) { - fitness = (double) (bits1 ^ bits2).CountZeros(); - } - else { - fitness = (double) (bits1 ^ bits2).CountOnes(); - } + if (!org1.IsEmpty()) { + org1.SetTrait(score_trait, match_score); + } + if (record_both && !org2.IsEmpty()) { + org2.SetTrait(score_trait, match_score); + } - if (fitness > best_match) best_match = fitness; + return match_score; + } - // Store the count on the second organism in the fitness trait. - org2.SetTrait(fitness_trait, fitness); - } + double Evaluate(Collection orgs1, Collection orgs2) { + emp_assert(control.GetNumPopulations() >= 1); - // Store the count on the organism in the fitness trait. - org.SetTrait(fitness_trait, fitness); + // Loop through the populations and evaluate each organism pair. + double best_match = 0.0; - } + // @CAO Should be a user-level error. + emp_assert (orgs1.GetSize() == orgs2.GetSize(), + "EvalMatchBits::Evaluate requires two OrgLists of the same size."); - // If pop2 is bigger, make sure to mark any extra organisms as having a zero match fitness. - for (size_t pos = pop1.GetSize(); pos < pop2.GetSize(); pos++) { - if (pop2.IsOccupied(pos)) pop2[pos].SetTrait(fitness_trait, 0.0); + auto it1 = orgs1.begin(); + auto it2 = orgs2.begin(); + while (it1 != orgs1.end()) { + const double match = EvaluateMatch(*it1, *it2); + if (match > best_match) best_match = match; + ++it1; ++it2; } + return best_match; } }; From 47816da936b0e6fdce38e353b204e66e728890d5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 16 Nov 2021 17:19:40 -0500 Subject: [PATCH 369/445] Clean up in several files. --- source/Emplode/AST.hpp | 5 ----- source/Emplode/SymbolTable.hpp | 6 +++--- source/core/OrgIterator.hpp | 3 +++ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 43465710..6bf1097d 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -264,11 +264,6 @@ namespace emplode { symbol_ptr_t lhs = children[0]->Process(); // Determine the left-hand-side value. symbol_ptr_t rhs = children[1]->Process(); // Determine the right-hand-side value. - if (lhs->IsObject() && lhs->AsObject().GetObjectType().GetName() == "mabe::Population") { - std::cout << "BREAKPOINT!" << std::endl; - //emp_error("BREAKPOINT! line=", line_id); - } - // @CAO Should make sure that lhs is properly assignable. bool success = lhs->CopyValue(*rhs); if (!success) { diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index 71c5bc06..50842312 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -129,7 +129,7 @@ namespace emplode { return AddType(type_name, desc, init_fun, copy_fun, true); } - /// Make a new Symbol_Object using the provided TypeInfo, variable name, and scope. + /// Make a new Symbol_Object using the provided *TypeInfo*, variable name, and scope. Symbol_Object & MakeObjSymbol( TypeInfo & type_info, const std::string & var_name, @@ -152,13 +152,13 @@ namespace emplode { return new_obj_symbol; } - /// Make a new Symbol_Object using the provided type name, variable name, and scope. + /// Make a new Symbol_Object using the provided type NAME, variable name, and scope. Symbol_Object & MakeObjSymbol(const std::string & type_name, const std::string & var_name, Symbol_Scope & scope) { return MakeObjSymbol(*type_map[type_name], var_name, scope); } - /// Make a new Symbol_Object using the provided TypeID, variable name, and scope. + /// Make a new Symbol_Object using the provided *TypeID*, variable name, and scope. Symbol_Object & MakeObjSymbol(emp::TypeID type_id, const std::string & var_name, Symbol_Scope & scope) { return MakeObjSymbol(*typeid_map[type_id], var_name, scope); diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index b5c3feb8..a07e0855 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -139,6 +139,9 @@ namespace mabe { bool IsEmpty() const { return IsValid() && OrgPtr()->IsEmpty(); } bool IsOccupied() const { return IsValid() && !OrgPtr()->IsEmpty(); } + /// Is this position in the specified population? + bool IsInPop(const Population & pop) const { return ConstPopPtr() == &pop; } + /// Advance iterator to the next non-empty cell in the world. DERIVED_T & operator++() { IncPosition(); return AsDerived(); } From 5303dd4a2184721d91d5291332aabbe16b25bcd1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 17 Nov 2021 22:40:49 -0500 Subject: [PATCH 370/445] Changed ModuleInfo container from a set to a map. --- source/core/ModuleBase.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index cd5be291..fc820851 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -318,15 +318,15 @@ namespace mabe { bool operator<(const ModuleInfo & in) const { return name < in.name; } }; - static std::set & GetModuleInfo() { - static std::set mod_type_info; - return mod_type_info; + static std::map & GetModuleMap() { + static std::map mod_type_map; + return mod_type_map; } static void PrintModuleInfo() { - auto & mod_info = GetModuleInfo(); - for (auto & mod : mod_info) { - std::cout << mod.name << " : " << mod.desc << std::endl; + auto & mod_type_map = GetModuleMap(); + for (auto & [name,mod] : mod_type_map) { + std::cout << name << " : " << mod.desc << std::endl; } } } From 29218b6833a0310dca217581580537dfa1e7f35d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 18 Nov 2021 18:21:53 -0500 Subject: [PATCH 371/445] Setup Module and ManagerModule to deal with module collection as a map, not set. --- source/core/ManagerModule.hpp | 3 ++- source/core/Module.hpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp index ff486381..7faa7102 100644 --- a/source/core/ManagerModule.hpp +++ b/source/core/ManagerModule.hpp @@ -114,6 +114,7 @@ namespace mabe { template struct ManagerModuleRegistrar { ManagerModuleRegistrar(const std::string & type_name, const std::string & desc) { + emp_assert(!emp::Has(GetModuleMap(), type_name), "Module name used multiple times.", type_name); ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; @@ -121,7 +122,7 @@ namespace mabe { return &control.AddModule(name, desc); }; new_info.type_init_fun = [](emplode::TypeInfo & info){ MODULE_T::InitType(info); }; - GetModuleInfo().insert(new_info); + GetModuleMap()[type_name] = new_info; } }; diff --git a/source/core/Module.hpp b/source/core/Module.hpp index c1dda4af..44424221 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -380,6 +380,7 @@ namespace mabe { template struct ModuleRegistrar { ModuleRegistrar(const std::string & type_name, const std::string & desc) { + emp_assert(!emp::Has(GetModuleMap(), type_name), "Module name used multiple times.", type_name); ModuleInfo new_info; new_info.name = type_name; new_info.desc = desc; @@ -388,7 +389,7 @@ namespace mabe { }; new_info.type_init_fun = [](emplode::TypeInfo & info){ T::InitType(info); }; new_info.type_id = emp::GetTypeID(); - GetModuleInfo().insert(new_info); + GetModuleMap()[type_name] = new_info; } }; From b64e78b8196b5acc82c0588470adbd05bd6d472e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 19 Nov 2021 09:54:29 -0500 Subject: [PATCH 372/445] Updated NK.mabe example with all new MABE features. --- settings/NK.mabe | 87 ++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/settings/NK.mabe b/settings/NK.mabe index 8cdd5fcd..139f1b35 100644 --- a/settings/NK.mabe +++ b/settings/NK.mabe @@ -1,63 +1,62 @@ random_seed = 0; // Seed for random number generator; use 0 to base on time. -Population main_pop; // Collection of organisms -Population next_pop; // Collection of organisms +Value pop_size = 1000; // Number of organisms to evaluate in the population. +Value num_bits = 100; // Number of bits in each organism (and the NK landscape) -Value pop_size = 1000; +Population main_pop; // Main population for managing candidate solutions. +Population next_pop; // Temporary population while constructing the next generation. -CommandLine cl { // Handle basic I/O on the command line. - target_pop = "main_pop"; // Which population should we print stats about? +BitsOrg bits_org { // Organism consisting of a series of N bits. + output_name = "bits"; // Name of variable to contain bit sequence. + N = num_bits; // Number of bits in organism + mut_prob = 0.01; // Probability of each bit mutating on reproduction. } + EvalNK eval_nk { // Evaluate bitstrings on an NK fitness lanscape. - target = "main_pop"; // Which population should we evaluate? - N = 100; // Number of bits required in output + N = num_bits; // Number of bits required in output K = 3; // Number of bits used in each gene bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? fitness_trait = "fitness"; // Which trait should we store NK fitness in? } -FileOutput output { - filename = "output.csv"; // Name of file for output data. - - // Column format to use in the file. - format = "fitness,fitness:richness,fitness:mode,fitness:max,fitness:mean,fitness:stddev,fitness:entropy,bits"; - target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:10"; // Which updates should we output data? -} - -SelectElite select_e { // Choose the top fitness organisms for replication. - select_pop = "main_pop"; // Which population should we select parents from? - birth_pop = "next_pop"; // Which population should births go into? +SelectElite elite { // Choose the top fitness organisms for replication. top_count = 5; // Number of top-fitness orgs to be replicated - copy_count = 5; // Number of copies to make of replicated organisms - fitness_trait = "fitness"; // Which trait provides the fitness value to use? - Value total_count = top_count * copy_count; + fitness_fun = "fitness"; // Which trait provides the fitness value to use? } -SelectTournament select_t { // Select the top fitness organisms from random subgroups for replication. - select_pop = "main_pop"; // Which population should we select parents from? - birth_pop = "next_pop"; // Which population should births go into? +SelectTournament tournament { // Select the top fitness organisms from random subgroups for replication. tournament_size = 7; // Number of orgs in each tournament - - num_tournaments = pop_size - select_e.total_count; // Number of tournaments to run - fitness_trait = "fitness"; // Which trait provides the fitness value to use? + fitness_fun = "fitness"; // Which trait provides the fitness value to use? } -GrowthPlacement place_next { // Always appened births to the end of a population. - target = "next_pop,main_pop"; // Population(s) to manage. -} +DataFile fit_file { filename="fitness.csv"; } +fit_file.ADD_COLUMN( "Average Fitness", "main_pop.CALC_MEAN('fitness')" ); +fit_file.ADD_COLUMN( "Maximum Fitness", "main_pop.CALC_MAX('fitness')" ); +fit_file.ADD_COLUMN( "Dominant Fitness", "main_pop.CALC_MODE('fitness')" ); -MovePopulation sync_gen { // Move organisms from one populaiton to another. - from_pop = "next_pop"; // Population to move organisms from. - to_pop = "main_pop"; // Population to move organisms into. - reset_to = 1; // Should we erase organisms at the destination? -} +DataFile max_file { filename="max_org.csv"; } +OrgList best_org; +max_file.ADD_SETUP( "best_org = main_pop.FIND_MAX('fitness')" ); +max_file.ADD_COLUMN( "Fitness", "best_org.TRAIT('fitness')" ); +max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); -BitsOrg bits_org { // Organism consisting of a series of N bits. - output_name = "bits"; // Name of variable to contain bit sequence. - N = eval_nk.N; // Number of bits in organism - mut_prob = 0.01; // Probability of each bit mutating on reproduction. + +@start() PRINT("random_seed = ", random_seed, "\n"); // Print seed at run start. +@start() main_pop.INJECT("bits_org", pop_size); // Inject starting population. + +// Actions to perform every update. +@update(0,1) { + eval_nk.EVAL(main_pop); + PRINT("UD:", GET_UPDATE(), + " Main pop size=", main_pop.SIZE(), + " Max Fitness=", main_pop.CALC_MAX("fitness")); + fit_file.WRITE(); + max_file.WRITE(); + + OrgList elite_offspring = elite.SELECT(main_pop, next_pop, 25); + + Value num_tournaments = pop_size - elite_offspring.SIZE(); // Calc number of tournaments to run + OrgList tourny_offspring = tournament.SELECT(main_pop, next_pop, num_tournaments); + + main_pop.REPLACE_WITH(next_pop); } -@start() print("random_seed = ", random_seed, "\n"); -@start() inject("bits_org", "main_pop", pop_size); -@update(500) select_t.tournament_size = 4; -@update(1000) exit(); +@update(1000) EXIT(); From 941d7d887a25039b07d3177914eb1d28801c8ae4 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 20 Nov 2021 11:36:12 -0500 Subject: [PATCH 373/445] Updated Selection modules to use member functions. --- source/select/SelectElite.hpp | 67 ++++++++++++----------- source/select/SelectTournament.hpp | 85 +++++++++++++++--------------- 2 files changed, 79 insertions(+), 73 deletions(-) diff --git a/source/select/SelectElite.hpp b/source/select/SelectElite.hpp index 12853001..9137c882 100644 --- a/source/select/SelectElite.hpp +++ b/source/select/SelectElite.hpp @@ -20,50 +20,57 @@ namespace mabe { /// Add elite selection with the current population. class SelectElite : public Module { private: - std::string trait; ///< Which trait should we select on? - size_t top_count=1; ///< Top how-many should we select? - size_t copy_count=1; ///< How many copies of each should we make? - int select_pop_id = 0; ///< Which population are we selecting from? - int birth_pop_id = 1; ///< Which population should births go into? + std::string fit_equation; ///< Which equation should we select on? + size_t top_count=1; ///< Top how-many should we select? + + Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { + auto fit_fun = control.BuildTraitEquation(fit_equation); + + // Construct a map of all IDs to their associated fitness values. + emp::valsort_map id_fit_map; // @CAO: Better to use a heap? + for (auto it = select_pop.begin(); it != select_pop.end(); it++) { + id_fit_map.Set(it.AsPosition(), fit_fun(*it)); + } + + // Loop through the IDs in fitness order (from highest), replicating each + Collection placement_list; + for (auto it = id_fit_map.crvbegin(); it != id_fit_map.crvend() && top_count; it++) { + size_t copy_count = std::ceil(((double)num_births) / (double) top_count--); + num_births -= copy_count; + placement_list += control.Replicate(it->first, birth_pop, copy_count); + } + return placement_list; + } public: SelectElite(mabe::MABE & control, - const std::string & name="SelectElite", - const std::string & desc="Module to choose the top fitness organisms for replication.", - const std::string & in_trait="fitness", size_t tcount=1, size_t ccount=1) + const std::string & name="SelectElite", + const std::string & desc="Module to choose the top fitness organisms for replication.", + const std::string & in_fit_equation="fitness", size_t tcount=1) : Module(control, name, desc) - , trait(in_trait), top_count(tcount), copy_count(ccount) + , fit_equation(in_fit_equation), top_count(tcount) { SetSelectMod(true); ///< Mark this module as a selection module. } ~SelectElite() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction( + "SELECT", + [](SelectElite & mod, Population & from, Population & to, double count) { + return mod.Select(from,to,count); + }, + "Perform elite selection on the provided organisms."); + } + void SetupConfig() override { - LinkPop(select_pop_id, "select_pop", "Which population should we select parents from?"); - LinkPop(birth_pop_id, "birth_pop", "Which population should births go into?"); + LinkVar(fit_equation, "fitness_fun", "Function used as fitness for selection?"); LinkVar(top_count, "top_count", "Number of top-fitness orgs to be replicated"); - LinkVar(copy_count, "copy_count", "Number of copies to make of replicated organisms"); - LinkVar(trait, "fitness_trait", "Which trait provides the fitness value to use?"); } void SetupModule() override { - AddRequiredTrait(trait); ///< The fitness trait must be set by another module. - } - - void OnUpdate(size_t /* update */) override { - // Construct a map of all IDs to their associated fitness values. - emp::valsort_map id_fit_map; - Collection select_col = control.GetAlivePopulation(select_pop_id); - for (auto it = select_col.begin(); it != select_col.end(); it++) { - id_fit_map.Set(it.AsPosition(), it->GetTrait(trait)); - } - - // Loop through the IDs in fitness order (from highest), replicating each - size_t num_reps = 0; - Population & birth_pop = control.GetPopulation(birth_pop_id); - for (auto it = id_fit_map.crvbegin(); it != id_fit_map.crvend() && num_reps++ < top_count; it++) { - control.Replicate(it->first, birth_pop, copy_count); - } + AddRequiredEquation(fit_equation); ///< The fitness traits must be set by another module. } }; diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index fe88d7b0..0c271edc 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2020. + * @date 2019-2021. * * @file SelectTournament.hpp * @brief MABE module to enable tournament selection (choose T random orgs and return "best") @@ -20,58 +20,27 @@ namespace mabe { private: std::string fit_equation; ///< Trait function that we should select on size_t tourny_size; ///< Number of organisms in each tournament - size_t num_tournies; ///< Number of tournaments to run - int select_pop_id = 0; ///< Population that we are selecting from - int birth_pop_id = 1; ///< Population that births should go into - - public: - SelectTournament(mabe::MABE & control, - const std::string & name="SelectTournament", - const std::string & desc="Module to select the top fitness organisms from random subgroups for replication.", - const std::string & in_fit="fitness", - size_t t_size=7, size_t num_t=1) - : Module(control, name, desc) - , fit_equation(in_fit), tourny_size(t_size), num_tournies(num_t) - { - SetSelectMod(true); ///< Mark this module as a selection module. - } - ~SelectTournament() { } - - void SetupConfig() override { - LinkPop(select_pop_id, "select_pop", "Population from which to select parents"); - LinkPop(birth_pop_id, "birth_pop", "Population into which offspring should be placed"); - LinkVar(tourny_size, "tournament_size", "Number of orgs in each tournament"); - LinkVar(num_tournies, "num_tournaments", "Number of tournaments to run"); - LinkVar(fit_equation, "fitness_fun", "Trait equation that produces fitness value to use"); - } - - void SetupModule() override { - AddRequiredEquation(fit_equation); ///< The fitness traits must be set by another module. - } - - void OnUpdate(size_t ud) override { - control.Verbose("UD ", ud, ": Running SelectTournament::OnUpdate()"); + Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { emp::Random & random = control.GetRandom(); - Population & select_pop = control.GetPopulation(select_pop_id); - Population & birth_pop = control.GetPopulation(birth_pop_id); const size_t N = select_pop.GetSize(); if (select_pop.GetNumOrgs() == 0) { AddError("Trying to run Tournament Selection on an Empty Population."); - return; + return Collection(); } - // Setup the fitness function + // Setup the fitness function - redo this each time in case it changes. auto fit_fun = control.BuildTraitEquation(fit_equation); - // @CAO if we have a sparse Population, we probably want to take that into account. + // Track where all organisms are placed. + Collection placement_list; // Loop through each round of tournament selection. - for (size_t round = 0; round < num_tournies; round++) { + for (size_t round = 0; round < num_births; round++) { // Find a random organism in the population and call it "best" size_t best_id = random.GetUInt(N); - while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); + while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt(N); // @CAO: better way for sparse pop? double best_fit = fit_fun(select_pop[best_id]); // Loop through other organisms for the rest of the tournament size, and pick best. @@ -86,12 +55,42 @@ namespace mabe { } // Replicate the organism that did best in this tournament. - control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); + placement_list += control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); } - control.Verbose(" - After ", num_tournies, " tournaments, select_pop has", - select_pop.GetNumOrgs(), "organisms and birth pop has", - birth_pop.GetNumOrgs(), "."); + return placement_list; + } + + public: + SelectTournament(mabe::MABE & control, + const std::string & name="SelectTournament", + const std::string & desc="Module to select the top fitness organisms from random subgroups for replication.", + const std::string & in_fit="fitness", + size_t t_size=7) + : Module(control, name, desc) + , fit_equation(in_fit), tourny_size(t_size) + { + SetSelectMod(true); ///< Mark this module as a selection module. + } + ~SelectTournament() { } + + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction( + "SELECT", + [](SelectTournament & mod, Population & from, Population & to, double count) { + return mod.Select(from,to,count); + }, + "Perform tournament selection on the provided organisms."); + } + + void SetupConfig() override { + LinkVar(tourny_size, "tournament_size", "Number of orgs in each tournament"); + LinkVar(fit_equation, "fitness_fun", "Trait equation that produces fitness value to use"); + } + + void SetupModule() override { + AddRequiredEquation(fit_equation); ///< The fitness traits must be set by another module. } }; From a4fa2a61e978b5d7736d1c11d906a3b6779240d2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 22 Nov 2021 16:34:11 -0500 Subject: [PATCH 374/445] Expanded MABE --help; Added FILTER() member in Population; Setup BuildTraitSummary() to preprocess --- source/core/MABE.hpp | 71 ++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index ece3029f..35811d53 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -61,6 +61,7 @@ namespace mabe { bool verbose = false; ///< Should we output extra information during setup? bool show_help = false; ///< Should we show "help" before exiting? + std::string help_topic=""; ///< What topic should we give help about? bool exit_now = false; ///< Do we need to immediately clean up and exit the run? ErrorManager error_man; ///< Object to manage warnings and errors. @@ -339,7 +340,8 @@ namespace mabe { /// Build a function to scan a single data map and run the provided equation on the /// enties in there, returning the result. - trait_equation_t BuildTraitEquation(const std::string & equation) { + trait_equation_t BuildTraitEquation(std::string equation) { + equation = Preprocess(equation); auto dm_fun = dm_parser.BuildMathFunction(org_data_map, equation); return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; } @@ -355,7 +357,7 @@ namespace mabe { /// the result as a string. Output is a function in the form: TO_T(const FROM_T &) template std::function - BuildTraitSummary(const std::string & trait_fun, std::string trait_filter); + BuildTraitSummary(std::string trait_fun, std::string trait_filter); /// Build a function that takes a trait equation, builds it, and runs it on a container. /// Output is a function in the form: TO_T(const FROM_T &, trait_equation) @@ -413,16 +415,31 @@ namespace mabe { /// Print information on how to run the software. void MABE::ShowHelp() { - std::cout << "MABE v" << VERSION << "\n" - << "Usage: " << args[0] << " [options]\n" - << "Options:\n"; - for (const auto & cur_arg : arg_set) { - std::cout << " " << cur_arg.flag << " " << cur_arg.args - << " : " << cur_arg.desc << " (or " << cur_arg.name << ")" - << std::endl; - } + std::cout << "MABE v" << VERSION << "\n"; on_help_sig.Trigger(); - std::cout << "Note: Settings and files are applied in the order provided.\n"; + + if (help_topic == "") { + std::cout << "Usage: " << args[0] << " [options]\n" + << "Options:\n"; + for (const auto & cur_arg : arg_set) { + std::cout << " " << cur_arg.flag << " " << cur_arg.args + << " : " << cur_arg.desc << " (or " << cur_arg.name << ")" + << std::endl; + } + } + else { + auto & mod_map = GetModuleMap(); + std::cout << "TOPIC: " << help_topic << std::endl; + if (emp::Has(mod_map, help_topic)) { + const auto & info = mod_map[help_topic]; + std::cout << "--- MABE Module ---\n" + << "Description: " << info.desc << "\n"; + } + else { + std::cout << "Unknown keyword.\n"; + } + + } exit_now = true; } @@ -434,8 +451,8 @@ namespace mabe { // std::cout << " " << mod_ptr->GetName() << " : " << mod_ptr->GetDesc() << "\n"; // } std::cout << "Available modules:\n"; - for (auto & info : GetModuleInfo()) { - std::cout << " " << info.name << " : " << info.desc << "\n"; + for (auto & [type_name,mod] : GetModuleMap()) { + std::cout << " " << type_name << " : " << mod.desc << "\n"; } exit_now = true;; } @@ -459,7 +476,10 @@ namespace mabe { } }); arg_set.emplace_back("--help", "-h", " ", "Help; print command-line options for MABE", - [this](const emp::vector &){ show_help = true; } ); + [this](const emp::vector & in){ + show_help = true; + if (in.size()) help_topic = in[0]; + }); arg_set.emplace_back("--modules", "-m", " ", "Module list", [this](const emp::vector &){ ShowModules(); } ); arg_set.emplace_back("--set", "-s", "[param=value] ", "Set specified parameter", @@ -659,6 +679,16 @@ namespace mabe { return pop.IteratorAt(trait_fun(pop)).AsPosition(); }, "Produce OrgList with just the org with the minimum value of the provided function."); + pop_type.AddMemberFunction("FILTER", + [this](Population & pop, const std::string & trait_equation) -> Collection { + auto filter = BuildTraitEquation(trait_equation); + Collection out_collect; + for (auto it = pop.begin(); it != pop.end(); ++it) { + if (filter(*it)) out_collect.Insert(it); + } + return out_collect; + }, + "Produce OrgList with just the orgs that pass through the filter criteria."); collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), "Return the value of the provided trait for the first organism"); @@ -700,11 +730,11 @@ namespace mabe { "Produce OrgList with just the org with the minimum value of the provided function."); // Setup all known modules as available types in the config file. - for (auto & mod : GetModuleInfo()) { - auto mod_init_fun = [this,&mod](const std::string & name) -> emp::Ptr { - return mod.obj_init_fun(*this,name); + for (auto & [type_name,mod] : GetModuleMap()) { + auto mod_init_fun = [this,mod=&mod](const std::string & name) -> emp::Ptr { + return mod->obj_init_fun(*this,name); }; - auto & type_info = config.AddType(mod.name, mod.desc, mod_init_fun, nullptr, mod.type_id); + auto & type_info = config.AddType(type_name, mod.desc, mod_init_fun, nullptr, mod.type_id); mod.type_init_fun(type_info); // Setup functions for this module. } @@ -1070,7 +1100,7 @@ namespace mabe { template std::function MABE::BuildTraitSummary( - const std::string & trait_fun, + std::string trait_fun, std::string trait_filter ) { static_assert( std::is_same() || std::is_same(), @@ -1078,6 +1108,9 @@ namespace mabe { static_assert( std::is_same() || std::is_same(), "BuildTraitSummary TO_T must be double or std::string." ); + // Pre-process the trait function to allow for use of regular config variables. + trait_fun = Preprocess(trait_fun); + // The trait input has two components: // (1) the trait (or trait function) and // (2) how to calculate the trait SUMMARY, such as min, max, ave, etc. From 4515761c98c8b09dc9380eacece7aed07f51a303 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 22 Nov 2021 16:57:37 -0500 Subject: [PATCH 375/445] Removed WRITE function from MABE now that we have DataFile. --- source/core/MABE.hpp | 75 ++------------------------------------------ 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 35811d53..82492b3a 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -13,8 +13,8 @@ * specific base class functions. See Module.h for a full list of available signals. */ -#ifndef MABE_MABE_H -#define MABE_MABE_H +#ifndef MABE_MABE_HPP +#define MABE_MABE_HPP #include #include @@ -370,12 +370,6 @@ namespace mabe { } - // Handler for printing trait data - void OutputTraitData(std::ostream & os, - Collection target_collect, - std::string format, - bool print_headers=false); - // --- Manage configuration scope --- /// Setup the configuration options for MABE, including for each module. @@ -754,21 +748,9 @@ namespace mabe { config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); - // 'WRITE' will collect data and write it to a file. - auto & files = config.GetSymbolTable().GetFileManager(); - std::function write_fun = - [this,&files](const std::string & filename, const std::string & collection, std::string format) { - const bool file_exists = files.Has(filename); // Is file is already setup? - std::ostream & file = files.GetOutputStream(filename); // File to write to. - OutputTraitData(file, ToCollection(collection), format, !file_exists); - return 0; - }; - config.AddFunction("WRITE", write_fun, - "Write the provided trait-based data to file; args: filename, collection, format."); - - // --- ORGANISM-BASED FUNCTIONS --- + auto & files = config.GetSymbolTable().GetFileManager(); std::function trace_eval_fun = [this,&files](const std::string & filename, const std::string & target, double id) { Collection c = ToCollection(target); // Collection with organisms @@ -1165,57 +1147,6 @@ namespace mabe { else return fun; } - // Handler for printing trait data - void MABE::OutputTraitData(std::ostream & os, - Collection target_collect, - std::string format, - bool print_headers) - { - emp::vector trait_functions; ///< Summary functions to call each update. - emp::remove_whitespace(format); - - // If we need headers, set them up! - if (print_headers) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Print the headers into the file. - os << "#update"; - for (size_t i = 0; i < cols.size(); i++) { - os << ", " << cols[i]; - } - os << '\n'; - } - - // Pre-process the format to deal with config variables that need translating. - format = Preprocess(format); - - // Check the cache for the functions to run; if they don't exist yet, set them up! - auto fun_it = file_fun_cache.find(format); - if (fun_it == file_fun_cache.end()) { - // Identify the contents of each column. - emp::vector cols = emp::slice(format, ','); - - // Setup a function to collect data associated with each column. - trait_functions.resize(cols.size()); - for (size_t i = 0; i < cols.size(); i++) { - std::string trait_filter = cols[i]; - std::string trait_name = emp::string_pop(trait_filter,':'); - trait_functions[i] = BuildTraitSummary(trait_name, trait_filter); - } - - // Insert the new entry into the cache and update the iterator. - fun_it = file_fun_cache.insert({format, trait_functions}).first; - } - else trait_functions = fun_it->second; - - // And, finally, print the data! - os << GetUpdate(); - for (auto & fun : trait_functions) { - os << ", " << fun(target_collect); - } - os << std::endl; - } void MABE::SetupConfig() { // Setup main MABE variables. From 6715739ef4387559be1f0d5d752c94f193a87447 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 22 Nov 2021 17:07:17 -0500 Subject: [PATCH 376/445] Expunged tracing of evaluation from several files; should be done as a member function. --- source/core/MABE.hpp | 17 ----------------- source/core/MABEBase.hpp | 3 --- source/core/Module.hpp | 9 --------- source/core/ModuleBase.hpp | 5 ----- source/evaluate/games/EvalMancala.hpp | 2 +- 5 files changed, 1 insertion(+), 35 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 82492b3a..8c793965 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -124,9 +124,6 @@ namespace mabe { /// List all of the available modules included in the current compilation. void ShowModules(); - /// Ask evaluation modules to trace the execution of the provided organism. - void TraceEval(Organism & org, std::ostream & os) { trace_eval_sig.Trigger(org, os); } - /// Process all of the arguments that were passed in on the command line. void ProcessArgs(); @@ -398,7 +395,6 @@ namespace mabe { bool OnError_IsTriggered(mod_ptr_t mod) { return on_error_sig.cur_mod == mod; }; bool OnWarning_IsTriggered(mod_ptr_t mod) { return on_warning_sig.cur_mod == mod; }; bool BeforeExit_IsTriggered(mod_ptr_t mod) { return before_exit_sig.cur_mod == mod; }; - bool TraceEval_IsTriggered(mod_ptr_t mod) { return trace_eval_sig.cur_mod == mod; }; bool OnHelp_IsTriggered(mod_ptr_t mod) { return on_help_sig.cur_mod == mod; }; }; @@ -748,19 +744,6 @@ namespace mabe { config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); - // --- ORGANISM-BASED FUNCTIONS --- - - auto & files = config.GetSymbolTable().GetFileManager(); - std::function trace_eval_fun = - [this,&files](const std::string & filename, const std::string & target, double id) { - Collection c = ToCollection(target); // Collection with organisms - Organism & org = c.At((size_t) id); // Specific organism to analyze. - std::ostream & file = files.GetOutputStream(filename); // File to write to. - TraceEval(org, file); - return 0; - }; - config.AddFunction("TRACE_EVAL", trace_eval_fun, "Collect information about how evaluations are performed."); - // --- TRAIT-BASED FUNCTIONS --- std::function trait_string_fun = diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 50ec5657..943c5ae1 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -74,8 +74,6 @@ namespace mabe { SigListener before_exit_sig; // OnHelp() SigListener on_help_sig; - // TraceEval() - SigListener trace_eval_sig; /// If a module fails to use a signal, we never check it again UNLESS we are explicitly /// told to rescan the signals (perhaps because new functionality was enabled.) @@ -101,7 +99,6 @@ namespace mabe { , on_warning_sig("on_warning", ModuleBase::SIG_OnWarning, &ModuleBase::OnWarning, sig_ptrs) , before_exit_sig("before_exit", ModuleBase::SIG_BeforeExit, &ModuleBase::BeforeExit, sig_ptrs) , on_help_sig("on_help", ModuleBase::SIG_OnHelp, &ModuleBase::OnHelp, sig_ptrs) - , trace_eval_sig("trace_eval", ModuleBase::SIG_TraceEval, &ModuleBase::TraceEval, sig_ptrs) { ; } public: diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 44424221..60247633 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -332,14 +332,6 @@ namespace mabe { control.RescanSignals(); } - // Format: TraceEval(Organism & trace_org, std::ostream & out_stream) - // Trigger: Request to print a trace of the evaluation of an organism. - // Args: Organism to be traces, stream to print trace to. - void TraceEval(Organism &, std::ostream &) override { - has_signal[SIG_TraceEval] = false; - control.RescanSignals(); - } - /// Turn off all signals in this function. void Deactivate() override { @@ -371,7 +363,6 @@ namespace mabe { bool OnWarning_IsTriggered() override { return control.OnWarning_IsTriggered(this); }; bool BeforeExit_IsTriggered() override { return control.BeforeExit_IsTriggered(this); }; bool OnHelp_IsTriggered() override { return control.OnHelp_IsTriggered(this); }; - bool TraceEval_IsTriggered() override { return control.TraceEval_IsTriggered(this); }; bool OK() const override { return true; } }; diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index fc820851..cf0982aa 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -56,8 +56,6 @@ * : Run immediately before MABE is about to exit. * OnHelp() * : Run when the --help option is called at startup. - * TraceEval(Organism & org, ostream & out_stream) - * : Print a trace of the evaluation of an organism. * ... */ @@ -141,7 +139,6 @@ namespace mabe { SIG_OnWarning, SIG_BeforeExit, SIG_OnHelp, - SIG_TraceEval, NUM_SIGNALS, SIG_UNKNOWN }; @@ -259,7 +256,6 @@ namespace mabe { virtual void OnWarning(const std::string &) = 0; virtual void BeforeExit() = 0; virtual void OnHelp() = 0; - virtual void TraceEval(Organism &, std::ostream &) = 0; virtual void Deactivate() = 0; ///< Turn off all signals in this function. virtual void Activate() = 0; ///< Turn on all signals in this function. @@ -282,7 +278,6 @@ namespace mabe { virtual bool OnWarning_IsTriggered() = 0; virtual bool BeforeExit_IsTriggered() = 0; virtual bool OnHelp_IsTriggered() = 0; - virtual bool TraceEval_IsTriggered() = 0; virtual bool OK() const = 0; // For debugging purposes only. diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 3ae5c62f..9849d0e1 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -218,7 +218,7 @@ namespace mabe { } /// Trace the evaluation of an organism, sending output to a specified stream. - void TraceEval(Organism & org, std::ostream & os) override { + void TraceEval(Organism & org, std::ostream & os) { EvalGame(org, control.GetRandom(), 0, true, os); } From 3501e1d7635c47bfc1a96065c6ac76bb68ab0359 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 22 Nov 2021 18:21:49 -0500 Subject: [PATCH 377/445] Cleanup on MABE.hpp; removed DoRun(), type helpers, and collapsed lots of comments. --- source/core/MABE.hpp | 104 +++++++++++++------------------------------ 1 file changed, 31 insertions(+), 73 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 8c793965..e01e1364 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -65,31 +65,21 @@ namespace mabe { bool exit_now = false; ///< Do we need to immediately clean up and exit the run? ErrorManager error_man; ///< Object to manage warnings and errors. - // Setup helper types. - using trait_equation_t = std::function; - using trait_summary_t = std::function; - using symbol_ptr_t = emp::Ptr; - - // Setup a cache for functions used to collect data for files. @CAO: Move to module! - std::unordered_map> file_fun_cache; - /// Populations used; generated in the configuration file. emp::vector< emp::Ptr > pops; /// Organism pointer to use for all empty cells. emp::Ptr empty_org = nullptr; - TraitManager trait_man; ///< Manages which modules are allowed to use each trait. - /// Trait information to be stored on each organism. Tracks the name, type, and current /// value of all traits that modules associate with organisms. emp::DataMap org_data_map; - emp::DataMapParser dm_parser; ///< Parser to process functions on a data map. - - emp::Random random; ///< Master random number generator - size_t cur_pop_id = (size_t) -1; ///< Which population is currently active? - size_t update = 0; ///< How many times has Update() been called? + TraitManager trait_man; ///< Manage consistent read/write access to traits + emp::DataMapParser dm_parser; ///< Parser to process functions on a data map + emp::Random random; ///< Master random number generator + size_t cur_pop_id = (size_t) -1; ///< Which population is currently active? + size_t update = 0; ///< How many times has Update() been called? // --- Config information for command-line arguments --- @@ -116,27 +106,15 @@ namespace mabe { emplode::Emplode config; ///< Configuration information for this run. - // ----------- Helper Functions ----------- - - /// Print information on how to run the software. - void ShowHelp(); - - /// List all of the available modules included in the current compilation. - void ShowModules(); - - /// Process all of the arguments that were passed in on the command line. - void ProcessArgs(); + // ----------- Helper Functions ----------- + void ShowHelp(); ///< Print information on how to run the software. + void ShowModules(); ///< List all available modules in the current compilation. + void ProcessArgs(); ///< Process all arguments passed in on the command line. // -- Helper functions to be called inside of Setup() -- - - /// Run SetupModule() method on each module we've loaded. - void Setup_Modules(); - - /// Load organism traits that modules need to read or write and test for conflicts. - void Setup_Traits(); - - /// Link signals to the modules that implement responses to those signals. - void UpdateSignals(); + void Setup_Modules(); ///< Run SetupModule() method on each module we've loaded. + void Setup_Traits(); ///< Load organism traits and test for module conflicts. + void UpdateSignals(); ///< Link signals only to modules that respond to them. /// Find any instances of ${X} and eval the X. std::string Preprocess(const std::string & in_string); @@ -149,8 +127,10 @@ namespace mabe { MABE(const MABE &) = delete; MABE(MABE &&) = delete; ~MABE() { - for (auto mod_ptr : modules) mod_ptr.Delete(); // Delete all modules. - for (auto pop_ptr : pops) { // Delete all populations. + before_exit_sig.Trigger(); // Notify modules of end... + + for (auto mod_ptr : modules) mod_ptr.Delete(); // Delete all modules. + for (auto pop_ptr : pops) { // Delete all populations. ClearPop(*pop_ptr); pop_ptr.Delete(); } @@ -172,14 +152,11 @@ namespace mabe { // --- Tools to setup runs --- bool Setup(); - /// Setup an organism as a placeholder for all "empty" positions in the population. + /// Build a placeholder organism for "empty" positions in a Population template void SetupEmpty(); /// Update MABE a single time step. - void Update(); - - /// Update MABE a specified number of time steps. - void DoRun(size_t num_updates); + void Update(size_t num_updates=1); // --- Population Management --- @@ -337,7 +314,7 @@ namespace mabe { /// Build a function to scan a single data map and run the provided equation on the /// enties in there, returning the result. - trait_equation_t BuildTraitEquation(std::string equation) { + auto BuildTraitEquation(std::string equation) { equation = Preprocess(equation); auto dm_fun = dm_parser.BuildMathFunction(org_data_map, equation); return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; @@ -583,8 +560,7 @@ namespace mabe { } void MABE::Deprecate(const std::string & old_name, const std::string & new_name) { - std::function &)> dep_fun = - [this,old_name,new_name](const emp::vector &){ + auto dep_fun = [this,old_name,new_name](const emp::vector> &){ std::cerr << "Function '" << old_name << "' deprecated; use '" << new_name << "'\n"; exit_now = true; return 0; @@ -809,28 +785,19 @@ namespace mabe { return (error_man.GetNumErrors() == 0); } - /// Update MABE a single step. - void MABE::Update() { - // When in debug mode, check the integrity of MABE each update. - emp_assert(OK(), update); - - // If informaiton on any of the signals has changed, update them. - if (rescan_signals) UpdateSignals(); - - // Signal that a new update is about to begin. - before_update_sig.Trigger(update); - - // Increment 'update' to start new update. - update++; - - // Run Update on all modules... - on_update_sig.Trigger(update); - - // Trigger any events that are supposed to occur in config at this update. - config.UpdateEventValue("update", update); + /// Update MABE world. + void MABE::Update(size_t num_updates) { + if (update == 0) config.TriggerEvents("start"); + for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { + emp_assert(OK(), update); // In debug mode, keep checking MABE integrity + if (rescan_signals) UpdateSignals(); // If we have reason to, update module signals + before_update_sig.Trigger(update); // Signal that a new update is about to begin + update++; // Increment 'update' to start new update + on_update_sig.Trigger(update); // Signal all modules about the new update + config.UpdateEventValue("update", update); // Trigger any updated-based events + } } - /// Setup an organism as a placeholder for all "empty" positions in the population. template void MABE::SetupEmpty() { @@ -842,15 +809,6 @@ namespace mabe { empty_org = empty_manager.template Make(); } - /// Update MABE a specified number of time steps. - void MABE::DoRun(size_t num_updates) { - config.TriggerEvents("start"); - for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { - Update(); - } - before_exit_sig.Trigger(); - } - /// New populations must be given a name and an optional size. Population & MABE::AddPopulation(const std::string & name, size_t pop_size) { cur_pop_id = (int) pops.size(); // Set new pop to "current" From 93e7103d72e4f434363c35cbaae2b4d9f4188e08 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 23 Nov 2021 12:25:31 -0500 Subject: [PATCH 378/445] Cleanup of comments throughout MABE.hpp; removed unused functionality. --- source/core/MABE.hpp | 73 ++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 50 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index e01e1364..d5292667 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -46,12 +46,9 @@ namespace mabe { /// @brief The main MABE controller class /// - /// The MABE controller class manages interactions between all modules, - /// ensures that all need components are present at startup, and triggers - /// signals as needed. - /// - /// Note that this class is derived from MABEBase, which handles all population - /// manipulation and signal management. + /// The MABE controller class manages interactions among modules, ensures that needed + /// components are present at startup, and triggers signals as needed. Derived from + /// MABEBase, which handles all population manipulation and signal management. class MABE : public MABEBase { private: @@ -78,16 +75,15 @@ namespace mabe { TraitManager trait_man; ///< Manage consistent read/write access to traits emp::DataMapParser dm_parser; ///< Parser to process functions on a data map emp::Random random; ///< Master random number generator - size_t cur_pop_id = (size_t) -1; ///< Which population is currently active? size_t update = 0; ///< How many times has Update() been called? // --- Config information for command-line arguments --- struct ArgInfo { - std::string name; ///< E.g.: "help" which would be called with "--help" - std::string flag; ///< E.g.: "h" which would be called with -h - std::string args; ///< Type of arguments needed: E.g.: "[filename...]" - std::string desc; ///< E.g.: "Print available command-line options." + std::string name; ///< E.g.: "help" which would be called with "--help" + std::string flag; ///< E.g.: "h" which would be called with -h + std::string args; ///< Type of arguments needed: E.g.: "[filename...]" + std::string desc; ///< E.g.: "Print available command-line options." /// Function to call when triggered. using fun_t = std::function &)>; @@ -164,15 +160,10 @@ namespace mabe { int GetPopID(std::string_view pop_name) const { return emp::FindEval(pops, [pop_name](const auto & p){ return p->GetName() == pop_name; }); } - const Population & GetPopulation(size_t id) const { return *pops[id]; } Population & GetPopulation(size_t id) { return *pops[id]; } - - /// New populations must be given a name and an optional size. + const Population & GetPopulation(size_t id) const { return *pops[id]; } Population & AddPopulation(const std::string & name, size_t pop_size=0); - /// If GetPopulation() is called without an ID, return the current population or create one. - Population & GetPopulation(); - /// Move an organism from one position to another; kill anything that previously occupied /// the target position. void MoveOrg(OrgPosition from_pos, OrgPosition to_pos) { @@ -187,7 +178,6 @@ namespace mabe { /// by MABE. Return the position the organism was placed in. OrgPosition InjectInstance(Population & pop, emp::Ptr org_ptr); - /// Add one or more organisms of a specified type (provide the type name; the MABE controller /// will create instances of it.) Returns the positions the organisms were placed. Collection Inject(Population & pop, const std::string & type_name, size_t copy_count=1); @@ -228,7 +218,7 @@ namespace mabe { return DoBirth(*ppos, ppos, target_pop, birth_count, do_mutations); } - /// Remove all organisms from a population. + /// Remove all organisms from a population; does not change size. void ClearPop(Population & pop) { for (PopIterator pos = pop.begin(); pos != pop.end(); ++pos) ClearOrgAt(pos); } @@ -239,8 +229,9 @@ namespace mabe { MABEBase::ResizePop(pop, new_size); } + /// Copy all of the organisms into a new population (clearing orgs already there) void CopyPop(const Population & from_pop, Population & to_pop) { - EmptyPop(to_pop, from_pop.GetSize()); // Clear all current orgs in the to_pop and resize. + EmptyPop(to_pop, from_pop.GetSize()); for (size_t pos=0; pos < from_pop.GetSize(); ++pos) { if (from_pop.IsEmpty(pos)) continue; InjectAt(from_pop[pos], to_pop.IteratorAt(pos)); @@ -268,10 +259,7 @@ namespace mabe { // --- Collection Management --- - std::string ToString(const mabe::Collection & collect) const { - return collect.ToString(); - } - + std::string ToString(const mabe::Collection & collect) const { return collect.ToString(); } Collection ToCollection(const std::string & load_str); Collection GetAlivePopulation(size_t id) { @@ -311,17 +299,15 @@ namespace mabe { // --- Deal with Organism TRAITS --- TraitManager & GetTraitManager() { return trait_man; } - /// Build a function to scan a single data map and run the provided equation on the - /// enties in there, returning the result. - + /// Build a function to scan a data map, run a provided equation on its entries, + /// and return the result. auto BuildTraitEquation(std::string equation) { equation = Preprocess(equation); auto dm_fun = dm_parser.BuildMathFunction(org_data_map, equation); return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; } - /// Scan a provided equation and return the names of all traits used in that equation. - + /// Scan an equation and return the names of all traits it is using. const std::set & GetEquationTraits(const std::string & equation) { return dm_parser.GetNamesUsed(equation); } @@ -345,15 +331,12 @@ namespace mabe { // --- Manage configuration scope --- - - /// Setup the configuration options for MABE, including for each module. - void SetupConfig(); - - /// Sanity checks for debugging - bool OK(); + + void SetupConfig(); ///< Setup config options for MABE, including for each module. + bool OK(); ///< Sanity checks for debugging - // Checks for which modules are currently being triggered. + // Checks for which modules are actively being triggered. using mod_ptr_t = emp::Ptr; bool BeforeUpdate_IsTriggered(mod_ptr_t mod) { return before_update_sig.cur_mod == mod; }; bool OnUpdate_IsTriggered(mod_ptr_t mod) { return on_update_sig.cur_mod == mod; }; @@ -811,12 +794,11 @@ namespace mabe { /// New populations must be given a name and an optional size. Population & MABE::AddPopulation(const std::string & name, size_t pop_size) { - cur_pop_id = (int) pops.size(); // Set new pop to "current" - emp::Ptr new_pop = - emp::NewPtr(name, cur_pop_id, pop_size, empty_org); // Create new population. - pops.push_back(new_pop); // Record new population. + int pop_id = (int) pops.size(); + emp::Ptr new_pop = emp::NewPtr(name, pop_id, pop_size, empty_org); + pops.push_back(new_pop); - // Setup default functions for the new population. + // Setup default placement functions for the new population. new_pop->SetPlaceBirthFun( [this,new_pop](Organism & /*org*/, OrgPosition /*ppos*/) { return PushEmpty(*new_pop); }); @@ -832,15 +814,6 @@ namespace mabe { return *new_pop; } - /// If GetPopulation() is called without an ID, return the current population or create one. - Population & MABE::GetPopulation() { - if (pops.size() == 0) { // If we don't have a population, add one! - emp_assert(cur_pop_id == (size_t) -1); // Current population should now be default; - AddPopulation("main"); // Default population is named main. - } - return *pops[cur_pop_id]; - } - /// Inject a copy of the provided organism and return the position it was placed in; /// if more than one is added, return the position of the final injection. Collection MABE::Inject(Population & pop, const Organism & org, size_t copy_count) { From 7c33eada239cf2fb134cc81b055025362c14c69a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:03:46 -0500 Subject: [PATCH 379/445] Moved MABE-specific scripting code into a new class derived from Emplode, called MABEScript --- source/core/MABEScript.hpp | 364 +++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 source/core/MABEScript.hpp diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp new file mode 100644 index 00000000..afd66abd --- /dev/null +++ b/source/core/MABEScript.hpp @@ -0,0 +1,364 @@ +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021. + * + * @file MABEScript.hpp + * @brief Customized Emplode scripting language instance for MABE runs. + */ + +#ifndef MABE_MABE_SCRIPT_HPP +#define MABE_MABE_SCRIPT_HPP + +#include +#include +#include + +#include "emp/base/array.hpp" +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/data/DataMap.hpp" +#include "emp/data/DataMapParser.hpp" +#include "emp/datastructs/vector_utils.hpp" +#include "emp/math/Random.hpp" +#include "emp/tools/string_utils.hpp" + +#include "../Emplode/Emplode.hpp" + +#include "Collection.hpp" +#include "data_collect.hpp" +#include "ErrorManager.hpp" +#include "MABEBase.hpp" +#include "ModuleBase.hpp" +#include "Population.hpp" +#include "SigListener.hpp" +#include "TraitManager.hpp" + +namespace mabe { + + /// @brief The MABE scripting language. + + class MABEScript : public emplode::Emplode { + private: + MABEBase & control; + emp::DataMapParser dm_parser; ///< Parser to process functions on a data map + + public: + /// Build a function to scan a data map, run a provided equation on its entries, + /// and return the result. + auto BuildTraitEquation(const emp::DataLayout & data_layout, std::string equation) { + equation = Preprocess(equation); + auto dm_fun = dm_parser.BuildMathFunction(data_layout, equation); + return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; + } + + /// Scan an equation and return the names of all traits it is using. + const std::set & GetEquationTraits(const std::string & equation) { + return dm_parser.GetNamesUsed(equation); + } + + /// Find any instances of ${X} and eval the X. + std::string Preprocess(const std::string & in_string) { + std::string out_string = in_string; + + // Seek out instances of "${" to indicate the start of pre-processing. + for (size_t i = 0; i < out_string.size(); ++i) { + if (out_string[i] != '$') continue; // Replacement tag must start with a '$'. + if (out_string.size() <= i+2) break; // Not enough room for a replacement tag. + if (out_string[i+1] == '$') { // Compress two $$ into one $ + out_string.erase(i,1); + continue; + } + if (out_string[i+1] != '{') continue; // Eval must be surrounded by braces. + + // If we made it this far, we have a starting match! + size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); + if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? + const std::string new_text = Execute(emp::view_string_range(out_string, i+2, end_pos)); + out_string.replace(i, end_pos-i+1, new_text); + + i += new_text.size(); // Continue from the end point... + } + + return out_string; + } + + + /// Build a function to scan a collection of organisms, calculating a given trait_fun for each, + /// aggregating those values based on the mode, and returning the result as the specifed type. + /// + /// 'mode' option are: + /// : Default to the value of the trait for the first organism in the collection. + /// [ID] : Value of this trait for the organism at the given index of the collection. + /// [OP][VALUE] : Count how often this value has the [OP] relationship with [VALUE]. + /// [OP] can be ==, !=, <, >, <=, or >= + /// [VALUE] can be any numeric value + /// [OP][TRAIT] : Count how often this trait has the [OP] relationship with [TRAIT] + /// [OP] can be ==, !=, <, >, <=, or >= + /// [TRAIT] can be any other trait name + /// unique : Return the number of distinct value for this trait (alias="richness"). + /// mode : Return the most common value in this collection (aliases="dom","dominant"). + /// min : Return the smallest value of this trait present. + /// max : Return the largest value of this trait present. + /// ave : Return the average value of this trait (alias="mean"). + /// median : Return the median value of this trait. + /// variance : Return the variance of this trait. + /// stddev : Return the standard deviation of this trait. + /// sum : Return the summation of all values of this trait (alias="total") + /// entropy : Return the Shannon entropy of this value. + /// :trait : Return the mutual information with another provided trait. + + template + std::function BuildTraitSummary( + std::string trait_fun, // Function to calculate on each organism + std::string mode, // Method to combine organism results + emp::DataLayout & data_layout // DataLayout to assume for this summary + ) { + static_assert( std::is_same() || std::is_same(), + "BuildTraitSummary FROM_T must be Collection or Population." ); + static_assert( std::is_same() || std::is_same(), + "BuildTraitSummary TO_T must be double or std::string." ); + + // Pre-process the trait function to allow for use of regular config variables. + trait_fun = Preprocess(trait_fun); + + // The trait input has two components: + // (1) the trait (or trait function) and + // (2) how to calculate the trait SUMMARY, such as min, max, ave, etc. + + // If we have a single trait, we may want to use a string type. + if (emp::is_identifier(trait_fun) // If we have a single trait... + && data_layout.HasName(trait_fun) // ...and it's in the data map... + && !data_layout.IsNumeric(trait_fun) // ...and it's not numeric... + ) { + size_t trait_id = data_layout.GetID(trait_fun); + emp::TypeID result_type = data_layout.GetType(trait_id); + + auto get_fun = [trait_id, result_type](const Organism & org) { + return emp::to_literal( org.GetTraitAsString(trait_id, result_type) ); + }; + auto fun = emp::BuildCollectFun(mode, get_fun); + + // Go through all combinations of TO/FROM to return the correct types. + if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & p){ return emp::from_string(fun( Collection(p) )); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & c){ return emp::from_string(fun(c)); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const FROM_T & p){ return fun( Collection(p) ); }; + } + else return fun; + } + + // If we made it here, we are numeric. + auto get_fun = BuildTraitEquation(data_layout, trait_fun); + auto fun = emp::BuildCollectFun(mode, get_fun); + + // If we don't have a fun, we weren't able to build an aggregation function. + if (!fun) { + control.AddError("Unknown trait filter '", mode, "' for trait '", trait_fun, "'."); + return [](const FROM_T &){ return TO_T(); }; + } + + // Go through all combinations of TO/FROM to return the correct types. + // @CAO need to adjust BuildCollectFun so that it returns correct type; not always string. + if constexpr (std::is_same() && std::is_same()) { + return [fun](const Population & p){ return emp::from_string(fun( Collection(p) )); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const Collection & c){ return emp::from_string(fun(c)); }; + } + else if constexpr (std::is_same() && std::is_same()) { + return [fun](const Population & p){ return fun( Collection(p) ); }; + } + else return fun; + } + + + /// Build a function that takes a trait equation, builds it, and runs it on a container. + /// Output is a function in the form: TO_T(const FROM_T &, string equation, TO_T default) + template + auto BuildTraitFunction(const std::string & fun_type, TO_T default_val=TO_T{}) { + return [this,fun_type,default_val](FROM_T & pop, const std::string & equation) { + if (pop.IsEmpty()) return default_val; + auto trait_fun = BuildTraitSummary(equation, fun_type, pop.GetDataLayout()); + return trait_fun(pop); + }; + } + + private: + /// ======= Helper functions === + + /// Set up all of the functions and globals in MABEScript + void Initialize() { + // Setup main MABE variables. + auto & root_scope = GetSymbolTable().GetRootScope(); + root_scope.LinkFuns("random_seed", + [this](){ return control.GetRandomSeed(); }, + [this](int seed){ control.SetRandomSeed(seed); }, + "Seed for random number generator; use 0 to base on time."); + + // Setup "Population" as a type in the config file. + auto pop_init_fun = [this](const std::string & name) { return &control.AddPopulation(name); }; + auto pop_copy_fun = [this](const EmplodeType & from, EmplodeType & to) { + emp::Ptr from_pop = dynamic_cast(&from); + emp::Ptr to_pop = dynamic_cast(&to); + if (!from_pop || !to_pop) return false; // Wrong type! + control.CopyPop(*from_pop, *to_pop); // Do the actual copy. + return true; + }; + auto & pop_type = AddType("Population", "Collection of organisms", + pop_init_fun, pop_copy_fun); + + // Setup "Collection" as another config type. + auto & collect_type = AddType("OrgList", "Collection of organism pointers"); + + pop_type.AddMemberFunction("REPLACE_WITH", + [this](Population & to_pop, Population & from_pop){ + control.MoveOrgs(from_pop, to_pop, true); return 0; + }, "Move all organisms organisms from another population, removing current orgs." ); + pop_type.AddMemberFunction("APPEND", + [this](Population & to_pop, Population & from_pop){ + control.MoveOrgs(from_pop, to_pop, false); return 0; + }, "Move all organisms organisms from another population, adding after current orgs." ); + + pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + "Return the value of the provided trait for the first organism"); + pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + "Count the number of distinct values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + "Identify the most common value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + "Calculate the average value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + "Find the smallest value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + "Find the largest value of a trait (or equation)."); + pop_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + "Find the index of the smallest value of a trait (or equation)."); + pop_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + "Find the index of the largest value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + "Find the 50-percentile value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + "Find the variance of the distribution of values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + "Find the variance of the distribution of values of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + "Add up the total value of a trait (or equation)."); + pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + "Determine the entropy of values for a trait (or equation)."); + pop_type.AddMemberFunction("FIND_MIN", + [this](Population & pop, const std::string & trait_equation) -> Collection { + if (pop.GetNumOrgs() == 0) Collection{}; + auto trait_fun = + BuildTraitSummary(trait_equation, "min_id", pop.GetDataLayout()); + return pop.IteratorAt(trait_fun(pop)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + pop_type.AddMemberFunction("FIND_MAX", + [this](Population & pop, const std::string & trait_equation) -> Collection { + if (pop.GetNumOrgs() == 0) Collection{}; + auto trait_fun = + BuildTraitSummary(trait_equation, "max_id", pop.GetDataLayout()); + return pop.IteratorAt(trait_fun(pop)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + pop_type.AddMemberFunction("FILTER", + [this](Population & pop, const std::string & trait_equation) -> Collection { + Collection out_collect; + if (pop.GetNumOrgs() > 0) { // Only do this work if we actually have organisms! + auto filter = BuildTraitEquation(pop.GetDataLayout(), trait_equation); + for (auto it = pop.begin(); it != pop.end(); ++it) { + if (filter(*it)) out_collect.Insert(it); + } + } + return out_collect; + }, + "Produce OrgList with just the orgs that pass through the filter criteria."); + + collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + "Return the value of the provided trait for the first organism"); + collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + "Count the number of distinct values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + "Identify the most common value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + "Calculate the average value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + "Find the smallest value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + "Find the largest value of a trait (or equation)."); + collect_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + "Find the index of the smallest value of a trait (or equation)."); + collect_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + "Find the index of the largest value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + "Find the 50-percentile value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + "Find the variance of the distribution of values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + "Find the variance of the distribution of values of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + "Add up the total value of a trait (or equation)."); + collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + "Determine the entropy of values for a trait (or equation)."); + collect_type.AddMemberFunction("FIND_MIN", + [this](Collection & collect, const std::string & trait_equation) -> Collection { + if (collect.IsEmpty()) return Collection{}; + auto trait_fun = + BuildTraitSummary(trait_equation, "min_id", collect.GetDataLayout()); + return collect.IteratorAt(trait_fun(collect)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + collect_type.AddMemberFunction("FIND_MAX", + [this](Collection & collect, const std::string & trait_equation) -> Collection { + if (collect.IsEmpty()) return Collection{}; + auto trait_fun = + BuildTraitSummary(trait_equation, "max_id", collect.GetDataLayout()); + return collect.IteratorAt(trait_fun(collect)).AsPosition(); + }, + "Produce OrgList with just the org with the minimum value of the provided function."); + + // ------ DEPRECATED FUNCTION NAMES ------ + Deprecate("EVAL", "EXEC"); + Deprecate("exit", "EXIT"); + Deprecate("inject", "INJECT"); + Deprecate("print", "PRINT"); + + // Add other built-in functions to the config file. + AddFunction("EXIT", [this](){ control.RequestExit(); return 0; }, "Exit from this MABE run."); + AddFunction("GET_UPDATE", [this](){ return control.GetUpdate(); }, "Get current update."); + AddFunction("GET_VERBOSE", [this](){ return control.GetVerbose(); }, "Has the verbose flag been set?"); + + std::function preprocess_fun = + [this](const std::string & str) { return Preprocess(str); }; + AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); + + // Add in built-in event triggers; these are used to indicate when events should happen. + AddEventType("start"); // Triggered at the beginning of a run. + AddEventType("update"); // Tested every update. + } + + + void Deprecate(const std::string & old_name, const std::string & new_name) { + auto dep_fun = [this,old_name,new_name](const emp::vector> &){ + std::cerr << "Function '" << old_name << "' deprecated; use '" << new_name << "'\n"; + control.RequestExit(); + return 0; + }; + + AddFunction(old_name, dep_fun, std::string("Deprecated. Use: ") + new_name); + } + + public: + MABEScript(MABEBase & in) : control(in) { Initialize(); } + ~MABEScript() { } + + }; + +} + +#endif From c6391ee1d4648594357dd3d4b79caefd9bc8ecf1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:04:40 -0500 Subject: [PATCH 380/445] Updated Emplode to provide a GetType() member for identifying variable types. --- source/Emplode/Emplode.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 4ae784cb..fb9d52a3 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -231,6 +231,14 @@ namespace emplode { return symbol_table.AddType( std::forward(args)... ); } + TypeInfo & GetType(const std::string & type_name) { + return symbol_table.GetType(type_name); + } + + const TypeInfo & GetType(const std::string & type_name) const { + return symbol_table.GetType(type_name); + } + /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided function must take a From c581cc34400a58d4c7cff025622869c6d6ab2bcc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:05:23 -0500 Subject: [PATCH 381/445] Added GetType() to SymbolTable as well. --- source/Emplode/SymbolTable.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index 50842312..e4bdbc53 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -67,6 +67,18 @@ namespace emplode { bool HasType(const std::string & name) const { return emp::Has(type_map, name); } bool HasTypeID(emp::TypeID id) const { return emp::Has(typeid_map, id); } + TypeInfo & GetType(const std::string & type_name) { + auto type_it = type_map.find(type_name); + emp_assert(type_it != type_map.end(), "Type name not found in symbol table.", type_name); + return *(type_it->second); + } + + const TypeInfo & GetType(const std::string & type_name) const { + auto type_it = type_map.find(type_name); + emp_assert(type_it != type_map.end(), "Type name not found in symbol table.", type_name); + return *(type_it->second); + } + /// To add a built-in function (at the root level) provide it with a name and description. /// As long as the function only requires types known to the config system, it should be /// converted properly. For a variadic function, the provided function must take a From af6ec7f6e108f756b33537349b43000c757f3aae Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:06:58 -0500 Subject: [PATCH 382/445] Added pure virtual IsEmpty() to OrgIterator base. --- source/core/OrgIterator.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index a07e0855..4045fc25 100644 --- a/source/core/OrgIterator.hpp +++ b/source/core/OrgIterator.hpp @@ -35,6 +35,7 @@ namespace mabe { virtual std::string GetName() const { return ""; } virtual int GetID() const noexcept { return -1; } virtual size_t GetSize() const noexcept = 0; + virtual bool IsEmpty() const noexcept = 0; virtual Organism & At(size_t org_id) = 0; virtual const Organism & At(size_t org_id) const = 0; From cd3fe58c83573c86920eb5dc99c50014f66ddf0b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:08:06 -0500 Subject: [PATCH 383/445] Added IsEmpty() and GetDataLayout() To Collections. --- source/core/Collection.hpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index 45939229..b255a495 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -173,6 +173,16 @@ namespace mabe { pos_set.SetAll(); // Initially include all orgs. full_pop = false; // Record that pop is no longer officially full. } + + bool IsEmpty(pop_ptr_t pop_ptr) { + if (full_pop) return pop_ptr->IsEmpty(); + size_t cur_pos = 0; + while(cur_pos < pop_ptr->GetSize()) { + cur_pos = pos_set.FindOne(cur_pos+1); + if (!pop_ptr->IsEmpty()) return false; + } + return true; + } }; // Link each population in the collection (by its pointer) to info about which organisms @@ -324,6 +334,15 @@ namespace mabe { return count; } + /// Determine if there are any (living) organisms in this collection. + bool IsEmpty() const noexcept override { + // If we find an organism in any population, return false; otherwise return true. + for (auto [pop_ptr, pop_info] : pos_map) { + if (!pop_info.IsEmpty(pop_ptr)) return false; + } + return true; + } + iterator_t IteratorAt(size_t org_id) { return iterator_t(*this, org_id); } const_iterator_t IteratorAt(size_t org_id) const { return const_iterator_t(*this, org_id); } const_iterator_t ConstIteratorAt(size_t org_id) const { return IteratorAt(org_id); } @@ -411,6 +430,15 @@ namespace mabe { const_pop_ptr_t ConstGetFirstPop() const { return GetFirstPop(); } + emp::DataLayout & GetDataLayout() { + emp_assert(GetFirstPop()); + return GetFirstPop()->GetDataLayout(); + } + const emp::DataLayout & GetDataLayout() const { + emp_assert(GetFirstPop()); + return GetFirstPop()->GetDataLayout(); + } + template void IncPosition(T & it) const { const_pop_ptr_t cur_pop = it.PopPtr(); From 709a823a6359e7880477d602b9a23c6315efcaa9 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:09:43 -0500 Subject: [PATCH 384/445] Added IsEmpty() and data layout tracking (for organism trait data) to Population. --- source/core/Population.hpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 4bce1485..5a42e63e 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -86,12 +86,16 @@ namespace mabe { class Population : public OrgContainer { friend class MABEBase; private: - std::string name=""; ///< Unique name for this population. - size_t pop_id = (size_t) -1; ///< Position in world of this population. - emp::vector> orgs; ///< Info on all organisms in this population. - size_t num_orgs = 0; ///< How many living organisms are in this population? + std::string name=""; ///< Unique name for this population. + size_t pop_id = (size_t) -1; ///< Position in world of this population. + emp::vector> orgs; ///< Info on all organisms in this population. + size_t num_orgs = 0; ///< How many LIVING organisms are in this population? - emp::Ptr empty_org = nullptr; ///< Organism to fill in empty cells (does have data map!) + /// Pointer to layout used in data maps of orgs. + emp::Ptr data_layout_ptr = nullptr; + + /// Organism to fill in empty cells (does have data map!) + emp::Ptr empty_org = nullptr; std::function place_birth_fun; std::function place_inject_fun; @@ -123,6 +127,17 @@ namespace mabe { int GetID() const noexcept override { return pop_id; } size_t GetSize() const noexcept override { return orgs.size(); } size_t GetNumOrgs() const noexcept { return num_orgs; } + bool IsEmpty() const noexcept override { return num_orgs == 0; } + + bool HasDataLayout() const { return data_layout_ptr; } + emp::DataLayout & GetDataLayout() noexcept { + emp_assert(HasDataLayout()); + return *data_layout_ptr; + } + const emp::DataLayout & GetDataLayout() const noexcept { + emp_assert(HasDataLayout()); + return *data_layout_ptr; + } bool IsValid(size_t pos) const { return pos < orgs.size(); } bool IsEmpty(size_t pos) const { return IsValid(pos) && orgs[pos]->IsEmpty(); } @@ -160,6 +175,9 @@ namespace mabe { emp_assert(!org_ptr->IsEmpty()); // Use ExtractOrg if you want to make a cell empty. orgs[pos] = org_ptr; org_ptr->SetPopulation(*this); + if (!data_layout_ptr) data_layout_ptr = &org_ptr->GetDataMap().GetLayout(); + // @CAO If an organism with the wrong data map type is added, should throw a USER error. + emp_assert( &org_ptr->GetDataMap().GetLayout() == data_layout_ptr ); num_orgs++; } From fabccefd1c8a8d420ffa6fe849c42f7c516dfde3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:11:24 -0500 Subject: [PATCH 385/445] Setup MABEBase as a proper MABE base class for use by MABEScript, with required virtual accessors. --- source/core/MABEBase.hpp | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 943c5ae1..a6d7aec8 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -16,6 +16,7 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" +#include "ErrorManager.hpp" #include "ModuleBase.hpp" #include "Population.hpp" #include "SigListener.hpp" @@ -29,6 +30,12 @@ namespace mabe { class MABEBase { protected: + ErrorManager error_man; ///< Manage warnings and errors that occur. + bool exit_now=false; ///< Do we need to immediately clean up and exit the run? + emp::Random random; ///< Master random number generator + size_t update = 0; ///< How many times has Update() been called? + bool verbose = false; ///< Should we output extra information during setup? + /// Maintain a master array of pointers to all SigListeners. using sig_base_t = SigListenerBase; emp::array< emp::Ptr, (size_t) ModuleBase::NUM_SIGNALS > sig_ptrs; @@ -81,7 +88,9 @@ namespace mabe { // Protected constructor so that base class cannot be instantiated except from derived class. MABEBase() - : before_update_sig("before_update", ModuleBase::SIG_BeforeUpdate, &ModuleBase::BeforeUpdate, sig_ptrs) + : error_man( [this](const std::string & msg){ on_error_sig.Trigger(msg); }, + [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) + , before_update_sig("before_update", ModuleBase::SIG_BeforeUpdate, &ModuleBase::BeforeUpdate, sig_ptrs) , on_update_sig("on_update", ModuleBase::SIG_OnUpdate, &ModuleBase::OnUpdate, sig_ptrs) , before_repro_sig("before_repro", ModuleBase::SIG_BeforeRepro, &ModuleBase::BeforeRepro, sig_ptrs) , on_offspring_ready_sig("on_offspring_ready", ModuleBase::SIG_OnOffspringReady, &ModuleBase::OnOffspringReady, sig_ptrs) @@ -102,6 +111,31 @@ namespace mabe { { ; } public: + virtual ~MABEBase() { } + + bool SetupBase() { + error_man.Activate(); + return (error_man.GetNumErrors() == 0); // Only return success if there were no errors. + } + + // --- Basic accessors --- + emp::Random & GetRandom() { return random; } + size_t GetUpdate() const noexcept { return update; } + bool GetVerbose() const { return verbose; } + + /// Provide an interface for reporting warnings. + template + void AddWarning(Ts &&... args) { error_man.AddWarning(std::forward(args)...); } + + /// Provide an interface for reporting errors. + template + void AddError(Ts &&... args) { error_man.AddError(std::forward(args)...); } + + /// Access the full error manager. + ErrorManager & GetErrorManager() { return error_man; } + + /// Trigger exit from run. + void RequestExit() { exit_now = true; } /// Setup signals to be rescanned; call this if any signal is updated in a module. void RescanSignals() { rescan_signals = true; } @@ -167,6 +201,13 @@ namespace mabe { on_pop_resize_sig.Trigger(pop, pop.GetSize()-1); return it; } + + // Interface function for MABEScript + virtual size_t GetRandomSeed() const = 0; + virtual void SetRandomSeed(size_t in_seed) = 0; + virtual Population & AddPopulation(const std::string & name, size_t pop_size=0) = 0; + virtual void CopyPop(const Population & from_pop, Population & to_pop) = 0; + virtual void MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to) = 0; }; } From 0ff6edbd58c2d8e78f6727d5d582dabe15f8f56c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 26 Nov 2021 16:11:57 -0500 Subject: [PATCH 386/445] Removed all functionality now in MABEScript, forwarding requests as needed. --- source/core/MABE.hpp | 408 ++++--------------------------------------- 1 file changed, 37 insertions(+), 371 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index d5292667..66ab84a6 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -35,8 +35,8 @@ #include "Collection.hpp" #include "data_collect.hpp" -#include "ErrorManager.hpp" #include "MABEBase.hpp" +#include "MABEScript.hpp" #include "ModuleBase.hpp" #include "Population.hpp" #include "SigListener.hpp" @@ -55,12 +55,8 @@ namespace mabe { const std::string VERSION = "0.0.1"; // --- Variables to handle configuration, initialization, and error reporting --- - - bool verbose = false; ///< Should we output extra information during setup? bool show_help = false; ///< Should we show "help" before exiting? std::string help_topic=""; ///< What topic should we give help about? - bool exit_now = false; ///< Do we need to immediately clean up and exit the run? - ErrorManager error_man; ///< Object to manage warnings and errors. /// Populations used; generated in the configuration file. emp::vector< emp::Ptr > pops; @@ -73,10 +69,6 @@ namespace mabe { emp::DataMap org_data_map; TraitManager trait_man; ///< Manage consistent read/write access to traits - emp::DataMapParser dm_parser; ///< Parser to process functions on a data map - emp::Random random; ///< Master random number generator - size_t update = 0; ///< How many times has Update() been called? - // --- Config information for command-line arguments --- struct ArgInfo { @@ -99,24 +91,18 @@ namespace mabe { emp::vector config_filenames; ///< Names of configuration files to load. emp::vector config_settings; ///< Additional config commands to run. std::string gen_filename; ///< Name of output file to generate. - emplode::Emplode config; ///< Configuration information for this run. + MABEScript config_script; ///< Configuration information for this run. // ----------- Helper Functions ----------- - void ShowHelp(); ///< Print information on how to run the software. - void ShowModules(); ///< List all available modules in the current compilation. - void ProcessArgs(); ///< Process all arguments passed in on the command line. + void ShowHelp(); ///< Print information on how to run the software. + void ShowModules(); ///< List all available modules in the current compilation. + void ProcessArgs(); ///< Process all arguments passed in on the command line. // -- Helper functions to be called inside of Setup() -- - void Setup_Modules(); ///< Run SetupModule() method on each module we've loaded. - void Setup_Traits(); ///< Load organism traits and test for module conflicts. - void UpdateSignals(); ///< Link signals only to modules that respond to them. - - /// Find any instances of ${X} and eval the X. - std::string Preprocess(const std::string & in_string); - - /// Setup a function as deprecated so we can phase it out. - void Deprecate(const std::string & old_name, const std::string & new_name); + void Setup_Modules(); ///< Run SetupModule() method on each module we've loaded. + void Setup_Traits(); ///< Load organism traits and test for module conflicts. + void UpdateSignals(); ///< Link signals only to modules that respond to them. public: MABE(int argc, char* argv[]); ///< MABE command-line constructor. @@ -134,17 +120,14 @@ namespace mabe { if (empty_org) empty_org.Delete(); } - // --- Basic accessors --- - emp::Random & GetRandom() { return random; } - size_t GetUpdate() const noexcept { return update; } - bool GetVerbose() const { return verbose; } - mabe::ErrorManager & GetErrorManager() { return error_man; } - /// Output args if (and only if) we are in verbose mode. template void Verbose(Ts &&... args) { if (verbose) std::cout << emp::to_string(std::forward(args)...) << std::endl; } + size_t GetRandomSeed() const override { return random.GetSeed(); } + void SetRandomSeed(size_t in_seed) override { random.ResetSeed(in_seed); } + // --- Tools to setup runs --- bool Setup(); @@ -162,7 +145,7 @@ namespace mabe { } Population & GetPopulation(size_t id) { return *pops[id]; } const Population & GetPopulation(size_t id) const { return *pops[id]; } - Population & AddPopulation(const std::string & name, size_t pop_size=0); + Population & AddPopulation(const std::string & name, size_t pop_size=0) override; /// Move an organism from one position to another; kill anything that previously occupied /// the target position. @@ -230,7 +213,7 @@ namespace mabe { } /// Copy all of the organisms into a new population (clearing orgs already there) - void CopyPop(const Population & from_pop, Population & to_pop) { + void CopyPop(const Population & from_pop, Population & to_pop) override { EmptyPop(to_pop, from_pop.GetSize()); for (size_t pos=0; pos < from_pop.GetSize(); ++pos) { if (from_pop.IsEmpty(pos)) continue; @@ -239,7 +222,7 @@ namespace mabe { } /// Move all organisms from one population to another. - void MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to); + void MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to) override; /// Return a ramdom position from a desginated population. OrgPosition GetRandomPos(Population & pop) { @@ -299,43 +282,16 @@ namespace mabe { // --- Deal with Organism TRAITS --- TraitManager & GetTraitManager() { return trait_man; } - /// Build a function to scan a data map, run a provided equation on its entries, - /// and return the result. - auto BuildTraitEquation(std::string equation) { - equation = Preprocess(equation); - auto dm_fun = dm_parser.BuildMathFunction(org_data_map, equation); - return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; + auto BuildTraitEquation(const emp::DataLayout & data_layout, const std::string & equation) { + return config_script.BuildTraitEquation(data_layout, equation); } - /// Scan an equation and return the names of all traits it is using. const std::set & GetEquationTraits(const std::string & equation) { - return dm_parser.GetNamesUsed(equation); + return config_script.GetEquationTraits(equation); } - /// Build a function to scan a collection of organisms, reading the value for the given - /// trait function from each, aggregating those values based on the trait_filter and returning - /// the result as a string. Output is a function in the form: TO_T(const FROM_T &) - template - std::function - BuildTraitSummary(std::string trait_fun, std::string trait_filter); - - /// Build a function that takes a trait equation, builds it, and runs it on a container. - /// Output is a function in the form: TO_T(const FROM_T &, trait_equation) - template - auto BuildTraitFunction(const std::string & fun_type) { - return [this,fun_type](FROM_T & pop, const std::string & trait_equation) { - auto trait_fun = BuildTraitSummary(trait_equation, fun_type); - return trait_fun(pop); - }; - } - - - // --- Manage configuration scope --- - - void SetupConfig(); ///< Setup config options for MABE, including for each module. bool OK(); ///< Sanity checks for debugging - // Checks for which modules are actively being triggered. using mod_ptr_t = emp::Ptr; bool BeforeUpdate_IsTriggered(mod_ptr_t mod) { return before_update_sig.cur_mod == mod; }; @@ -419,7 +375,7 @@ namespace mabe { // MABE config files can be generated FROM a *.gen file, typically creating a *.mabe // file. If output file is *.gen assume an error. (for now; override should be allowed) if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { - error_man.AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); + AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); exit_now = true; } else gen_filename = in[0]; @@ -515,219 +471,37 @@ namespace mabe { rescan_signals = false; } - /// Find any instances of ${X} and eval the X. - std::string MABE::Preprocess(const std::string & in_string) { - std::string out_string = in_string; - - // Seek out instances of "${" to indicate the start of pre-processing. - for (size_t i = 0; i < out_string.size(); ++i) { - if (out_string[i] != '$') continue; // Replacement tag must start with a '$'. - if (out_string.size() <= i+2) break; // Not enough room for a replacement tag. - if (out_string[i+1] == '$') { // Compress two $$ into on $ - out_string.erase(i,1); - continue; - } - if (out_string[i+1] != '{') continue; // Eval must be surrounded by braces. - - // If we made it this far, we have a starting match! - size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); - if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? - const std::string replacement_text = - config.Execute(emp::view_string_range(out_string, i+2, end_pos)); - out_string.replace(i, end_pos-i+1, replacement_text); - - i += replacement_text.size(); // Continue from the end point... - } - - return out_string; - } - - void MABE::Deprecate(const std::string & old_name, const std::string & new_name) { - auto dep_fun = [this,old_name,new_name](const emp::vector> &){ - std::cerr << "Function '" << old_name << "' deprecated; use '" << new_name << "'\n"; - exit_now = true; - return 0; - }; - - config.AddFunction(old_name, dep_fun, std::string("Deprecated. Use: ") + new_name); - } // ---------------- PUBLIC MEMBER FUNCTIONS ----------------- MABE::MABE(int argc, char* argv[]) - : error_man( [this](const std::string & msg){ on_error_sig.Trigger(msg); }, - [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) - , trait_man(error_man) + : trait_man(GetErrorManager()) , args(emp::cl::args_to_strings(argc, argv)) + , config_script(*this) { - // Setup "Population" as a type in the config file. - auto pop_init_fun = [this](const std::string & name) { return &AddPopulation(name); }; - auto pop_copy_fun = [this](const EmplodeType & from, EmplodeType & to) { - emp::Ptr from_pop = dynamic_cast(&from); - emp::Ptr to_pop = dynamic_cast(&to); - if (!from_pop || !to_pop) return false; // Wrong type! - CopyPop(*from_pop, *to_pop); // Do the actual copy. - return true; - }; - auto & pop_type = config.AddType("Population", "Collection of organisms", - pop_init_fun, pop_copy_fun); - - // Setup "Collection" as another config type. - auto & collect_type = config.AddType("OrgList", "Collection of organism pointers"); + // Updates to scripting language that require full controller functionality. // 'INJECT' allows a user to add an organism to a population; returns collection of added orgs. + emplode::TypeInfo & pop_type = config_script.GetType("Population"); std::function inject_fun = [this](Population & pop, const std::string & org_type_name, size_t count) { return Inject(pop, org_type_name, count); }; pop_type.AddMemberFunction("INJECT", inject_fun, - "Inject organisms into population. Args: org_name, org_count. Return: OrgList of injected orgs."); - pop_type.AddMemberFunction("REPLACE_WITH", - [this](Population & to_pop, Population & from_pop){ MoveOrgs(from_pop, to_pop, true); return 0; }, - "Move all organisms organisms from another population, removing current orgs." ); - pop_type.AddMemberFunction("APPEND", - [this](Population & to_pop, Population & from_pop){ MoveOrgs(from_pop, to_pop, false); return 0; }, - "Move all organisms organisms from another population, adding after current orgs." ); - - pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), - "Return the value of the provided trait for the first organism"); - pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), - "Count the number of distinct values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), - "Identify the most common value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), - "Calculate the average value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), - "Find the smallest value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), - "Find the largest value of a trait (or equation)."); - pop_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), - "Find the index of the smallest value of a trait (or equation)."); - pop_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), - "Find the index of the largest value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), - "Find the 50-percentile value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), - "Find the variance of the distribution of values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), - "Find the variance of the distribution of values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), - "Add up the total value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), - "Determine the entropy of values for a trait (or equation)."); - pop_type.AddMemberFunction("FIND_MIN", - [this](Population & pop, const std::string & trait_equation) -> Collection { - auto trait_fun = BuildTraitSummary(trait_equation, "min_id"); - return pop.IteratorAt(trait_fun(pop)).AsPosition(); - }, - "Produce OrgList with just the org with the minimum value of the provided function."); - pop_type.AddMemberFunction("FIND_MAX", - [this](Population & pop, const std::string & trait_equation) -> Collection { - auto trait_fun = BuildTraitSummary(trait_equation, "max_id"); - return pop.IteratorAt(trait_fun(pop)).AsPosition(); - }, - "Produce OrgList with just the org with the minimum value of the provided function."); - pop_type.AddMemberFunction("FILTER", - [this](Population & pop, const std::string & trait_equation) -> Collection { - auto filter = BuildTraitEquation(trait_equation); - Collection out_collect; - for (auto it = pop.begin(); it != pop.end(); ++it) { - if (filter(*it)) out_collect.Insert(it); - } - return out_collect; - }, - "Produce OrgList with just the orgs that pass through the filter criteria."); - - collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), - "Return the value of the provided trait for the first organism"); - collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), - "Count the number of distinct values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), - "Identify the most common value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), - "Calculate the average value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), - "Find the smallest value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), - "Find the largest value of a trait (or equation)."); - collect_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), - "Find the index of the smallest value of a trait (or equation)."); - collect_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), - "Find the index of the largest value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), - "Find the 50-percentile value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), - "Find the variance of the distribution of values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), - "Find the variance of the distribution of values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), - "Add up the total value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), - "Determine the entropy of values for a trait (or equation)."); - collect_type.AddMemberFunction("FIND_MIN", - [this](Collection & collect, const std::string & trait_equation) -> Collection { - auto trait_fun = BuildTraitSummary(trait_equation, "min_id"); - return collect.IteratorAt(trait_fun(collect)).AsPosition(); - }, - "Produce OrgList with just the org with the minimum value of the provided function."); - collect_type.AddMemberFunction("FIND_MAX", - [this](Collection & collect, const std::string & trait_equation) -> Collection { - auto trait_fun = BuildTraitSummary(trait_equation, "max_id"); - return collect.IteratorAt(trait_fun(collect)).AsPosition(); - }, - "Produce OrgList with just the org with the minimum value of the provided function."); + "Inject organisms into population. Args: org_name, org_count; Return: OrgList of injected orgs."); // Setup all known modules as available types in the config file. for (auto & [type_name,mod] : GetModuleMap()) { auto mod_init_fun = [this,mod=&mod](const std::string & name) -> emp::Ptr { return mod->obj_init_fun(*this,name); }; - auto & type_info = config.AddType(type_name, mod.desc, mod_init_fun, nullptr, mod.type_id); + auto & type_info = config_script.AddType(type_name, mod.desc, mod_init_fun, nullptr, mod.type_id); mod.type_init_fun(type_info); // Setup functions for this module. } - - - // ------ DEPRECATED FUNCTION NAMES ------ - Deprecate("EVAL", "EXEC"); - Deprecate("exit", "EXIT"); - Deprecate("inject", "INJECT"); - Deprecate("print", "PRINT"); - - // Add other built-in functions to the config file. - config.AddFunction("EXIT", [this](){ exit_now = true; return 0; }, "Exit from this MABE run."); - config.AddFunction("GET_UPDATE", [this](){ return GetUpdate(); }, "Get current update."); - - std::function preprocess_fun = - [this](const std::string & str) { return Preprocess(str); }; - config.AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); - - - // --- TRAIT-BASED FUNCTIONS --- - - std::function trait_string_fun = - [this](const std::string & target, std::string trait_filter) { - std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitSummary(trait_name, trait_filter); - return fun( ToCollection(target) ); - }; - config.AddFunction("TRAIT_STRING", trait_string_fun, "Collect information about a specified trait."); - - std::function trait_value_fun = - [this](const std::string & target, std::string trait_filter) { - std::string trait_name = emp::string_pop(trait_filter,':'); - auto fun = BuildTraitSummary(trait_name, trait_filter); - return emp::from_string(fun( ToCollection(target) )); - }; - config.AddFunction("TRAIT_VALUE", trait_value_fun, "Collect information about a specified trait."); - - // Add in built-in event triggers; these are used to indicate when events should happen. - config.AddEventType("start"); // Triggered at the beginning of a run. - config.AddEventType("update"); // Tested every update. } bool MABE::Setup() { - SetupConfig(); // Load all of the parameters needed by modules, etc. ProcessArgs(); // Deal with command-line inputs. // Sometimes command-line arguments will require an immediate exit (such as after '--help') @@ -736,18 +510,18 @@ namespace mabe { // If configuration filenames have been specified, load each of them in order. if (config_filenames.size()) { std::cout << "Loading file(s): " << emp::to_quoted_list(config_filenames) << std::endl; - config.Load(config_filenames); // Load files + config_script.Load(config_filenames); // Load files } if (config_settings.size()) { std::cout << "Loading command-line settings." << std::endl; - config.LoadStatements(config_settings, "command-line settings"); + config_script.LoadStatements(config_settings, "command-line settings"); } // If we are writing a file, do so and then exit. if (gen_filename != "") { std::cout << "Generating file '" << gen_filename << "'." << std::endl; - config.Write(gen_filename); + config_script.Write(gen_filename); exit_now = true; } @@ -762,22 +536,19 @@ namespace mabe { UpdateSignals(); // Setup the appropriate modules to be linked with each signal. - error_man.Activate(); - - // Only return success if there were no errors. - return (error_man.GetNumErrors() == 0); + return SetupBase(); // Call Setup on MABEBase (which is tracking and will report errors) } /// Update MABE world. void MABE::Update(size_t num_updates) { - if (update == 0) config.TriggerEvents("start"); + if (update == 0) config_script.TriggerEvents("start"); for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { emp_assert(OK(), update); // In debug mode, keep checking MABE integrity if (rescan_signals) UpdateSignals(); // If we have reason to, update module signals before_update_sig.Trigger(update); // Signal that a new update is about to begin update++; // Increment 'update' to start new update on_update_sig.Trigger(update); // Signal all modules about the new update - config.UpdateEventValue("update", update); // Trigger any updated-based events + config_script.UpdateEventValue("update", update); // Trigger any updated-based events } } @@ -828,7 +599,7 @@ namespace mabe { placement_set.Insert(pos); } else { inject_org.Delete(); - error_man.AddError("Invalid position; failed to inject organism ", i, "!"); + AddError("Invalid position; failed to inject organism ", i, "!"); } } return placement_set; @@ -843,7 +614,7 @@ namespace mabe { if (pos.IsValid()) AddOrgAt( org_ptr, pos); else { org_ptr.Delete(); - error_man.AddError("Invalid position; failed to inject organism!"); + AddError("Invalid position; failed to inject organism!"); } return pos; } @@ -874,10 +645,10 @@ namespace mabe { size_t copy_count) { int pop_id = GetPopID(pop_name); if (pop_id == -1) { - error_man.AddError("Invalid population name used in inject: ", - "org_type= '", type_name, "'; ", - "pop_name= '", pop_name, "'; ", - "copy_count=", copy_count); + AddError("Invalid population name used in inject: ", + "org_type= '", type_name, "'; ", + "pop_name= '", pop_name, "'; ", + "copy_count=", copy_count); } Population & pop = GetPopulation(pop_id); return Inject(pop, type_name, copy_count); // Inject the organisms. @@ -962,116 +733,12 @@ namespace mabe { auto slices = emp::view_slices(load_str, ','); for (auto name : slices) { int pop_id = GetPopID(name); - if (pop_id == -1) error_man.AddError("Unknown population: ", name); + if (pop_id == -1) AddError("Unknown population: ", name); else out.Insert(GetPopulation(pop_id)); } return out; } - - /// Build a function to scan a collection of organisms, reading the value for the given - /// trait_name from each, aggregating those values based on the trait_filter and returning - /// the result as a string. - /// - /// trait_filter option are: - /// : Default to the value of the trait for the first organism in the collection. - /// [ID] : Value of this trait for the organism at the given index of the collection. - /// [OP][VALUE] : Count how often this value has the [OP] relationship with [VALUE]. - /// [OP] can be ==, !=, <, >, <=, or >= - /// [VALUE] can be any numeric value - /// [OP][TRAIT] : Count how often this trait has the [OP] relationship with [TRAIT] - /// [OP] can be ==, !=, <, >, <=, or >= - /// [TRAIT] can be any other trait name - /// unique : Return the number of distinct value for this trait (alias="richness"). - /// mode : Return the most common value in this collection (aliases="dom","dominant"). - /// min : Return the smallest value of this trait present. - /// max : Return the largest value of this trait present. - /// ave : Return the average value of this trait (alias="mean"). - /// median : Return the median value of this trait. - /// variance : Return the variance of this trait. - /// stddev : Return the standard deviation of this trait. - /// sum : Return the summation of all values of this trait (alias="total") - /// entropy : Return the Shannon entropy of this value. - /// :trait : Return the mutual information with another provided trait. - - template - std::function MABE::BuildTraitSummary( - std::string trait_fun, - std::string trait_filter - ) { - static_assert( std::is_same() || std::is_same(), - "BuildTraitSummary FROM_T must be Collection or Population." ); - static_assert( std::is_same() || std::is_same(), - "BuildTraitSummary TO_T must be double or std::string." ); - - // Pre-process the trait function to allow for use of regular config variables. - trait_fun = Preprocess(trait_fun); - - // The trait input has two components: - // (1) the trait (or trait function) and - // (2) how to calculate the trait SUMMARY, such as min, max, ave, etc. - - // If we have a single trait, we may want to use a string type. - if (emp::is_identifier(trait_fun) // If we have a single trait... - && org_data_map.HasName(trait_fun) // ...and it's in the data map... - && !org_data_map.IsNumeric(trait_fun) // ...and it's not numeric... - ) { - size_t trait_id = org_data_map.GetID(trait_fun); - emp::TypeID result_type = org_data_map.GetType(trait_id); - - auto get_fun = [trait_id, result_type](const Organism & org) { - return emp::to_literal( org.GetTraitAsString(trait_id, result_type) ); - }; - auto fun = emp::BuildCollectFun(trait_filter, get_fun); - - // Go through all combinations of TO/FROM to return the correct types. - if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & p){ return emp::from_string(fun( Collection(p) )); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & c){ return emp::from_string(fun(c)); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & p){ return fun( Collection(p) ); }; - } - else return fun; - } - - // If we made it here, we are numeric. - auto get_fun = BuildTraitEquation(trait_fun); - auto fun = emp::BuildCollectFun(trait_filter, get_fun); - - // If we don't have a fun, we weren't able to build an aggregation function. - if (!fun) { - error_man.AddError("Unknown trait filter '", trait_filter, "' for trait '", trait_fun, "'."); - return [](const FROM_T &){ return TO_T(); }; - } - - // Go through all combinations of TO/FROM to return the correct types. - // @CAO need to adjust BuildCollectFun so that it returns correct type; not always string. - if constexpr (std::is_same() && std::is_same()) { - return [fun](const Population & p){ return emp::from_string(fun( Collection(p) )); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const Collection & c){ return emp::from_string(fun(c)); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const Population & p){ return fun( Collection(p) ); }; - } - else return fun; - } - - - void MABE::SetupConfig() { - // Setup main MABE variables. - auto & root_scope = config.GetSymbolTable().GetRootScope(); - root_scope.LinkFuns("random_seed", - [this](){ return random.GetSeed(); }, - [this](int seed){ random.ResetSeed(seed); }, - "Seed for random number generator; use 0 to base on time."); - } - - bool MABE::OK() { bool result = true; for (auto mod_ptr : modules) result &= mod_ptr->OK(); // Ensure modules are okay. @@ -1079,7 +746,6 @@ namespace mabe { return result; } - } #endif From 103a6a1da14a99bb4065537bf0071ecd9530c45d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 27 Nov 2021 10:57:53 -0500 Subject: [PATCH 387/445] Updated DevloperNotes with MABEBase.hpp and MABEScript.hpp --- source/core/DeveloperNotes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 58e314a0..38250adc 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -19,6 +19,8 @@ Organism.hpp - Information about a single agent; ModuleBase is interface OrgIterator.hpp - Tools for identifying organism locations and stepping through sets of them. Population.hpp - Collection of Organisms (some of which could be EmptyOrganisms) Collection.hpp - A more flexible collection of organisms or whole populations for manipulation. +MABEBase.hpp - Handles restricted core MABE functionality and provides interface to MABEScript +MABEScript.hpp - Builds on Emplode to provide the full MABE scripting language. MABE.hpp - Main controller object; manipulates Populations and Organisms Module.hpp - Modify main MABE controller functions. FactoryModule.hpp - Framework to build specialty modules that manage other config objects. From 5f58dd4a30330d033e24c656e5629865a0c5c082 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 28 Nov 2021 22:42:59 -0500 Subject: [PATCH 388/445] added BuildTrairEquation that pulls triat layout from population. --- source/core/MABE.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 66ab84a6..0bfb3e34 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -282,10 +282,17 @@ namespace mabe { // --- Deal with Organism TRAITS --- TraitManager & GetTraitManager() { return trait_man; } + /// Build a lambda function that takes an organism applied the provided equation to it. + /// (The provided data layout must match that or the organisms.) auto BuildTraitEquation(const emp::DataLayout & data_layout, const std::string & equation) { return config_script.BuildTraitEquation(data_layout, equation); } + /// Build a trait equations for organisms in a given population. + auto BuildTraitEquation(const Population & pop, const std::string & equation) { + return BuildTraitEquation(pop.GetDataLayout(), equation); + } + const std::set & GetEquationTraits(const std::string & equation) { return config_script.GetEquationTraits(equation); } From 50fd5776ed07892b46001b8e819793577119707b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 29 Nov 2021 13:01:34 -0500 Subject: [PATCH 389/445] Updated SelectRoulette to new format. --- source/select/SelectRoulette.hpp | 70 +++++++++++++++++--------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/source/select/SelectRoulette.hpp b/source/select/SelectRoulette.hpp index 1dbf8c7a..dd28153a 100644 --- a/source/select/SelectRoulette.hpp +++ b/source/select/SelectRoulette.hpp @@ -20,11 +20,32 @@ namespace mabe { /// Add roulette selection with the current population. class SelectRoulette : public Module { private: - std::string fitness_trait="fitness"; ///< Which trait should we select on? - size_t select_count=1; ///< How many times to run roulette? - size_t copy_count=1; ///< How many copies of each should we make? - int select_pop_id = 0; ///< Which population are we selecting from? - int birth_pop_id = 1; ///< Which population should births go into? + std::string fit_equation; ///< Which equation should we select on? + + Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { + if (select_pop.GetID() == birth_pop.GetID()) { + AddError("Birth_pop and select_pop must be different."); + return; + } + + auto fit_fun = control.BuildTraitEquation(select_pop, fit_equation); + + emp::IndexMap fit_map(select_pop.GetSize(), 0.0); + for (size_t org_pos = 0; org_pos < select_pop.GetSize(); org_pos++) { + if (select_pop.IsEmpty(org_pos)) continue; + fit_map[org_pos] = fit_fun(select_pop[org_pos]); + } + + // Loop through picking IDs proportional to fitness_trait, replicating each + emp::Random & random = control.GetRandom(); + Collection placement_list; + for (size_t birth_id = 0; birth_id < num_births; birth_id++) { + size_t org_id = fit_map.Index( random.GetDouble(fit_map.GetWeight()) ); + placement+list += control.Replicate(select_pop.IteratorAt(org_id), birth_pop); + } + + return placement_list; + } public: SelectRoulette( @@ -37,39 +58,24 @@ namespace mabe { } ~SelectRoulette() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction( + "SELECT", + [](SelectRoulette & mod, Population & from, Population & to, double count) { + return mod.Select(from,to,count); + }, + "Perform roulette selection on the provided organisms."); + } + void SetupConfig() override { - LinkPop(select_pop_id, "select_pop", "Which population should we select parents from?"); - LinkPop(birth_pop_id, "birth_pop", "Which population should births go into?"); - LinkVar(select_count, "select_count", "How many organisms should we choose to replicate?"); - LinkVar(copy_count, "copy_count", "Number of copies to make of replicated organisms"); - LinkVar(fitness_trait, "fitness_trait", "Which trait provides the fitness value to use?"); + LinkVar(fit_equation, "fitness_fun", "Function used as fitness for selection?"); } void SetupModule() override { - AddRequiredTrait(fitness_trait); ///< The fitness trait must be set by another module. + AddRequiredEquation(fit_equation); // The fitness traits must be set by another module. } - void OnUpdate(size_t /* update */) override { - if (select_pop_id == birth_pop_id) { - AddError("For now, birth_pop and select_pop must be different."); - return; - } - - Population & select_pop = control.GetPopulation(select_pop_id); - emp::IndexMap fit_map(select_pop.GetSize(), 0.0); - for (size_t org_pos = 0; org_pos < select_pop.GetSize(); org_pos++) { - if (select_pop.IsEmpty(org_pos)) continue; - fit_map[org_pos] = select_pop[org_pos].GetTrait(fitness_trait); - } - - // Loop through picking IDs proportional to fitness_trait, replicating each - Population & birth_pop = control.GetPopulation(birth_pop_id); - emp::Random & random = control.GetRandom(); - for (size_t num_reps = 0; num_reps < select_count; num_reps++) { - size_t org_id = fit_map.Index( random.GetDouble(fit_map.GetWeight()) ); - control.Replicate(select_pop.IteratorAt(org_id), birth_pop, copy_count); - } - } }; MABE_REGISTER_MODULE(SelectRoulette, "Randomly choose organisms to replicate weighted by fitness."); From 050f300513c6c0782873db76739523fd5300917b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 29 Nov 2021 13:02:20 -0500 Subject: [PATCH 390/445] Cleanup on SelectElite and SelectTournament --- source/select/SelectElite.hpp | 4 ++-- source/select/SelectTournament.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/select/SelectElite.hpp b/source/select/SelectElite.hpp index 9137c882..346af584 100644 --- a/source/select/SelectElite.hpp +++ b/source/select/SelectElite.hpp @@ -24,7 +24,7 @@ namespace mabe { size_t top_count=1; ///< Top how-many should we select? Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { - auto fit_fun = control.BuildTraitEquation(fit_equation); + auto fit_fun = control.BuildTraitEquation(select_pop, fit_equation); // Construct a map of all IDs to their associated fitness values. emp::valsort_map id_fit_map; // @CAO: Better to use a heap? @@ -70,7 +70,7 @@ namespace mabe { } void SetupModule() override { - AddRequiredEquation(fit_equation); ///< The fitness traits must be set by another module. + AddRequiredEquation(fit_equation); // The fitness traits must be set by another module. } }; diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index 0c271edc..c7743e18 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -31,7 +31,7 @@ namespace mabe { } // Setup the fitness function - redo this each time in case it changes. - auto fit_fun = control.BuildTraitEquation(fit_equation); + auto fit_fun = control.BuildTraitEquation(select_pop, fit_equation); // Track where all organisms are placed. Collection placement_list; From bc816e11be368d320d7f1f890b17e25de39b0dc2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 2 Dec 2021 14:54:04 -0500 Subject: [PATCH 391/445] Updated NK.mabe example file with more output and comments on plans. --- settings/NK.mabe | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/settings/NK.mabe b/settings/NK.mabe index 139f1b35..3a96dd03 100644 --- a/settings/NK.mabe +++ b/settings/NK.mabe @@ -44,10 +44,23 @@ max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); // Actions to perform every update. @update(0,1) { + // @update(Value update) { + // IF ([10:10].HAS(update)) EXIT(); + eval_nk.EVAL(main_pop); + Value mode_fit = main_pop.CALC_MODE("fitness"); + OrgList list_less = main_pop.FILTER("fitness < ${mode_fit}"); + OrgList list_equ = main_pop.FILTER("fitness == ${mode_fit}"); + OrgList list_gtr = main_pop.FILTER("fitness > ${mode_fit}"); PRINT("UD:", GET_UPDATE(), - " Main pop size=", main_pop.SIZE(), - " Max Fitness=", main_pop.CALC_MAX("fitness")); + " MainPopSize=", main_pop.SIZE(), + " AveFitness=", main_pop.CALC_MEAN("fitness"), + " MaxFitness=", main_pop.CALC_MAX("fitness"), + " ModeFitness=", mode_fit, + "\nMODE_LESS=", list_less.SIZE(), + " MODE_EQU=", list_equ.SIZE(), + " MODE_GTR=", list_gtr.SIZE(), + ); fit_file.WRITE(); max_file.WRITE(); From 16a161ee2482e2abee47ed21284655b88fb7eff0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 3 Dec 2021 16:36:32 -0500 Subject: [PATCH 392/445] Removed CommandLine from the list of available module.s --- source/modules.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/modules.hpp b/source/modules.hpp index c7c1fc4d..af10051b 100644 --- a/source/modules.hpp +++ b/source/modules.hpp @@ -16,7 +16,6 @@ #include "evaluate/static/EvalRoyalRoad.hpp" // Interface Modules -#include "interface/CommandLine.hpp" // Placement Modules From ea5af9fe4506974cd1d07083192f9bac0cbb3690 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 3 Dec 2021 16:49:00 -0500 Subject: [PATCH 393/445] Fixed syntax errors in SelectRoulette --- source/select/SelectRoulette.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/select/SelectRoulette.hpp b/source/select/SelectRoulette.hpp index dd28153a..a915c0de 100644 --- a/source/select/SelectRoulette.hpp +++ b/source/select/SelectRoulette.hpp @@ -24,8 +24,8 @@ namespace mabe { Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { if (select_pop.GetID() == birth_pop.GetID()) { - AddError("Birth_pop and select_pop must be different."); - return; + AddError("SelectRoulette currently requires birth_pop and select_pop to be different."); + return Collection{}; } auto fit_fun = control.BuildTraitEquation(select_pop, fit_equation); @@ -41,7 +41,7 @@ namespace mabe { Collection placement_list; for (size_t birth_id = 0; birth_id < num_births; birth_id++) { size_t org_id = fit_map.Index( random.GetDouble(fit_map.GetWeight()) ); - placement+list += control.Replicate(select_pop.IteratorAt(org_id), birth_pop); + placement_list += control.Replicate(select_pop.IteratorAt(org_id), birth_pop); } return placement_list; From 3937b39073f6e4e7fb651da64dc17f725b667cbf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 3 Dec 2021 16:49:19 -0500 Subject: [PATCH 394/445] Updated SelectLexicase to new setup. --- source/select/SelectLexicase.hpp | 109 +++++++++++++++---------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/source/select/SelectLexicase.hpp b/source/select/SelectLexicase.hpp index c1cc9315..b1c73acb 100644 --- a/source/select/SelectLexicase.hpp +++ b/source/select/SelectLexicase.hpp @@ -29,71 +29,32 @@ namespace mabe { std::string trait_inputs; ///< Which set of trait values should we select on? TraitSet trait_set; ///< Processed version of trait_inputs. double epsilon = 0.0; ///< Range from max value to be preserved? (fraction of max) - int select_pop_id = 0; ///< Which population are we selecting from? - int birth_pop_id = 1; ///< Which population should births go into? - size_t num_births = 1; ///< How many offspring organisms should we produce? size_t sample_traits = 0; ///< Number of test cases to use each generation (0=off) - public: - SelectLexicase(mabe::MABE & control, - const std::string & name="SelectLexicase", - const std::string & desc="Module to choose the top fitness organisms for replication.") - : Module(control, name, desc) - { - SetSelectMod(true); ///< Mark this module as a selection module. - } - ~SelectLexicase() { } - - void SetupConfig() override { - LinkPop(select_pop_id, "select_pop", "Which population should we select parents from?"); - LinkPop(birth_pop_id, "birth_pop", "Which population should births go into?"); - LinkVar(trait_inputs, "fitness_traits", "Which traits provide the fitness values to use?"); - LinkVar(epsilon, "epsilon", "Range from max value to be preserved? (fraction of max)"); - LinkVar(num_births, "num_births", "Number of offspring organisms to produce"); - LinkVar(sample_traits, "sample_traits", "Number of test cases to use each generation (0=all)" ); - } - - void SetupModule() override { - // We should always have a minimal epsilon to handle mathematical imprecision of doubles. - if (epsilon <= 0.0) epsilon = 0.000000001; // One billionth. - - // All of the traits used are required to be generated by another module. - emp::vector trait_names = emp::slice(trait_inputs); - for (const std::string & name : trait_names) { - AddRequiredTrait>(name); + Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { + if (num_births > 1 && select_pop.GetID() == birth_pop.GetID()) { + AddError("SelectLexicase requires birth_pop and select_pop to be different if selecting multiple organisms."); + return Collection(); } - } - - void SetupDataMap(emp::DataMap & dmap) override { - trait_set.SetLayout(dmap.GetLayout()); ///< Give this trait set a layout to optimize. - trait_set.SetTraits(trait_inputs); ///< Parse set of trait inputs passed in. - } - - void OnUpdate(size_t /* update */) override { - // Collect information about the population we're using. - mabe::Population & select_pop = control.GetPopulation(select_pop_id); - mabe::Population & birth_pop = control.GetPopulation(birth_pop_id); - const size_t num_orgs = select_pop.GetSize(); - emp_assert(num_orgs > 0); // Build a trait vector to hold the scores for each organism. - emp::vector< emp::vector > trait_scores(num_orgs); + emp::vector< emp::vector > trait_scores(select_pop.GetSize()); // Find a living organism to setup traits. size_t live_id = 0; while (select_pop.IsEmpty(live_id)) live_id++; - if (live_id == select_pop.size()) return; // @CAO + error? No living orgs!! + if (live_id == select_pop.size()) return Collection(); // No living orgs!! size_t num_traits = trait_set.CountValues(select_pop[live_id].GetDataMap()); + emp::Random & random = control.GetRandom(); + // If we're not using all of the traits, determine which ones to select on. emp::vector traits_used; - if (sample_traits) { - emp::Choose(control.GetRandom(), num_traits, sample_traits, traits_used); - } + if (sample_traits) emp::Choose(random, num_traits, sample_traits, traits_used); // Loop through each organism to collect its trait information. emp::vector start_orgs; - for (size_t org_id = live_id; org_id < num_orgs; ++org_id) { + for (size_t org_id = live_id; org_id < num_births; ++org_id) { if (select_pop.IsEmpty(org_id)) continue; // Skip empty positions in the population. // This cell is not empty so add it to the full set of organisms. @@ -118,14 +79,12 @@ namespace mabe { emp::vector cur_orgs, next_orgs; // Create the correct number of offspring. + Collection placement_list; for (size_t birth_id = 0; birth_id < num_births; ++birth_id) { - // For each offspring, start with full population - cur_orgs = start_orgs; - - // Shuffle traits into a random order. - emp::Shuffle(control.GetRandom(), traits_used); + cur_orgs = start_orgs; // For each offspring, start with full population + emp::Shuffle(random, traits_used); // Shuffle traits into a random order. - // then step through traits and filter based on each. + // Step through traits and filter based on each. for (size_t trait_id : traits_used) { // Find the minimum and maximum values of the current trait. double min_value = std::numeric_limits::max(); @@ -158,18 +117,52 @@ namespace mabe { // If there's only one organism left, replicate it! if (cur_orgs.size() == 1) { - control.Replicate(select_pop.IteratorAt(cur_orgs[0]), birth_pop); + placement_list += control.Replicate(select_pop.IteratorAt(cur_orgs[0]), birth_pop); } // Otherwise pick a random organism from the ones remaining. else { - int org_id = cur_orgs[ control.GetRandom().GetUInt(cur_orgs.size()) ]; - control.Replicate(select_pop.IteratorAt(org_id), birth_pop); + int org_id = cur_orgs[ random.GetUInt(cur_orgs.size()) ]; + placement_list += control.Replicate(select_pop.IteratorAt(org_id), birth_pop); } } + return placement_list; } + + public: + SelectLexicase(mabe::MABE & control, + const std::string & name="SelectLexicase", + const std::string & desc="Module to choose the top fitness organisms for replication.") + : Module(control, name, desc) + { + SetSelectMod(true); ///< Mark this module as a selection module. + } + ~SelectLexicase() { } + + void SetupConfig() override { + LinkVar(trait_inputs, "fitness_traits", "Which traits provide the fitness values to use?"); + LinkVar(epsilon, "epsilon", "Range from max value to be preserved? (fraction of max)"); + LinkVar(sample_traits, "sample_traits", "Number of test cases to use each generation (0=all)" ); + } + + void SetupModule() override { + // We should always have a minimal epsilon to handle mathematical imprecision of doubles. + if (epsilon <= 0.0) epsilon = 0.000000001; // One billionth. + + // All of the traits used are required to be generated by another module. + emp::vector trait_names = emp::slice(trait_inputs); + for (const std::string & name : trait_names) { + AddRequiredTrait>(name); + } + } + + void SetupDataMap(emp::DataMap & dmap) override { + trait_set.SetLayout(dmap.GetLayout()); ///< Give this trait set a layout to optimize. + trait_set.SetTraits(trait_inputs); ///< Parse set of trait inputs passed in. + } + }; MABE_REGISTER_MODULE(SelectLexicase, "Shuffle traits each time an organism is chose for replication."); From 07359f1f744f9c9c842cb2fdc8259e9658067e1d Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 4 Dec 2021 23:47:11 -0500 Subject: [PATCH 395/445] Reserved a series of keywords for Emplode. --- source/Emplode/Lexer.hpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/source/Emplode/Lexer.hpp b/source/Emplode/Lexer.hpp index aff54767..4797e1e1 100644 --- a/source/Emplode/Lexer.hpp +++ b/source/Emplode/Lexer.hpp @@ -17,11 +17,12 @@ namespace emplode { class Lexer : public emp::Lexer { private: - int token_identifier = -1; ///< Token id for identifiers - int token_number = -1; ///< Token id for literal numbers - int token_string = -1; ///< Token id for literal strings - int token_dots = -1; ///< Token id for a series of dots (...) - int token_symbol = -1; ///< Token id for other symbols + int token_keyword = -1; ///< Token id for "IF", "WHILE", and other keywords + int token_identifier = -1; ///< Token id for identifiers + int token_number = -1; ///< Token id for literal numbers + int token_string = -1; ///< Token id for literal strings + int token_dots = -1; ///< Token id for a series of dots (...) + int token_symbol = -1; ///< Token id for other symbols public: Lexer() { @@ -30,6 +31,14 @@ namespace emplode { IgnoreToken("//-Comments", "//.*"); IgnoreToken("/*...*/-Comments", "/[*]([^*]|([*]+[^*/]))*[*]+/"); + // Keywords have top priority, especially over identifiers. Most are simply reserved words. + token_keyword = AddToken("Keyword", + "(AND)|(AUTO)|(BREAK)|(CASE)|(CAST)|(CATCH)|(CLASS)|(CONST)|(CONTINUE)|(DEBUG)" + "|(DEFAULT)|(DEFINE)|(DELETE)|(DO)|(ELSE)|(EVENT)|(FALSE)|(FOR)|(FOREACH)" + "|(FUN)|(GOTO)|(IF)|(INCLUDE)|(MUTABLE)|(NAMESPACE)|(NEW)|(OR)|(PRIVATE)" + "|(PROTECTED)|(PUBLIC)|(RETURN)|(STATIC)|(SWITCH)|(TEMPLATE)|(THIS)" + "|(THROW)|(TRIGGER)|(TRUE)|(TRY)|(TYPE)|(UNION)|(USING)|(WHILE)|(YIELD)"); + // Meaningful tokens have next priority. token_identifier = AddToken("Identifier", "[a-zA-Z_][a-zA-Z0-9_]*"); token_number = AddToken("Literal Number", "[0-9]+(\\.[0-9]+)?"); From d1af09b328c9b2a75f520b58d7fea0c104fc050a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Dec 2021 23:47:09 -0500 Subject: [PATCH 396/445] Added IsKeyword to Lexer; separeted out implemented keywords. --- source/Emplode/Lexer.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Lexer.hpp b/source/Emplode/Lexer.hpp index 4797e1e1..e1021f1d 100644 --- a/source/Emplode/Lexer.hpp +++ b/source/Emplode/Lexer.hpp @@ -33,9 +33,11 @@ namespace emplode { // Keywords have top priority, especially over identifiers. Most are simply reserved words. token_keyword = AddToken("Keyword", - "(AND)|(AUTO)|(BREAK)|(CASE)|(CAST)|(CATCH)|(CLASS)|(CONST)|(CONTINUE)|(DEBUG)" - "|(DEFAULT)|(DEFINE)|(DELETE)|(DO)|(ELSE)|(EVENT)|(FALSE)|(FOR)|(FOREACH)" - "|(FUN)|(GOTO)|(IF)|(INCLUDE)|(MUTABLE)|(NAMESPACE)|(NEW)|(OR)|(PRIVATE)" + "(ELSE)|(IF)" + // Reserved keywords below. + "|(AND)|(AUTO)|(BREAK)|(CASE)|(CAST)|(CATCH)|(CLASS)|(CONST)|(CONTINUE)|(DEBUG)" + "|(DEFAULT)|(DEFINE)|(DELETE)|(DO)|(EVENT)|(FALSE)|(FOR)|(FOREACH)" + "|(FUN)|(GOTO)|(INCLUDE)|(MUTABLE)|(NAMESPACE)|(NEW)|(OR)|(PRIVATE)" "|(PROTECTED)|(PUBLIC)|(RETURN)|(STATIC)|(SWITCH)|(TEMPLATE)|(THIS)" "|(THROW)|(TRIGGER)|(TRUE)|(TRY)|(TYPE)|(UNION)|(USING)|(WHILE)|(YIELD)"); @@ -50,6 +52,7 @@ namespace emplode { token_symbol = AddToken("Symbol", ".|\"::\"|\"==\"|\"!=\"|\"<=\"|\">=\"|\"->\"|\"&&\"|\"||\"|\"<<\"|\">>\"|\"++\"|\"--\"|\"**\""); } + bool IsKeyword(const emp::Token token) const noexcept { return token.token_id == token_keyword; } bool IsID(const emp::Token token) const noexcept { return token.token_id == token_identifier; } bool IsNumber(const emp::Token token) const noexcept { return token.token_id == token_number; } bool IsString(const emp::Token token) const noexcept { return token.token_id == token_string; } From 7655906b867141cb4f80a0fa470a7065e00a0878 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Dec 2021 23:56:21 -0500 Subject: [PATCH 397/445] Added Parser::UseIfLexeme() helper; implemented IF/ELSE statements. --- source/Emplode/Parser.hpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 96235a16..eea04137 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -78,6 +78,7 @@ namespace emplode { "']"); } + bool IsKeyword() const { return pos && lexer->IsKeyword(*pos); } bool IsID() const { return pos && lexer->IsID(*pos); } bool IsNumber() const { return pos && lexer->IsNumber(*pos); } bool IsString() const { return pos && lexer->IsString(*pos); } @@ -102,6 +103,13 @@ namespace emplode { return out; } + /// Return whether the current token is the specified lexeme; if so also advance token stream. + bool UseIfLexeme(const std::string & test_str) { + if (AsLexeme() != test_str) return false; + ++pos; + return true; + } + /// Return whether the current token is the specified char; if so also advance token stream. bool UseIfChar(char test_char) { if (AsChar() != test_char) return false; @@ -549,6 +557,21 @@ namespace emplode { // Allow event definitions if a statement begins with an '@' if (state.AsChar() == '@') return ParseEvent(state); + // Allow select commands that are only possible at the full statement level (not expressions) + if (state.IsKeyword()) { + size_t keyword_line = state.GetLine(); + + if (state.UseIfLexeme("IF")) { + state.UseRequiredChar('(', "Expected '(' to begin IF test condition."); + emp::Ptr test_node = ParseExpression(state); + state.UseRequiredChar(')', "Expected ')' to end IF test condition."); + emp::Ptr true_node = ParseStatement(state); + emp::Ptr else_node = nullptr; + if (state.UseIfLexeme("ELSE")) else_node = ParseStatement(state); + return emp::NewPtr(test_node, true_node, else_node, keyword_line); + } + } + // Allow this statement to be a declaration if it begins with a type. if (state.IsType()) { Symbol & new_symbol = ParseDeclaration(state); From 9761fc966eb26c5e20c1e0d1df7f1a29feae6ea2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 5 Dec 2021 23:57:40 -0500 Subject: [PATCH 398/445] Added ASTNode_If --- source/Emplode/AST.hpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 6bf1097d..74291ff6 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -273,11 +273,45 @@ namespace emplode { if (rhs->IsTemporary()) rhs.Delete(); return lhs; } + }; + + class ASTNode_If : public ASTNode_Internal { + public: + ASTNode_If(node_ptr_t test, node_ptr_t true_node, node_ptr_t else_node, int _line=-1) { + AddChild(test); + AddChild(true_node); + if (else_node) AddChild(else_node); + line_id = _line; + } + + symbol_ptr_t Process() override { + symbol_ptr_t test = children[0]->Process(); // Determine the left-hand-side value. + + // Handle TRUE + if (test->AsDouble() != 0.0) { + symbol_ptr_t result = children[1]->Process(); + if (result && result->IsTemporary()) result.Delete(); + } + + // Handle FALSE + else if (children.size() > 2) { + symbol_ptr_t result = children[2]->Process(); + if (result && result->IsTemporary()) result.Delete(); + } + + if (test->IsTemporary()) test.Delete(); + return nullptr; + } void Write(std::ostream & os, const std::string & offset) const override { + os << "IF ("; children[0]->Write(os, offset); - os << " = "; + os << ") "; children[1]->Write(os, offset); + if (children.size() > 2) { + os << "\n" << offset << "ELSE "; + children[2]->Write(os, offset); + } } }; From cd26c6b87d70ccffb298e0814e8c6017259fa182 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 10:38:17 -0500 Subject: [PATCH 399/445] Removed IsBool(), IsInt(), and IsDouble from Symbol, leaving IsNumeric() --- source/Emplode/Symbol.hpp | 7 ------- source/Emplode/Symbol_Linked.hpp | 6 ------ 2 files changed, 13 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index df719fe3..abf265e9 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -102,9 +102,6 @@ namespace emplode { virtual std::string GetTypename() const = 0; ///< Derived classes must provide type info. virtual bool IsNumeric() const { return false; } ///< Is symbol any kind of number? - virtual bool IsBool() const { return false; } ///< Is symbol a Boolean value? - virtual bool IsDouble() const { return false; } ///< Is symbol a floting point value? - virtual bool IsInt() const { return false; } ///< Is symbol a integer value? virtual bool IsString() const { return false; } ///< Is symbol a string? virtual bool IsError() const { return false; } ///< Does symbol flag an error? @@ -337,10 +334,6 @@ namespace emplode { } bool IsNumeric() const override { return std::is_scalar_v; } - bool IsBool() const override { return std::is_same(); } - bool IsInt() const override { return std::is_same(); } - bool IsDouble() const override { return std::is_same(); } - bool IsLocal() const override { return true; } bool CopyValue(const Symbol & in) override { SetValue(in.AsDouble()); return true; } diff --git a/source/Emplode/Symbol_Linked.hpp b/source/Emplode/Symbol_Linked.hpp index d561364a..f062d6c2 100644 --- a/source/Emplode/Symbol_Linked.hpp +++ b/source/Emplode/Symbol_Linked.hpp @@ -46,9 +46,6 @@ namespace emplode { } bool IsNumeric() const override { return std::is_scalar_v; } - bool IsBool() const override { return std::is_same(); } - bool IsInt() const override { return std::is_same(); } - bool IsDouble() const override { return std::is_same(); } bool CopyValue(const Symbol & in) override { var = in.AsDouble(); return true; } }; @@ -114,9 +111,6 @@ namespace emplode { } bool IsNumeric() const override { return std::is_scalar_v; } - bool IsBool() const override { return std::is_same(); } - bool IsInt() const override { return std::is_same(); } - bool IsDouble() const override { return std::is_same(); } bool IsString() const override { return std::is_same(); } bool CopyValue(const Symbol & in) override { SetString( in.AsString() ); return true; } From cdd0e73b7aa3221a5040a3c8f19fe0f47ba82bdf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 11:10:23 -0500 Subject: [PATCH 400/445] Removed template from Symbol_Var; now tracks just double and std::string --- source/Emplode/Symbol.hpp | 91 ++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index abf265e9..c2db7e50 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -300,74 +300,57 @@ namespace emplode { }; - /// A generic version of a symbol for an internally maintained variable. - template + /// A symbol for an internally maintained variable. class Symbol_Var : public Symbol { private: - T value = 0; + double num_value = 0.0; + std::string str_value = ""; + bool is_num = true; public: - static_assert(std::is_arithmetic(), "Symbol_Var must use std::string or arithmetic values."); - - using this_t = Symbol_Var; - - template Symbol_Var(const std::string & in_name, - T default_val, - const std::string & in_desc="", - emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope), value(default_val) { ; } - Symbol_Var(const Symbol_Var &) = default; - - std::string GetTypename() const override { - if constexpr (std::is_scalar_v) return "Value"; - else return "Illegal type as Symbol_Var"; - } + double default_val, + const std::string & in_desc="", + emp::Ptr in_scope=nullptr) + : Symbol(in_name, in_desc, in_scope), num_value(default_val), is_num(true) { ; } + Symbol_Var(const std::string & in_name, + const std::string & default_val, + const std::string & in_desc="", + emp::Ptr in_scope=nullptr) + : Symbol(in_name, in_desc, in_scope), str_value(default_val), is_num(false) { ; } + Symbol_Var(const Symbol_Var &) = default; - symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } + std::string GetTypename() const override { return "Var"; } - double AsDouble() const override { return (double) value; } - std::string AsString() const override { return emp::to_string(value); } - Symbol & SetValue(double in) override { value = (T) in; return *this; } + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } + + double AsDouble() const override { + return is_num ? num_value : emp::from_string(str_value); + } + std::string AsString() const override { + return is_num ? emp::to_string(num_value) : str_value; + } + Symbol & SetValue(double in) override { + num_value = in; + is_num = true; + return *this; + } Symbol & SetString(const std::string & in) override { - value = emp::from_string(in); + str_value = in; + is_num = false; return *this; } - bool IsNumeric() const override { return std::is_scalar_v; } + bool IsNumeric() const override { return is_num; } + bool IsString() const override { return !is_num; } bool IsLocal() const override { return true; } - bool CopyValue(const Symbol & in) override { SetValue(in.AsDouble()); return true; } + bool CopyValue(const Symbol & in) override { + if (in.IsNumeric()) SetValue(in.AsDouble()); + else SetString(in.AsString()); + return true; + } }; - using Symbol_DoubleVar = Symbol_Var; - /// Symbol as a temporary variable of type STRING. - template<> - class Symbol_Var : public Symbol { - private: - std::string value; - public: - using this_t = Symbol_Var; - - template - Symbol_Var(const std::string & in_name, const std::string & in_val, ARGS &&... args) - : Symbol(in_name, std::forward(args)...), value(in_val) { ; } - Symbol_Var(const Symbol_Var &) = default; - - std::string GetTypename() const override { return "String"; } - - symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } - - double AsDouble() const override { return emp::from_string(value); } - std::string AsString() const override { return value; } - Symbol & SetValue(double in) override { value = emp::to_string(in); return *this; } - Symbol & SetString(const std::string & in) override { value = in; return *this; } - - bool IsString() const override { return true; } - bool IsLocal() const override { return true; } - - bool CopyValue(const Symbol & in) override { value = in.AsString(); return true; } - }; - using Symbol_StringVar = Symbol_Var; /// A Symbol to transmit an error due to invalid parsing. /// The description provides the error and the IsError() flag is set to true. From 3471d1ad3d8873858f665118ef582237631ea74b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 11:11:00 -0500 Subject: [PATCH 401/445] Setup Parser to use Var for local variables, not separate String and Value. --- source/Emplode/Parser.hpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index eea04137..8f226070 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -170,11 +170,8 @@ namespace emplode { return *out_symbol; } - Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { - return GetScope().AddStringVar(name, desc); - } - Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { - return GetScope().AddValueVar(name, desc); + Symbol_Var & AddLocalVar(const std::string & name, const std::string & desc) { + return GetScope().AddLocalVar(name, desc); } Symbol_Scope & AddScope(const std::string & name, const std::string & desc) { return GetScope().AddScope(name, desc); @@ -491,15 +488,8 @@ namespace emplode { state.RequireID("Type name '", type_name, "' must be followed by variable to declare."); std::string var_name = state.UseLexeme(); - if (type_name == "String") { - return state.AddStringVar(var_name, "Local string variable."); - } - else if (type_name == "Value") { - return state.AddValueVar(var_name, "Local value variable."); - } - else if (type_name == "Struct") { - return state.AddScope(var_name, "Local struct"); - } + if (type_name == "Var") return state.AddLocalVar(var_name, "Local variable."); + else if (type_name == "Struct") return state.AddScope(var_name, "Local struct"); // Otherwise we have an object of a custom type to add. Debug("Building object '", var_name, "' of type '", type_name, "'"); From b1d21dc21e03fc3513fe5cfd695fc84a2ee354dc Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 11:12:07 -0500 Subject: [PATCH 402/445] Setup Scopes, AST, and SymbolTable to recognize unified local Var type. --- source/Emplode/AST.hpp | 4 ++-- source/Emplode/SymbolTable.hpp | 9 ++++----- source/Emplode/SymbolTableBase.hpp | 2 +- source/Emplode/Symbol_Scope.hpp | 9 ++------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 74291ff6..55545549 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -135,13 +135,13 @@ namespace emplode { // Helper functions for making temporary leaves. emp::Ptr MakeTempLeaf(double val) { - auto out_ptr = emp::NewPtr("", val, "Temporary double", nullptr); + auto out_ptr = emp::NewPtr("__Temp", val, "Temporary double", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); } emp::Ptr MakeTempLeaf(const std::string & val) { - auto out_ptr = emp::NewPtr("", val, "Temporary string", nullptr); + auto out_ptr = emp::NewPtr("__Temp", val, "Temporary string", nullptr); out_ptr->SetTemporary(); return emp::NewPtr(out_ptr); } diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index e4bdbc53..b7b38d33 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -42,14 +42,13 @@ namespace emplode { // Initialize the type map. type_map["INVALID"] = emp::NewPtr( *this, 0, "/*ERROR*/", "Error, Invalid type!" ); type_map["Void"] = emp::NewPtr( *this, 1, "Void", "Non-type variable; no value" ); - type_map["Value"] = emp::NewPtr( *this, 2, "Value", "Numeric variable" ); - type_map["String"] = emp::NewPtr( *this, 3, "String", "String variable" ); - type_map["Struct"] = emp::NewPtr( *this, 4, "Struct", "User-made structure" ); + type_map["Var"] = emp::NewPtr( *this, 2, "Var", "Numeric or String variable" ); + type_map["Struct"] = emp::NewPtr( *this, 3, "Struct", "User-made structure" ); // Those types typeid_map[emp::GetTypeID()] = type_map["Void"]; - typeid_map[emp::GetTypeID()] = type_map["Value"]; - typeid_map[emp::GetTypeID()] = type_map["String"]; + typeid_map[emp::GetTypeID()] = type_map["Var"]; + typeid_map[emp::GetTypeID()] = type_map["Var"]; file_map.SetOutputDefaultFile(); // Stream manager should default to 'file' output. } diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index 09f69c98..93cf253e 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -43,7 +43,7 @@ namespace emplode { if constexpr (std::is_base_of()) { return MakeTempObjSymbol(emp::GetTypeID(), &value); } else { - auto out_symbol = emp::NewPtr>("__Temp", value, "", nullptr); + auto out_symbol = emp::NewPtr("__Temp", value, "", nullptr); out_symbol->SetTemporary(); return out_symbol; } diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp index 17fe6cec..be54b60b 100644 --- a/source/Emplode/Symbol_Scope.hpp +++ b/source/Emplode/Symbol_Scope.hpp @@ -166,13 +166,8 @@ namespace emplode { } /// Add an internal variable of type String. - Symbol_StringVar & AddStringVar(const std::string & name, const std::string & desc) { - return Add(name, "", desc, this); - } - - /// Add an internal variable of type Value. - Symbol_DoubleVar & AddValueVar(const std::string & name, const std::string & desc) { - return Add(name, 0.0, desc, this); + Symbol_Var & AddLocalVar(const std::string & name, const std::string & desc) { + return Add(name, 0.0, desc, this); } /// Add an internal scope inside of this one. From ca1c52d328fbb2d42f1994ff46c02e7e1b347edf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 11:32:38 -0500 Subject: [PATCH 403/445] Comment cleanup in Emplode. --- source/Emplode/Emplode.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index fb9d52a3..8263efcf 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -290,12 +290,12 @@ namespace emplode { // Load the provided statement and run it. std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { - if (!scope) scope = &symbol_table.GetRootScope(); // Default scope to root level. + if (!scope) scope = &symbol_table.GetRootScope(); // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. - tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. - pos_t pos = tokens.begin(); // Start are beginning of stream. + tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. + pos_t pos = tokens.begin(); // Start are beginning of stream. ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; - auto cur_expr = parser.ParseStatement(state); // Convert tokens to AST + auto cur_expr = parser.ParseStatement(state); // Convert tokens to AST // Now place the expression in a temporary block. auto cur_block = emp::NewPtr(symbol_table.GetRootScope(), 0); From 7467faff5da0a1aa3f989574c5490e5e66d8b6d7 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 13:58:21 -0500 Subject: [PATCH 404/445] Rejuggled Parser to that declarations are tied more closely to expressions. --- source/Emplode/Parser.hpp | 71 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 8f226070..9cdae9fb 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -229,7 +229,12 @@ namespace emplode { emp::Ptr value2); /// Calculate a full expression found in a token sequence, using the provided scope. - [[nodiscard]] emp::Ptr ParseExpression(ParseState & state, size_t prec_limit=1000); + /// @param state The current start of the parser and input stream + /// @param decl_ok Can this expression begin with a declaration of a variable? + /// @param prec_limit What is the highest precedence that expression should process? + [[nodiscard]] emp::Ptr ParseExpression(ParseState & state, + bool decl_ok=false, + size_t prec_limit=1000); /// Parse the declaration of a variable and return the newly created Symbol Symbol & ParseDeclaration(ParseState & state); @@ -435,11 +440,33 @@ namespace emplode { } - // Calculate an expression in the provided scope. - emp::Ptr Parser::ParseExpression(ParseState & state, size_t prec_limit) { - Debug("Running ParseExpression(", state.AsString(), ", limit=", prec_limit, ")"); + /// Calculate a full expression found in a token sequence, using the provided scope. + /// @param state The current start of the parser and input stream + /// @param decl_ok Can this expression begin with a declaration of a variable? + /// @param prec_limit What is the highest precedence that expression should process? - // @CAO Should test for unary operators at the beginning of an expression. + emp::Ptr Parser::ParseExpression(ParseState & state, bool decl_ok, size_t prec_limit) { + Debug("Running ParseExpression(", state.AsString(), ", decl_ok=", decl_ok, ", limit=", prec_limit, ")"); + + // Allow this statement to be a declaration if it begins with a type. + if (decl_ok && state.IsType()) { + Symbol & new_symbol = ParseDeclaration(state); + + // If this symbol is a new scope, it can be populated now either directly (in braces) + // or indirectly (with an assignment) + if (new_symbol.IsScope()) { + if (state.UseIfChar('{')) { + state.PushScope(new_symbol.AsScope()); + emp::Ptr out_node = ParseStatementList(state); + state.PopScope(); + state.UseRequiredChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); + return out_node; + } + } + + // Otherwise rewind so that the new variable can be used to start an expression. + --state; + } /// Process a value (and possibly more!) emp::Ptr cur_node = ParseValue(state); @@ -469,7 +496,7 @@ namespace emplode { // Otherwise we must have a binary math operation. else { - emp::Ptr node2 = ParseExpression(state, precedence_map[op]); + emp::Ptr node2 = ParseExpression(state, false, precedence_map[op]); cur_node = ProcessOperation(op_token, cur_node, node2); } @@ -562,36 +589,8 @@ namespace emplode { } } - // Allow this statement to be a declaration if it begins with a type. - if (state.IsType()) { - Symbol & new_symbol = ParseDeclaration(state); - - // If the next symbol is a ';' this is a declaration without an assignment. - if (state.UseIfChar(';')) return nullptr; // We are done! - - // If this symbol is a new scope, it can be populated now either directly (with in braces) - // or indirectly (with and assignment) - if (new_symbol.IsScope()) { - if (state.UseIfChar('{')) { - state.PushScope(new_symbol.AsScope()); - emp::Ptr out_node = ParseStatementList(state); - state.PopScope(); - state.UseRequiredChar('}', "Expected scope '", new_symbol.GetName(), "' to end with a '}'."); - return out_node; - } - - state.RequireChar('=', "Expected scope '", new_symbol.GetName(), - "' definition to start with a '{' or '='; found ''", state.AsLexeme(), "'."); - - } - - // Otherwise rewind so that the new variable can be used to start an expression. - --state; - } - - - // If we made it here, remainder should be an expression. - emp::Ptr out_node = ParseExpression(state); + // If we made it here, remainder should be an expression; it may begin with a declaration. + emp::Ptr out_node = ParseExpression(state, true); // Expressions must end in a semi-colon. state.UseRequiredChar(';', "Expected ';' at the end of a statement; found: ", state.AsLexeme()); From ce1c9930c01ad999fe12b36e26eb6ec6ffc41b4e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 6 Dec 2021 15:34:52 -0500 Subject: [PATCH 405/445] Moved processing of keyword-led statements into Parser::ParseKeywordStatement() --- source/Emplode/Parser.hpp | 46 ++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index 9cdae9fb..c711b62e 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -242,6 +242,9 @@ namespace emplode { /// Parse an event description. emp::Ptr ParseEvent(ParseState & state); + /// Parse a specialty keyword statement (such as IF, WHILE, etc) + emp::Ptr ParseKeywordStatement(ParseState & state); + /// Parse the next input in the specified Struct. A statement can be a variable declaration, /// an expression, or an event. [[nodiscard]] emp::Ptr ParseStatement(ParseState & state); @@ -556,6 +559,35 @@ namespace emplode { return emp::NewPtr(event_name, action, args, setup_event, start_token.line_id); } + /// Parse a specialty keyword statement (such as IF, WHILE, etc) + emp::Ptr Parser::ParseKeywordStatement(ParseState & state) { + size_t keyword_line = state.GetLine(); + + if (state.UseIfLexeme("IF")) { + state.UseRequiredChar('(', "Expected '(' to begin IF test condition."); + emp::Ptr test_node = ParseExpression(state); + state.UseRequiredChar(')', "Expected ')' to end IF test condition."); + emp::Ptr true_node = ParseStatement(state); + emp::Ptr else_node = nullptr; + if (state.UseIfLexeme("ELSE")) else_node = ParseStatement(state); + return emp::NewPtr(test_node, true_node, else_node, keyword_line); + } + + + // If we made it this far, we have an error. Identify and deal with it! + + if (state.UseIfLexeme("ELSE")) { + state.Error("'ELSE' must be preceded by an 'IF' statement."); + } + + // Unimplemented keyword! + else { + state.Error("Keyword '", state.AsLexeme(), "' not yet implemented."); + } + + return nullptr; + } + // Process the next input in the specified Struct. emp::Ptr Parser::ParseStatement(ParseState & state) { Debug("Running ParseStatement(", state.AsString(), ")"); @@ -575,19 +607,7 @@ namespace emplode { if (state.AsChar() == '@') return ParseEvent(state); // Allow select commands that are only possible at the full statement level (not expressions) - if (state.IsKeyword()) { - size_t keyword_line = state.GetLine(); - - if (state.UseIfLexeme("IF")) { - state.UseRequiredChar('(', "Expected '(' to begin IF test condition."); - emp::Ptr test_node = ParseExpression(state); - state.UseRequiredChar(')', "Expected ')' to end IF test condition."); - emp::Ptr true_node = ParseStatement(state); - emp::Ptr else_node = nullptr; - if (state.UseIfLexeme("ELSE")) else_node = ParseStatement(state); - return emp::NewPtr(test_node, true_node, else_node, keyword_line); - } - } + if (state.IsKeyword()) return ParseKeywordStatement(state); // If we made it here, remainder should be an expression; it may begin with a declaration. emp::Ptr out_node = ParseExpression(state, true); From a032e8d23ce54977b70f13214a742557bb16e4c1 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 7 Dec 2021 13:55:21 -0500 Subject: [PATCH 406/445] Renamed ConvertReturn() to more generic ValueToSymbol() --- source/Emplode/SymbolTableBase.hpp | 38 +++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index 93cf253e..6c1cf082 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -60,23 +60,19 @@ namespace emplode { }; } - template - decltype(auto) ConvertReturn( const std::string & fun_name, RETURN_T && return_value ) { - constexpr bool is_ref = std::is_lvalue_reference(); - using base_t = std::remove_reference_t; - - // if (fun_name == "INJECT") { - // emp_debug("INJECT! Return type=", emp::GetTypeID()); - // } + template + decltype(auto) ValueToSymbol( T && return_value, const std::string & location ) { + constexpr bool is_ref = std::is_lvalue_reference(); + using base_t = std::remove_reference_t; // If a return value is already a symbol pointer, just pass it through. - if constexpr (std::is_same()) { + if constexpr (std::is_same()) { return return_value; } // If a return value is a basic type, wrap it in a temporary symbol - else if constexpr (std::is_same() || - std::is_arithmetic()) { + else if constexpr (std::is_same() || + std::is_arithmetic()) { return MakeTempSymbol(return_value); } @@ -92,8 +88,8 @@ namespace emplode { // For now these are the only legal return type; raise error otherwise! else { - std::cerr << "Failed to convert return type for function " << fun_name << std::endl; - static_assert(emp::dependent_false(), + std::cerr << "Failed to convert return type in " << location << std::endl; + static_assert(emp::dependent_false(), "Invalid return value in Symbol_Function::SetFunction()"); } } @@ -109,7 +105,7 @@ namespace emplode { static auto ConvertFun([[maybe_unused]] const std::string & name, FUN_T fun, SymbolTableBase & st) { return [name=name,fun=fun,&st]([[maybe_unused]] const symbol_vector_t & args) { emp_assert(args.size() == 0, "Too many arguments (expected 0)", name, args.size()); - return st.ConvertReturn( name, fun() ); + return st.ValueToSymbol( fun(), name ); }; } @@ -129,7 +125,7 @@ namespace emplode { // just pass it along. if constexpr (sizeof...(PARAM_Ts) == 0 && std::is_same_v) { - return st.ConvertReturn( name, fun(args) ); + return st.ValueToSymbol( fun(args), name ); } // Otherwise make sure we have the correct arguments. @@ -143,9 +139,9 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( - name, - fun(args[0]->As(), args[INDEX_VALS+1]->template As()...) + return st.ValueToSymbol( + fun(args[0]->As(), args[INDEX_VALS+1]->template As()...), + name ); } }; @@ -177,14 +173,14 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( name, fun(*typed_ptr) ); + return st.ValueToSymbol( fun(*typed_ptr), name ); } // If this function already takes a const symbol_vector_t & as its only extra parameter, // just pass it along. else if constexpr (sizeof...(PARAM_Ts) == 1 && std::is_same_v, const symbol_vector_t &>) { - return st.ConvertReturn( name, fun(*typed_ptr, args) ); + return st.ValueToSymbol( fun(*typed_ptr, args), name ); } // Otherwise make sure we have the correct arguments. @@ -198,7 +194,7 @@ namespace emplode { } //@CAO should collect file position information for the above errors. - return st.ConvertReturn( name, fun(*typed_ptr, args[INDEX_VALS]->template As()...) ); + return st.ValueToSymbol( fun(*typed_ptr, args[INDEX_VALS]->template As()...), name ); } }; } From 0220eb62a4b146b5257df9ad25ee94072b51efce Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 15:07:14 -0500 Subject: [PATCH 407/445] Started rebuilding Events.hpp and EventManager.hpp --- source/Emplode/EventManager.hpp | 175 ++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 source/Emplode/EventManager.hpp diff --git a/source/Emplode/EventManager.hpp b/source/Emplode/EventManager.hpp new file mode 100644 index 00000000..39c44323 --- /dev/null +++ b/source/Emplode/EventManager.hpp @@ -0,0 +1,175 @@ +/** + * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2019-2021. + * + * @file EventManager.hpp + * @brief Manages events for configurations. + * @note Status: BETA + * + * Manages different sets of events that can be triggered. + * + * An EVENT is a set of actions to be executed when an associated signal is triggered. + * + * An ACTION is an AST tree to be executed, possibly with parameters. + * + * A SIGNAL has a string (identifier) and is associated with a set of zero or more + * actions to take when triggered. + * + * A event TRIGGER occurs to signify an event in a run (such as a new update or a + * collision); it specifies the signal that it is triggering and a set of associated + * data (to provide args to the actions) + * + */ + +#ifndef EMPLODE_EVENT_MANAGER_HPP +#define EMPLODE_EVENT_MANAGER_HPP + +#include + +#include "emp/base/map.hpp" +#include "emp/base/Ptr.hpp" + +#include "AST.hpp" + +namespace emplode { + + class EventManager { + private: + using symbol_ptr_t = emp::Ptr; + using symbol_vec_t = emp::vector; + using node_ptr_t = emp::Ptr; + using node_vec_t = emp::vector< node_ptr_t >; + class Event; + + std::unordered_map> event_map; + SymbolTableBase & symbol_table; + + struct Action { + std::string signal_name; + node_vec_t params; + node_ptr_t action; + size_t def_line; + + Action(const std::string & _signal, node_vec_t _params, node_ptr_t _action, size_t _line) + : signal_name(_signal), params(_params), action(_action), def_line(_line) { } + ~Action() { } + + void Trigger(node_vec_t args, size_t trigger_line) { + if (args.size() < params.size()) { + std::cerr << "ERROR (line " << trigger_line << "): Trigger for signal '" << signal_name + << "' (defined on " << def_line << ") called with " << args.size() + << " arguments, but " << params.size() << " parameters need values." + << std::endl; + exit(1); + } + + // Setup all of the parameters. + for (size_t param_id = 0; param_id < params.size(); ++param_id) { + symbol_ptr_t param_sym = params[param_id]->Process(); + symbol_ptr_t arg_sym = args[param_id]->Process(); + + if (param_sym->IsTemporary()) { + std::cerr << "ERROR (line " << def_line << "): parameter " << param_id + << " is invalid; not a proper lvalue." << std::endl; + exit(1); + } + + bool success = param_sym->CopyValue(*arg_sym); + if (!success) { + std::cerr << "ERROR (line " << trigger_line << "): setting action parameter '" + << param_sym->GetName() << "' failed" << std::endl; + exit(1); + } + + if (arg_sym->IsTemporary()) arg_sym.Delete(); // If we are done with arg; delete! + } + + // Once all of the parameter values are in place, run the action! + symbol_ptr_t result = action.Process(); + if (result && result->IsTemporary()) result.Delete(); + } + + void Write(const std::string & command, std::ostream & os) const { + os << "@" << signal_name << "("; + // @CAO: Write out parameters... + os << ") "; + ast_action->Write(os); + os << ";\n"; + } + }; + + struct Event { + std::string signal_name; + size_t num_params; + emp::vector> actions; + + Event(const std::string & _name) : signal_name(_name) { } + ~Event() { for (auto ptr : actions) ptr.Delete(); } + + void Trigger(node_vec_t args, size_t trigger_line) { + for (emp::Ptr action : actions) { + action->Trigger(args, trigger_line); + } + } + }; + + public: + EventManager(SymbolTableBase & _s_table) : symbol_table(_s_table) { ; } + ~EventManager() { + // Must delete all events in the queue. + for (auto [name, ptr] : event_map) { + ptr.Delete(); + } + } + + bool AddSignal(const std::string & signal_name, size_t num_params) { + if (emp::Has(event_map, signal_name)) { + // @CAO: Report an error! + return false; + } + + event_map[signal_name] = emp::NewPtr(signal_name, num_params); + + return true; + } + + /// Add a new event action + bool AddAction( + const std::string & signal_name, ///< Name of signal to trigger using + node_vec_t params, ///< Parameters to set before taking action + node_ptr_t action, ///< Abstract syntax tree to run when triggered + size_t def_line ///< What file line was this defined on? + ) { + // @CAO Needs to become a user-level error! + emp_assert(emp::Has(event_map, signal_name), "Unknown signal used!", signal_name); + + auto action_ptr = emp::NewPtr(signal_name, params, action, def_line); + event_map[signal_name].actions.push_back(action_ptr); + + return true; + } + + template + bool Trigger(const std::string & signal_name, size_t trigger_line, ARG_TS... args) { + // @CAO Make into user-level error. + emp_assert(emp::Has(event_map, signal_name), "Unknown signal being triggered!", signal_name); + + symbol_vec_t symbol_args = { symbol_table.ValueToSymbol(args)... }; + event_map[signal_name]->Trigger(symbol_args, trigger_line); + + return true; + } + + /// Print all of the events being tracked here. + void Write(const std::string & command, std::ostream & os) const { + for (const auto & x : queue) { + x.second->Write(command, os); + } + } + }; + + +} + +#endif From 12b91f2f82c8570d77ec40f1cd7de6d48a2ba999 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:52:31 -0500 Subject: [PATCH 408/445] More updates on Emplode keywords. --- source/Emplode/Lexer.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Lexer.hpp b/source/Emplode/Lexer.hpp index e1021f1d..ff9e00a1 100644 --- a/source/Emplode/Lexer.hpp +++ b/source/Emplode/Lexer.hpp @@ -36,9 +36,9 @@ namespace emplode { "(ELSE)|(IF)" // Reserved keywords below. "|(AND)|(AUTO)|(BREAK)|(CASE)|(CAST)|(CATCH)|(CLASS)|(CONST)|(CONTINUE)|(DEBUG)" - "|(DEFAULT)|(DEFINE)|(DELETE)|(DO)|(EVENT)|(FALSE)|(FOR)|(FOREACH)" - "|(FUN)|(GOTO)|(INCLUDE)|(MUTABLE)|(NAMESPACE)|(NEW)|(OR)|(PRIVATE)" - "|(PROTECTED)|(PUBLIC)|(RETURN)|(STATIC)|(SWITCH)|(TEMPLATE)|(THIS)" + "|(DEFAULT)|(DEFINE)|(DELETE)|(DO)|(EVENT)|(EVERY)|(FALSE)|(FOR)|(FOREACH)" + "|(FUNCTION)|(GOTO)|(IN)|(INCLUDE)|(MUTABLE)|(NAMESPACE)|(NEW)|(OR)|(PRIVATE)" + "|(PROTECTED)|(PUBLIC)|(RETURN)|(SIGNAL)|(STATIC)|(SWITCH)|(TEMPLATE)|(THIS)" "|(THROW)|(TRIGGER)|(TRUE)|(TRY)|(TYPE)|(UNION)|(USING)|(WHILE)|(YIELD)"); // Meaningful tokens have next priority. From b30bd47d7f751de6dcd31a09fe3496eb7f8d1b43 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:53:01 -0500 Subject: [PATCH 409/445] Cleanup on ValueToSymbol --- source/Emplode/SymbolTableBase.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index 6c1cf082..e1a9804c 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -61,36 +61,36 @@ namespace emplode { } template - decltype(auto) ValueToSymbol( T && return_value, const std::string & location ) { + decltype(auto) ValueToSymbol( T && value, const std::string & location ) { constexpr bool is_ref = std::is_lvalue_reference(); using base_t = std::remove_reference_t; // If a return value is already a symbol pointer, just pass it through. if constexpr (std::is_same()) { - return return_value; + return value; } // If a return value is a basic type, wrap it in a temporary symbol - else if constexpr (std::is_same() || - std::is_arithmetic()) { - return MakeTempSymbol(return_value); + else if constexpr (std::is_same() || + std::is_arithmetic()) { + return MakeTempSymbol(value); } - // If a return value is a REFERENCE to an Emplode type, return its Symbol_Object. + // If a value is a REFERENCE to an Emplode type, return its Symbol_Object. else if constexpr (is_ref && std::is_base_of()) { - return return_value.AsScope().AsObject(); + return value.AsScope().AsObject(); } // If a return value is an Emplode type VALUE, build a temporary symbol for it. else if constexpr (!is_ref && std::is_base_of()) { - return MakeTempSymbol(return_value); + return MakeTempSymbol(value); } // For now these are the only legal return type; raise error otherwise! else { std::cerr << "Failed to convert return type in " << location << std::endl; static_assert(emp::dependent_false(), - "Invalid return value in Symbol_Function::SetFunction()"); + "Invalid conversion of value to emplode::Symbol"); } } From 5c4878b179b2e473f1ad4a70222236c78de6d19e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:55:12 -0500 Subject: [PATCH 410/445] Cleanup throughout EventManager; now compiles. --- source/Emplode/EventManager.hpp | 67 +++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/source/Emplode/EventManager.hpp b/source/Emplode/EventManager.hpp index 39c44323..248716db 100644 --- a/source/Emplode/EventManager.hpp +++ b/source/Emplode/EventManager.hpp @@ -40,7 +40,7 @@ namespace emplode { using symbol_vec_t = emp::vector; using node_ptr_t = emp::Ptr; using node_vec_t = emp::vector< node_ptr_t >; - class Event; + struct Event; std::unordered_map> event_map; SymbolTableBase & symbol_table; @@ -55,9 +55,9 @@ namespace emplode { : signal_name(_signal), params(_params), action(_action), def_line(_line) { } ~Action() { } - void Trigger(node_vec_t args, size_t trigger_line) { + void Trigger(const symbol_vec_t & args) { if (args.size() < params.size()) { - std::cerr << "ERROR (line " << trigger_line << "): Trigger for signal '" << signal_name + std::cerr << "ERROR: Trigger for signal '" << signal_name << "' (defined on " << def_line << ") called with " << args.size() << " arguments, but " << params.size() << " parameters need values." << std::endl; @@ -67,7 +67,6 @@ namespace emplode { // Setup all of the parameters. for (size_t param_id = 0; param_id < params.size(); ++param_id) { symbol_ptr_t param_sym = params[param_id]->Process(); - symbol_ptr_t arg_sym = args[param_id]->Process(); if (param_sym->IsTemporary()) { std::cerr << "ERROR (line " << def_line << "): parameter " << param_id @@ -75,26 +74,24 @@ namespace emplode { exit(1); } - bool success = param_sym->CopyValue(*arg_sym); + bool success = param_sym->CopyValue(*args[param_id]); if (!success) { - std::cerr << "ERROR (line " << trigger_line << "): setting action parameter '" + std::cerr << "ERROR: setting action parameter '" << param_sym->GetName() << "' failed" << std::endl; exit(1); } - - if (arg_sym->IsTemporary()) arg_sym.Delete(); // If we are done with arg; delete! } // Once all of the parameter values are in place, run the action! - symbol_ptr_t result = action.Process(); + symbol_ptr_t result = action->Process(); if (result && result->IsTemporary()) result.Delete(); } - void Write(const std::string & command, std::ostream & os) const { + void Write(std::ostream & os) const { os << "@" << signal_name << "("; // @CAO: Write out parameters... os << ") "; - ast_action->Write(os); + action->Write(os); os << ";\n"; } }; @@ -104,14 +101,22 @@ namespace emplode { size_t num_params; emp::vector> actions; - Event(const std::string & _name) : signal_name(_name) { } + Event(const std::string & _name, size_t _params) + : signal_name(_name), num_params(_params) { } ~Event() { for (auto ptr : actions) ptr.Delete(); } - void Trigger(node_vec_t args, size_t trigger_line) { + void Trigger(symbol_vec_t args) { for (emp::Ptr action : actions) { - action->Trigger(args, trigger_line); + action->Trigger(args); } } + + void Write(std::ostream & os) const { + for (emp::Ptr action : actions) { + action->Write(os); + } + } + }; public: @@ -123,11 +128,15 @@ namespace emplode { } } + bool HasSignal(const std::string & signal_name) const { + return emp::Has(event_map, signal_name); + } + bool AddSignal(const std::string & signal_name, size_t num_params) { - if (emp::Has(event_map, signal_name)) { - // @CAO: Report an error! - return false; - } + std::cerr << "DEBUG: Adding new signal '" << signal_name << "'." << std::endl; + + // @CAO Needs to become a user-level error! + emp_assert(!emp::Has(event_map, signal_name), "Signal reused!", signal_name); event_map[signal_name] = emp::NewPtr(signal_name, num_params); @@ -141,30 +150,38 @@ namespace emplode { node_ptr_t action, ///< Abstract syntax tree to run when triggered size_t def_line ///< What file line was this defined on? ) { + std::cerr << "DEBUG: Adding an action onto '" << signal_name << "'." << std::endl; + // @CAO Needs to become a user-level error! emp_assert(emp::Has(event_map, signal_name), "Unknown signal used!", signal_name); auto action_ptr = emp::NewPtr(signal_name, params, action, def_line); - event_map[signal_name].actions.push_back(action_ptr); + event_map[signal_name]->actions.push_back(action_ptr); return true; } template - bool Trigger(const std::string & signal_name, size_t trigger_line, ARG_TS... args) { + bool Trigger(const std::string & signal_name, ARG_TS... args) { // @CAO Make into user-level error. emp_assert(emp::Has(event_map, signal_name), "Unknown signal being triggered!", signal_name); - symbol_vec_t symbol_args = { symbol_table.ValueToSymbol(args)... }; - event_map[signal_name]->Trigger(symbol_args, trigger_line); + const std::string location = emp::to_string("trigger of ", signal_name); + symbol_vec_t symbol_args = { symbol_table.ValueToSymbol(args, location)... }; + event_map[signal_name]->Trigger(symbol_args); + + // Now that all of the actions have been run, clean up the symbol_args. + for (auto symbol_ptr : symbol_args) { + if (symbol_ptr->IsTemporary()) symbol_ptr.Delete(); + } return true; } /// Print all of the events being tracked here. - void Write(const std::string & command, std::ostream & os) const { - for (const auto & x : queue) { - x.second->Write(command, os); + void Write(std::ostream & os) const { + for (auto [name, ptr] : event_map) { + ptr->Write(os); } } }; From 9b81017e75d402c84160246b8fbc99fb62b41c4c Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:56:04 -0500 Subject: [PATCH 411/445] Shifted SymbolTable to use new EventManager. --- source/Emplode/SymbolTable.hpp | 45 ++++++++++++++-------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp index b7b38d33..a536a431 100644 --- a/source/Emplode/SymbolTable.hpp +++ b/source/Emplode/SymbolTable.hpp @@ -21,7 +21,7 @@ #include "emp/io/StreamManager.hpp" #include "emp/meta/TypeID.hpp" -#include "Events.hpp" +#include "EventManager.hpp" #include "Symbol_Scope.hpp" #include "SymbolTableBase.hpp" @@ -31,14 +31,14 @@ namespace emplode { class SymbolTable : public SymbolTableBase { protected: Symbol_Scope root_scope; ///< Outermost (global) scope. - std::map events_map; ///< Events, lookup by name. + EventManager event_manager; ///< Event setup & tracking std::unordered_map> type_map; ///< Types, lookup by name. std::unordered_map> typeid_map; ///< Types, lookup by TypeID. emp::StreamManager file_map; ///< File streams by name. public: SymbolTable(const std::string & name) - : root_scope(name, "Global scope", nullptr) { + : root_scope(name, "Global scope", nullptr), event_manager(*this) { // Initialize the type map. type_map["INVALID"] = emp::NewPtr( *this, 0, "/*ERROR*/", "Error, Invalid type!" ); type_map["Void"] = emp::NewPtr( *this, 1, "Void", "Non-type variable; no value" ); @@ -62,7 +62,7 @@ namespace emplode { const Symbol_Scope & GetRootScope() const { return root_scope; } emp::StreamManager & GetFileManager() { return file_map; } - bool HasEvent(const std::string & name) const { return emp::Has(events_map, name); } + bool HasSignal(const std::string & name) const { return event_manager.HasSignal(name); } bool HasType(const std::string & name) const { return emp::Has(type_map, name); } bool HasTypeID(emp::TypeID id) const { return emp::Has(typeid_map, id); } @@ -196,38 +196,29 @@ namespace emplode { /// Create a new type of event that can be used in the scripting language. - Events & AddEventType(const std::string & name) { - emp_assert(!HasEvent(name), "Event type already exists!", name); - return events_map[name]; + bool AddSignal(const std::string & name, size_t num_params=0) { + return event_manager.AddSignal(name, num_params); } /// Add an instance of an event with an action that should be triggered. - void AddEvent(const std::string & name, emp::Ptr action, - double first=0.0, double repeat=0.0, double max=-1.0) { - emp_assert(HasEvent(name), name); - // Debug ("Adding event instance for '", name, "' (", first, ":", repeat, ":", max, ")"); - events_map[name].AddEvent(action, first, repeat, max); - } - - /// Indicate the an event trigger value has been updated; trigger associated events. - void UpdateEventValue(const std::string & name, double new_value) { - emp_assert(HasEvent(name), name); - // Debug("Uppdating event value '", name, "' to ", new_value); - events_map[name].UpdateValue(new_value); + bool AddAction( + const std::string & name, + emp::vector< emp::Ptr > params, + emp::Ptr action, + size_t def_line + ) { + action->SetSymbolTable(*this); + return event_manager.AddAction(name, params, action, def_line); } /// Trigger all events of a type (ignoring trigger values) - void TriggerEvents(const std::string & name) { - emp_assert(HasEvent(name), name); - events_map[name].TriggerAll(); + template + bool Trigger(const std::string & signal_name, ARG_Ts... args) { + return event_manager.Trigger(signal_name, std::forward(args)...); } /// Print all of the events to the provided stream. - void PrintEvents(std::ostream & os) const { - for (const auto & x : events_map) { - x.second.Write(x.first, os); - } - } + void PrintEvents(std::ostream & os) const { event_manager.Write(os); } }; From bb58695a41c31f84e81df6baf1799655aaa6d530 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:56:58 -0500 Subject: [PATCH 412/445] Shifted Parser to recognize new Signal/Action events system. --- source/Emplode/Parser.hpp | 44 +++++++++++++++------------------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index c711b62e..de70c710 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -84,7 +84,7 @@ namespace emplode { bool IsString() const { return pos && lexer->IsString(*pos); } bool IsDots() const { return pos && lexer->IsDots(*pos); } - bool IsEvent() const { return symbol_table->HasEvent(AsLexeme()); } + bool IsSignal() const { return symbol_table->HasSignal(AsLexeme()); } bool IsType() const { return symbol_table->HasType(AsLexeme()); } /// Convert the current state to a character; use \0 if cur token is not a symbol. @@ -182,7 +182,7 @@ namespace emplode { /// Add an instance of an event with an action that should be triggered. template - void AddEvent(Ts &&... args) { symbol_table->AddEvent(std::forward(args)...); } + void AddAction(Ts &&... args) { symbol_table->AddAction(std::forward(args)...); } }; @@ -530,33 +530,29 @@ namespace emplode { emp::Ptr Parser::ParseEvent(ParseState & state) { emp::Token start_token = state.AsToken(); state.UseRequiredChar('@', "All event declarations must being with an '@'."); - state.RequireID("Events must start by specifying event name."); - const std::string & event_name = state.UseLexeme(); - state.UseRequiredChar('(', "Expected parentheses after '", event_name, "' for args."); + state.RequireID("Events must start by specifying signal name."); + const std::string & trigger_name = state.UseLexeme(); + state.UseRequiredChar('(', "Expected parentheses after '", trigger_name, "' for args."); emp::vector> args; while (state.AsChar() != ')') { - args.push_back( ParseExpression(state) ); + args.push_back( ParseExpression(state, true) ); state.UseIfChar(','); // Skip comma if next (does allow trailing comma) } state.UseRequiredChar(')', "Event args must end in a ')'"); - emp::Ptr action = ParseStatement(state); + auto action_block = emp::NewPtr(state.GetScope(), state.GetLine()); + action_block->SetSymbolTable(state.GetSymbolTable()); + emp::Ptr action_node = ParseStatement(state); - Debug("Building event '", event_name, "' with args ", args); + // If the action statement is real, add it to the action block. + if (!action_node.IsNull()) action_block->AddChild( action_node ); - auto setup_event = - [state, event_name](emp::Ptr action, const emp::vector> & args) mutable - { - state.AddEvent( - event_name, action, - (args.size() > 0) ? args[0]->AsDouble() : 0.0, - (args.size() > 1) ? args[1]->AsDouble() : 0.0, - (args.size() > 2) ? args[2]->AsDouble() : -1.0 - ); - }; + Debug("Building event '", trigger_name, "' with args ", args); - return emp::NewPtr(event_name, action, args, setup_event, start_token.line_id); + state.AddAction(trigger_name, args, action_block, start_token.line_id); + + return nullptr; } /// Parse a specialty keyword statement (such as IF, WHILE, etc) @@ -576,14 +572,8 @@ namespace emplode { // If we made it this far, we have an error. Identify and deal with it! - if (state.UseIfLexeme("ELSE")) { - state.Error("'ELSE' must be preceded by an 'IF' statement."); - } - - // Unimplemented keyword! - else { - state.Error("Keyword '", state.AsLexeme(), "' not yet implemented."); - } + if (state.UseIfLexeme("ELSE")) state.Error("'ELSE' must be preceded by an 'IF' statement."); + else state.Error("Keyword '", state.AsLexeme(), "' not yet implemented."); return nullptr; } From 14663663b820d845cf6b05d5d1692636ebe477c2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 8 Dec 2021 18:57:53 -0500 Subject: [PATCH 413/445] Shifted Emplode to new Signal/Action events system. --- source/Emplode/Emplode.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 8263efcf..4778371a 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -81,7 +81,7 @@ #include "AST.hpp" #include "DataFile.hpp" #include "EmplodeType.hpp" -#include "Events.hpp" +#include "EventManager.hpp" #include "Lexer.hpp" #include "Parser.hpp" #include "Symbol_Function.hpp" @@ -216,16 +216,14 @@ namespace emplode { Emplode & operator=(Emplode &&) = delete; /// Create a new type of event that can be used in the scripting language. - Events & AddEventType(const std::string & name) { return symbol_table.AddEventType(name); } + bool AddSignal(const std::string & name) { return symbol_table.AddSignal(name); } - /// Indicate the an event trigger value has been updated; trigger associated events. - void UpdateEventValue(const std::string & name, double new_value) { - symbol_table.UpdateEventValue(name, new_value); + /// Trigger all actions linked to a signal. + template + void Trigger(const std::string & name, ARG_Ts... args) { + symbol_table.Trigger(name, std::forward(args)...); } - /// Trigger all events of a type (ignoring trigger values) - void TriggerEvents(const std::string & name) { symbol_table.TriggerEvents(name); } - template TypeInfo & AddType(ARG_Ts &&... args) { return symbol_table.AddType( std::forward(args)... ); From 90e51d7da10276e50c858d7073f99e572f512892 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 9 Dec 2021 09:10:31 -0500 Subject: [PATCH 414/445] Removed old Events.hpp --- source/Emplode/Events.hpp | 152 -------------------------------------- 1 file changed, 152 deletions(-) delete mode 100644 source/Emplode/Events.hpp diff --git a/source/Emplode/Events.hpp b/source/Emplode/Events.hpp deleted file mode 100644 index 968cded9..00000000 --- a/source/Emplode/Events.hpp +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 - * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2021. - * - * @file Events.hpp - * @brief Manages events for configurations. - * @note Status: BETA - * - * DEVELOPER NOTES: - * - We could use a more dynamic function to determine when an event should be triggered next, - * rather than assuming all repeating events will be evenly spaced. - */ - -#ifndef EMPLODE_EVENTS_HPP -#define EMPLODE_EVENTS_HPP - -#include - -#include "emp/base/map.hpp" -#include "emp/base/Ptr.hpp" - -#include "AST.hpp" - -namespace emplode { - - class Events { - private: - - // Structure to track the timings for a single event. - struct TimedEvent { - size_t id = 0; // A unique ID for this event. - emp::Ptr ast_action; // Parse tree to execute when triggered. - double next = 0.0; // When should we start triggering this event. - double repeat = 0.0; // How often should it repeat (0.0 for no repeat) - double max = -1.0; // Maximum value that this value can reach (neg for no max) - bool active = true; // Is this event still active? - - TimedEvent(size_t _id, emp::Ptr _node, - double _next, double _repeat, double _max) - : id(_id), ast_action(_node), next(_next), repeat(_repeat), max(_max), active(next <= max) - { ; } - ~TimedEvent() { /* Do not delete ast_action; it will be handled in the main AST tree. */ } - - // Trigger a single event as having occurred; return true/false base on whether this event - // should continue to be considered active. - bool Trigger() { - auto result_symbol = ast_action->Process(); - if (result_symbol && result_symbol->IsTemporary()) result_symbol.Delete(); - next += repeat; - - if (max != -1.0 && next > max) repeat = 0.0; - - // Return "active" if we ARE repeating and the next time is still within range. - return (repeat != 0.0); - } - - void Write(const std::string & command, std::ostream & os) const { - os << "@" << command << "(" << next; - if (repeat > 0.0) { - os << ", " << repeat; - if (max >= 0.0) os << ", " << max; - } - os << ") "; - ast_action->Write(os); - os << ";\n"; - } - }; - - emp::multimap> queue; ///< Priority queue of events to fun. - double cur_value = 0.0; ///< Current value of monitored variable. - size_t next_id = 1; ///< Assign unique IDs to internal events. - - // -- Helper functions. -- - void AddEvent(emp::Ptr in_event) { - queue.insert({in_event->next, in_event}); - } - - [[nodiscard]] emp::Ptr PopEvent() { - emp::Ptr out_event = queue.begin()->second; - queue.erase(queue.begin()); - return out_event; - } - - public: - Events() { ; } - ~Events() { - // Must delete all events in the queue. - for (auto [time, event_ptr] : queue) { - event_ptr.Delete(); - } - } - - /// Add a new event, providing: - /// action : An abstract syntax tree indicating the actions to take when triggered. - /// first : Timing the this event should initially be triggered. - /// repeat : How often should this event be triggered? - /// max : When should we stop triggering this event? - - bool AddEvent(emp::Ptr action, double first=0.0, double repeat=0.0, double max=-1.0) { - emp_assert(first >= 0.0, first); - emp_assert(repeat >= 0.0, repeat); - - // Skip all events before the current time. - if (first < cur_value) { - if (repeat == 0.0) return false; // If no repeat, we simply missed this one. - double offset = cur_value - first; // Figure out how far we need to advance this event. - double steps = ceil(offset / repeat); // How many steps through repeat will this be? - first += repeat * steps; // Fast-forward! - } - - // If we are already after max time, this event cannot be triggered. - if (max >= 0.0 && first > max) return false; - - AddEvent( emp::NewPtr(next_id++, action, first, repeat, max) ); - - return true; - } - - /// Update a value associated with these events; trigger all events up to new timepoint. - void UpdateValue(size_t in_value) { - while (queue.size() && queue.begin()->first <= in_value) { - emp::Ptr cur_event = PopEvent(); - bool do_repeat = cur_event->Trigger(); - if (do_repeat) AddEvent(cur_event); - else cur_event.Delete(); - } - cur_value = in_value; - } - - /// Trigger all events of this type, regardless of associated values. - /// Note that events will be removed with no repeats. - void TriggerAll() { - while (queue.size()) { - emp::Ptr cur_event = PopEvent(); - cur_event->Trigger(); - cur_event.Delete(); - } - } - - /// Print all of the events being tracked here. - void Write(const std::string & command, std::ostream & os) const { - for (const auto & x : queue) { - x.second->Write(command, os); - } - } - }; - - -} - -#endif From 6bb203b238a9e7d7bdb75af50831851eb6f2514b Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 11 Dec 2021 17:47:10 -0500 Subject: [PATCH 415/445] Added IsBlock() to ASTNode --- source/Emplode/AST.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp index 55545549..10056142 100644 --- a/source/Emplode/AST.hpp +++ b/source/Emplode/AST.hpp @@ -51,6 +51,7 @@ namespace emplode { virtual bool IsLeaf() const { return false; } virtual bool IsInternal() const { return false; } + virtual bool IsBlock() const { return false; } virtual size_t GetNumChildren() const { return 0; } virtual node_ptr_t GetChild(size_t /* id */) { emp_assert(false); return nullptr; } @@ -156,7 +157,9 @@ namespace emplode { line_id = in_line; } - emp::Ptr GetScope() override { return scope_ptr; } + bool IsBlock() const override { return true; } + + emp::Ptr GetScope() override { return scope_ptr; } SymbolTableBase & GetSymbolTable() override { if (symbol_table) return *symbol_table; From dd606db5451c1cf7fe4c2edf8aec1c8f574c2d5e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 11 Dec 2021 17:48:13 -0500 Subject: [PATCH 416/445] Updated MABE updates to trigger UPDATE (and trigger START at STATE) --- source/core/MABE.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 0bfb3e34..1bc8dbe4 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -548,14 +548,14 @@ namespace mabe { /// Update MABE world. void MABE::Update(size_t num_updates) { - if (update == 0) config_script.TriggerEvents("start"); + if (update == 0) config_script.Trigger("START"); for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { - emp_assert(OK(), update); // In debug mode, keep checking MABE integrity - if (rescan_signals) UpdateSignals(); // If we have reason to, update module signals - before_update_sig.Trigger(update); // Signal that a new update is about to begin - update++; // Increment 'update' to start new update - on_update_sig.Trigger(update); // Signal all modules about the new update - config_script.UpdateEventValue("update", update); // Trigger any updated-based events + emp_assert(OK(), update); // In debug mode, keep checking MABE integrity + if (rescan_signals) UpdateSignals(); // If we have reason to, update module signals + before_update_sig.Trigger(update); // Signal that a new update is about to begin + update++; // Increment 'update' to start new update + on_update_sig.Trigger(update); // Signal all modules about the new update + config_script.Trigger("UPDATE", update); // Trigger any updated-based events } } From 4af102fc2957b4a313c5a2b7c430944781633748 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sun, 12 Dec 2021 23:23:34 -0500 Subject: [PATCH 417/445] Updated MABEScript to use START and UPDATE events in all caps. --- source/core/MABEScript.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp index afd66abd..7b5ed2dd 100644 --- a/source/core/MABEScript.hpp +++ b/source/core/MABEScript.hpp @@ -338,8 +338,8 @@ namespace mabe { AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); // Add in built-in event triggers; these are used to indicate when events should happen. - AddEventType("start"); // Triggered at the beginning of a run. - AddEventType("update"); // Tested every update. + AddSignal("START"); // Triggered at the beginning of a run. + AddSignal("UPDATE"); // Tested every update. } From 36ee082620f8f4a476fad1af3fca138a1054520e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Sat, 25 Dec 2021 23:26:48 -0500 Subject: [PATCH 418/445] Fixed MABE main to use Update() instead of DoRun() --- build/MABE.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/MABE.cpp b/build/MABE.cpp index cd6df42c..eee97f31 100644 --- a/build/MABE.cpp +++ b/build/MABE.cpp @@ -33,5 +33,5 @@ int main(int argc, char* argv[]) if (control.Setup() == false) return 0; // Start the run! - control.DoRun(1000000); + control.Update(1000000); } From b14bc7a21072d127d8fd22651f6d1790693c28bf Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Wed, 29 Dec 2021 19:58:24 -0500 Subject: [PATCH 419/445] Fixed EventManager to properly delete ASTs held in Actions. --- source/Emplode/EventManager.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Emplode/EventManager.hpp b/source/Emplode/EventManager.hpp index 248716db..34295875 100644 --- a/source/Emplode/EventManager.hpp +++ b/source/Emplode/EventManager.hpp @@ -53,7 +53,10 @@ namespace emplode { Action(const std::string & _signal, node_vec_t _params, node_ptr_t _action, size_t _line) : signal_name(_signal), params(_params), action(_action), def_line(_line) { } - ~Action() { } + ~Action() { + for (auto x : params) x.Delete(); + action.Delete(); + } void Trigger(const symbol_vec_t & args) { if (args.size() < params.size()) { From 6f096ac5176ffe1f4cb07882fe31eae8d89a60ae Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 16:54:58 -0500 Subject: [PATCH 420/445] Temporaty update of Diagnostics.mabe in preparation for updating. --- settings/Diagnostics.mabe | 61 ++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/settings/Diagnostics.mabe b/settings/Diagnostics.mabe index a352808f..f694925a 100644 --- a/settings/Diagnostics.mabe +++ b/settings/Diagnostics.mabe @@ -1,4 +1,4 @@ -random_seed = 0; // Seed for random number generator; use 0 to base on time. +random_seed = 1; // Seed for random number generator; use 0 to base on time. Value pop_size = 512; Value num_gens = 50000; @@ -7,10 +7,10 @@ Population next_pop; CommandLine cl { // Handle basic I/O on the command line. target = "main_pop"; // Which population should we print stats about? - format = "fitness:max,fitness:mean,fitness:min,fitness,vals,scores"; // Column format. + format = "fitness:max,fitness:mean,fitness:min,fitness"; // Column format. } -EvalDiagnostic eval { // Evaluate set of values with a specified diagnostic problem. +EvalDiagnostic eval_diagnostics { // Evaluate set of values with a specified diagnostic problem. target = "main_pop"; // Which population(s) should we evaluate? vals_trait = "vals"; // Which trait stores the values to evaluate? scores_trait = "scores"; // Which trait should we store revised scores in? @@ -23,6 +23,16 @@ EvalDiagnostic eval { // Evaluate set of values with a specified diagn // "weak_diversity": Only count max value; all others locked at zero. } +SelectRoulette select_r { // Choose the top fitness organisms for replication. + _active = 1; // Should we activate this module? (0=off, 1=on) + _desc = ""; // Special description for those object. + select_pop = "main_pop"; // Which population should we select parents from? + birth_pop = "next_pop"; // Which population should births go into? + select_count = pop_size; // Number of top-fitness orgs to be replicated + copy_count = 1; // Number of copies to make of replicated organisms + fitness_trait = "fitness"; // Which trait provides the fitness value to use? +} + SelectElite select_e { // Choose the top fitness organisms for replication. _active = 0; // Should we activate this module? (0=off, 1=on) _desc = ""; // Special description for those object. @@ -39,10 +49,10 @@ SelectTournament select_t { // Select the top fitness organisms from random birth_pop = "next_pop"; // Which population should births go into? tournament_size = 7; // Number of orgs in each tournament num_tournaments = pop_size; // Number of tournaments to run - fitness_trait = "fitness"; // Which trait provides the fitness value to use? + fitness_fun = "fitness"; // Which trait provides the fitness value to use? } SelectLexicase select_l { // Shuffle traits each time an organism is chose for replication. - _active = 1; // Should we activate this module? (0=off, 1=on) + _active = 0; // Should we activate this module? (0=off, 1=on) _desc = ""; // Special description for those object. select_pop = "main_pop"; // Which population should we select parents from? birth_pop = "next_pop"; // Which population should births go into? @@ -51,32 +61,17 @@ SelectLexicase select_l { // Shuffle traits each time an organism is chose num_births = pop_size; // Number of offspring organisms to produce } -FileOutput output { // Output collected data into a specified file. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. - filename = "output.csv"; // Name of file for output data. - format = "fitness:max,fitness:mean,fitness:min,fitness,vals,scores"; // Column format to use in the file. - target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:10"; // Which updates should we output data? -} - -GrowthPlacement place_next { // Always appened births to the end of a population. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. +GrowthPlacement place_next { // Always append births to the end of a population. target = "main_pop,next_pop"; // Population(s) to manage. } -MovePopulation sync_gen { // Move organisms from one populaiton to another. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. +MovePopulation sync_gen { // Move organisms from one population to another. from_pop = "next_pop"; // Population to move organisms from. to_pop = "main_pop"; // Population to move organisms into. reset_to = 1; // Should we erase organisms at the destination? } ValsOrg vals_org { // Organism consisting of a series of N floating-point values. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. N = 100; // Number of values in organism - mut_prob = 0.007; // Probability of each value mutating on reproduction. + mut_prob = 0.007; // Probability of each value mutating on reproduction. mut_size = 1.0; // Standard deviation on size of mutations. min_value = 0; // Lower limit for value fields. max_value = 100; // Upper limit for value fields. @@ -94,6 +89,20 @@ ValsOrg vals_org { // Organism consisting of a series of N floating total_name = "total"; // Name of variable to contain total of all values. } -@start() print("random_seed = ", random_seed, "\n"); -@start() inject("vals_org", "main_pop", pop_size); -@update(num_gens) exit(); +@start() PRINT("random_seed = ", random_seed, "\n"); +@start() main_pop.INJECT("vals_org", pop_size); +@update(num_gens) EXIT(); + +Value best_id; +@update(10, 10) { + best_id = TRAIT_VALUE("main_pop", "fitness:max_id"); + WRITE("output.csv", "main_pop", "fitness:max,fitness:max_id,fitness:mean,vals:${best_id},scores:${best_id}"); +} + +@update(5) PRINT("Popsize = ", SIZE("main_pop"), ", ", SIZE("next_pop"), "\n"); + +// @update(10, 10) output("output.csv", "main_pop", "fitness:max,fitness:max_id,fitness:mean,genome:0"); +// @update(10, 10) output("output.csv", "main_pop", "fitness:max,fitness:max_id,fitness:mean,genome:$fitness:max_id"); +// @update(10, 10) output("output.csv", "main_pop", "fitness:max,fitness:max_id,fitness:mean,genome:$1"); +// @update(10, 10) output("output.csv", "main_pop", "fitness:max,best=fitness:max_id,fitness:mean,genome:$best"); +// @update(10, 10) output("output.csv", "main_pop", "fitness:max,fitness:mean,genome:$best|best=fitness:max_id"); From 48b3cd5728ed7a26e091ff80b36b9ab4ce0227cb Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 16:55:40 -0500 Subject: [PATCH 421/445] Updated NK.mabe to new MABE formatting. --- settings/NK.mabe | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/settings/NK.mabe b/settings/NK.mabe index 3a96dd03..a8179005 100644 --- a/settings/NK.mabe +++ b/settings/NK.mabe @@ -1,6 +1,6 @@ random_seed = 0; // Seed for random number generator; use 0 to base on time. -Value pop_size = 1000; // Number of organisms to evaluate in the population. -Value num_bits = 100; // Number of bits in each organism (and the NK landscape) +Var pop_size = 1000; // Number of organisms to evaluate in the population. +Var num_bits = 100; // Number of bits in each organism (and the NK landscape) Population main_pop; // Main population for managing candidate solutions. Population next_pop; // Temporary population while constructing the next generation. @@ -9,46 +9,53 @@ BitsOrg bits_org { // Organism consisting of a series of N bits. output_name = "bits"; // Name of variable to contain bit sequence. N = num_bits; // Number of bits in organism mut_prob = 0.01; // Probability of each bit mutating on reproduction. -} +}; EvalNK eval_nk { // Evaluate bitstrings on an NK fitness lanscape. N = num_bits; // Number of bits required in output K = 3; // Number of bits used in each gene bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? fitness_trait = "fitness"; // Which trait should we store NK fitness in? -} +}; SelectElite elite { // Choose the top fitness organisms for replication. top_count = 5; // Number of top-fitness orgs to be replicated fitness_fun = "fitness"; // Which trait provides the fitness value to use? -} +}; SelectTournament tournament { // Select the top fitness organisms from random subgroups for replication. tournament_size = 7; // Number of orgs in each tournament fitness_fun = "fitness"; // Which trait provides the fitness value to use? -} +}; -DataFile fit_file { filename="fitness.csv"; } +DataFile fit_file { filename="fitness.csv"; }; fit_file.ADD_COLUMN( "Average Fitness", "main_pop.CALC_MEAN('fitness')" ); fit_file.ADD_COLUMN( "Maximum Fitness", "main_pop.CALC_MAX('fitness')" ); fit_file.ADD_COLUMN( "Dominant Fitness", "main_pop.CALC_MODE('fitness')" ); -DataFile max_file { filename="max_org.csv"; } +DataFile max_file { filename="max_org.csv"; }; OrgList best_org; max_file.ADD_SETUP( "best_org = main_pop.FIND_MAX('fitness')" ); max_file.ADD_COLUMN( "Fitness", "best_org.TRAIT('fitness')" ); max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); -@start() PRINT("random_seed = ", random_seed, "\n"); // Print seed at run start. -@start() main_pop.INJECT("bits_org", pop_size); // Inject starting population. +@START() { + PRINT("random_seed = ", random_seed, "\n"); // Print seed at run start. + main_pop.INJECT("bits_org", pop_size); // Inject starting population. +} // Actions to perform every update. -@update(0,1) { - // @update(Value update) { + +// @UPDATE(Var ud IN [100:100]) OrgList altruists = main_pop.FILTER("altruism > 0"); +// @BEFOREDIVIDE(OrgList parent IN altruists) PRINT("Altruist Birth!"); + +@UPDATE(Var ud) { + // @UPDATE(Var update) { // IF ([10:10].HAS(update)) EXIT(); + // IF (update == 1000) EXIT; eval_nk.EVAL(main_pop); - Value mode_fit = main_pop.CALC_MODE("fitness"); + Var mode_fit = main_pop.CALC_MODE("fitness"); OrgList list_less = main_pop.FILTER("fitness < ${mode_fit}"); OrgList list_equ = main_pop.FILTER("fitness == ${mode_fit}"); OrgList list_gtr = main_pop.FILTER("fitness > ${mode_fit}"); @@ -66,10 +73,10 @@ max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); OrgList elite_offspring = elite.SELECT(main_pop, next_pop, 25); - Value num_tournaments = pop_size - elite_offspring.SIZE(); // Calc number of tournaments to run + Var num_tournaments = pop_size - elite_offspring.SIZE(); // Calc number of tournaments to run OrgList tourny_offspring = tournament.SELECT(main_pop, next_pop, num_tournaments); main_pop.REPLACE_WITH(next_pop); } -@update(1000) EXIT(); +@UPDATE(Var ud2) IF (ud2 == 1000) EXIT(); From 3a391f3be04dbb2d26331d575ba9fdc1628f3b26 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 16:57:34 -0500 Subject: [PATCH 422/445] Updated MABE.hpp to use emp::notify warnings and errors. --- source/core/MABE.hpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 1bc8dbe4..fc3d763c 100644 --- a/source/core/MABE.hpp +++ b/source/core/MABE.hpp @@ -23,6 +23,7 @@ #include "emp/base/array.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" +#include "emp/base/notify.hpp" #include "emp/config/command_line.hpp" #include "emp/control/Signal.hpp" #include "emp/data/DataMap.hpp" @@ -315,8 +316,6 @@ namespace mabe { bool OnSwap_IsTriggered(mod_ptr_t mod) { return on_swap_sig.cur_mod == mod; }; bool BeforePopResize_IsTriggered(mod_ptr_t mod) { return before_pop_resize_sig.cur_mod == mod; }; bool OnPopResize_IsTriggered(mod_ptr_t mod) { return on_pop_resize_sig.cur_mod == mod; }; - bool OnError_IsTriggered(mod_ptr_t mod) { return on_error_sig.cur_mod == mod; }; - bool OnWarning_IsTriggered(mod_ptr_t mod) { return on_warning_sig.cur_mod == mod; }; bool BeforeExit_IsTriggered(mod_ptr_t mod) { return before_exit_sig.cur_mod == mod; }; bool OnHelp_IsTriggered(mod_ptr_t mod) { return on_help_sig.cur_mod == mod; }; }; @@ -382,7 +381,8 @@ namespace mabe { // MABE config files can be generated FROM a *.gen file, typically creating a *.mabe // file. If output file is *.gen assume an error. (for now; override should be allowed) if (in[0].size() > 4 && in[0].substr(in[0].size()-4) == ".gen") { - AddError("Error: generated file ", in[0], " not allowed to be *.gen; typically should end in *.mabe."); + emp::notify::Error("Generated file ", in[0], + " not allowed to be *.gen; typically should end in *.mabe."); exit_now = true; } else gen_filename = in[0]; @@ -429,7 +429,7 @@ namespace mabe { } } if (found == false) { - std::cout << "Error: unknown command line argument '" << args[pos] << "'." << std::endl; + emp::notify::Message("Error: unknown command line argument '", args[pos], "'."); show_help = true; break; } @@ -483,8 +483,7 @@ namespace mabe { MABE::MABE(int argc, char* argv[]) - : trait_man(GetErrorManager()) - , args(emp::cl::args_to_strings(argc, argv)) + : args(emp::cl::args_to_strings(argc, argv)) , config_script(*this) { // Updates to scripting language that require full controller functionality. @@ -538,12 +537,12 @@ namespace mabe { // Allow traits to be linked. trait_man.Unlock(); - Setup_Modules(); // Run SetupModule() on each module for linking traits or other setup. - Setup_Traits(); // Make sure module traits do not clash. + Setup_Modules(); // Run SetupModule() on each module for linking traits or other setup. + Setup_Traits(); // Make sure module traits do not clash. + UpdateSignals(); // Setup the appropriate modules to be linked with each signal. + SetupBase(); // Call Setup on MABEBase (which will report errors) - UpdateSignals(); // Setup the appropriate modules to be linked with each signal. - - return SetupBase(); // Call Setup on MABEBase (which is tracking and will report errors) + return true; } /// Update MABE world. @@ -606,7 +605,7 @@ namespace mabe { placement_set.Insert(pos); } else { inject_org.Delete(); - AddError("Invalid position; failed to inject organism ", i, "!"); + emp::notify::Error("Invalid position; failed to inject organism ", i, "!"); } } return placement_set; @@ -621,7 +620,7 @@ namespace mabe { if (pos.IsValid()) AddOrgAt( org_ptr, pos); else { org_ptr.Delete(); - AddError("Invalid position; failed to inject organism!"); + emp::notify::Error("Invalid position; failed to inject organism!"); } return pos; } @@ -652,10 +651,10 @@ namespace mabe { size_t copy_count) { int pop_id = GetPopID(pop_name); if (pop_id == -1) { - AddError("Invalid population name used in inject: ", - "org_type= '", type_name, "'; ", - "pop_name= '", pop_name, "'; ", - "copy_count=", copy_count); + emp::notify::Error("Invalid population name used in inject: ", + "org_type= '", type_name, "'; ", + "pop_name= '", pop_name, "'; ", + "copy_count=", copy_count); } Population & pop = GetPopulation(pop_id); return Inject(pop, type_name, copy_count); // Inject the organisms. @@ -740,7 +739,7 @@ namespace mabe { auto slices = emp::view_slices(load_str, ','); for (auto name : slices) { int pop_id = GetPopID(name); - if (pop_id == -1) AddError("Unknown population: ", name); + if (pop_id == -1) emp::notify::Error("Unknown population: ", name); else out.Insert(GetPopulation(pop_id)); } return out; From 640c8e2498b087e65dd9b4bcb67074a79f84f475 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 16:58:07 -0500 Subject: [PATCH 423/445] Removed ErrorManager from MABEBase; not uses emp::notify for error management. --- source/core/MABEBase.hpp | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index a6d7aec8..718b7719 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -13,10 +13,10 @@ #include #include "emp/base/array.hpp" +#include "emp/base/notify.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "ErrorManager.hpp" #include "ModuleBase.hpp" #include "Population.hpp" #include "SigListener.hpp" @@ -30,7 +30,6 @@ namespace mabe { class MABEBase { protected: - ErrorManager error_man; ///< Manage warnings and errors that occur. bool exit_now=false; ///< Do we need to immediately clean up and exit the run? emp::Random random; ///< Master random number generator size_t update = 0; ///< How many times has Update() been called? @@ -73,10 +72,6 @@ namespace mabe { SigListener before_pop_resize_sig; // OnPopResize(Population & pop, size_t old_size) SigListener on_pop_resize_sig; - // OnError(const std::string & msg) - SigListener on_error_sig; - // OnWarning(const std::string & msg) - SigListener on_warning_sig; // BeforeExit() SigListener before_exit_sig; // OnHelp() @@ -88,9 +83,7 @@ namespace mabe { // Protected constructor so that base class cannot be instantiated except from derived class. MABEBase() - : error_man( [this](const std::string & msg){ on_error_sig.Trigger(msg); }, - [this](const std::string & msg){ on_warning_sig.Trigger(msg); } ) - , before_update_sig("before_update", ModuleBase::SIG_BeforeUpdate, &ModuleBase::BeforeUpdate, sig_ptrs) + : before_update_sig("before_update", ModuleBase::SIG_BeforeUpdate, &ModuleBase::BeforeUpdate, sig_ptrs) , on_update_sig("on_update", ModuleBase::SIG_OnUpdate, &ModuleBase::OnUpdate, sig_ptrs) , before_repro_sig("before_repro", ModuleBase::SIG_BeforeRepro, &ModuleBase::BeforeRepro, sig_ptrs) , on_offspring_ready_sig("on_offspring_ready", ModuleBase::SIG_OnOffspringReady, &ModuleBase::OnOffspringReady, sig_ptrs) @@ -104,8 +97,6 @@ namespace mabe { , on_swap_sig("on_swap", ModuleBase::SIG_OnSwap, &ModuleBase::OnSwap, sig_ptrs) , before_pop_resize_sig("before_pop_resize", ModuleBase::SIG_BeforePopResize, &ModuleBase::BeforePopResize, sig_ptrs) , on_pop_resize_sig("on_pop_resize", ModuleBase::SIG_OnPopResize, &ModuleBase::OnPopResize, sig_ptrs) - , on_error_sig("on_error", ModuleBase::SIG_OnError, &ModuleBase::OnError, sig_ptrs) - , on_warning_sig("on_warning", ModuleBase::SIG_OnWarning, &ModuleBase::OnWarning, sig_ptrs) , before_exit_sig("before_exit", ModuleBase::SIG_BeforeExit, &ModuleBase::BeforeExit, sig_ptrs) , on_help_sig("on_help", ModuleBase::SIG_OnHelp, &ModuleBase::OnHelp, sig_ptrs) { ; } @@ -113,9 +104,8 @@ namespace mabe { public: virtual ~MABEBase() { } - bool SetupBase() { - error_man.Activate(); - return (error_man.GetNumErrors() == 0); // Only return success if there were no errors. + void SetupBase() { + emp::notify::Unpause(); } // --- Basic accessors --- @@ -123,17 +113,6 @@ namespace mabe { size_t GetUpdate() const noexcept { return update; } bool GetVerbose() const { return verbose; } - /// Provide an interface for reporting warnings. - template - void AddWarning(Ts &&... args) { error_man.AddWarning(std::forward(args)...); } - - /// Provide an interface for reporting errors. - template - void AddError(Ts &&... args) { error_man.AddError(std::forward(args)...); } - - /// Access the full error manager. - ErrorManager & GetErrorManager() { return error_man; } - /// Trigger exit from run. void RequestExit() { exit_now = true; } From 458af8a3388bae082ae0588c4e0afe6a2970f89a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 16:59:18 -0500 Subject: [PATCH 424/445] Updated modules to use emp::notify for error handling; removed OnError and OnWarning. --- source/core/Module.hpp | 26 +++++--------------------- source/core/ModuleBase.hpp | 26 +------------------------- 2 files changed, 6 insertions(+), 46 deletions(-) diff --git a/source/core/Module.hpp b/source/core/Module.hpp index 60247633..07178c36 100644 --- a/source/core/Module.hpp +++ b/source/core/Module.hpp @@ -39,7 +39,7 @@ namespace mabe { class Module : public ModuleBase { public: Module(MABE & in_control, const std::string & in_name, const std::string & in_desc="") - : ModuleBase(in_control, in_name, in_desc) { error_man = &control.GetErrorManager(); } + : ModuleBase(in_control, in_name, in_desc) { } Module(const Module &) = delete; Module(Module &&) = delete; @@ -60,7 +60,9 @@ namespace mabe { std::function set_fun = [this,&var](const std::string & name){ var = control.GetPopID(name); - if (var == -1) AddError("Trying to access population '", name, "'; does not exist."); + if (var == -1) { + emp::notify::Error("Trying to access population '", name, "'; does not exist."); + } }; return AsScope().LinkFuns(name, get_fun, set_fun, desc); @@ -95,7 +97,7 @@ namespace mabe { std::function set_fun = [this,&var](const std::string & name){ var = control.GetModuleID(name); - if (var == -1) AddError("Trying to access module '", name, "'; does not exist."); + if (var == -1) emp::notify::Error("Trying to access module '", name, "'; does not exist."); }; return AsScope().LinkFuns(name, get_fun, set_fun, desc); @@ -302,22 +304,6 @@ namespace mabe { control.RescanSignals(); } - // Format: OnError(const std::string & msg) - // Trigger: An error has occurred and the user should be notified. - // Args: Message associated with this error. - void OnError(const std::string &) override { - has_signal[SIG_OnError] = false; - control.RescanSignals(); - } - - // Format: OnWarning(const std::string & msg) - // Trigger: A atypical condition has occurred and the user should be notified. - // Args: Message associated with this warning. - void OnWarning(const std::string &) override { - has_signal[SIG_OnWarning] = false; - control.RescanSignals(); - } - // Format: BeforeExit() // Trigger: Run immediately before MABE is about to exit. void BeforeExit() override { @@ -359,8 +345,6 @@ namespace mabe { bool OnSwap_IsTriggered() override { return control.OnSwap_IsTriggered(this); }; bool BeforePopResize_IsTriggered() override { return control.BeforePopResize_IsTriggered(this); }; bool OnPopResize_IsTriggered() override { return control.OnPopResize_IsTriggered(this); }; - bool OnError_IsTriggered() override { return control.OnError_IsTriggered(this); }; - bool OnWarning_IsTriggered() override { return control.OnWarning_IsTriggered(this); }; bool BeforeExit_IsTriggered() override { return control.BeforeExit_IsTriggered(this); }; bool OnHelp_IsTriggered() override { return control.OnHelp_IsTriggered(this); }; diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index cf0982aa..f6ab5d83 100644 --- a/source/core/ModuleBase.hpp +++ b/source/core/ModuleBase.hpp @@ -48,10 +48,6 @@ * : Full population is about to be resized. * OnPopResize(Population & pop, size_t old_size) * : Full population has just been resized. - * OnError(const std::string & msg) - * : An error has occurred and the user should be notified. - * OnWarning(const std::string & msg) - * : A atypical condition has occurred and the user should be notified. * BeforeExit() * : Run immediately before MABE is about to exit. * OnHelp() @@ -66,6 +62,7 @@ #include #include "emp/base/map.hpp" +#include "emp/base/notify.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" #include "emp/datastructs/map_utils.hpp" @@ -73,7 +70,6 @@ #include "../Emplode/Emplode.hpp" -#include "ErrorManager.hpp" #include "TraitInfo.hpp" namespace mabe { @@ -94,12 +90,9 @@ namespace mabe { mabe::MABE & control; ///< Reference to main mabe controller using module bool is_builtin=false; ///< Is this a built-in module not for config? - emp::Ptr error_man = nullptr; ///< Redirection for errors. - /// Informative tags about this module. Expected tags include: /// "Analyze" : Makes measurements on the population. /// "Archive" : Store specific types of data. - /// "ErrorHandle" : Deals with errors as they occur and need to be reported. /// "Evaluate" : Examines organisms and annotates the data map. /// "Interface" : Provides mechanisms for the user to interact with the world. /// "ManageOrgs" : Manages a type of organism in the world. @@ -135,8 +128,6 @@ namespace mabe { SIG_OnSwap, SIG_BeforePopResize, SIG_OnPopResize, - SIG_OnError, - SIG_OnWarning, SIG_BeforeExit, SIG_OnHelp, NUM_SIGNALS, @@ -147,15 +138,6 @@ namespace mabe { // Setup a BitSet to track if this module has each signal implemented. emp::BitSet has_signal; - // ---- Helper functions ---- - - /// All internal errors should be processed through AddError(...) - template - void AddError(Ts &&... args) { - error_man->AddError(std::forward(args)...); - } - - // Core implementation for ManagerModule functionality. virtual emp::Ptr CloneObject_impl(const OrgType &) { emp_assert(false, "CloneObject_impl() must be overridden for ManagerModule."); @@ -203,7 +185,6 @@ namespace mabe { void SetBuiltIn(bool _in=true) { is_builtin = _in; } bool IsAnalyzeMod() const { return emp::Has(action_tags, "Analyze"); } - bool IsErrorHandleMod() const { return emp::Has(action_tags, "ErrorHandle"); } bool IsEvaluateMod() const { return emp::Has(action_tags, "Evaluate"); } bool IsInterfaceMod() const { return emp::Has(action_tags, "Interface"); } bool IsManageMod() const { return emp::Has(action_tags, "ManageOrgs"); } @@ -219,7 +200,6 @@ namespace mabe { } ModuleBase & SetAnalyzeMod(bool in=true) { return SetActionTag("Analyze", in); } - ModuleBase & SetErrorHandleMod(bool in=true) { return SetActionTag("ErrorHandle", in); } ModuleBase & SetEvaluateMod(bool in=true) { return SetActionTag("Evaluate", in); } ModuleBase & SetInterfaceMod(bool in=true) { return SetActionTag("Interface", in); } ModuleBase & SetManageMod(bool in=true) { return SetActionTag("ManageOrgs", in); } @@ -252,8 +232,6 @@ namespace mabe { virtual void OnSwap(OrgPosition, OrgPosition) = 0; virtual void BeforePopResize(Population &, size_t) = 0; virtual void OnPopResize(Population &, size_t) = 0; - virtual void OnError(const std::string &) = 0; - virtual void OnWarning(const std::string &) = 0; virtual void BeforeExit() = 0; virtual void OnHelp() = 0; @@ -274,8 +252,6 @@ namespace mabe { virtual bool OnSwap_IsTriggered() = 0; virtual bool BeforePopResize_IsTriggered() = 0; virtual bool OnPopResize_IsTriggered() = 0; - virtual bool OnError_IsTriggered() = 0; - virtual bool OnWarning_IsTriggered() = 0; virtual bool BeforeExit_IsTriggered() = 0; virtual bool OnHelp_IsTriggered() = 0; From 218f4186109d9ac313f87bc7986a6f036191ca34 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 17:00:18 -0500 Subject: [PATCH 425/445] Removed ErrorManager from TraitManager; updated to emp::notify for error handling. --- source/core/TraitManager.hpp | 54 +++++++++++++++--------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/source/core/TraitManager.hpp b/source/core/TraitManager.hpp index e4f487ab..6615b463 100644 --- a/source/core/TraitManager.hpp +++ b/source/core/TraitManager.hpp @@ -37,14 +37,11 @@ namespace mabe { /// Configuration should happen BEFORE traits are created, so this calls starts locked. bool locked = true; - /// ErrorManager passed in from main program. - mabe::ErrorManager & error_man; - /// Count the total number of errors encountered. int error_count = 0; public: - TraitManager(mabe::ErrorManager & in_error_man) : error_man(in_error_man) { } + TraitManager() { } ~TraitManager() { for (auto [name,trait_ptr] : trait_map) { trait_ptr.Delete(); // Delete all trait info. @@ -57,11 +54,6 @@ namespace mabe { void Lock() { locked = true; } void Unlock() { locked = false; } - template - void AddError(Ts &&... args) { - error_man.AddError(std::forward(args)...); - } - /// Register all of the traits in the the provided DataMap. void RegisterAll(emp::DataMap & data_map) { for (auto [name,trait_ptr] : trait_map) { @@ -90,14 +82,14 @@ namespace mabe { // Traits must be added in the SetupModule() function for the given modules; // afterward the trait manager is locked and additional new traits are not allowed. if (locked) { - AddError("Module '", mod_name, "' adding trait '", trait_name, - "' before config files have loaded; should be done in SetupModule()."); + emp::notify::Error("Module '", mod_name, "' adding trait '", trait_name, + "' before config files have loaded; should be done in SetupModule()."); } // Traits cannot be added without access information. if (access == TraitInfo::UNKNOWN) { - AddError("Module ", mod_name, " trying to add trait named '", trait_name, - "' with UNKNOWN access type."); + emp::notify::Error("Module ", mod_name, " trying to add trait named '", trait_name, + "' with UNKNOWN access type."); } // Determine the type options this module can handle. @@ -119,7 +111,7 @@ namespace mabe { // Make sure that the SAME module isn't defining a trait twice. if (cur_trait->HasAccess(mod_ptr)) { - AddError("Module ", mod_name, " is creating multiple traits named '", + emp::notify::Error("Module ", mod_name, " is creating multiple traits named '", trait_name, "'."); } @@ -141,11 +133,11 @@ namespace mabe { // Otherwise we have incompatable types... else { - AddError("Module ", mod_name, " is trying to use trait '", - trait_name, "' of type ", emp::GetTypeID(), - "; Previously defined in module(s) ", - emp::to_english_list(cur_trait->GetModuleNames()), - " as type ", cur_trait->GetType()); + emp::notify::Error("Module ", mod_name, " is trying to use trait '", + trait_name, "' of type ", emp::GetTypeID(), + "; Previously defined in module(s) ", + emp::to_english_list(cur_trait->GetModuleNames()), + " as type ", cur_trait->GetType()); } } @@ -167,7 +159,7 @@ namespace mabe { bool VerifyValid(const std::string & trait_name, emp::Ptr trait_ptr) { // NO traits should be of UNKNOWN access. if (trait_ptr->GetUnknownCount()) { - error_man.AddError("Unknown access mode for trait '", trait_name, + emp::notify::Error("Unknown access mode for trait '", trait_name, "' in module(s) ", emp::to_english_list(trait_ptr->GetUnknownNames()), " (internal error!)"); return false; @@ -186,17 +178,17 @@ namespace mabe { << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" << " module-specific prefix. Otherwise modules need to be edited to not have\n" << " trait private.]"; - error_man.AddError(error_msg.str()); + emp::notify::Error(error_msg.str()); return false; } if (trait_ptr->GetPrivateCount() && trait_ptr->GetModuleCount() > 1) { - error_man.AddError("Trait '", trait_name, "' is private in module '", - trait_ptr->GetPrivateNames()[0], - "'; should not be used by other modules.\n", - "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", - " module-specific prefix. Otherwise module needs to be edited to not have\n", - " trait private.]"); + emp::notify::Error("Trait '", trait_name, "' is private in module '", + trait_ptr->GetPrivateNames()[0], + "'; should not be used by other modules.\n", + "[Suggestion: if traits are supposed to be distinct, prepend private name with a\n", + " module-specific prefix. Otherwise module needs to be edited to not have\n", + " trait private.]"); return false; } @@ -216,13 +208,13 @@ namespace mabe { << "[Suggestion: if traits are supposed to be distinct, prepend names with a\n" << " module-specific prefix. Otherwise modules should be edited to change trait\n" << " to be SHARED (and all can modify) or have all but one shift to REQUIRED.]"; - error_man.AddError(error_msg.str()); + emp::notify::Error(error_msg.str()); return false; } if (claim_count && trait_ptr->IsShared()) { auto mod_names = emp::Concat(trait_ptr->GetOwnedNames(), trait_ptr->GetGeneratedNames()); - error_man.AddError("Trait '", trait_name, + emp::notify::Error("Trait '", trait_name, "' is fully OWNED by module '", mod_names[0], "'; it cannot be SHARED (written to) by other modules:", emp::to_english_list(trait_ptr->GetSharedNames()), @@ -240,7 +232,7 @@ namespace mabe { // A REQUIRED trait must have another module write to it (i.e. OWNED, GENERATED or SHARED). if (trait_ptr->IsRequired() && !trait_ptr->IsOwned() && !trait_ptr->IsShared() && !trait_ptr->IsGenerated()) { - error_man.AddError("Trait '", trait_name, "' marked REQUIRED by module(s) ", + emp::notify::Error("Trait '", trait_name, "' marked REQUIRED by module(s) ", emp::to_english_list(trait_ptr->GetRequiredNames()), "'; must be written to by other modules.\n", "[Suggestion: set another module to write to this trait (where it is either\n", @@ -250,7 +242,7 @@ namespace mabe { // A GENERATED trait requires another module to read (REQUIRE) it. else if (trait_ptr->IsGenerated() && !trait_ptr->IsRequired()) { - error_man.AddError("Trait '", trait_name, "' marked GENERATED by module(s) ", + emp::notify::Error("Trait '", trait_name, "' marked GENERATED by module(s) ", emp::to_english_list(trait_ptr->GetGeneratedNames()), "'; must be read by other modules."); return false; From 65fa2347f6fc2c03b098e1e56697d25e1a82ec9a Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 17:01:28 -0500 Subject: [PATCH 426/445] Updated EvalManacala to new MABE structure; has Evaluate() instead of OnUpdate() --- source/evaluate/games/EvalMancala.hpp | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp index 9849d0e1..68c0a8fd 100644 --- a/source/evaluate/games/EvalMancala.hpp +++ b/source/evaluate/games/EvalMancala.hpp @@ -19,8 +19,6 @@ namespace mabe { class EvalMancala : public Module { private: - Collection target_collect; ///< Which organisms should we evaluate? - std::string input_trait = "input"; ///< Trait to put input values. std::string output_trait = "output"; ///< Trait to find output values. std::string scoreA_trait = "scoreA"; ///< Trait for this player's game results. @@ -43,14 +41,19 @@ namespace mabe { const std::string & name="EvalMancala", const std::string & desc="Evaluate organisms by having them play Mancala.") : Module(control, name, desc) - , target_collect(control.GetPopulation(0)) { SetEvaluateMod(true); } ~EvalMancala() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + info.AddMemberFunction("EVAL", + [](EvalMancala & mod, Collection list) { return mod.Evaluate(list); }, + "Evaluate organism's ability to play the game Mancala."); + } + void SetupConfig() override { - LinkCollection(target_collect, "target", "Which population(s) should we evaluate?"); LinkVar(input_trait, "input_trait", "Into which trait should input values be placed?"); LinkVar(output_trait, "output_trait", "Out of which trait should output values be read?"); LinkVar(scoreA_trait, "scoreA_trait", "Trait to save score for this player."); @@ -222,20 +225,17 @@ namespace mabe { EvalGame(org, control.GetRandom(), 0, true, os); } - void OnUpdate(size_t ud) override { - control.Verbose("UD ", ud, ": Running EvalMancala::OnUpdate()"); - - emp_assert(control.GetNumPopulations() >= 1); - + double Evaluate(const Collection & orgs) { // Determine the type of competitions to perform. // ==> @CAO: For the moment, just doing a random opponent!! // Loop through the living organisms in the target collection to evaluate each. - mabe::Collection alive_collect( target_collect.GetAlive() ); + mabe::Collection alive_collect( orgs.GetAlive() ); control.Verbose(" - ", alive_collect.GetSize(), " organisms found."); size_t org_count = 0; + double max_fitness = 0.0; for (Organism & org : alive_collect) { control.Verbose("...eval org #", org_count++); double & scoreA = org.GetTrait(scoreA_trait); @@ -253,8 +253,18 @@ namespace mabe { scoreB += results.scoreB; num_errors += results.num_errors; fitness += results.CalcFitness(); + + if (fitness > max_fitness) max_fitness = fitness; } + + return max_fitness; } + + // If a population is provided to Evaluate, first convert it to a Collection. + double Evaluate(Population & pop) { return Evaluate( Collection(pop) ); } + + // If a string is provided to Evaluate, convert it to a Collection. + double Evaluate(const std::string & in) { return Evaluate( control.ToCollection(in) ); } }; MABE_REGISTER_MODULE(EvalMancala, "Evaluate organisms on their ability to play Mancala."); From eb8378fc88424adb149bcc862d173d0bdff3fc65 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 30 Dec 2021 17:02:29 -0500 Subject: [PATCH 427/445] Updated various files across MABE to use emp::notify for error handling. --- source/core/Collection.hpp | 6 ++---- source/core/MABEScript.hpp | 3 +-- source/core/Population.hpp | 7 +++++-- source/evaluate/static/EvalNK.hpp | 6 +++--- source/select/SelectLexicase.hpp | 2 +- source/select/SelectRoulette.hpp | 2 +- source/select/SelectTournament.hpp | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index b255a495..34ad03fc 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -358,8 +358,7 @@ namespace mabe { org_id -= pop_info.GetSize(pop_ptr); } - // @CAO Should report error to user! - emp_error("Trying to find org id out of range for a collection."); + emp::notify::Error("Trying to find org id out of range for a collection."); return pos_map.begin()->first->At(0); // Return the first organism since out of range. } @@ -372,8 +371,7 @@ namespace mabe { org_id -= pop_info.GetSize(pop_ptr); } - // @CAO Should report error to user! - emp_error("Trying to find org id out of range for a collection."); + emp::notify::Error("Trying to find org id out of range for a collection."); return pos_map.begin()->first->At(0); // Return the first organism since out of range. } diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp index 7b5ed2dd..4f828368 100644 --- a/source/core/MABEScript.hpp +++ b/source/core/MABEScript.hpp @@ -27,7 +27,6 @@ #include "Collection.hpp" #include "data_collect.hpp" -#include "ErrorManager.hpp" #include "MABEBase.hpp" #include "ModuleBase.hpp" #include "Population.hpp" @@ -158,7 +157,7 @@ namespace mabe { // If we don't have a fun, we weren't able to build an aggregation function. if (!fun) { - control.AddError("Unknown trait filter '", mode, "' for trait '", trait_fun, "'."); + emp::notify::Error("Unknown trait filter '", mode, "' for trait '", trait_fun, "'."); return [](const FROM_T &){ return TO_T(); }; } diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 5a42e63e..fe0047ea 100644 --- a/source/core/Population.hpp +++ b/source/core/Population.hpp @@ -176,8 +176,11 @@ namespace mabe { orgs[pos] = org_ptr; org_ptr->SetPopulation(*this); if (!data_layout_ptr) data_layout_ptr = &org_ptr->GetDataMap().GetLayout(); - // @CAO If an organism with the wrong data map type is added, should throw a USER error. - emp_assert( &org_ptr->GetDataMap().GetLayout() == data_layout_ptr ); + + if ( &org_ptr->GetDataMap().GetLayout() != data_layout_ptr ) { + emp::notify::Error("Trying to insert an organism into population '", name, + "' with the incorrect trait set."); + } num_orgs++; } diff --git a/source/evaluate/static/EvalNK.hpp b/source/evaluate/static/EvalNK.hpp index b61d2123..e84bcabe 100644 --- a/source/evaluate/static/EvalNK.hpp +++ b/source/evaluate/static/EvalNK.hpp @@ -76,9 +76,9 @@ namespace mabe { org.GenerateOutput(); const auto & bits = org.GetTrait(bits_trait); if (bits.size() != N) { - AddError("Org returns ", bits.size(), " bits, but ", - N, " bits needed for NK landscape.", - "\nOrg: ", org.ToString()); + emp::notify::Error("Org returns ", bits.size(), " bits, but ", + N, " bits needed for NK landscape.", + "\nOrg: ", org.ToString()); } double fitness = landscape.GetFitness(bits); org.SetTrait(fitness_trait, fitness); diff --git a/source/select/SelectLexicase.hpp b/source/select/SelectLexicase.hpp index b1c73acb..8c1a21d5 100644 --- a/source/select/SelectLexicase.hpp +++ b/source/select/SelectLexicase.hpp @@ -33,7 +33,7 @@ namespace mabe { Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { if (num_births > 1 && select_pop.GetID() == birth_pop.GetID()) { - AddError("SelectLexicase requires birth_pop and select_pop to be different if selecting multiple organisms."); + emp::notify::Error("SelectLexicase requires birth_pop and select_pop to be different if selecting multiple organisms."); return Collection(); } diff --git a/source/select/SelectRoulette.hpp b/source/select/SelectRoulette.hpp index a915c0de..dd4a1df3 100644 --- a/source/select/SelectRoulette.hpp +++ b/source/select/SelectRoulette.hpp @@ -24,7 +24,7 @@ namespace mabe { Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { if (select_pop.GetID() == birth_pop.GetID()) { - AddError("SelectRoulette currently requires birth_pop and select_pop to be different."); + emp::notify::Error("SelectRoulette currently requires birth_pop and select_pop to be different."); return Collection{}; } diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index c7743e18..63494dbf 100644 --- a/source/select/SelectTournament.hpp +++ b/source/select/SelectTournament.hpp @@ -26,7 +26,7 @@ namespace mabe { const size_t N = select_pop.GetSize(); if (select_pop.GetNumOrgs() == 0) { - AddError("Trying to run Tournament Selection on an Empty Population."); + emp::notify::Error("Trying to run Tournament Selection on an Empty Population."); return Collection(); } From e2099ca77564d724b7bd2e046d3182af679860b0 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 31 Dec 2021 13:10:34 -0500 Subject: [PATCH 428/445] Updated Symbol_Var so that it can be used more dynamically. --- source/Emplode/Symbol.hpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index c2db7e50..2e19c07c 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -124,6 +124,8 @@ namespace emplode { virtual Symbol & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } + Symbol & operator=(double in) { return SetValue(in); } + Symbol & operator=(const std::string & in) { return SetString(in); } virtual emp::Ptr AsFunctionPtr() { return nullptr; } virtual emp::Ptr AsFunctionPtr() const { return nullptr; } @@ -308,16 +310,26 @@ namespace emplode { bool is_num = true; public: Symbol_Var(const std::string & in_name, - double default_val, + double in_val, const std::string & in_desc="", emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope), num_value(default_val), is_num(true) { ; } + : Symbol(in_name, in_desc, in_scope), num_value(in_val), is_num(true) {} Symbol_Var(const std::string & in_name, - const std::string & default_val, + const std::string & in_val, const std::string & in_desc="", emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope), str_value(default_val), is_num(false) { ; } + : Symbol(in_name, in_desc, in_scope), str_value(in_val), is_num(false) {} + Symbol_Var(const std::string & in_name, + const Symbol_Var & in_val, + const std::string & in_desc="", + emp::Ptr in_scope=nullptr) + : Symbol(in_name, in_desc, in_scope) + , num_value(in_val.num_value), str_value(in_val.str_value), is_num(in_val.is_num) {} Symbol_Var(const Symbol_Var &) = default; + Symbol_Var(double _val) + : Symbol("__Auto__", "", nullptr), num_value(_val), is_num(true) {} + Symbol_Var(const std::string & _val) + : Symbol("__Auto__", "", nullptr), str_value(_val), is_num(false) {} std::string GetTypename() const override { return "Var"; } From 0c33d5e135ebe2cfaeb9a9da2b060dcc32074f24 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 31 Dec 2021 13:11:28 -0500 Subject: [PATCH 429/445] Updated ValueToSymbol() to handle copying a Symbol_Var --- source/Emplode/SymbolTableBase.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index e1a9804c..e36d662e 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -61,7 +61,7 @@ namespace emplode { } template - decltype(auto) ValueToSymbol( T && value, const std::string & location ) { + symbol_ptr_t ValueToSymbol( T && value, const std::string & location ) { constexpr bool is_ref = std::is_lvalue_reference(); using base_t = std::remove_reference_t; @@ -72,7 +72,8 @@ namespace emplode { // If a return value is a basic type, wrap it in a temporary symbol else if constexpr (std::is_same() || - std::is_arithmetic()) { + std::is_arithmetic() || + std::is_same()) { return MakeTempSymbol(value); } From aed3d21c80362cd41c0f471f9ff7b599fc020962 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 31 Dec 2021 13:12:26 -0500 Subject: [PATCH 430/445] Setup MABEScript to use Symbol_Var rather than explicity std::string and double. --- source/core/MABEScript.hpp | 104 ++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp index 4f828368..4d6017f4 100644 --- a/source/core/MABEScript.hpp +++ b/source/core/MABEScript.hpp @@ -42,6 +42,8 @@ namespace mabe { MABEBase & control; emp::DataMapParser dm_parser; ///< Parser to process functions on a data map + using Symbol_Var = emplode::Symbol_Var; + public: /// Build a function to scan a data map, run a provided equation on its entries, /// and return the result. @@ -107,16 +109,14 @@ namespace mabe { /// entropy : Return the Shannon entropy of this value. /// :trait : Return the mutual information with another provided trait. - template - std::function BuildTraitSummary( + template + std::function BuildTraitSummary( std::string trait_fun, // Function to calculate on each organism std::string mode, // Method to combine organism results emp::DataLayout & data_layout // DataLayout to assume for this summary ) { static_assert( std::is_same() || std::is_same(), "BuildTraitSummary FROM_T must be Collection or Population." ); - static_assert( std::is_same() || std::is_same(), - "BuildTraitSummary TO_T must be double or std::string." ); // Pre-process the trait function to allow for use of regular config variables. trait_fun = Preprocess(trait_fun); @@ -136,40 +136,27 @@ namespace mabe { auto get_fun = [trait_id, result_type](const Organism & org) { return emp::to_literal( org.GetTraitAsString(trait_id, result_type) ); }; - auto fun = emp::BuildCollectFun(mode, get_fun); + auto fun = BuildCollectFun(mode, get_fun); - // Go through all combinations of TO/FROM to return the correct types. - if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & p){ return emp::from_string(fun( Collection(p) )); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & c){ return emp::from_string(fun(c)); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const FROM_T & p){ return fun( Collection(p) ); }; + // If we are coming from a Population, first convert to a collection. + if constexpr (std::is_same()) { + return [fun](const Population & p){ return fun( Collection(p) ); }; } else return fun; } // If we made it here, we are numeric. auto get_fun = BuildTraitEquation(data_layout, trait_fun); - auto fun = emp::BuildCollectFun(mode, get_fun); + auto fun = BuildCollectFun(mode, get_fun); // If we don't have a fun, we weren't able to build an aggregation function. if (!fun) { emp::notify::Error("Unknown trait filter '", mode, "' for trait '", trait_fun, "'."); - return [](const FROM_T &){ return TO_T(); }; + return [](const FROM_T &){ return Symbol_Var(0); }; } // Go through all combinations of TO/FROM to return the correct types. - // @CAO need to adjust BuildCollectFun so that it returns correct type; not always string. - if constexpr (std::is_same() && std::is_same()) { - return [fun](const Population & p){ return emp::from_string(fun( Collection(p) )); }; - } - else if constexpr (std::is_same() && std::is_same()) { - return [fun](const Collection & c){ return emp::from_string(fun(c)); }; - } - else if constexpr (std::is_same() && std::is_same()) { + if constexpr (std::is_same()) { return [fun](const Population & p){ return fun( Collection(p) ); }; } else return fun; @@ -178,11 +165,10 @@ namespace mabe { /// Build a function that takes a trait equation, builds it, and runs it on a container. /// Output is a function in the form: TO_T(const FROM_T &, string equation, TO_T default) - template - auto BuildTraitFunction(const std::string & fun_type, TO_T default_val=TO_T{}) { - return [this,fun_type,default_val](FROM_T & pop, const std::string & equation) { - if (pop.IsEmpty()) return default_val; - auto trait_fun = BuildTraitSummary(equation, fun_type, pop.GetDataLayout()); + template + auto BuildTraitFunction(const std::string & fun_type) { + return [this,fun_type](FROM_T & pop, const std::string & equation) { + auto trait_fun = BuildTraitSummary(equation, fun_type, pop.GetDataLayout()); return trait_fun(pop); }; } @@ -223,37 +209,37 @@ namespace mabe { control.MoveOrgs(from_pop, to_pop, false); return 0; }, "Move all organisms organisms from another population, adding after current orgs." ); - pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + pop_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), "Return the value of the provided trait for the first organism"); - pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + pop_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), "Count the number of distinct values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + pop_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), "Identify the most common value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + pop_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), "Calculate the average value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + pop_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), "Find the smallest value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + pop_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), "Find the largest value of a trait (or equation)."); - pop_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + pop_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), "Find the index of the smallest value of a trait (or equation)."); - pop_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + pop_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), "Find the index of the largest value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + pop_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), "Find the 50-percentile value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + pop_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), "Find the variance of the distribution of values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + pop_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), "Find the variance of the distribution of values of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + pop_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), "Add up the total value of a trait (or equation)."); - pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + pop_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), "Determine the entropy of values for a trait (or equation)."); pop_type.AddMemberFunction("FIND_MIN", [this](Population & pop, const std::string & trait_equation) -> Collection { if (pop.GetNumOrgs() == 0) Collection{}; auto trait_fun = - BuildTraitSummary(trait_equation, "min_id", pop.GetDataLayout()); + BuildTraitSummary(trait_equation, "min_id", pop.GetDataLayout()); return pop.IteratorAt(trait_fun(pop)).AsPosition(); }, "Produce OrgList with just the org with the minimum value of the provided function."); @@ -261,7 +247,7 @@ namespace mabe { [this](Population & pop, const std::string & trait_equation) -> Collection { if (pop.GetNumOrgs() == 0) Collection{}; auto trait_fun = - BuildTraitSummary(trait_equation, "max_id", pop.GetDataLayout()); + BuildTraitSummary(trait_equation, "max_id", pop.GetDataLayout()); return pop.IteratorAt(trait_fun(pop)).AsPosition(); }, "Produce OrgList with just the org with the minimum value of the provided function."); @@ -278,37 +264,37 @@ namespace mabe { }, "Produce OrgList with just the orgs that pass through the filter criteria."); - collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), + collect_type.AddMemberFunction("TRAIT", BuildTraitFunction("0"), "Return the value of the provided trait for the first organism"); - collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), + collect_type.AddMemberFunction("CALC_RICHNESS", BuildTraitFunction("richness"), "Count the number of distinct values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), + collect_type.AddMemberFunction("CALC_MODE", BuildTraitFunction("mode"), "Identify the most common value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), + collect_type.AddMemberFunction("CALC_MEAN", BuildTraitFunction("mean"), "Calculate the average value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), + collect_type.AddMemberFunction("CALC_MIN", BuildTraitFunction("min"), "Find the smallest value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), + collect_type.AddMemberFunction("CALC_MAX", BuildTraitFunction("max"), "Find the largest value of a trait (or equation)."); - collect_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), + collect_type.AddMemberFunction("ID_MIN", BuildTraitFunction("min_id"), "Find the index of the smallest value of a trait (or equation)."); - collect_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), + collect_type.AddMemberFunction("ID_MAX", BuildTraitFunction("max_id"), "Find the index of the largest value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), + collect_type.AddMemberFunction("CALC_MEDIAN", BuildTraitFunction("median"), "Find the 50-percentile value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), + collect_type.AddMemberFunction("CALC_VARIANCE", BuildTraitFunction("variance"), "Find the variance of the distribution of values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), + collect_type.AddMemberFunction("CALC_STDDEV", BuildTraitFunction("stddev"), "Find the variance of the distribution of values of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), + collect_type.AddMemberFunction("CALC_SUM", BuildTraitFunction("sum"), "Add up the total value of a trait (or equation)."); - collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), + collect_type.AddMemberFunction("CALC_ENTROPY", BuildTraitFunction("entropy"), "Determine the entropy of values for a trait (or equation)."); collect_type.AddMemberFunction("FIND_MIN", [this](Collection & collect, const std::string & trait_equation) -> Collection { if (collect.IsEmpty()) return Collection{}; auto trait_fun = - BuildTraitSummary(trait_equation, "min_id", collect.GetDataLayout()); + BuildTraitSummary(trait_equation, "min_id", collect.GetDataLayout()); return collect.IteratorAt(trait_fun(collect)).AsPosition(); }, "Produce OrgList with just the org with the minimum value of the provided function."); @@ -316,7 +302,7 @@ namespace mabe { [this](Collection & collect, const std::string & trait_equation) -> Collection { if (collect.IsEmpty()) return Collection{}; auto trait_fun = - BuildTraitSummary(trait_equation, "max_id", collect.GetDataLayout()); + BuildTraitSummary(trait_equation, "max_id", collect.GetDataLayout()); return collect.IteratorAt(trait_fun(collect)).AsPosition(); }, "Produce OrgList with just the org with the minimum value of the provided function."); From d6b774b9d12c6df4b8780583e10498fb635e3de6 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Fri, 31 Dec 2021 13:12:59 -0500 Subject: [PATCH 431/445] Setup data_collect.hpp to use Symbol_Var rather than explicity std::string and double. --- source/core/data_collect.hpp | 64 +++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/source/core/data_collect.hpp b/source/core/data_collect.hpp index 09b7b6dd..01d2c74f 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2020. + * @date 2020-2021. * * @file data_collect.hpp * @brief Functions to collect data from containers. @@ -20,31 +20,33 @@ #include #include "emp/tools/string_utils.hpp" +#include "../Emplode/Symbol.hpp" -namespace emp { +namespace mabe { namespace DataCollect { + using Symbol_Var = emplode::Symbol_Var; // Return the value at a specified index. template - std::string Index(const CONTAIN_T & container, FUN_T get_fun, const size_t index) { - if (container.size() <= index) return "Nan"s; - return emp::to_string( get_fun( container.At(index) ) ); + Symbol_Var Index(const CONTAIN_T & container, FUN_T get_fun, const size_t index) { + if (container.size() <= index) return std::string{"nan"}; + return get_fun( container.At(index) ); } // Count up the number of distinct values. template - auto Unique(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Unique(const CONTAIN_T & container, FUN_T get_fun) { std::unordered_set vals; for (const auto & entry : container) { vals.insert( get_fun(entry) ); } - return emp::to_string(vals.size()); + return vals.size(); } template - auto Mode(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Mode(const CONTAIN_T & container, FUN_T get_fun) { std::map vals; for (const auto & entry : container) { vals[ get_fun(entry) ]++; @@ -58,11 +60,11 @@ namespace emp { mode_val = cur_val; } } - return emp::to_string(mode_val); + return mode_val; } template - auto Min(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Min(const CONTAIN_T & container, FUN_T get_fun) { DATA_T min{}; if constexpr (std::is_arithmetic_v) { min = std::numeric_limits::max(); @@ -74,11 +76,11 @@ namespace emp { const DATA_T cur_val = get_fun(entry); if (cur_val < min) min = cur_val; } - return emp::to_string(min); + return min; } template - auto Max(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Max(const CONTAIN_T & container, FUN_T get_fun) { DATA_T max{}; if constexpr (std::is_arithmetic_v) { max = std::numeric_limits::lowest(); @@ -87,11 +89,11 @@ namespace emp { const DATA_T cur_val = get_fun(entry); if (cur_val > max) max = cur_val; } - return emp::to_string(max); + return max; } template - auto MinID(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var MinID(const CONTAIN_T & container, FUN_T get_fun) { DATA_T min{}; if constexpr (std::is_arithmetic_v) { min = std::numeric_limits::max(); @@ -106,11 +108,11 @@ namespace emp { if (cur_val < min) { min = cur_val; min_id = id; } ++id; } - return emp::to_string(min_id); + return min_id; } template - auto MaxID(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var MaxID(const CONTAIN_T & container, FUN_T get_fun) { DATA_T max{}; if constexpr (std::is_arithmetic_v) { max = std::numeric_limits::lowest(); @@ -122,11 +124,11 @@ namespace emp { if (cur_val > max) { max = cur_val; max_id = id; } ++id; } - return emp::to_string(max_id); + return max_id; } template - auto Mean(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Mean(const CONTAIN_T & container, FUN_T get_fun) { if constexpr (std::is_arithmetic_v) { double total = 0.0; size_t count = 0; @@ -134,24 +136,24 @@ namespace emp { total += (double) get_fun(entry); count++; } - return emp::to_string( total / count ); + return total / count; } return std::string{"nan"}; } template - auto Median(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Median(const CONTAIN_T & container, FUN_T get_fun) { emp::vector values(container.size()); size_t count = 0; for (const auto & entry : container) { values[count++] = get_fun(entry); } emp::Sort(values); - return emp::to_string( values[count/2] ); + return values[count/2]; } template - auto Variance(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Variance(const CONTAIN_T & container, FUN_T get_fun) { if constexpr (std::is_arithmetic_v) { double total = 0.0; const double N = (double) container.size(); @@ -165,13 +167,13 @@ namespace emp { var_total += cur_val * cur_val; } - return emp::to_string( var_total / (N-1) ); + return var_total / (N-1); } return std::string{"nan"}; } template - auto StandardDeviation(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var StandardDeviation(const CONTAIN_T & container, FUN_T get_fun) { if constexpr (std::is_arithmetic_v) { double total = 0.0; const double N = (double) container.size(); @@ -185,25 +187,25 @@ namespace emp { var_total += cur_val * cur_val; } - return emp::to_string( sqrt(var_total / (N-1)) ); + return sqrt(var_total / (N-1)); } return std::string{"nan"}; } template - auto Sum(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Sum(const CONTAIN_T & container, FUN_T get_fun) { if constexpr (std::is_arithmetic_v) { double total = 0.0; for (const auto & entry : container) { total += (double) get_fun(entry); } - return emp::to_string( total ); + return total; } return std::string{"nan"}; } template - auto Entropy(const CONTAIN_T & container, FUN_T get_fun) { + Symbol_Var Entropy(const CONTAIN_T & container, FUN_T get_fun) { std::map vals; for (const auto & entry : container) { vals[ get_fun(entry) ]++; @@ -214,12 +216,12 @@ namespace emp { double p = ((double) count) / (double) N; entropy -= p * log2(p); } - return emp::to_string(entropy); + return entropy; } } // End namespace DataCollect template - std::function + std::function BuildCollectFun(std::string action, FUN_T get_fun) { // ### DEFAULT // If no trait function is specified, assume that we should use the first index. @@ -317,7 +319,7 @@ namespace emp { }; } - return std::function(); + return std::function(); } } From 1d0dcc76b810676690604569ab7bbca9c021c5e3 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 3 Jan 2022 16:10:29 -0500 Subject: [PATCH 432/445] Cleaned up Symbol_Var to use emp::Datum under the hood. --- source/Emplode/Symbol.hpp | 68 ++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index 2e19c07c..068c5f27 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2021. + * @date 2019-2022. * * @file Symbol.hpp * @brief Manages a single configuration entry (e.g., variables + base for scopes and functions). @@ -27,6 +27,7 @@ #include "emp/base/assert.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" +#include "emp/data/Datum.hpp" #include "emp/math/Range.hpp" #include "emp/meta/meta.hpp" #include "emp/meta/TypeID.hpp" @@ -121,6 +122,7 @@ namespace emplode { virtual double AsDouble() const { return std::nan("NaN"); } virtual std::string AsString() const { return "[[__INVALID SYMBOL CONVERSION__]]"; } + virtual void Print(std::ostream & os) const { os << AsString(); } virtual Symbol & SetValue(double in) { (void) in; emp_assert(false, in); return *this; } virtual Symbol & SetString(const std::string & in) { (void) in; emp_assert(false, in); return *this; } @@ -305,55 +307,39 @@ namespace emplode { /// A symbol for an internally maintained variable. class Symbol_Var : public Symbol { private: - double num_value = 0.0; - std::string str_value = ""; - bool is_num = true; + emp::Datum value; + + using scope_ptr_t = emp::Ptr; public: - Symbol_Var(const std::string & in_name, - double in_val, - const std::string & in_desc="", - emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope), num_value(in_val), is_num(true) {} - Symbol_Var(const std::string & in_name, - const std::string & in_val, - const std::string & in_desc="", - emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope), str_value(in_val), is_num(false) {} - Symbol_Var(const std::string & in_name, - const Symbol_Var & in_val, - const std::string & in_desc="", - emp::Ptr in_scope=nullptr) - : Symbol(in_name, in_desc, in_scope) - , num_value(in_val.num_value), str_value(in_val.str_value), is_num(in_val.is_num) {} + Symbol_Var(const std::string & _n, double _v, const std::string & _d="", scope_ptr_t _s=nullptr) + : Symbol(_n, _d, _s), value(_v) {} + Symbol_Var(const std::string & _n, const std::string & _v, const std::string & _d="", scope_ptr_t _s=nullptr) + : Symbol(_n, _d, _s), value(_v) {} + Symbol_Var(const std::string & _n, const emp::Datum & _v, const std::string & _d="", scope_ptr_t _s=nullptr) + : Symbol(_n, _d, _s), value(_v) {} + Symbol_Var(const std::string & _n, const Symbol_Var & _v, const std::string & _d="", scope_ptr_t _s=nullptr) + : Symbol(_n, _d, _s), value(_v.value) {} + Symbol_Var(const Symbol_Var &) = default; - Symbol_Var(double _val) - : Symbol("__Auto__", "", nullptr), num_value(_val), is_num(true) {} - Symbol_Var(const std::string & _val) - : Symbol("__Auto__", "", nullptr), str_value(_val), is_num(false) {} + Symbol_Var(double _val) : Symbol("__Auto__", "", nullptr), value(_val) {} + Symbol_Var(const std::string & _val) : Symbol("__Auto__", "", nullptr), value(_val) {} + Symbol_Var(const emp::Datum & _val) : Symbol("__Auto__", "", nullptr), value(_val) {} std::string GetTypename() const override { return "Var"; } symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } - double AsDouble() const override { - return is_num ? num_value : emp::from_string(str_value); - } - std::string AsString() const override { - return is_num ? emp::to_string(num_value) : str_value; - } - Symbol & SetValue(double in) override { - num_value = in; - is_num = true; - return *this; - } - Symbol & SetString(const std::string & in) override { - str_value = in; - is_num = false; - return *this; + double AsDouble() const override { return value.AsDouble(); } + std::string AsString() const override { return value.AsString(); } + void Print(std::ostream & os) const override { + if (value.IsDouble()) os << value.NativeDouble(); + else os << value.NativeString(); } + Symbol & SetValue(double in) override { value = in; return *this; } + Symbol & SetString(const std::string & in) override { value = in; return *this; } - bool IsNumeric() const override { return is_num; } - bool IsString() const override { return !is_num; } + bool IsNumeric() const override { return value.IsDouble(); } + bool IsString() const override { return value.IsString(); } bool IsLocal() const override { return true; } bool CopyValue(const Symbol & in) override { From 2ab7fee17e339f090831b9c6056e235c402f7e79 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 3 Jan 2022 16:11:11 -0500 Subject: [PATCH 433/445] Allow Datum to be a viable return type for Emplode functions. --- source/Emplode/SymbolTableBase.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp index e36d662e..36cee66f 100644 --- a/source/Emplode/SymbolTableBase.hpp +++ b/source/Emplode/SymbolTableBase.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. + * @date 2021-2022. * * @file SymbolTableBase.hpp * @brief Tools for working with Symbol objects, especially for wrapping functions. @@ -73,6 +73,7 @@ namespace emplode { // If a return value is a basic type, wrap it in a temporary symbol else if constexpr (std::is_same() || std::is_arithmetic() || + std::is_same() || std::is_same()) { return MakeTempSymbol(value); } From 07bab5c5ce8aa882a25e82ba1df2333614b722ed Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 3 Jan 2022 16:12:15 -0500 Subject: [PATCH 434/445] Setup Emplode::Execute() to return emp::Datum to allow for either double or string. --- source/Emplode/Emplode.hpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp index 4778371a..0c1627ae 100644 --- a/source/Emplode/Emplode.hpp +++ b/source/Emplode/Emplode.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2019-2021. + * @date 2019-2022. * * @file Emplode.hpp * @brief Manages all configuration with Emplode language. @@ -75,6 +75,8 @@ #include "emp/base/assert.hpp" #include "emp/base/map.hpp" +#include "emp/base/notify.hpp" +#include "emp/data/Datum.hpp" #include "emp/meta/TypeID.hpp" #include "emp/tools/string_utils.hpp" @@ -130,7 +132,7 @@ namespace emplode { // 'PRINT' is a simple debugging command to output the value of a variable. auto print_fun = [](const emp::vector> & args) { - for (auto entry_ptr : args) std::cout << entry_ptr->AsString(); + for (auto entry_ptr : args) entry_ptr->Print(std::cout); std::cout << std::endl; return 0; }; @@ -286,8 +288,8 @@ namespace emplode { ast_root.AddChild(cur_block); } - // Load the provided statement and run it. - std::string Execute(std::string_view statement, emp::Ptr scope=nullptr) { + // Load the provided statement, run it, and return the resulting value. + emp::Datum Execute(std::string_view statement, emp::Ptr scope=nullptr) { if (!scope) scope = &symbol_table.GetRootScope(); // Default scope to root level. auto tokens = lexer.Tokenize(statement, "eval command"); // Convert to a TokenStream. tokens.push_back(lexer.ToToken(";")); // Ensure a semi-colon at end. @@ -302,10 +304,11 @@ namespace emplode { // Process just the expressions so that we can get a result from it. auto result_ptr = cur_expr->Process(); // Process AST to get result symbol. - std::string result = ""; // Default result to an empty string. + emp::Datum result; if (result_ptr) { - result = result_ptr->AsString(); // Convert result to output string. - if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete the result symbol if done. + if (result_ptr->IsNumeric()) result = result_ptr->AsDouble(); // Result is numeric output. + else result = result_ptr->AsString(); // Result is string output. + if (result_ptr->IsTemporary()) result_ptr.Delete(); // Delete temp result symbol. } cur_block.Delete(); // Delete the temporary AST. return result; // Return the result string. From 3086989a61d9815481f498dcfaed05c1b8377a37 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 3 Jan 2022 16:13:15 -0500 Subject: [PATCH 435/445] Setup MABE Pre-process to allow for extra numeric values to be maintained exactly. --- source/core/MABEScript.hpp | 59 +++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp index 4d6017f4..9f834cf6 100644 --- a/source/core/MABEScript.hpp +++ b/source/core/MABEScript.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of MABE, https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. + * @date 2021-2022. * * @file MABEScript.hpp * @brief Customized Emplode scripting language instance for MABE runs. @@ -44,12 +44,17 @@ namespace mabe { using Symbol_Var = emplode::Symbol_Var; + struct PreprocessResults { + std::string result; // Updated string + emp::vector values; // Numerical values kept aside, if preserve_nums=true; + }; + public: /// Build a function to scan a data map, run a provided equation on its entries, /// and return the result. auto BuildTraitEquation(const emp::DataLayout & data_layout, std::string equation) { - equation = Preprocess(equation); - auto dm_fun = dm_parser.BuildMathFunction(data_layout, equation); + auto pp_equ = Preprocess(equation, true); + auto dm_fun = dm_parser.BuildMathFunction(data_layout, pp_equ.result, pp_equ.values); return [dm_fun](const Organism & org){ return dm_fun(org.GetDataMap()); }; } @@ -59,29 +64,43 @@ namespace mabe { } /// Find any instances of ${X} and eval the X. - std::string Preprocess(const std::string & in_string) { - std::string out_string = in_string; + PreprocessResults Preprocess(const std::string & in_string, bool preserve_nums=false) { + PreprocessResults pp_out; + pp_out.result = in_string; // Seek out instances of "${" to indicate the start of pre-processing. - for (size_t i = 0; i < out_string.size(); ++i) { - if (out_string[i] != '$') continue; // Replacement tag must start with a '$'. - if (out_string.size() <= i+2) break; // Not enough room for a replacement tag. - if (out_string[i+1] == '$') { // Compress two $$ into one $ - out_string.erase(i,1); + for (size_t i = 0; i < pp_out.result.size(); ++i) { + if (pp_out.result[i] != '$') continue; // Replacement tag must start with a '$'. + if (pp_out.result.size() <= i+2) break; // Not enough room for a replacement tag. + if (pp_out.result[i+1] == '$') { // Compress two $$ into one $ + pp_out.result.erase(i,1); continue; } - if (out_string[i+1] != '{') continue; // Eval must be surrounded by braces. + if (pp_out.result[i+1] != '{') continue; // Eval must be surrounded by braces. // If we made it this far, we have a starting match! - size_t end_pos = emp::find_paren_match(out_string, i+1, '{', '}', false); - if (end_pos == i+1) return out_string; // No end brace found! @CAO -- exception here? - const std::string new_text = Execute(emp::view_string_range(out_string, i+2, end_pos)); - out_string.replace(i, end_pos-i+1, new_text); - - i += new_text.size(); // Continue from the end point... + size_t end_pos = emp::find_paren_match(pp_out.result, i+1, '{', '}', false); + if (end_pos == i+1) { + emp::notify::Warning("In pre-processing:\n '", in_string, + "',\nfound '${' with no matching '}'."); + return pp_out; // Stop where we are... No end brace found! + } + emp::Datum replacement = Execute(emp::view_string_range(pp_out.result, i+2, end_pos)); + // Test if we should drop the replacement results directly in-line. + if (!preserve_nums || replacement.IsString()) { + std::string new_str = replacement.AsString(); // Get new text. + pp_out.result.replace(i, end_pos-i+1, new_str); // Put into place. + i += new_str.size(); // Continue at the next position... + } + else { // Replacement is numerical and needs to be preserved... + std::string new_str = emp::to_string("$",pp_out.values.size()); // Generate the '$#' + pp_out.result.replace(i, end_pos-i+1, new_str); // Put it in place. + pp_out.values.push_back(replacement.NativeDouble()); // Store associated value + i += new_str.size(); // Find continue pos + } } - return out_string; + return pp_out; } @@ -119,7 +138,7 @@ namespace mabe { "BuildTraitSummary FROM_T must be Collection or Population." ); // Pre-process the trait function to allow for use of regular config variables. - trait_fun = Preprocess(trait_fun); + trait_fun = Preprocess(trait_fun).result; // The trait input has two components: // (1) the trait (or trait function) and @@ -319,7 +338,7 @@ namespace mabe { AddFunction("GET_VERBOSE", [this](){ return control.GetVerbose(); }, "Has the verbose flag been set?"); std::function preprocess_fun = - [this](const std::string & str) { return Preprocess(str); }; + [this](const std::string & str) { return Preprocess(str).result; }; AddFunction("PP", preprocess_fun, "Preprocess a string (replacing any ${...} with result.)"); // Add in built-in event triggers; these are used to indicate when events should happen. From 6637641cd3a829188bede2c278b9f442a7e61e41 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 20 Jan 2022 17:47:52 -0500 Subject: [PATCH 436/445] Another shift to using notify::Error in Parser. --- source/Emplode/Parser.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp index de70c710..7e111c5f 100644 --- a/source/Emplode/Parser.hpp +++ b/source/Emplode/Parser.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. + * @date 2021-2022. * * @file Parser.hpp * @brief Manages parsing of Emplode language input streams. @@ -121,8 +121,9 @@ namespace emplode { template void Error(Ts &&... args) const { std::string line_info = pos.AtEnd() ? "end of input" : emp::to_string("line ", pos->line_id); - std::cout << "Error (" << line_info << " in '" << pos.GetTokenStream().GetName() << "'): " - << emp::to_string(std::forward(args)...) << "\nAborting." << std::endl; + + emp::notify::Error("(", line_info, " in '", pos.GetTokenStream().GetName(), "'): ", + emp::to_string(std::forward(args)...), "\nAborting."); exit(1); } From 77d508446106e7d071ab4d99e1ce201030bb873e Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 20 Jan 2022 17:48:37 -0500 Subject: [PATCH 437/445] Explicitly include error.hpp in Symbol.hpp; need to shift more to notify. --- source/Emplode/Symbol.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/Emplode/Symbol.hpp b/source/Emplode/Symbol.hpp index 068c5f27..bd334a8d 100644 --- a/source/Emplode/Symbol.hpp +++ b/source/Emplode/Symbol.hpp @@ -25,6 +25,7 @@ #include #include "emp/base/assert.hpp" +#include "emp/base/error.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" #include "emp/data/Datum.hpp" From 8c55cd518edb58c280a34027981dd8399e14bbc5 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Thu, 20 Jan 2022 17:49:12 -0500 Subject: [PATCH 438/445] Remove description of ErrorManager.hpp from DeveloperNotes.md --- source/core/DeveloperNotes.md | 1 - 1 file changed, 1 deletion(-) diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 38250adc..a25fe407 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -6,7 +6,6 @@ changed for individual experiments. The core components of MABE are below. This first group are tools that have minimal internal dependencies (indicated by indentation below the requirement). data_collect.hpp - Tools to extract data from elements in a container. -ErrorManager.hpp - Track any run-time errors as they occur. SigListener.hpp - Tool to trigger a specified member function on other classes when triggered. TraitInfo.hpp - Specifications for module/trait interactions (on organisms, populations, etc.) TraitManager.hpp - Manager for dealing with many requests for trait management. From 7a2c92047a8fff9ff6ab23bda881801491923db2 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Mon, 24 Jan 2022 23:59:00 -0500 Subject: [PATCH 439/445] Added error.hpp include. --- source/Emplode/Symbol_Object.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp index 7101c776..3e145ea8 100644 --- a/source/Emplode/Symbol_Object.hpp +++ b/source/Emplode/Symbol_Object.hpp @@ -1,7 +1,7 @@ /** * @note This file is part of Emplode, currently within https://github.com/mercere99/MABE2 * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md - * @date 2021. + * @date 2021-2022. * * @file Symbol_Object.hpp * @brief Extension of scope when there is an external object associated with the structure. @@ -11,6 +11,7 @@ #ifndef EMPLODE_SYMBOL_OBJECT_HPP #define EMPLODE_SYMBOL_OBJECT_HPP +#include "emp/base/error.hpp" #include "emp/base/map.hpp" #include "EmplodeType.hpp" From 3f4f538982853660b3c90a6b31fc1ea5f0c650ff Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 25 Jan 2022 14:49:39 -0500 Subject: [PATCH 440/445] Updated Mancala config to newest version of MABE. --- settings/Mancala.mabe | 95 ++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/settings/Mancala.mabe b/settings/Mancala.mabe index 482b3c8a..58b4621d 100644 --- a/settings/Mancala.mabe +++ b/settings/Mancala.mabe @@ -1,19 +1,19 @@ -random_seed = 1; // Seed for random number generator; use 0 to base on time. +random_seed = 2; // Seed for random number generator; use 0 to base on time. Population main_pop; // Collection of organisms Population next_pop; // Collection of organisms -Value pop_size = 200; // Local value variable. -CommandLine cl { // Handle basic I/O on the command line. - target = "main_pop"; // Which population should we print stats about? - format = "fitness:max,fitness:mean"; // Column format to use in the file. -} -FileOutput output { // Output collected data into a specified file. - filename = "output.csv"; // Name of file for output data. - format = "fitness:max,fitness:mean"; // Column format to use in the file. - target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:1"; // Which updates should we output data? -} +Var pop_size = 200; // Local value variable. + + +AvidaGPOrg avida_org { // Organism consisting of Avida instructions. + mut_prob = 0.01; // Probability of each instruction mutating on reproduction. + N = 50; // Initial number of instructions in genome + init_random = 1; // Should we randomize ancestor? (0 = "blank" default) + eval_time = 200; // How many CPU cycles should we give organisms to run? + input_name = "input"; // Where to find inputs + output_name = "output"; // Where to write outputs +}; + EvalMancala eval { // Evaluate organisms on their ability to play Mancala. - target = "main_pop"; // Which population(s) should we evaluate? input_trait = "input"; // Into which trait should input values be placed? output_trait = "output"; // Out of which trait should output values be read? scoreA_trait = "scoreA"; // Trait to save score for this player. @@ -24,33 +24,46 @@ EvalMancala eval { // Evaluate organisms on their ability to play M // random: Always choose a random, legal move. // ai: Human supplied (but not very good) AI // random_org: Pick another random organism from collection. -} -SelectTournament select_t { // Select the top fitness organisms from random subgroups for replication. - select_pop = "main_pop"; // Which population should we select parents from? - birth_pop = "next_pop"; // Which population should births go into? +}; + +SelectTournament select { // Select top fitness orgs from random subgroups for replication. tournament_size = 7; // Number of orgs in each tournament - num_tournaments = pop_size; // Number of tournaments to run - fitness_trait = "fitness"; // Which trait provides the fitness value to use? -} - -GrowthPlacement place_next { // Always append births to the end of a population. - target = "main_pop,next_pop"; // Population(s) to manage. -} -MovePopulation sync_gen { // Move organisms from one population to another. - from_pop = "next_pop"; // Population to move organisms from. - to_pop = "main_pop"; // Population to move organisms into. - reset_to = 1; // Should we erase organisms at the destination? -} -AvidaGPOrg avida_org { // Organism consisting of Avida instructions. - mut_prob = 0.01; // Probability of each instruction mutating on reproduction. - N = 50; // Initial number of instructions in genome - init_random = 1; // Should we randomize ancestor? (0 = "blank" default) - eval_time = 200; // How many CPU cycles should we give organisms to run? - input_name = "input"; // Where to find inputs - output_name = "output"; // Where to write outputs -} + fitness_fun = "scoreA - scoreB - num_errors*10"; // How should we calculate fitness? +}; + +DataFile fit_file { filename="fitness.csv"; }; +fit_file.ADD_COLUMN( "Average Fitness", "main_pop.CALC_MEAN('fitness')" ); +fit_file.ADD_COLUMN( "Maximum Fitness", "main_pop.CALC_MAX('fitness')" ); +fit_file.ADD_COLUMN( "Dominant Fitness", "main_pop.CALC_MODE('fitness')" ); + + +@START() { + PRINT("random_seed = ", random_seed, "\n"); // Print seed at run start. + main_pop.INJECT("avida_org", pop_size); // Inject starting population. +}; + +@UPDATE(Var ud) { + IF (ud == 300) EXIT(); + PRINT("UPDATE: ", ud); + + eval.EVAL(main_pop); + Var mode_fit = main_pop.CALC_MODE("fitness"); + OrgList list_less = main_pop.FILTER("fitness < ${mode_fit}"); + OrgList list_equ = main_pop.FILTER("fitness == ${mode_fit}"); + OrgList list_gtr = main_pop.FILTER("fitness > ${mode_fit}"); + PRINT("MainPopSize=", main_pop.SIZE(), + " AveFitness=", main_pop.CALC_MEAN("fitness"), + " MaxFitness=", main_pop.CALC_MAX("fitness"), + " ModeFitness=", mode_fit, + "\nMODE_LESS=", list_less.SIZE(), + " MODE_EQU=", list_equ.SIZE(), + " MODE_GTR=", list_gtr.SIZE(), + ); + fit_file.WRITE(); + // max_file.WRITE(); + + OrgList offspring = select.SELECT(main_pop, next_pop, pop_size); + main_pop.REPLACE_WITH(next_pop); +}; -@start(0) PRINT("random_seed = ", random_seed, "\n"); -@start(0) INJECT("avida_org", "main_pop", pop_size); -@update(1000) EXIT(); -@update(10,10) TRACE_EVAL("output.dat", "main_pop", 0); +// @update(10,10) TRACE_EVAL("output.dat", "main_pop", 0); From 6f837c210a87846dd1c429e64de5b41d089af598 Mon Sep 17 00:00:00 2001 From: Charles Ofria Date: Tue, 25 Jan 2022 14:50:05 -0500 Subject: [PATCH 441/445] Minor cleanups on NK.mabe (no change in functionality) --- settings/NK.mabe | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/settings/NK.mabe b/settings/NK.mabe index a8179005..6b86723d 100644 --- a/settings/NK.mabe +++ b/settings/NK.mabe @@ -50,9 +50,7 @@ max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); // @BEFOREDIVIDE(OrgList parent IN altruists) PRINT("Altruist Birth!"); @UPDATE(Var ud) { - // @UPDATE(Var update) { - // IF ([10:10].HAS(update)) EXIT(); - // IF (update == 1000) EXIT; + IF (ud == 1000) EXIT(); eval_nk.EVAL(main_pop); Var mode_fit = main_pop.CALC_MODE("fitness"); @@ -78,5 +76,3 @@ max_file.ADD_COLUMN( "Genome", "best_org.TRAIT('bits')" ); main_pop.REPLACE_WITH(next_pop); } - -@UPDATE(Var ud2) IF (ud2 == 1000) EXIT(); From d931910673f4799e8510aca28829b12d0024905a Mon Sep 17 00:00:00 2001 From: Acacia Ackles Date: Thu, 17 Feb 2022 14:06:46 -0500 Subject: [PATCH 442/445] Remove quickstart guide because of config file changes --- docs/first_steps/01_quickstart.rst | 174 +---------------------------- 1 file changed, 1 insertion(+), 173 deletions(-) diff --git a/docs/first_steps/01_quickstart.rst b/docs/first_steps/01_quickstart.rst index 686125d9..95771dfb 100644 --- a/docs/first_steps/01_quickstart.rst +++ b/docs/first_steps/01_quickstart.rst @@ -1,176 +1,4 @@ ========== Quickstart ========== - -The ``.mabe`` File -------------------- - -The ``.mabe`` file is generated from your chosen set of modules, and is the configuration page -that you can use to run your experiments! In the ``.mabe`` file, you can change variables that -have been set up in the different modules connected to the ``.mabe`` file. - -To generate your ``.mabe`` file, first you will want to make sure that you have run the ``make`` command -since your last updates. To do so, navigate to the ``build`` directory and set up a clean run. From the ``MABE2`` directory, run the following commands: - -.. code-block:: - - cd build - make clean ; make - -If there are any errors that pop up, now is the time to fix them! - -Next, you will navigate to the ``settings`` directory inside of ``build``, and check to see you have an appropriate ``.gen`` file. To do so, first make sure you -are in the ``build`` directory, and then list all of the files in the settings to make sure the ``.gen`` file is there. - -.. code-block:: - - cd settings - ls - -From here, you'll want to make sure you can see your .gen file. You should see a file labled ``.gen``. If not, you can -follow the directions to `write your .gen file <000_write_gen_file.html>`_. - - -Now we're ready to create our ``.mabe`` file! To do so, we'll navigate back up to ``build`` and then create the ``.mabe`` file. You'll run the -following commands to do so: - -.. code-block:: - - cd .. - ./MABE -f settings/.gen -g settings/.mabe - - -This will generate a ``.mabe`` file named ``.mabe`` with the specifications from the ``.gen`` file. -You can check to see that it exists by going into the ``settings`` directory and checking that it's there. To do so, run the following: - -.. code-block:: - - cd settings - ls - -Congratulations! You've created your first ``.mabe`` file! - -Summary -********* - -Step 1: In the ``build`` directory, run the following: - -.. code-block:: - - make clean ; make - - -Step 2: Then run these commands to make sure your ``.gen`` file exists. - -.. code-block:: - - cd settings - ls - -If you don't see it, you can create a ``.gen`` file by following the steps -to `write your .gen file <000_write_gen_file.html>`_. - -Step 3: Create your ``.mabe`` file and check to make sure it's created by running the following: - -.. code-block:: - - cd .. - ./MABE -f settings/.gen -g settings/.mabe - cd settings - ls - - -Running the ``.mabe`` File ---------------------------- - -To run your ``.mabe`` file, navigate to the ``build`` directory and run your ``.mabe`` file. To do so, start in the ``MABE2`` folder and run the following commands: - -.. code-block:: - - cd build - ./MABE -f settings/.mabe - - -Changing the ``.mabe`` File ---------------------------- - -You can modify your experiment by changing the variable values inside of the ``.mabe`` file. - -If you don't have the ``.mabe`` file open, simply open it in your text editor of choice. - -From there, modifying your ``.mabe`` file is as easy as changing the values associated with the specific variable(s) that -you want to change. You can even add new variables, as long as they only rely on information you are accessing from the ``.mabe`` file -you're editing! However, if you want a new variable that takes new data inputs, then you will need to modify the specific module -that is associated with gathering that data. You can learn more about the different modules and what they do by reading the documentation for each -module, located in the `Modules Page <../modules/00_module_overview.html>`_ . - -To run your modified ``.mabe`` file, first make sure you have saved your file, then simply run the following command from the ``build`` directory: - -.. code-block:: - - ./MABE -f settings/.mabe - - - -Viewing and Saving Your Data ------------------------------ - -The data you have collected has been saved in a CSV file called ``output.csv``, which is located in the ``build`` directory. -From the main ``MABE2`` folder, you can find this file by running the following commands: - -.. code-block:: - - cd build - ls - -To open the file, you can do so from the terminal, -or navigate to the same ``build`` folder from your file manager and open the file from there. - -Every time that you run your ``.mabe`` file, ``output.csv`` is overwritten, which means that it is important that if you want to save your data, you do so between -runs. There are a couple of ways to save your data. - -Copy the CSV File -***************** - -The first way to save your data is to create a copy of ``output.csv`` (which can be done by through your file manager). -Since there is a copy of the CSV file, you can run your ``.mabe`` file again and not worry about losing your data. - -Create a New CSV File from ``.mabe`` -************************************* - -The second way to save your data is to modify the ``.mabe`` file itself so that it saves in a different place. - -To do so, first open the ``.mabe`` file in question in your preferred text editor. It will be in the ``settings`` folder inside of ``build``. - -Within the ``.mabe`` file, there is a section called ``FileOutput``, which looks something like this: - -.. code-block:: - - FileOutput output { // Output collected data into a specified file. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. - filename = "output.csv"; // Name of file for output data. - format = "fitness:max,fitness:mean";// Column format to use in the file. - target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:1"; // Which updates should we output data? - } - -Locate the variable ``filename``. Notice that right now it is labled ``"output.csv"``. -You can modify this name to be something new, and when you run the ``.mabe`` file, a new CSV file -with that name will appear in the same directory as the original ``output.csv`` file. Below is an example -of a new CSV filename inserted called ``NEW_FILE_NAME``. - -.. code-block:: - - FileOutput output { // Output collected data into a specified file. - _active = 1; // Should we activate this module? (0=off, 1=on) - _desc = ""; // Special description for those object. - filename = "NEW_FILE_NAME.csv"; // Name of file for output data. - format = "fitness:max,fitness:mean";// Column format to use in the file. - target = "main_pop"; // Which population(s) should we print from? - output_updates = "0:1"; // Which updates should we output data? - } - -Since the ``.mabe`` file is now saving to ``NEW_FILE_NAME.csv``, the original data in ``output.csv`` is unchanged. - - +Quickstart coming soon. \ No newline at end of file From a3834ffd35e8b54f9d25873b6eb7f118212eb7cd Mon Sep 17 00:00:00 2001 From: Emily Dolson Date: Mon, 7 Mar 2022 01:46:58 -0500 Subject: [PATCH 443/445] Add evaluator docs --- docs/evaluate/00_eval_overview.rst | 152 +++++++++++++++++++++++++++- docs/modules/00_module_overview.rst | 2 +- docs/modules/01_module_types.rst | 2 +- source/third-party/empirical | 2 +- 4 files changed, 154 insertions(+), 4 deletions(-) diff --git a/docs/evaluate/00_eval_overview.rst b/docs/evaluate/00_eval_overview.rst index c1bcacde..bade0c89 100644 --- a/docs/evaluate/00_eval_overview.rst +++ b/docs/evaluate/00_eval_overview.rst @@ -3,4 +3,154 @@ What is an Evaluator? ====================== -Landing page for evaluators; under construction. \ No newline at end of file +Evaluators are modules that measure how well organisms perform under a given circumstance. +They generally correspond to problems/fitness landscapes. +When used on a population, they assign a trait to each organism indicating how well it performed. +In simple configurations, this trait may be used directly as a "fitness" criterion on which to base selection. + +Using an evaluator +------------------ + +In addition to other configurable parameters that are specific to a given evaluator, all evaluators have a parameter called ``fitness_trait``. +This parameter indicates the name of the trait where the score calculated by the evaluator should be stored. + +All evaluators have an ``EVAL`` member function. This function takes a list of organisms as an argument and runs the evaluation function on all of them, storing the results in the trait designated by the ``fitness_trait`` parameter. + +For example, the NK evaluator can be used as follows:: + + // Create a population + Population main_pop; // Main population for managing candidate solutions. + + // Configure an EvalNK module with the name eval_nk + EvalNK eval_nk { // Evaluate bitstrings on an NK fitness lanscape. + N = num_bits; // Number of bits required in output + K = 3; // Number of bits used in each gene + bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? + fitness_trait = "fitness"; // Which trait should we store NK fitness in? + }; + + // Other modules would be configured here + + // Configure what happens on each time step + @UPDATE(Var ud) { + + // Run the evaluator on the population + eval_nk.EVAL(main_pop); + } + + +Writing an evaluator +-------------------- + +Writing an evaluator module is the same as writing any other type of module, except for the following: + +* The constructor must call ``SetEvaluateMod(true);`` to tell MABE that this is an evaluator module. + +* The module must have a config parameter called ``fitness_trait`` that specifies where the calculated fitness should be stored. + +* The module must have a member function named ``EVAL`` that takes a ``Collection`` as input, runs the evaluator on each member of the collection, and stores the result in ``fitness_trait``. + +Example:: + + // All modules should be in the MABE namespace + namespace mabe { + + // All modules should inherit from Module + class EvalExample : public Module { + private: + // The trait that fitness should be stored in + std::string fitness_trait; + + public: + EvalNK(mabe::MABE & control, + const std::string & name="EvalNK", + const std::string & desc="Module to evaluate bitstrings on an NK Fitness Lanscape", + const std::string & _ftrait="fitness") + : Module(control, name, desc) + , fitness_trait(_ftrait) + { + // Notify MABE that this is an Evaluate module + SetEvaluateMod(true); + } + ~EvalNK() { } + + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + // Setup the EVAL function + // The arguments are a reference to this module and a Collection of organisms + // For convenience, this just calls a member function called Evaluate, which + // we'll define a little later + info.AddMemberFunction("EVAL", + [](EvalExample & mod, Collection list) { return mod.Evaluate(list); }, + "Evaluate all orgs in an OrgList."); + + // Other functions you want the user to be able to call in the config file + // could be set up here + } + + // Setup the config settings for this module + // This needs to include fitness_trait, but could also + // include anything else you want the user to be able to configure + void SetupConfig() override { + // Our fitness_trait member variable will end up containing a string + // (which by default is "fitness_trait") containing the name of the trait + // where the fitness will be stored + LinkVar(fitness_trait, "fitness_trait", "Which trait should we store fitness in?"); + + // Other config parameter could be set up here + } + + // Tell the module about any traits that it depends on + void SetupModule() override { + // Setup the traits. + // This module creates the fitness_trait trait, which defaults to 0 + AddOwnedTrait(fitness_trait, "Fitness value", 0.0); + + // Other setup could happen here + } + + // Actually implement Evaluate method that we used earlier in the + // EVAL function + double Evaluate(const Collection & orgs) { + // Loop through the population and evaluate each organism. + + // If you want to only evaluate orgs that are alive, + // it may be helpful to create a new collection that + // only contains the organisms within the original collection + // that are actually alive. + mabe::Collection alive_orgs( orgs.GetAlive() ); + + // Loop over all the organisms + // (alive_orgs could be replaced with orgs if we didn't bother + // with the filtering step above) + for (Organism & org : alive_orgs) { + // If you're going to look at any other traits to evaluate + // fitness, make sure to call GenerateOutput on the organism + // to ensure all traits are populated + org.GenerateOutput(); + + // Figure out what you want this organism's fitness to be + // (you probably want to do something more elaborate here) + double fitness = 1; + + // Set this organisms fitness trait equal to the calculated fitness + org.SetTrait(fitness_trait, fitness); + } + + return max_fitness; + } + + // Alternate version of Evaluate that takes a Population instead of a Collection + // If a population is provided to Evaluate, first convert it to a Collection. + double Evaluate(Population & pop) { return Evaluate( Collection(pop) ); } + + // Alternate version of Evaluate that takes a string instead of a Collection + // If a string is provided to Evaluate, convert it to a Collection. + double Evaluate(const std::string & in) { return Evaluate( control.ToCollection(in) ); } + }; + + // You always need to call MABE_REGISTER_MODULE after defining a new module, + // to inform MABE that the module exists. + // Remember to also add it in modules.hpp as an include. + MABE_REGISTER_MODULE(EvalExample, "Example evaluator."); + } \ No newline at end of file diff --git a/docs/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index cab3dd78..6f3ec985 100644 --- a/docs/modules/00_module_overview.rst +++ b/docs/modules/00_module_overview.rst @@ -9,7 +9,7 @@ MABE2 implements seven types of modules: :ref:`organisms`, evaluators, `selectio `placement modules <01_module_types.html>`_, `schema modules <01_module_types.html>`_, `analysis modules <01_module_types.html>`_, and `interface modules <01_module_types.html>`_. Modules of a given type are interchangeable, so switching from one brain type to another is as simple as changing a brain type parameter. This feature allows -users to focus their efforts specific aspects of their projecrts by only developing or modifying the modules of interest +users to focus their efforts specific aspects of their projects by only developing or modifying the modules of interest to them, by reusing existing modules when possible, and by not requiring detailed understanding of the entirety of MABE2. diff --git a/docs/modules/01_module_types.rst b/docs/modules/01_module_types.rst index ffa87081..ffa0186e 100644 --- a/docs/modules/01_module_types.rst +++ b/docs/modules/01_module_types.rst @@ -42,7 +42,7 @@ states, and internal data structures. Evaluators ========== -For detailed information on genomes, see the `evaluator page <../evaluate/EvalPacking.html>`_ . +For detailed information on genomes, see the `evaluator page <../evaluate/00_eval_overview.html>`_ . Evaluators are the functions on which organisms get tested. Evaluation modules are categorized by the types of IO they work with. diff --git a/source/third-party/empirical b/source/third-party/empirical index b5095369..75b16057 160000 --- a/source/third-party/empirical +++ b/source/third-party/empirical @@ -1 +1 @@ -Subproject commit b509536940feb0db1ecc495bd7f8aae378cd05e8 +Subproject commit 75b16057d18e9dafeeb501101cd120fe1b02cb8d From 02e55822dea4f83f506c4e2a026189469dd2600d Mon Sep 17 00:00:00 2001 From: Emily Dolson Date: Tue, 8 Mar 2022 01:00:48 -0500 Subject: [PATCH 444/445] Write quickstart guide --- docs/first_steps/00_installation.rst | 50 +- docs/first_steps/01_quickstart.md | 886 +++++++++++++++++++++++++++ docs/first_steps/01_quickstart.rst | 4 - 3 files changed, 933 insertions(+), 7 deletions(-) create mode 100644 docs/first_steps/01_quickstart.md delete mode 100644 docs/first_steps/01_quickstart.rst diff --git a/docs/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index 63e1ca1c..bef2d6ba 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -2,6 +2,22 @@ Installation ============ +Quick Installation +================== + +Assuming you already have git and your compiler set up, you should be able to +download and compile MABE by executing the following lines in your terminal: + +.. code-block:: + + git clone --recurse-submodules https://github.com/mercere99/MABE2.git + cd MABE2/build + make + +.. + +If that doesn't work, read the rest of this page for more detail. + Installing Git ============== @@ -50,11 +66,12 @@ For more information about SSH keys, checkout `this guide Cloning into `MABE2`... > remote: Counting objects: 10, done. > remote: Compressing objects: 100% (8/8), done. @@ -130,6 +147,33 @@ The Windows Subsystem for Linux (WSL) makes it easy to run a GNU/Linux environme directly on Windows. For information on installing WSL, checkout `this guide `__. Once WSL is installed you can follow the same instructions as above. + +Compiling MABE +============== + +To compile MABE, go to the ``build`` sub-directory within MABE and run ``make``: + +.. code-block:: + + cd MABE2/build + make + +.. + +MABE can also be compiled in debug mode, which does various additional checking to make sure +everything is running according to plan. Debug mode will make MABE run substantially slower, so it +is not recommended for use when you are not actively debugging, but it very helpful for identifying the +source of errors: + +To compile MABE in debug mode, use the ``debug`` make target: + +.. code-block:: + + cd MABE2/build + make debug + +.. + Next Steps ========== diff --git a/docs/first_steps/01_quickstart.md b/docs/first_steps/01_quickstart.md new file mode 100644 index 00000000..60be5ddf --- /dev/null +++ b/docs/first_steps/01_quickstart.md @@ -0,0 +1,886 @@ +# Quickstart + +## Installation + +For more detail, see the [intallation and compilation](00_installation) instructions. For most people, though, running the following instructions in a terminal should be sufficient: + +```shell +git clone --recurse-submodules https://github.com/mercere99/MABE2.git +cd MABE2/build +make +``` + +## Running MABE + +In order to run, MABE needs a configuration file telling it how to set up the evolutionary scenario you want to study. By convention, MABE configuration files use the `.mabe` file extension. + +To quickly get familiar with how MABE works, the easiest thing to do is run it with a pre-written configuration file. For this example, we will use the one that comes with MABE in `settings/NK.mabe`. This configuration file evolves bitstrings (i.e. sequence of 1s and 0s) on an [NK landscape](../evaluate/EvalNK). + +You can tell MABE which configuration file to use with the `-f` flag. Thus, the full command to run mabe with the NK example file is: + +```shell +cd build +./MABE -f ../settings/NK.mabe +``` + +Ta-da! You have now used MABE! This example will run for 1000 generations and print some data on fitness in the population as it goes. It will also print out some data files containing more information. + +## Writing your own configuration file + +Using pre-existing configuration files is all well and good, but to really use MABE for research purposes you will need to be able to write your own configuration files. + +Your configuration file needs to set up two main things: 1) variables/modules, and 2) signals. + +### Setting up variables/modules + +Variables can be declared anywhere in MABE configuration files and used to hold multiple types of objects, modules, and values. Specifically, variables may have the following types: + +- Generic variables (`Var`): Hold arbitrary numbers or strings +- Populations (`Population`): Hold populations of organisms +- Lists of organisms (`OrgList`): Hold lists of organisms (which may be a subset of the organisms in a whole population or may contain organisms from multiple populations) +- Random seed: `random_seed` is a special value that is automatically declared. Thus, you do not need to specify a type for it. Instead, you can just set it to whatever number you choose. This will control the seed given to the random number generator that MABE uses whenever it needs a random number. +- Data files (`DataFile`): Contain instructions for printing data to a files. +- Modules: Modules are initialized in MABE configuration files in the same way that other objects are. Modules are the core components of MABE - they are used to encapsulate the different components of a computational evolution system that can be plugged together. + +These variables can be initialized at any point in the configuration file (including within signal responses) by listing the type and name of the variable. When the variable require configuration information (as with modules or data files), this configuration information is specified in curly braces after the variable name. For example: + +```cpp + +random_seed = 0; // Set the random seed to 0 (note that 0 is a special value that tells the random number generator to pick a seed based on the time) + +Var my_number = 10; // Creates a generic variable called my_number that contains the value 10 + +Population my_population; // Creates a population called my_population. Populations can be manipulated by calling methods and functions. + +OrgList my_orgs; // Creates an empty OrgList called my_orgs. This list can be populated by calling other functions and storing the results in it + +DataFile my_file { filename="my_file.csv"; }; // Creates a DataFile that will store data in the file my_file.csv. To tell the datafile what to write, you will need to call methods on it. To tell the datafile when to write, you will need to call its WRITE() method in a signal handler + +ExampleModule my_module { // Creates an ExampleModule called my_module + example_trait = "trait"; // Sets the module's example_trait parameter to "trait" + example_number = 3; // Sets the module's example_number parameter to 3 +}; // Note: ExampleModule is not a real module - this will not work in real configuration file +``` + +At a minimum, most configurations will need the following: + +- A population for your organisms to live in. If you are using synchronous/non-overlapping generations, you will need two populations, one for the current generation and one for the next. +- An organism module, to specify what type of organisms you are using. +- An evaluation module, to specify how fitness is assigned to your organisms +- A selection module, to specify how organisms are chosen to reproduce +- For clarity, we recommend setting the random seed explicitly, even if you are setting it to 0 (i.e. based on time) +- Assuming you have a fixed population size, we recommend storing it in a variable for convenience when setting up signals (see below) + +Here is an example of how to do those minimal steps for a configuration using synchronous generations: + +```cpp +random_seed = 0; // Base random seed on time. +Var pop_size = 500; // Set population size to 500 +Population main_pop; // Main population +Population next_pop; // Offspring population + +// The organism module. In this case, we are using BitsOrg, in which +// organisms are all bitstrings (series of 1s and 0s). +// to use a different type of organism, replace `BitsOrg` with something +// else. The organism stores its bit sequence (i.e. its "genome") in the +// trait specified by the output_name parameter +BitsOrg my_org_module { + output_name = "bits"; // Name of trait to contain bit sequence. + N = 10; // Number of bits in organism. Here we're using 10. + mut_prob = 0.01; // Probability of each bit mutating on reproduction. +}; + +// The evaluation module. In this case, we are using EvalCountBits, which +// assigns fitness based on the number of 1s or 0s in the organism. To use +// a different type of evaluator, replace `EvalCountBits` with something +// else. The bits_trait parameter tells this module where to look for +// the organism's bitstring. Thus, we need to make sure it matches the +// output_name parameter in the organism module. +EvalCountBits my_evaluator { + bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? + score_trait = "fitness"; // Which trait should we store fitness in? + count_type = 1; // Indicates that we should count 1s, not 0s. +}; + + +// The selection module. In this case, we are using SelectTournament, which +// selects organisms to reproduce using Tournament Selection (a set of +// individuals are randomly selected and the fittest gets to reproduce). +// To use a different kind of selection, replace `SelectTournament` with +// something else. The fitness_fun parameter tells this module where to +// look to find each organism's fitness. Thus, we need to make sure it +// matches the score_trait parameter in the evaluation module. +SelectTournament my_selector { + tournament_size = 7; // Number of orgs in each tournament + fitness_fun = "fitness"; // Which trait provides the fitness value? +}; +``` + +### Setting up signals + +There are various points in the running of MABE when you may want to make certain things happen. The two most important are at the very beginning of a run of MABE (`START`) and at the beginning of each new time step (`UPDATE`). To configure what happens at each of these times, you can write a simple function in the MABE configuration language. + +The syntax for writing this code is the `@` sign, followed by the name of the event you are writing code for, followed by parentheses containing any function arguments that are available for that event, followed by curly braces containing your code. For example: + +```cpp +@START() { // START does not take any arguments + // All code here will run on the start event +} + +// UPDATE takes one argument - a number +// indicating the current time step +@UPDATE(Var ud) { + // All code here will run at the beginning of each time step +} +``` + +The code that you can write for these events is very flexible. In most cases, you will probably want to do the following: + +On start: + +- Add organisms to the population (can be done using the INJECT method of populations) +- Optionally, print a message containing configuration information + +On update: + +- Check whether you have reached the final update and stop if so (to avoid an infinite loop) +- Run your evaluator module on your population +- Run your selection module on your population +- If your generations are synchronous, swap your offspring population into your main population +- Optionally, print information about the progress of your run + +Here is a minimal configuration that accomplishes those goals for a synchronous population (this is designed to be the second half of the configuration above): + +```cpp +@START() { + // Optional: print welcome message and population size + PRINT("Starting MABE! Population size = ", population_size, "\n"); + + // Initialize population by adding organisms of the type specified + // by the organism module we declared earlier and named "my_org_module". + // The second argument to INJECT indicates how many organisms to add. + // By adding pop_size, we ensure that the population starts out full. + main_pop.INJECT("my_org_module", pop_size); +} + +@UPDATE(Var ud) { + + // Check whether this is the 1000th update and stop MABE if it is + IF (ud == 1000) EXIT(); + + // Run evaluator on the main population + my_evaluator.EVAL(main_pop); + + // Optional: print out some stats + // This prints the current update, the size of the population, + // and the average fitness in the population. + // Note that this has to happen after we run EVAL, because + // that's what assigns fitness to each organism + PRINT("UD:", GET_UPDATE(), + " MainPopSize=", main_pop.SIZE(), + " AveFitness=", main_pop.CALC_MEAN("fitness"), + ); + + // Select organisms from main_pop, put their offspring in + // next_pop, and repeat this [pop_size] times to fill up + // the entire next population + my_selector.SELECT(main_pop, next_pop, pop_size); + + // Swap the population of offspring into the main population + main_pop.REPLACE_WITH(next_pop); +} + +``` + +### Starter configuration file + +For convenience, here is the whole vanilla configuration file described above in one place: + +```cpp +random_seed = 0; // Base random seed on time. +Var pop_size = 500; // Set population size to 500 +Population main_pop; // Main population +Population next_pop; // Offspring population + +// The organism module. In this case, we are using BitsOrg, in which +// organisms are all bitstrings (series of 1s and 0s). +// to use a different type of organism, replace `BitsOrg` with something +// else. The organism stores its bit sequence (i.e. its "genome") in the +// trait specified by the output_name parameter +BitsOrg my_org_module { + output_name = "bits"; // Name of trait to contain bit sequence. + N = 10; // Number of bits in organism. Here we're using 10. + mut_prob = 0.01; // Probability of each bit mutating on reproduction. +}; + +// The evaluation module. In this case, we are using EvalCountBits, which +// assigns fitness based on the number of 1s or 0s in the organism. To use +// a different type of evaluator, replace `EvalCountBits` with something +// else. The bits_trait parameter tells this module where to look for +// the organism's bitstring. Thus, we need to make sure it matches the +// output_name parameter in the organism module. +EvalCountBits my_evaluator { + bits_trait = "bits"; // Which trait stores the bit sequence to evaluate? + score_trait = "fitness"; // Which trait should we store fitness in? + count_type = 1; // Indicates that we should count 1s, not 0s. +}; + + +// The selection module. In this case, we are using SelectTournament, which +// selects organisms to reproduce using Tournament Selection (a set of +// individuals are randomly selected and the fittest gets to reproduce). +// To use a different kind of selection, replace `SelectTournament` with +// something else. The fitness_fun parameter tells this module where to +// look to find each organism's fitness. Thus, we need to make sure it +// matches the score_trait parameter in the evaluation module. +SelectTournament my_selector { + tournament_size = 7; // Number of orgs in each tournament + fitness_fun = "fitness"; // Which trait provides the fitness value? +}; + +@START() { + // Optional: print welcome message and population size + PRINT("Starting MABE! Population size = ", population_size, "\n"); + + // Initialize population by adding organisms of the type specified + // by the organism module we declared earlier and named "my_org_module". + // The second argument to INJECT indicates how many organisms to add. + // By adding pop_size, we ensure that the population starts out full. + main_pop.INJECT("my_org_module", pop_size); +} + +@UPDATE(Var ud) { + + // Check whether this is the 1000th update and stop MABE if it is + IF (ud == 1000) EXIT(); + + // Run evaluator on the main population + my_evaluator.EVAL(main_pop); + + // Optional: print out some stats + // This prints the current update, the size of the population, + // and the average fitness in the population. + // Note that this has to happen after we run EVAL, because + // that's what assigns fitness to each organism + PRINT("UD:", GET_UPDATE(), + " MainPopSize=", main_pop.SIZE(), + " AveFitness=", main_pop.CALC_MEAN("fitness"), + ); + + // Select organisms from main_pop, put their offspring in + // next_pop, and repeat this [pop_size] times to fill up + // the entire next population + my_selector.SELECT(main_pop, next_pop, pop_size); + + // Swap the population of offspring into the main population + main_pop.REPLACE_WITH(next_pop); + +``` + +### Using DataFiles + +If you're trying to use MABE to do science, you probably want to output some data to a file. MABE has a very flexible system for configuring what data you want to output. To use a DataFile, you need to do the following: + +#### Declare the data file + +```cpp +DataFile my_file { filename="my_file.csv"; }; +``` + +#### Configure the data file + +You can configure the data file by calling methods on it. You can do this at any point in the configuration file after the the data file has been declared (although you probably do not want to do it inside the code for an event). The two main methods to know about are `ADD_COLUMN`, which adds a column to the data table being stored in your file, and `ADD_SETUP`, which adds a function that should be run for each row of the table before calculating the column values. + +For example: + +```cpp +// Add a column called Average Fitness where the value for each row is +// calculated by calling the function main_pop.CALC_MEAN('fitness'), +// which will calculate the mean value of the fitness trait among the +// organisms in the population main_pop. +my_file.ADD_COLUMN( "Average Fitness", "main_pop.CALC_MEAN('fitness')" ); + +``` + +Or as an example that uses `ADD_SETUP`: + +```cpp +// Create a variable called best_org that will hold +// a list of the best organism in the population +OrgList best_org; + +// Tell the file to run this line of code before calculating the +// data for a given row. This will find the organism in main_pop +// that has the highest fitness and store it in the variable best_org +my_file.ADD_SETUP("best_org = main_pop.FIND_MAX('fitness')"); + +// Add a column called Best Genome where the value is calculated +// by accessing the `bits` trait of the organism stored in +// best_org. Note that this only works because we added the setup +// function, which will ensure that best_org is always correctly +// populated before we perform this calculation. +my_file.ADD_COLUMN( "Best Genome", "best_org.TRAIT('bits')" ); +``` + +#### Tell the data file when to output rows + +Finally, you need to tell the data file when to calculate and output new rows. You can tell the data file to calculate and output a single new row with the `WRITE` method. Most commonly, you will want to this every update (or perhaps every so many updates), which can be achieved by calling `WRITE` from inside the `@UPDATE` event code: + +```cpp +@UPDATE(Var ud) { + // Do stuff here + + // This tells the file to calculate and write a new row + my_file.WRITE(); + + // Do more stuff here +} +``` + +Or if you wanted to only write data every 10 updates, you could do: + +```cpp +@UPDATE(Var ud) { + // Do stuff here + + // Check whether this is a time step on which we want to output + // before we write a row + IF (ud % 10 == 0) { + my_file.WRITE(); + } + // Do more stuff here +} +``` + +## Writing your own module + +If MABE already has all the modules you need, feel free to skip this section! However, more likely than not, there will be some piece of additional functionality that you need (perhaps because it is something new that you are designing!). In that case, you will need to write a new module. MABE is designed with the goal of making it easy to write small, well-encapsulated modules that will plug-and-play with each other. + +Since MABE is all written in C++, modules need to be written in C++ too. However, we have tried hard to protect users from having to write any particularly complicated C++ code (although that comes at the cost of MABE's internals being fairly complex - don't be intimidated if you don't understand all of the code in the core directory). + +Each module is its own C++ class. All modules must inherit from the Module base class. Besides that, the only thing required of modules is that they contain the following four member functions (described in more detail below): + +- A constructor that tells MABE what kind of module it is +- SetupConfig() +- SetupModule() +- InitType() + +Optionally, modules can also contain member functions that indicate things that should happen when specific events occur in MABE: + +- `BeforeUpdate` - Occurs when current time step (update) is ending and new one is about to start. Arguments: Update ID that is just finishing. +- `OnUpdate` - Occurs when new time step (update) has just started. Arguments: Update ID just starting. +- `BeforeRepro` - An organism is about to reproduce. Arguments: Position of organism about to reproduce. +- `OnOffspringReady` - Offspring is ready to be placed. Arguments: Offspring to be born, position of parent, population to place offspring in. +- `OnInjectReady` - Organism to be injected (i.e. placed into the population without a parent) is ready to be placed. Arguments: Organism to be injected, population to inject into. +- `BeforePlacement` - Placement location has been identified (For birth or inject). Arguments: Organism to be placed, placement position, parent position (if available) +- `OnPlacement` - New organism has been placed in the population. Arguments: Position new organism was placed. +- `BeforeMutate` - Mutate is about to run on an organism. Arguments: Organism about to mutate. +- `OnMutate` - Organism has had its genome changed due to mutation. Arguments: Organism that just mutated. +- `BeforeDeath` - Organism is about to die. Arguments: Position of organism about to die. +- `BeforeSwap` - Two organisms' positions in the population are about to move. Arguments: Positions of organisms about to be swapped. +- `OnSwap` - Two organisms' positions in the population have just swapped. Arguments: Positions of organisms just swapped. +- `BeforePopResize` - Full population is about to be resized. Arguments: Population about to be resized, the size it will become. +- `OnPopResize` - Full population has just been resized. Arguments: Population just resized, previous size it was. +- `BeforeExit` - Run immediately before MABE is about to exit. No arguments. +- `OnHelp` - Run when the --help option is called at startup. No arguments. + +Any member function with one of these names will be run when the specified event occurs. + +You are free to create additional helper functions to assist in writing these functions. + +### Constructor + +All the body of the constructor needs to do is call one of the following functions to indicate what type of module this is: + +- `SetAnalyzeMod(true)` +- `SetEvaluateMod(true)` +- `SetInterfaceMod(true)` +- `SetManageMod(true)` +- `SetMutateMod(true)` +- `SetPlacementMod(true)` +- `SetSelectMod(true)` +- `SetVisualizeMod(true)` + +As arguments, the constructor should take 1) a reference to a `mabe::MABE` object, 2) a string indicting the module's name, 3) a string describing the module, and 4) all other parameters needed to initialize the module, with sensible defaults. The first three arguments should be passed to the constructor for the `Module` base class. + +For example, the constructor might look something like this: + +```cpp +MyModule(mabe::MABE & control, + const std::string & name="MyModule", + const std::string & desc="A description", + size_t _some_numeric_parameter=100, + const std::string & _some_string_parameter="a string") + : Module(control, name, desc) + , some_numeric_parameter(_some_numeric_parameter), + , some_string_parameter(_some_string_parameter) +{ + SetEvaluateMod(true); +} +``` + +Note: this assumes the class has member variables called `some_numeric_parameter` and `some_string_parameter`. + +### SetupConfig + +The SetupConfig member function sets up this module's user-configurable parameters. This is accomplished by calling the following functions: + +- `LinkVar`: links a member variable to a configuration parameter. The first argument is the variable to store the configured value in, the second is the name of the value in the config file, and the third is a description of the setting + +For example, here is the SetupConfig function from the `SelectElite` module: + +```cpp +void SetupConfig() override { + LinkVar(fit_equation, "fitness_fun", "Function used as fitness for selection?"); + LinkVar(top_count, "top_count", "Number of top-fitness orgs to be replicated"); +} +``` + +This creates two config options called "fitness_fun" and "top_count", linked to the member variables `fit_equation` and `top_count` respectively. + +### SetupModule + +The SetupModule member function is where the module indicates what traits it creates and what traits it requires other modules to have created. Recall that traits are the primary way that MABE modules communicate with each other and store information. + +You can use the following functions here: + +- `AddPrivateTrait` - Add trait that this module can READ & WRITE this trait Others cannot use it. Must provide name, description, and a default value to start at. +- `AddOwnedTrait` - Add trait that this module can READ & WRITE to; other modules can only read. Must provide name, description, and a default value to start at. +- `AddGeneratedTrait` - Add trait that this module can READ & WRITE to; at least one other module MUST read it. Must provide name, description, and a default value to start at. +- `AddSharedTrait` - Add trait that this module can READ & WRITE; other modules can too. +- `AddRequiredTrait` - Add trait that this module can READ, but another module must WRITE to it. That other module should also provide the description for the trait. +- `AddRequiredEquation` - Add all of the traits that that this module needs to be able to READ, in order to compute the provided equation. Another module must WRITE these traits and provide the descriptions. + +You can also do any other set-up that the module requires here. + +For example, here is the SetupModule function from the `EvalNK` module: + +```cpp +void SetupModule() override { + // Setup the traits. + + // Some other module needs to create a trait named the same thing + // as the string in the `bits_trait` variable. That trait should contain + // an object of type emp::BitVector. + AddRequiredTrait(bits_trait); + + // This module is creating a trait named whatever the variable `fitness_trait` + // contains. It holds a double, which starts at 0.0 as a default, and its + // description is "NK fitness value" + AddOwnedTrait(fitness_trait, "NK fitness value", 0.0); + + // Do other necessary set-up + landscape.Config(N, K, control.GetRandom()); +} +``` + +### InitType + +This function configures what methods the user is allowed to call on this module in the configuration file. It takes a reference to a `emplode::TypeInfo` object as input. Most of what you need to do in this function is to call the `AddMemberFunction` member function of that object, which allows you to specify the names, code, and descriptions for each user-callable function. + +If you are writing an Evaluation module, you must add a function called EVAL that does the evaluation. If you are writing a Selection module you must add a function called SELECT that does the selection. Beyond that, you are free to add whatever functions make sense. + +For readability, it often makes sense to put the bulk of the code for these functions into helper functions that are called from the function provided here. + +For example, here is the InitType function from the `EvalNK` module: + +```cpp +static void InitType(emplode::TypeInfo & info) { + + // Add the required EVAL method, which says to just call this module's + // Evaluate() helper function + info.AddMemberFunction("EVAL", + [](EvalNK & mod, Collection list) { return mod.Evaluate(list); }, + "Use NK landscape to evaluate all orgs in an OrgList."); + + // Add another method called RESET that resets the fitness landscape + info.AddMemberFunction("RESET", + [](EvalNK & mod) { mod.landscape.Config(mod.N, mod.K, mod.control.GetRandom()); return 0; }, + "Regenerate the NK landscape with current N and K."); +} +``` + + +### Making your new module available for configuration + +- Include the macro to setup the module (`MABE_REGISTER_MODULE(MyModule, "My description.");`). This should be placed after your class definition. +- Add the file to modules.h +- Place the module in an example .gen and .mabe pair of files. + +### Extras needed for official modules + +The rules above all assume that you are trying to build a MABE module that will be functional for your own needs and for you to share with others. If you are trying to build a module that you intend to be shipped with the core MABE distribution, there are a few other things that you will need to do. + +1. The heading at the top of your file must be done in doxygen style with a release under the MIT Software license and an @brief description of the module. + +2. The include guard should begin with MABE_* + +3. The module should be placed in the "mabe" namespace. + + +### Template module + +Feel free to copy this and adapt it as necessary for your own module. + +```cpp +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2019-2022. + * + * @file [File_Name_Here].hpp + * @brief Describe the file + */ + +#ifndef MABE_FILE_NAME_HERE_H +#define MABE_FILE_NAME_HERE_H + +#include "../../core/MABE.hpp" +#include "../../core/Module.hpp" + +// If you want your module to become an official part +// of MABE, it needs to be in the MABE namespace +namespace mabe { + + // By convention, the actual name of your module should + // start with Eval, Select, or Analyze depending on what + // type of module it is. If it as organism, it should end + // with Org. + class MyModule : public Module { + private: + // A string containing the name of a trait that this + // module needs to work with. Nearly all modules will + // need to interact with at least one trait, although + // exceptions are possible. + std::string example_trait; + + // Any other member variables you need go here + + public: + // Constructor for your module + // The first three arguments to the constructor are the + // same for all modules, because they need to be passed + // to the constructor for the Module class. Just + // customize the name and description to reflect the + // details of your module. + // + // Add any additional arguments that are necessary to + // initialize your member variables. Here, for example, + // we added _example_trait, which we use to set the value + // of example_trait. You should include reasonable + // defaults for all of these arguments. + // + // In general, most values that you would set up to + // control by parameters should probably be arguments + // to your constructor + MyModule(mabe::MABE & control, + const std::string & name="MyModule", + const std::string & desc="A description of your module should go here.", + const std::string & _example_trait="my_trait_name" + ) + : Module(control, name, desc) // Pass args to base class constructor + , example_trait(_example_trait) // Initialize example_trait + { + // The only thing that needs to happen in the constructor + // is that you need to call a function to tell MABE what + // kind of module this is. In this case we're setting + // this to be an Evaluate mod. + + SetEvaluateMod(true); + } + + // The default destructor should be fine for most modules + // but if you dynamically allocate any memory make sure + // to deallocate it here + ~MyModule() { } + + // Select and Evaluate modules need an InitType member + // function. It should take a reference to an + // emplode::TypeInfo object as an argument. Other + // module types can optionally have an InitType if they + // want to allow the user to run methods from the config + // file. + // + // This function is where all methods of this module + // that are callable from the config file are defined. + // At a minimum, Evaluate modules need an EVAL function + // and Selection modules need a SELECT function, but + // you can define others to. + // + // For an evaluate module, you can safely copy and paste + // the below code verbatim into your module (although + // ideally you should write an actual description), + // assuming you define an Evaluate method for this + // module (that's where you provide the actual code + // for evaluating an organism's fitness) + static void InitType(emplode::TypeInfo & info) { + // Tell the configuration system that: + // - this module has a method called EVAL + // - when EVAL is called, the lambda function in the + // second argument should be run. + // - It should use the third argument as a description + // of this method + info.AddMemberFunction("EVAL", + [](MyModule & mod, Collection list) { return mod.Evaluate(list); }, + "Description of EVAL."); + + // If you are writing a select module, the commented out + // code below is an example of what you could use to add + // a select method (you will need to define a Select method + // for this module that actually defines how + // selection works) + + // info.AddMemberFunction("SELECT", + // [](SelectElite & mod, + // Population & from, + // Population & to, + // double count) + // { + // return mod.Select(from,to,count); + // }, + // "Perform elite selection on the provided organisms."); + } + + // SetupConfig is where you tell MABE what config options + // this module should have available. + void SetupConfig() override { + // Call LinkVar for each config option you want to add + // The first argument should contain a member variable + // where you want to store the value the user sets. + // The second argument is the default value and the + // third is the description for this parameter. + // Here, we are linking our example_trait member variable + // to this parameter, and calling the parameter "my_trait_name". + LinkVar(example_trait, "my_trait_name", "Description here"); + + // Other necessary set-up for this module can happen here + } + + // SetupModule is where you tell MABE about this module's + // trait needs. Remember, traits are values stored on + // organisms, and are the primary way modules communicate + // within MABE. Traits can be either private (only usable + // by this module), owned (only writeable by this module + // but readable by others), shared (all modules can read + // and write), or required (another module should generate + // it, but this one needs to be able to read it). + void SetupModule() override { + // Tell MABE that another module needs to have created a trait + // called whatever value is stored in example_trait, that + // this module needs to be able to read that trait, and that + // it should contain an int. + AddRequiredTrait(example_trait); + + // Tell MABE that this module is creating a trait called + // another_trait (note: you usually shouldn't hard-code trait + // names like this - instead you should store them in a variable + // as with example_trait), and that other modules are allowed to + // read that trait but not write to it. It's description is + // "Another example", it's type is double, and its default value + // is 0.0. + AddOwnedTrait("another_trait", "Another example", 0.0); + } + + // For an evaluate module, this is the function that does most of + // the work. It takes a Collection of organisms as input, and it + // must somehow end up assigning + // + // Note: You only need this for evaluation modules! + double Evaluate(Collection orgs) { + + // Evaluate needs to return the maximum score, + // so we need to keep track of it + double max_score = 0.0; + + // Loop through the population and evaluate each organism. + // You may want to select the subset of organisms that are alive + mabe::Collection alive_collect( orgs.GetAlive() ); + for (Organism & org : alive_collect) { + // Make sure this organism has its traits ready to access. + org.GenerateOutput(); + + // Do stuff here to figure out this organism's fitness + // and store it in the variable fitness + double fitness = 1; + + // Store fitness in the example trait + // Note: if you're writing a real evaluator, you should + // call the trait you store fitness in either + // fitness_trait or score_trait, for consistency. + // Here we're storing it in example_trait because that's + // all we have + org.SetTrait(example_trait, fitness); + + // Keep track of maximum observed fitness + if (fitness > max_score) { + max_score = fitness; + } + } + + // Evaluate needs to return the maximum observed fitness + return max_score; + } + + // If we were instead writing a Select module, we would need a + // Select function. Here is a template. + // + // Note: you only need this for selection modules!!! + // + // Select takes references to two populations (the first is the + // one to select from and the second is the one to put offspring + // in) and a number indicating how many rounds of selection to do. + Collection Select(Population & select_pop, Population & birth_pop, size_t num_births) { + + // Grab a reference to the main random number generator for MABE + // so that we can genrate a random number + emp::Random & random = control.GetRandom(); + + // Select needs to return a Collection containing all newly added + // organisms. Initialize an empty collection to contain them + Collection placement_list; + + // Loop through each round of selection. + for (size_t round = 0; round < num_births; round++) { + + // Choose a random organism + size_t best_id = random.GetUInt(N); + while (select_pop[best_id].IsEmpty()) best_id = random.GetUInt + + // Code to actually do desired selection method goes here + + // Replicate the winner organism + placement_list += control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); + } + + return placement_list; + } + + // Functions associated with events + + // THESE ARE ALL OPTIONAL!!! + // Any that you aren't using should be removed + + // Format: BeforeUpdate(size_t update_ending) + // Trigger: Update is ending; new one is about to start + // Args: Update ID that is just finishing. + void BeforeUpdate(size_t update_ending) override { + + } + + // Format: OnUpdate(size_t new_update) + // Trigger: New update has just started. + // Args: Update ID just starting. + void OnUpdate(size_t new_update) override { + + } + + // Format: BeforeRepro(OrgPosition parent_pos) + // Trigger: Parent is about to reproduce. + // Args: Position of organism about to reproduce. + void BeforeRepro(OrgPosition parent_pos) override { + + } + + // Format: OnOffspringReady(Organism & offspring, OrgPosition parent_pos, Population & target_pop) + // Trigger: Offspring is ready to be placed. + // Args: Offspring to be born, position of parent, population to place offspring in. + void OnOffspringReady(Organism & offspring, OrgPosition parent_pos, Population & target_pop) override { + + } + + // Format: OnInjectReady(Organism & inject_org, Population & target_pop) + // Trigger: Organism to be injected is ready to be placed. + // Args: Organism to be injected, population to inject into. + void OnInjectReady(Organism & inject_org, Population & target_pop) override { + + } + + // Format: BeforePlacement(Organism & org, OrgPosition target_pos, OrgPosition parent_pos) + // Trigger: Placement location has been identified (For birth or inject) + // Args: Organism to be placed, placement position, parent position (if available) + void BeforePlacement(Organism & org, OrgPosition target_pos, OrgPosition parent_pos) override { + + } + + // Format: OnPlacement(OrgPosition placement_pos) + // Trigger: New organism has been placed in the population. + // Args: Position new organism was placed. + void OnPlacement(OrgPosition placement_pos) override { + + } + + // Format: BeforeMutate(Organism & org) + // Trigger: Mutate is about to run on an organism. + // Args: Organism about to mutate. + void BeforeMutate(Organism & org) override { + + } + + // Format: OnMutate(Organism & org) + // Trigger: Organism has had its genome changed due to mutation. + // Args: Organism that just mutated. + void OnMutate(Organism & org) override { + + } + + // Format: BeforeDeath(OrgPosition remove_pos) + // Trigger: Organism is about to die. + // Args: Position of organism about to die. + void BeforeDeath(OrgPosition remove_pos) override { + + } + + // Format: BeforeSwap(OrgPosition pos1, OrgPosition pos2) + // Trigger: Two organisms' positions in the population are about to move. + // Args: Positions of organisms about to be swapped. + void BeforeSwap(OrgPosition pos1, OrgPosition pos2) override { + + } + + // Format: OnSwap(OrgPosition pos1, OrgPosition pos2) + // Trigger: Two organisms' positions in the population have just swapped. + // Args: Positions of organisms just swapped. + void OnSwap(OrgPosition pos1, OrgPosition pos2) override { + + } + + // Format: BeforePopResize(Population & pop, size_t new_size) + // Trigger: Full population is about to be resized. + // Args: Population about to be resized, the size it will become. + void BeforePopResize(Population & pop, size_t new_size) override { + + } + + // Format: OnPopResize(Population & pop, size_t old_size) + // Trigger: Full population has just been resized. + // Args: Population just resized, previous size it was. + void OnPopResize(Population &, pop size_t old_size) override { + + } + + // Format: BeforeExit() + // Trigger: Run immediately before MABE is about to exit. + void BeforeExit() override { + + } + + // Format: OnHelp() + // Trigger: Run when the --help option is called at startup. + void OnHelp() override { + + } + + // Any other helper functions that your module needs go here + + }; + + // We always need to call MABE_REGISTER_MODULE to notify MABE + // about the new module + // Remember to also add it in modules.hpp as an include. + MABE_REGISTER_MODULE(MyModule, "My description."); +} + +#endif + + +``` \ No newline at end of file diff --git a/docs/first_steps/01_quickstart.rst b/docs/first_steps/01_quickstart.rst deleted file mode 100644 index 95771dfb..00000000 --- a/docs/first_steps/01_quickstart.rst +++ /dev/null @@ -1,4 +0,0 @@ -========== -Quickstart -========== -Quickstart coming soon. \ No newline at end of file From 1400d257d8484931f430d8b85613028c8affe2b3 Mon Sep 17 00:00:00 2001 From: Emily Dolson Date: Thu, 10 Mar 2022 03:25:25 -0500 Subject: [PATCH 445/445] Organism docs --- docs/evaluate/00_eval_overview.rst | 10 +- docs/first_steps/01_quickstart.md | 12 +- docs/first_steps/02_write_gen_file.rst | 7 - docs/modules/01_module_types.rst | 2 +- docs/organisms/00_organism_overview.md | 371 ++++++++++++++++++++++++ docs/organisms/00_organism_overview.rst | 7 - 6 files changed, 391 insertions(+), 18 deletions(-) delete mode 100644 docs/first_steps/02_write_gen_file.rst create mode 100644 docs/organisms/00_organism_overview.md delete mode 100644 docs/organisms/00_organism_overview.rst diff --git a/docs/evaluate/00_eval_overview.rst b/docs/evaluate/00_eval_overview.rst index bade0c89..561518f4 100644 --- a/docs/evaluate/00_eval_overview.rst +++ b/docs/evaluate/00_eval_overview.rst @@ -120,6 +120,10 @@ Example:: // that are actually alive. mabe::Collection alive_orgs( orgs.GetAlive() ); + // Keep track of the maximum score because + // Evaluate needs to return it + double max_score = 0.0; + // Loop over all the organisms // (alive_orgs could be replaced with orgs if we didn't bother // with the filtering step above) @@ -135,9 +139,13 @@ Example:: // Set this organisms fitness trait equal to the calculated fitness org.SetTrait(fitness_trait, fitness); + + if (fitness > max_score) { + max_score = fitness; + } } - return max_fitness; + return max_score; } // Alternate version of Evaluate that takes a Population instead of a Collection diff --git a/docs/first_steps/01_quickstart.md b/docs/first_steps/01_quickstart.md index 60be5ddf..babb6960 100644 --- a/docs/first_steps/01_quickstart.md +++ b/docs/first_steps/01_quickstart.md @@ -358,6 +358,9 @@ If MABE already has all the modules you need, feel free to skip this section! Ho Since MABE is all written in C++, modules need to be written in C++ too. However, we have tried hard to protect users from having to write any particularly complicated C++ code (although that comes at the cost of MABE's internals being fairly complex - don't be intimidated if you don't understand all of the code in the core directory). +Note: The following information applies to writing any type of module other than organism modules. If you want to write an organism module, see {ref}`_write_org_module`. + + Each module is its own C++ class. All modules must inherit from the Module base class. Besides that, the only thing required of modules is that they contain the following four member functions (described in more detail below): - A constructor that tells MABE what kind of module it is @@ -368,7 +371,7 @@ Each module is its own C++ class. All modules must inherit from the Module base Optionally, modules can also contain member functions that indicate things that should happen when specific events occur in MABE: - `BeforeUpdate` - Occurs when current time step (update) is ending and new one is about to start. Arguments: Update ID that is just finishing. -- `OnUpdate` - Occurs when new time step (update) has just started. Arguments: Update ID just starting. +- `OnUpdate` - Occurs when new time step (update) has just started. Arguments: Update ID just starting. NOTE: This is only for events where the order does not matter. If the timing of your event response matters relative to the timing of what other modules do, you should not use OnUpdate. Instead, you should use InitType to provide a method for the user to call so they can control what order things happen in. - `BeforeRepro` - An organism is about to reproduce. Arguments: Position of organism about to reproduce. - `OnOffspringReady` - Offspring is ready to be placed. Arguments: Offspring to be born, position of parent, population to place offspring in. - `OnInjectReady` - Organism to be injected (i.e. placed into the population without a parent) is ready to be placed. Arguments: Organism to be injected, population to inject into. @@ -771,7 +774,12 @@ namespace mabe { // Trigger: New update has just started. // Args: Update ID just starting. void OnUpdate(size_t new_update) override { - + // Before you write on OnUpdate member function, think carefully + // about what you are doing in it. Is it just something that has + // to happen regularly, or is it something that could interact + // with other modules? If it's the latter, strongly consider making + // it a method (initialized in InitType), to give the user control + // over the order of events. } // Format: BeforeRepro(OrgPosition parent_pos) diff --git a/docs/first_steps/02_write_gen_file.rst b/docs/first_steps/02_write_gen_file.rst deleted file mode 100644 index ae7104cc..00000000 --- a/docs/first_steps/02_write_gen_file.rst +++ /dev/null @@ -1,7 +0,0 @@ -========================== -Writing the ``.gen`` File -========================== - -What's a ``.gen`` file? Well, you're about to find out. - -This page is under construction! \ No newline at end of file diff --git a/docs/modules/01_module_types.rst b/docs/modules/01_module_types.rst index ffa0186e..9b74d6cd 100644 --- a/docs/modules/01_module_types.rst +++ b/docs/modules/01_module_types.rst @@ -5,7 +5,7 @@ Types of Modules Organisms ========= -For more detailed information on organisms, see the `organisms page <../organisms/traitinfo.html>`_ . +For more detailed information on organisms, see the :ref:`Organisms page <_organisms>`. An organism is an individual agent and the target of evolution in MABE2. The genetic material of an organisms is stored in the genome. Organisms use brains to process input and determine outputs. diff --git a/docs/organisms/00_organism_overview.md b/docs/organisms/00_organism_overview.md new file mode 100644 index 00000000..f9b868d3 --- /dev/null +++ b/docs/organisms/00_organism_overview.md @@ -0,0 +1,371 @@ +(_organisms)= + +# What is an Organism? + +Organisms are the entities that can inhabit populations. In an evolutionary computation context, they are what we would call candidate solutions (i.e. they are things that might solve our problem). In other contexts, you may see them referred to as agents, individuals, etc. + +Organisms in MABE can reproduce. Their offspring will generally be similar to the parent, but on every reproduction event there is a chance of mutation. + +Organism modules in MABE are responsible for specifying how organisms are encoded, any internal processing that they may do, and how they mutate. + +## Add an organism type to MABE + +Your configuration file should set up at least one organism module (e.g. `BitsOrg`, `ValsOrg`, `AvidaGPOrg`, etc.). At a minimum, most organism modules will require something akin to the following configuration parameters: 1) the name of a trait to store some representation of the organism in (often called `outout_name`), and 2) the mutation rate(s) or other information about how mutations should happen. In practice, many organism types will have a lot of other configuration parameters as well. + +To add an pre-written organism type to main, initialize it in your config file like other modules. For example, to add the BitsOrg module, add the following to your configuration file: + +```cpp +BitsOrg bits_org { // Organism consisting of a series of N bits. + output_name = "bits"; // Name of variable to contain bit sequence. + N = num_bits; // Number of bits in organism + mut_prob = 0.01; // Probability of each bit mutating on reproduction. +}; +``` + +## Genomes and brains + +Sometimes it makes sense to draw a distinction between the genetic encoding for an organism (the genome) and the controller for the organism (the brain). The genome is the part of the organism that mutates. It encodes the brain and any other aspect of the organism. + +Eventually, MABE will have genome and brain modules that can be combined flexibly to create a single organism. + +Organisms can also be created directly, without using a genome or brain. + +## Putting organisms in populations + +Organisms live inside of `Population` objects. If desired, organisms of multiple types can be freely added to the same population (although this is an advanced use case and probably not what you usually want to do). + +To add organisms to a population, you can use the `INJECT` action on a population in a config file: + +```cpp +// Declare a population +Population my_pop; + +// Add the desired organism type to your configuration +BitsOrg bits_org { // Organism consisting of a series of N bits. + output_name = "bits"; // Name of variable to contain bit sequence. + N = num_bits; // Number of bits in organism + mut_prob = 0.01; // Probability of each bit mutating on reproduction. +}; + +// Later, in one of the events, call INJECT +@START() { + // Add 100 BitsOrg organisms to my_pop + my_pop.INJECT(bits_org, 100); +} +``` + +(_write_org_module)= +## Writing an organism module + +Setting up an organism module is a little different than setting up other types of modules. Read on to learn how! + +### Class declaration + +Instead of inheriting form the `Module` class, it inherits from the `OrganismTemplate` class. One aspect that may be confusing is that this class needs to be templated off of the organism module class that your are currently writing. So the full declaration will look like `class MyOrg : public OrganismTemplate {`. This is something called the curiously recursive template pattern (CRTP). The CRTP is a really interesting C++ technique that, for our purposes here, you do not need to understand. Just trust us that about how to declare the class :). + +### Constructors + +When you write an organism class, there are a few constructors you'll want to declare: 1) one that just takes an `OrganismManger` (templated off of your organism type), 2) copy constructor (can set to default), and 3) a constructor that takes a genome to initialize your organism based on. + +```cpp + +class MyOrg : public OrganismTemplate { + protected: + // Your organism will probably have some sort of member variable + // representing its genome. In this example its an int, but it + // be whatever type you want. Just adjust the constructors + // accordingly + int my_genome_val; + + // You can have other member variables here too if you want + + public: + + // Version of constructor that just takes an OrganismManager + // templated off of your organism type + MyOrg(OrganismManager & _manager) + : OrganismTemplate(_manager), + my_genome_val(0) // Initialize genome to 0 by default + // You can initialize more member variables here if necessary + { } + + // Copy constructor - use the default + MyOrg(const MyOrg &) = default; + MyOrg(MyOrg &&) = default; + + // Version of constructor that takes a genome as input + // in addition to the organism manager + MyOrg(int in, OrganismManager & _manager) + : OrganismTemplate(_manager), + my_genome_val(in) { } + + // If you dynamically allocate any memory, remember to + // deallocate it in the destructor + ~MyOrg() { ; } + +``` + +### Declare ManagerData + +All organism modules need to declare an internal struct that inherits from the `Organism::ManagerData` class and holds all variables that are connected to configurable parameters: + +```cpp + struct ManagerData : public Organism::ManagerData { + double mut_prob = 0.01; ///< Probability of mutation + std::string output_name = "my_trait"; ///< Name of trait that should be used to access genome + }; +``` + +When you want to access these variables elsewhere in the class, you will need to access them by calling `SharedData()`. For example, to access `mut_prob`, you would use `SharedData().mut_prob`. + +### Required member functions + +There are also a number of required member functions that are specific to organism modules + +#### ToString + +This function should return a string representation of your organism. + +Example: + +```cpp +// emp::to_string can convert most things (includes vectors, etc.) into strings +std::string ToString() const override { return emp::to_string(my_genome_val); } +``` + +#### Mutate + +This function handles determining whether a mutation occurs on a given reproduction event, and applying that mutation if so. It takes a random number generator as an argument (since just about any mutation function you could write will need a random number generator). It should return the number of mutations that occurred. + +```cpp +size_t Mutate(emp::Random & random) override { + + // Retrieve mutation probability from SharedData + double p = SharedData().mut_prob; + + // random.P returns a 1 with with the given probability + // and otherwise returns a 0 + if (random.P(p)) { + // In this simplistic example, ever mutation increments + // the genome value by 1. You probably want to do something + // more interesting/elaborate + my_genome_val++; + + // There was 1 mutation so return 1 + return 1; + } + + // if we didn't mutate, return 0 + return 0; +} +``` + +#### Randomize + +This function should set the organism to a random value (in whatever way makes the most sense for your organism). Takes a random number generator as input (MABE will pass in the main one MABE is using). + +Example: + +```cpp +void Randomize(emp::Random & random) override { + // Sets the genome to a random integer + my_genome_val = random.GetUInt(); +} +``` + +#### Initialize + +This function should initialize a new organism. It might call randomize, if you want to start with a random organism by default, or it might do something else. + +Example: + +```cpp +void Initialize(emp::Random & random) override { + // In this example we'll just initialize it randomly + Randomize(random); +} +``` + +#### GenerateOutput + +This function makes sure that all traits the organism is supposed to store data in (probably the one holding the genome, at a minimum) are correctly populated. + +Example: + +```cpp +void GenerateOutput() override { + // Store the genome's value in whatever trait is specified + // in output_name (controlled via the config file) + // Note that SetTrait is templated off the time of the + // value you're storing in the trait + SetTrait(SharedData().output_name, my_genome_val); +} +``` + +#### SetupConfig + +Like in other modules, SetupConfig is where you tell the configuration system what parameters the user should be able to configure about this type of organism. The one difference is that rather than being able to call `LinkVar` directly, you need to go through the ManagerData struct: + +```cpp +/// Setup this organism type to be able to load from config. +void SetupConfig() override { + GetManager().LinkVar(SharedData().mut_prob, "mut_prob", + "Probability of mutating on reproduction."); + GetManager().LinkVar(SharedData().output_name, "output_name", + "Name of variable to contain genome."); +} + +``` + +#### SetupModule + +Similarly, SetupModule works like it does in other module types, except that you need to go through `SharedData()` and `GetManager()`. As with other modules, in SetupModule, you need to do any setup that should happen when the module is first intialized, and notify MABE of any trait requirements that the module has. + +Example: + +```cpp +/// Setup this organism type with the traits it need to track. +void SetupModule() override { + + // Setup the output trait. + GetManager().AddSharedTrait(SharedData().output_name, + "Genome output from organism.", + 0); +} +``` + +### Template organism module + +Feel free to copy and modify this for your organism class! + +```cpp +/** + * @note This file is part of MABE, https://github.com/mercere99/MABE2 + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date year. + * + * @file FILE_NAME.hpp + * @brief Description here. + */ + +#ifndef MABE_FILE_NAME_H +#define MABE_FILE_NAME_H + +#include "../core/MABE.hpp" +#include "../core/Organism.hpp" +#include "../core/OrganismManager.hpp" + +namespace mabe { + + class MyOrg : public OrganismTemplate { + protected: + + // Replace this with whatever type you want + int my_genome; + + // Add other private member variables here as necessary + + public: + + // Set your genome to whatever default you want + MyOrg(OrganismManager & _manager) + : OrganismTemplate(_manager), my_genome(0) { } + + // Change copy constructors if you need to do something + // complicated (e.g. dynamically allocated memory) + MyOrg(const MyOrg &) = default; + MyOrg(MyOrg &&) = default; + + // Change int to the the type of your genome + MyOrg(int in, OrganismManager & _manager) + : OrganismTemplate(_manager), my_genome(in) { } + + // If you dynamically allocate memory, remember to delete it + // in the destructor + ~MyOrg() { ; } + + // Declare your ManagerData struct + struct ManagerData : public Organism::ManagerData { + double mut_prob = 0.01; ///< Probability of mutating on reproduction. + std::string output_name = "genome"; ///< Name of trait that should be used to access genome. + }; + + // Unless your genome is something really weird or you want + // to add additional information, this should just work + std::string ToString() const override { return emp::to_string(my_genome); } + + // Mutation function. Gets called on reproduction + // Make this actually do what you want + size_t Mutate(emp::Random & random) override { + + // Retrieve mutation probability from SharedData + double p = SharedData().mut_prob; + + // random.P returns a 1 with with the given probability + // and otherwise returns a 0 + if (random.P(p)) { + // In this simplistic example, ever mutation increments + // the genome value by 1. You probably want to do + // something more interesting/elaborate + my_genome_val++; + + // There was 1 mutation so return 1 + return 1; + } + + // if we didn't mutate, return 0 + return 0; + } + + // Change this as appropriate for your genome + // Should produce a random organism from within the + // space of possible organisms + void Randomize(emp::Random & random) override { + my_genome_val = random.GetUInt(); + } + + // Change this as appropriate for your genome + // Should set up an organism to be however you want + // it to be by default (if not inheriting genetic + // material from a parent) + void Initialize(emp::Random & random) override { + Randomize(random); + } + + // Make sure your genome (and any other outputs) end up + // in the correct traits + void GenerateOutput() override { + // Store the genome's value in whatever trait is specified + // in output_name (controlled via the config file) + // Note that SetTrait is templated off the time of the + // value you're storing in the trait + SetTrait(SharedData().output_name, my_genome_val); + } + + /// Setup this organism type to be able to load from config. + void SetupConfig() override { + GetManager().LinkVar(SharedData().mut_prob, "mut_prob", + "Probability of mutating on reproduction."); + GetManager().LinkVar(SharedData().output_name, "output_name", + "Name of variable to contain genome."); + } + + /// Setup this organism type with the traits it need to track. + void SetupModule() override { + + // Setup the output trait. + GetManager().AddSharedTrait(SharedData().output_name, + "Genome output from organism.", + 0); + } + }; + + // Special MABE_REGISTER command for organisms + MABE_REGISTER_ORG_TYPE(MyOrg, "Example organism."); +} + +// MAKE SURE TO ALSO ADD THIS FILE TO modules.hpp + +#endif + +``` \ No newline at end of file diff --git a/docs/organisms/00_organism_overview.rst b/docs/organisms/00_organism_overview.rst deleted file mode 100644 index b9ecf125..00000000 --- a/docs/organisms/00_organism_overview.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _organisms: - -====================== -What is an Organism? -====================== - -Landing page for organisms; under construction. \ No newline at end of file