diff --git a/.gitignore b/.gitignore index c73b7751..63847958 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Auto-generated files *~ +*.dSYM .vscode +.DS_Store # Prerequisites *.d @@ -36,6 +38,10 @@ *.app examples/NK +build/*.csv +build/*.gen +build/*.mabe +build/MABE # Vim swap files *.swp 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/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); } 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; - -} diff --git a/docs/conf.py b/docs/conf.py index 19cb959d..3ec69634 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. @@ -51,4 +50,8 @@ # 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 --------------------------------------- + +autosectionlabel_prefix_document = True \ No newline at end of file diff --git a/docs/evaluate/00_eval_overview.rst b/docs/evaluate/00_eval_overview.rst new file mode 100644 index 00000000..561518f4 --- /dev/null +++ b/docs/evaluate/00_eval_overview.rst @@ -0,0 +1,164 @@ + +====================== +What is an Evaluator? +====================== + +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() ); + + // 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) + 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); + + if (fitness > max_score) { + max_score = fitness; + } + } + + return max_score; + } + + // 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/first_steps/000_write_gen_file.rst b/docs/first_steps/000_write_gen_file.rst deleted file mode 100644 index ae7104cc..00000000 --- a/docs/first_steps/000_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/first_steps/00_installation.rst b/docs/first_steps/00_installation.rst index cbe653b6..bef2d6ba 100644 --- a/docs/first_steps/00_installation.rst +++ b/docs/first_steps/00_installation.rst @@ -2,19 +2,35 @@ 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 ============== 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`. @@ -28,6 +44,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,28 +57,30 @@ 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. -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. 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. +Type ``git clone``, then paste the URL into your command line. Use the ``--recurse-submodules`` +flag to ensure that all dependencies are made available. -.. code-block:: cpp +.. code-block:: - $ git clone https://github.com/mercere99/MABE2.git + $ git clone --recurse-submodules https://github.com/mercere99/MABE2.git .. 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 + $ git clone --recurse-submodules https://github.com/mercere99/MABE2.git > Cloning into `MABE2`... > remote: Counting objects: 10, done. > remote: Compressing objects: 100% (8/8), done. @@ -70,7 +89,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 =================== @@ -87,7 +106,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 +116,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,23 +130,50 @@ 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 .. -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. + +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..babb6960 --- /dev/null +++ b/docs/first_steps/01_quickstart.md @@ -0,0 +1,894 @@ +# 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). + +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 +- 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. 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. +- `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 { + // 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) + // 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 e7132a4c..00000000 --- a/docs/first_steps/01_quickstart.rst +++ /dev/null @@ -1,175 +0,0 @@ -========== -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:: cpp - - 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:: cpp - - 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:: cpp - - 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:: cpp - - cd settings - ls - -Congratulations! You've created your first ``.mabe`` file! - -Summary -********* - -Step 1: In the ``build`` directory, run the following: - -.. code-block:: cpp - - make clean ; make - - -Step 2: Then run these commands to make sure your ``.gen`` file exists. - -.. code-block:: cpp - - 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:: cpp - - 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:: cpp - - 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:: cpp - ./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:: cpp - - 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:: cpp - - 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:: cpp - - 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. - - diff --git a/docs/index.rst b/docs/index.rst index 7ad75766..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:: @@ -90,18 +91,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/modules/00_module_overview.rst b/docs/modules/00_module_overview.rst index badfed74..6f3ec985 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 <01_module_types.html>`_, `evaluation modules <01_module_types.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 @@ -25,7 +24,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/modules/01_module_types.rst b/docs/modules/01_module_types.rst index ffa87081..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. @@ -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/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/traitinfo.rst b/docs/organisms/01_traitinfo.rst similarity index 100% rename from docs/organisms/traitinfo.rst rename to docs/organisms/01_traitinfo.rst 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. diff --git a/build/settings/Diagnostics.gen b/settings/Diagnostics.gen similarity index 92% rename from build/settings/Diagnostics.gen rename to settings/Diagnostics.gen index 0d08c467..b22f7051 100644 --- a/build/settings/Diagnostics.gen +++ b/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(); diff --git a/build/settings/Diagnostics.mabe b/settings/Diagnostics.mabe similarity index 70% rename from build/settings/Diagnostics.mabe rename to settings/Diagnostics.mabe index 8d4d01eb..f694925a 100644 --- a/build/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; @@ -6,10 +6,11 @@ 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"; // 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? @@ -22,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. @@ -38,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? @@ -50,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:1"; // 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. @@ -93,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(0) print("random_seed = ", random_seed, "\n"); -@start(0) 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"); diff --git a/settings/Mancala.gen b/settings/Mancala.gen new file mode 100644 index 00000000..9a75e6ae --- /dev/null +++ b/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/settings/Mancala.mabe b/settings/Mancala.mabe new file mode 100644 index 00000000..58b4621d --- /dev/null +++ b/settings/Mancala.mabe @@ -0,0 +1,69 @@ +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 +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. + 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. + 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 + // random_org: Pick another random organism from collection. +}; + +SelectTournament select { // Select top fitness orgs from random subgroups for replication. + tournament_size = 7; // Number of orgs in each tournament + 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); +}; + +// @update(10,10) TRACE_EVAL("output.dat", "main_pop", 0); diff --git a/build/settings/NK.gen b/settings/NK.gen similarity index 93% rename from build/settings/NK.gen rename to settings/NK.gen index 37c61e20..41c480e7 100644 --- a/build/settings/NK.gen +++ b/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; diff --git a/settings/NK.mabe b/settings/NK.mabe new file mode 100644 index 00000000..6b86723d --- /dev/null +++ b/settings/NK.mabe @@ -0,0 +1,78 @@ +random_seed = 0; // Seed for random number generator; use 0 to base on time. +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. + +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"; }; +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"; }; +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. + main_pop.INJECT("bits_org", pop_size); // Inject starting population. +} + +// Actions to perform every 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) { + IF (ud == 1000) EXIT(); + + eval_nk.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("UD:", GET_UPDATE(), + " 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 elite_offspring = elite.SELECT(main_pop, next_pop, 25); + + 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); +} 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/build/settings/NK.mabe b/settings/RoyalRoad.mabe similarity index 53% rename from build/settings/NK.mabe rename to settings/RoyalRoad.mabe index 8cdd5fcd..fa630fe9 100644 --- a/build/settings/NK.mabe +++ b/settings/RoyalRoad.mabe @@ -2,62 +2,52 @@ random_seed = 0; // Seed for random number generator; use 0 to ba Population main_pop; // Collection of organisms Population next_pop; // Collection of organisms -Value pop_size = 1000; +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? } -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 - K = 3; // Number of bits used in each gene +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 NK fitness in? + 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 { +FileOutput output { // Output collected data into a specified file. 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"; + 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:10"; // Which updates should we output data? + output_updates = "0:1"; // Which updates should we output data? } - -SelectElite select_e { // Choose the top fitness organisms for replication. +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 = 5; // Number of top-fitness orgs to be replicated - copy_count = 5; // Number of copies to make of replicated organisms + 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? - Value total_count = top_count * copy_count; } -SelectTournament select_t { // Select the top fitness organisms from random subgroups for replication. +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 = 7; // Number of orgs in each tournament - - num_tournaments = pop_size - select_e.total_count; // Number of tournaments to run + 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 = "next_pop,main_pop"; // Population(s) to manage. + 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. - N = eval_nk.N; // Number of bits in organism - mut_prob = 0.01; // Probability of each bit mutating on reproduction. + init_random = 0; // Should we randomize ancestor? (0 = all zeros) } -@start() print("random_seed = ", random_seed, "\n"); -@start() inject("bits_org", "main_pop", pop_size); -@update(500) select_t.tournament_size = 4; +@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 diff --git a/source/Emplode/AST.hpp b/source/Emplode/AST.hpp new file mode 100644 index 00000000..10056142 --- /dev/null +++ b/source/Emplode/AST.hpp @@ -0,0 +1,405 @@ +/** + * @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 AST.hpp + * @brief Manages Abstract Syntax Tree nodes for Emplode. + * @note Status: BETA + */ + +#ifndef EMPLODE_AST_HPP +#define EMPLODE_AST_HPP + +#include "emp/base/assert.hpp" +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" + +#include "Symbol.hpp" +#include "Symbol_Scope.hpp" +#include "Symbol_Object.hpp" +#include "SymbolTableBase.hpp" + +namespace emplode { + + /// Base class for all AST Nodes. + class ASTNode { + protected: + using symbol_ptr_t = emp::Ptr; + using symbol_vector_t = emp::vector; + + using node_ptr_t = emp::Ptr; + 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? + 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? + + 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; } + 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; + + virtual void Write(std::ostream & /* os */=std::cout, + const std::string & /* offset */="") const { } + }; + + /// An ASTNode representing an internal node. + class ASTNode_Internal : public ASTNode { + protected: + std::string name; + node_vector_t children; + + public: + ASTNode_Internal(const std::string & _name="") : name (_name) { } + ~ASTNode_Internal() { + for (auto child : children) child.Delete(); + } + + const std::string & GetName() const override { return name; } + + bool IsInternal() const override { return true; } + + 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); + child->SetParent(this); + } + }; + + /// An ASTNode representing a leaf in the tree (i.e., a variable or literal) + class ASTNode_Leaf : public ASTNode { + protected: + 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(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(); } + + const std::string & GetName() const override { return symbol_ptr->GetName(); } + Symbol & GetSymbol() { return *symbol_ptr; } + + 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 symbol_ptr->HasNumericReturn(); } + bool HasStringReturn() const override { return symbol_ptr->HasStringReturn(); } + + bool IsLeaf() const override { return true; } + + 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 = symbol_ptr->GetName(); + + // If it is a literal, print the value. + if (output == "") { + output = symbol_ptr->AsString(); + + // If the symbol is a string, convert it to a string literal. + if (symbol_ptr->IsString()) output = emp::to_literal(output); + } + os << output; + } + }; + + // Helper functions for making temporary leaves. + emp::Ptr MakeTempLeaf(double val) { + 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("__Temp", val, "Temporary string", nullptr); + out_ptr->SetTemporary(); + return emp::NewPtr(out_ptr); + } + + 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) { + line_id = in_line; + } + + bool IsBlock() const override { return true; } + + 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(); + if (out && out->IsTemporary()) out.Delete(); + } + return nullptr; + } + + void Write(std::ostream & os, const std::string & offset) const override { + for (auto child_ptr : children) { + child_ptr->Write(os, offset+" "); + os << ";\n" << offset; + } + } + }; + + /// Unary mathematical operations. + class ASTNode_Math1 : public ASTNode_Internal { + protected: + // A unary operator take in a double and returns another one. + std::function< double(double) > fun; + public: + 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; } + + void SetFun(std::function< double(double) > _fun) { fun = _fun; } + + symbol_ptr_t Process() override { + emp_assert(children.size() == 1); + 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 GetSymbolTable().MakeTempSymbol(output_value); + } + + void Write(std::ostream & os, const std::string & offset) const override { + os << name; + children[0]->Write(os, offset); + } + }; + + /// Binary operations. + template + class ASTNode_Op2 : public ASTNode_Internal { + protected: + std::function< RETURN_T(ARG1_T, ARG2_T) > fun; + public: + 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(); } + bool HasValue() const override { return true; } + + void SetFun(std::function< RETURN_T(ARG1_T, ARG2_T) > _fun) { fun = _fun; } + + symbol_ptr_t Process() override { + emp_assert(children.size() == 2); + 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 GetSymbolTable().MakeTempSymbol(out_val); + } + + void Write(std::ostream & os, const std::string & offset) const override { + children[0]->Write(os, offset); + os << " " << name << " "; + children[1]->Write(os, offset); + } + }; + + using ASTNode_Math2 = ASTNode_Op2; + + + class ASTNode_Assign : public ASTNode_Internal { + public: + 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(); } + 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(); } + + symbol_ptr_t Process() override { + 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. + 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; + } + }; + + 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 << ") "; + children[1]->Write(os, offset); + if (children.size() > 2) { + os << "\n" << offset << "ELSE "; + children[2]->Write(os, offset); + } + } + }; + + class ASTNode_Call : public ASTNode_Internal { + public: + 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(); } + 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. + + symbol_ptr_t Process() override { + emp_assert(children.size() >= 1); + symbol_ptr_t fun = children[0]->Process(); + + // Collect all arguments and call + symbol_vector_t args; + for (size_t i = 1; i < children.size(); i++) { + args.push_back(children[i]->Process()); + } + symbol_ptr_t result = fun->Call(args); + + // Cleanup and return + for (auto arg : args) if (arg->IsTemporary()) arg.Delete(); + return result; + } + + void Write(std::ostream & os, const std::string & offset) const override { + children[0]->Write(os, offset); // Function name + os << "("; + for (size_t i=1; i < children.size(); i++) { + if (i>1) os << ", "; + children[i]->Write(os, offset); + } + os << ")"; + } + }; + + class ASTNode_Event : public ASTNode_Internal { + protected: + using setup_fun_t = std::function; + 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, + 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 { + emp_assert(children.size() >= 1); + symbol_vector_t arg_entries; + for (size_t id = 1; id < children.size(); id++) { + arg_entries.push_back( children[id]->Process() ); + } + setup_event(children[0], arg_entries); + return nullptr; + } + + void Write(std::ostream & os, const std::string & offset) const override { + os << "@" << GetName() << "("; + for (size_t i = 1; i < children.size(); i++) { + if (i>1) os << ", "; + children[i]->Write(os, offset); + } + os << ") "; + children[0]->Write(os, offset); // Action. + } + }; + +} + +#endif diff --git a/source/Emplode/DataFile.hpp b/source/Emplode/DataFile.hpp new file mode 100644 index 00000000..77c16fee --- /dev/null +++ b/source/Emplode/DataFile.hpp @@ -0,0 +1,110 @@ +/** + * @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 DataFile.hpp + * @brief Manages a DataFile object for config. + * @note Status: BETA + */ + +#ifndef EMPLODE_DATA_FILE_HPP +#define EMPLODE_DATA_FILE_HPP + +#include +#include + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/io/StreamManager.hpp" + +#include "EmplodeType.hpp" + +namespace emplode { + + /// A DataFile maintains an output file that has specified columns and can be generate + /// dynamically. + class DataFile : public EmplodeType { + private: + using data_fun_t = std::function; + using setup_fun_t = std::function; + struct ColumnInfo { + std::string header; + data_fun_t fun; + }; + + 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. + emp::vector setup; ///< Commands to run before writing columns. + + public: + DataFile() = delete; + DataFile(const std::string & in_name, emp::StreamManager & _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. + static void InitType(TypeInfo & info) { + 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, 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. + + // 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'; + } + + // 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 << ","; + file << cols[i].fun(); + } + file << std::endl; + + return 1; + } + + static std::string EMPGetTypeName() { return "emplode::DataFile"; } + }; +} + +#endif diff --git a/source/config/TODO b/source/Emplode/DeveloperNotes.md similarity index 53% rename from source/config/TODO rename to source/Emplode/DeveloperNotes.md index 5acef959..326d023f 100644 --- a/source/config/TODO +++ b/source/Emplode/DeveloperNotes.md @@ -1,7 +1,42 @@ -* Config as a whole should move from MABE to Empirical +Type System +Symbol Tables +Parser -* We need a consistent and functional error system. -Right now we either output direct to the command line OR create a ConfigEntry_Error +LEVEL MAP: + +Symbol - [] +Lexer - [] + +SymbolTableBase - [Symbol] + +TypeInfo - [Symbol,SymbolTableBase] Basic information for a user-defined type. +Symbol_Function - [Symbol] +Symbol_Linked - [Symbol] + +Symbol_Scope - [Symbol,Symbol_Function,Symbol_Linked,TypeInfo] + +EmplodeType - [Symbol_Scope,TypeInfo] + +Symbol_Object - [Symbol_Scope,EmplodeType] + +AST - [Symbol_Object,Symbol,SymbolTableBase] + +Events - [AST] +DataFile - [EmplodeType] + +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. +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. @@ -16,26 +51,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 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. diff --git a/source/Emplode/Emplode.hpp b/source/Emplode/Emplode.hpp new file mode 100644 index 00000000..0c1627ae --- /dev/null +++ b/source/Emplode/Emplode.hpp @@ -0,0 +1,337 @@ +/** + * @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-2022. + * + * @file Emplode.hpp + * @brief Manages all configuration with Emplode language. + * @note Status: BETA + * + * Example usage: + * 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; + * } + * 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["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. + * m() = a * c; // Functions have parens after the variable name; evaluated when called. + * n(o,p) = o + p; // Functions may have arguments. + * q = 'q'; // Literal chars are translated immediately to their ascii value + * + * // use a : instead of a . to access built-in values. Note a leading colon uses current scope. + * r = k:scope_size; // = 3 (always a value) + * s = f:names; // = ["a","b","c","g","h","i","j"] (vector of strings in alphabetical order) + * t = c:string; // = "17" (convert value to string) + * u = (t+"00"):value; // = 1700 (convert string to value; can use temporaries!) + * // ALSO- :is_string, :is_value, :is_struct, :is_array (return 0 or 1) + * // :type (returns a string indicating type!) + * + * + * In practice, most settings will be pre-defined in typed scopes: + * MarkovBrain Sheep = { + * outputs = 10; + * node_weights = 0.75; + * recurrance = 5; + * } + * MarkovBrain Wolves = { + * outputs = 10; + * node_weights = 0.75; + * recurrance = 3; + * } + * modules = { + * Mutations = { + * copy_prob = 0.001; + * insert_prob = 0.05; + * } + * } + */ + +#ifndef EMPLODE_HPP +#define EMPLODE_HPP + +#include +#include +#include +#include + +#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" + +#include "AST.hpp" +#include "DataFile.hpp" +#include "EmplodeType.hpp" +#include "EventManager.hpp" +#include "Lexer.hpp" +#include "Parser.hpp" +#include "Symbol_Function.hpp" +#include "SymbolTable.hpp" +#include "TypeInfo.hpp" + +namespace emplode { + + class Emplode { + public: + using pos_t = emp::TokenStream::Iterator; + + protected: + std::string filename; ///< Source for for code to generate. + 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. + + std::string ConcatLexemes(pos_t start_pos, pos_t end_pos) const { + emp_assert(start_pos <= end_pos); + 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(); + } + + public: + Emplode(std::string in_filename="") + : filename(in_filename) + , symbol_table("Emplode") + , ast_root(symbol_table.GetRootScope()) + { + if (filename != "") Load(filename); + + // Setup default functions. + + // 'EXEC' dynamically executes the contents of a string. + 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. + auto print_fun = [](const emp::vector> & args) { + for (auto entry_ptr : args) entry_ptr->Print(std::cout); + std::cout << std::endl; + return 0; + }; + AddFunction("PRINT", print_fun, "Print out the provided variables."); + + // Default 1-input math functions + 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 + 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 + 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) { + return emp::NewPtr(name, symbol_table.GetFileManager()); + }; + 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( + "ADD_COLUMN", + [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); + return out_string; + }); + }, + "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' + Emplode(const Emplode &) = delete; + Emplode(Emplode &&) = delete; + Emplode & operator=(const Emplode &) = delete; + Emplode & operator=(Emplode &&) = delete; + + /// Create a new type of event that can be used in the scripting language. + bool AddSignal(const std::string & name) { return symbol_table.AddSignal(name); } + + /// Trigger all actions linked to a signal. + template + void Trigger(const std::string & name, ARG_Ts... args) { + symbol_table.Trigger(name, std::forward(args)...); + } + + template + TypeInfo & AddType(ARG_Ts &&... args) { + 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 + /// vector of ASTNode pointers, but may return any known type. + template + void AddFunction(const std::string & name, FUN_T fun, const std::string & desc) { + symbol_table.AddFunction(name, fun, desc); + } + + SymbolTable & GetSymbolTable() { return symbol_table; } + const SymbolTable & GetSymbolTable() const { return symbol_table; } + + // Load a single, specified configuration file. + void Load(const std::string & 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) + pos_t pos = tokens.begin(); // Start at the beginning of the file. + + // Parse and run the program, starting from the outer scope. + ParseState state{pos, symbol_table, symbol_table.GetRootScope(), lexer}; + auto cur_block = parser.ParseStatementList(state); + cur_block->Process(); + + // Store this AST onto the full set we're working with. + ast_root.AddChild(cur_block); + } + + // Sequentially load a series of configuration files. + void Load(const emp::vector & filenames) { + for ( const std::string & fn : filenames) Load(fn); + } + + // Load a single, specified configuration file. + // @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) { + 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 = parser.ParseStatementList(state); + cur_block->Process(); + + // Store this AST onto the full set we're working with. + ast_root.AddChild(cur_block); + } + + // 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. + 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 + + // 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. + emp::Datum result; + if (result_ptr) { + 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. + } + + + Emplode & Write(std::ostream & os=std::cout) { + symbol_table.GetRootScope().WriteContents(os); + os << '\n'; + symbol_table.PrintEvents(os); + return *this; + } + + Emplode & Write(const std::string & filename) { + // If the filename is empty or "_", output to standard out. + if (filename == "" || filename == "_") return Write(); + + // Otherwise generate an output file. + std::ofstream out_file(filename); + return Write(out_file); + } + }; + +} + +#endif diff --git a/source/Emplode/EmplodeType.hpp b/source/Emplode/EmplodeType.hpp new file mode 100644 index 00000000..c893150f --- /dev/null +++ b/source/Emplode/EmplodeType.hpp @@ -0,0 +1,169 @@ +/** + * @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 EmplodeType.hpp + * @brief Setup types for use in scripting. + * @note Status: BETA + */ + +#ifndef EMPLODE_TYPE_HPP +#define EMPLODE_TYPE_HPP + +#include "emp/base/assert.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 = nullptr; + + 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(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. + } + + /// 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. + 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(); + } + const Symbol_Scope & AsScope() const { + emp_assert(!symbol_ptr.IsNull()); + return *symbol_ptr.DynamicCast(); + } + + /// Setup an instance of a new EmplodeType object; provide it with its symbol and type information. + void Setup(Symbol_Object & in_symbol) { + symbol_ptr = &in_symbol; + + // Link specialized variable for the derived type. + SetupConfig(); + + // 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 = symbol_ptr->GetTypeInfoPtr()->GetMemberFunctions(); + + // std::cout << "Loading member functions for '" << in_symbol.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); + }; + 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; + } + } + + + // ---== Configuration Management ==--- + + /// Link a variable to a configuration entry - the value will default to the + /// variables current value, but be updated when configs are loaded. + template + Symbol_Linked & LinkVar(VAR_T & var, + const std::string & name, + const std::string & desc, + bool is_builtin = false) { + return AsScope().LinkVar(name, var, desc, is_builtin); + } + + /// 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 + Symbol_LinkedFunctions & LinkFuns(std::function get_fun, + std::function set_fun, + const std::string & name, + const std::string & desc, + bool is_builtin = false) { + return AsScope().LinkFuns(name, get_fun, set_fun, desc, is_builtin); + } + + // Helper functions and info. + template + struct MenuEntry { + VAR_T value; + std::string name; + std::string desc; + + MenuEntry(VAR_T v, const std::string & n, const std::string & d) + : value(v), name(n), desc(d) {} + }; + + /// Link a set of menu option to a variable value. + /// Each option should include three arguments: + /// The return value, the option name, and the option description. + template + Symbol_LinkedFunctions & LinkMenu(VAR_T & var, + const std::string & name, + const std::string & desc, + const Ts &... entries) { + auto menu = emp::BuildObjVector, 3>(entries...); + + // Build the "get" function: take the current value of the menu and return the name. + std::function get_fun = + [&var,menu](){ + for (const MenuEntry & entry : menu) { + if (var == entry.value) return entry.name; + } + return std::string("UNKNOWN"); + }; + + // Build the "set" function: take the name of the menu option and update variable.. + std::function set_fun = + [&var,name,menu](const std::string & entry_name){ + for (const MenuEntry & entry : menu) { + if (entry_name == entry.name) { var = entry.value; return; } + }; + // AddError("Trying to set menu '", name, "' to '", entry_name, "'; does not exist."); + }; + + // Update the description to list all of the menu options. + std::stringstream new_desc; + + // Start with the input description and add the description for each menu option. + new_desc << desc; + for (const MenuEntry & entry : menu) { + new_desc << "\n " << entry.name << ": " << entry.desc; + } + + return AsScope().LinkFuns(name, get_fun, set_fun, new_desc.str()); + } + }; +} + +#endif diff --git a/source/Emplode/EventManager.hpp b/source/Emplode/EventManager.hpp new file mode 100644 index 00000000..34295875 --- /dev/null +++ b/source/Emplode/EventManager.hpp @@ -0,0 +1,195 @@ +/** + * @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 >; + struct 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() { + for (auto x : params) x.Delete(); + action.Delete(); + } + + void Trigger(const symbol_vec_t & args) { + if (args.size() < params.size()) { + 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; + 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(); + + 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(*args[param_id]); + if (!success) { + std::cerr << "ERROR: setting action parameter '" + << param_sym->GetName() << "' failed" << std::endl; + exit(1); + } + } + + // 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(std::ostream & os) const { + os << "@" << signal_name << "("; + // @CAO: Write out parameters... + os << ") "; + action->Write(os); + os << ";\n"; + } + }; + + struct Event { + std::string signal_name; + size_t num_params; + emp::vector> actions; + + Event(const std::string & _name, size_t _params) + : signal_name(_name), num_params(_params) { } + ~Event() { for (auto ptr : actions) ptr.Delete(); } + + void Trigger(symbol_vec_t args) { + for (emp::Ptr action : actions) { + action->Trigger(args); + } + } + + void Write(std::ostream & os) const { + for (emp::Ptr action : actions) { + action->Write(os); + } + } + + }; + + 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 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) { + 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); + + 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? + ) { + 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); + + return true; + } + + template + 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); + + 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(std::ostream & os) const { + for (auto [name, ptr] : event_map) { + ptr->Write(os); + } + } + }; + + +} + +#endif diff --git a/source/Emplode/Lexer.hpp b/source/Emplode/Lexer.hpp new file mode 100644 index 00000000..ff9e00a1 --- /dev/null +++ b/source/Emplode/Lexer.hpp @@ -0,0 +1,64 @@ +/** + * @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 Lexer.hpp + * @brief A Lexer used to tokenize Emplode config files. + * @note Status: BETA + **/ + +#ifndef EMPLODE_LEXER_HPP +#define EMPLODE_LEXER_HPP + +#include "emp/compiler/Lexer.hpp" + +namespace emplode { + + class Lexer : public emp::Lexer { + private: + 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() { + // Whitespace and comments should always be dismissed (top priority) + IgnoreToken("Whitespace", "[ \t\n\r]+"); + IgnoreToken("//-Comments", "//.*"); + IgnoreToken("/*...*/-Comments", "/[*]([^*]|([*]+[^*/]))*[*]+/"); + + // Keywords have top priority, especially over identifiers. Most are simply reserved words. + token_keyword = AddToken("Keyword", + "(ELSE)|(IF)" + // Reserved keywords below. + "|(AND)|(AUTO)|(BREAK)|(CASE)|(CAST)|(CATCH)|(CLASS)|(CONST)|(CONTINUE)|(DEBUG)" + "|(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. + 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_dots = AddToken("Dots", "\".\"+"); + + /// Symbol tokens should have least priority. They include any solitary character not listed + /// above, or pre-specified multi-character groups. + 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; } + 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; } + }; +} + +#endif diff --git a/source/Emplode/Parser.hpp b/source/Emplode/Parser.hpp new file mode 100644 index 00000000..7e111c5f --- /dev/null +++ b/source/Emplode/Parser.hpp @@ -0,0 +1,612 @@ +/** + * @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-2022. + * + * @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 "AST.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(const 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. + 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(); + } + 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 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); } + bool IsDots() const { return pos && lexer->IsDots(*pos); } + + 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. + 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(); } + + /// 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 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; + ++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); + + emp::notify::Error("(", line_info, " in '", pos.GetTokenStream().GetName(), "'): ", + emp::to_string(std::forward(args)...), "\nAborting."); + 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_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); + } + 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 AddAction(Ts &&... args) { symbol_table->AddAction(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 emp::Token & op_token, + emp::Ptr value1, + emp::Ptr value2); + + /// 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? + [[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); + + /// 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); + + /// 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(), state.GetLine()); + cur_block->SetSymbolTable(state.GetSymbolTable()); + 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) + { + 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()) { + 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, start_line); + } + + // 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(), ")"); + + // First check for a unary negation at the start of the value. + if (state.UseIfChar('-')) { + auto out_val = emp::NewPtr("unary negation", state.GetLine()); + 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); + + // 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 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 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 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, 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, 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; } ); + 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, 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); + + 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, op_token.line_id); + out_val->SetFun(fun); + out_val->AddChild(in_node1); + out_val->AddChild(in_node2); + + return out_val; + } + else { + 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; }); + 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 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? + + 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); + emp::Token op_token = state.AsToken(); + 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, op_token.line_id); + } + + // Otherwise we must have a binary math operation. + else { + emp::Ptr node2 = ParseExpression(state, false, precedence_map[op]); + 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()); + 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 == "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, "'"); + return state.AddObject(type_name, var_name); + } + + // 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 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, true) ); + state.UseIfChar(','); // Skip comma if next (does allow trailing comma) + } + state.UseRequiredChar(')', "Event args must end in a ')'"); + + auto action_block = emp::NewPtr(state.GetScope(), state.GetLine()); + action_block->SetSymbolTable(state.GetSymbolTable()); + emp::Ptr action_node = ParseStatement(state); + + // If the action statement is real, add it to the action block. + if (!action_node.IsNull()) action_block->AddChild( action_node ); + + Debug("Building event '", trigger_name, "' with args ", args); + + state.AddAction(trigger_name, args, action_block, start_token.line_id); + + return nullptr; + } + + /// 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."); + 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(), ")"); + + // 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 select commands that are only possible at the full statement level (not expressions) + 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); + + // 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/Symbol.hpp b/source/Emplode/Symbol.hpp new file mode 100644 index 00000000..bd334a8d --- /dev/null +++ b/source/Emplode/Symbol.hpp @@ -0,0 +1,381 @@ +/** + * @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-2022. + * + * @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 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 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 EMPLODE_SYMBOL_HPP +#define EMPLODE_SYMBOL_HPP + +#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" +#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" + +namespace emplode { + + class EmplodeType; + class Symbol_Function; + class Symbol_Object; + class Symbol_Scope; + class TypeInfo; + + class Symbol { + protected: + 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 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, + BOOL, INT, UNSIGNED, DOUBLE, // Values + STRING, FILENAME, PATH, URL, ALPHABETIC, ALPHANUMERIC, NUMERIC // Strings + }; + Format format = Format::NONE; + + // If we know the constraints on this parameter we can perform better error checking. + 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 symbol_ptr_t = emp::Ptr; + + // Helper functions. + + /// Write out the provided description at the comment_offset. The start_pos is where the + /// text currently is. For multi-line comments, make sure to indent properly. + void WriteDesc(std::ostream & os, size_t comment_offset, size_t start_pos) const { + // If there is no description, provide a newline and stop. + if (desc.size() == 0) { + std::cout << '\n'; + return; + } + + // Break the description at the newlines. + emp::vector lines = emp::slice(desc); + + for (const auto & line : lines) { + // Find the current line to print. + while (start_pos++ < comment_offset) os << " "; + os << "// " << line << '\n'; + start_pos = 0; + } + } + + public: + Symbol(const std::string & _name, + const std::string & _desc, + emp::Ptr _scope) + : name(_name), desc(_desc), scope(_scope) { } + 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; } + 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 = 0; ///< Derived classes must provide type info. + + virtual bool IsNumeric() const { return false; } ///< Is symbol any kind of number? + virtual bool IsString() const { return false; } ///< Is symbol a string? + + virtual bool IsError() const { return false; } ///< Does symbol flag an error? + virtual bool IsFunction() const { return false; } ///< Is symbol a function? + virtual bool IsObject() const { return false; } ///< Is symbol associated with C++ object? + 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? + + 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 { 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; } + 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; } + 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_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; } + 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 + 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_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_substitutable) { + return static_cast(AsDouble()); + } + else if constexpr (std::is_same() || + std::is_same()) { + return AsString(); + } + + // 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; } + + // 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 EmplodeType. + else if constexpr (std::is_base_of()) { + emp::Ptr obj_ptr = GetObjectPtr(); + // 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... + else { + 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; + } + } + + Symbol & SetMin(double min) { range.SetLower(min); 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; } + + /// 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 + 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) LookupSymbol(in_name); } + + /// 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 EmplodeType&() { return *GetObjectPtr(); } + + /// Allocate a duplicate of this class. + virtual symbol_ptr_t Clone() const = 0; + + 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 symbol, don't print it. + if (IsBuiltin()) return *this; + + // Setup this symbol. + std::string cur_line = prefix; + if (IsLocal()) cur_line += emp::to_string(GetTypename(), " ", name, " = "); + else cur_line += emp::to_string(name, " = "); + + // Print the current value of this variable; if it's a string make sure to turn it to a literal. + cur_line += IsString() ? emp::to_literal(AsString()) : AsString(); + cur_line += ";"; + os << cur_line; + + // Write out the description for this line. + WriteDesc(os, comment_offset, cur_line.size()); + + 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 symbol for an internally maintained variable. + class Symbol_Var : public Symbol { + private: + emp::Datum value; + + using scope_ptr_t = emp::Ptr; + public: + 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), 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 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 value.IsDouble(); } + bool IsString() const override { return value.IsString(); } + bool IsLocal() const override { return true; } + + bool CopyValue(const Symbol & in) override { + if (in.IsNumeric()) SetValue(in.AsDouble()); + else SetString(in.AsString()); + return true; + } + }; + + + /// A Symbol to transmit an error due to invalid parsing. + /// The description provides the error and the IsError() flag is set to true. + class Symbol_Error : public Symbol { + private: + using this_t = Symbol_Error; + public: + template + 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; } + + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } + }; + + + //////////////////////////////////////////////////// + // Function definitions... + + emp::Ptr Symbol::Call( const emp::vector & /* args */ ) { + return emp::NewPtr("Cannot call a function on non-function '", name, "'."); + } + +} + +#endif diff --git a/source/Emplode/SymbolTable.hpp b/source/Emplode/SymbolTable.hpp new file mode 100644 index 00000000..a536a431 --- /dev/null +++ b/source/Emplode/SymbolTable.hpp @@ -0,0 +1,226 @@ +/** + * @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 + * + */ + +#ifndef EMPLODE_SYMBOL_TABLE_HPP +#define EMPLODE_SYMBOL_TABLE_HPP + +#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 "EventManager.hpp" +#include "Symbol_Scope.hpp" +#include "SymbolTableBase.hpp" + + +namespace emplode { + + class SymbolTable : public SymbolTableBase { + protected: + Symbol_Scope root_scope; ///< Outermost (global) scope. + 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), 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" ); + 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["Var"]; + typeid_map[emp::GetTypeID()] = type_map["Var"]; + + 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 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); } + + 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 + /// vector of ASTNode pointers, but may return any known type. + template + void AddFunction(const std::string & name, FUN_T fun, const std::string & 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 + /// 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, + 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( *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]; + } + + /// 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, + 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, copy_fun, + emp::GetTypeID(), is_config_owned); + OBJECT_T::InitType(info); + return info; + } + + /// 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) { + auto init_fun = [](const std::string & /*name*/){ return emp::NewPtr(); }; + auto copy_fun = DefaultCopyFun(); + return AddType(type_name, desc, init_fun, copy_fun, true); + } + + /// 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. + 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, type_info, is_config_owned); + + // Let the new object know about its scope. + new_obj->Setup(new_obj_symbol); + + 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, + 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.", + 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(); // 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. + 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. + 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) + 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 { event_manager.Write(os); } + + }; + +} +#endif diff --git a/source/Emplode/SymbolTableBase.hpp b/source/Emplode/SymbolTableBase.hpp new file mode 100644 index 00000000..36cee66f --- /dev/null +++ b/source/Emplode/SymbolTableBase.hpp @@ -0,0 +1,248 @@ +/** + * @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-2022. + * + * @file SymbolTableBase.hpp + * @brief Tools for working with Symbol objects, especially for wrapping functions. + * @note Status: BETA + */ + +#ifndef EMPLODE_SYMBOL_TABLE_BASE_HPP +#define EMPLODE_SYMBOL_TABLE_BASE_HPP + +#include + +#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" + +#include "Symbol.hpp" + +namespace emplode { + + // 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 & ); + + // 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(), &value); + } else { + 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 + 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; + + // If a return value is already a symbol pointer, just pass it through. + if constexpr (std::is_same()) { + 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() || + std::is_same() || + std::is_same()) { + return MakeTempSymbol(value); + } + + // If a value is a REFERENCE to an Emplode type, return its Symbol_Object. + else if constexpr (is_ref && std::is_base_of()) { + 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(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 conversion of value to emplode::Symbol"); + } + } + + + 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.ValueToSymbol( fun(), name ); + }; + } + + }; + + // 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.ValueToSymbol( fun(args), name ); + } + + // 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.ValueToSymbol( + fun(args[0]->As(), args[INDEX_VALS+1]->template As()...), + name + ); + } + }; + } + + 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.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.ValueToSymbol( fun(*typed_ptr, args), name ); + } + + // 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.ValueToSymbol( fun(*typed_ptr, args[INDEX_VALS]->template As()...), name ); + } + }; + } + + }; + + // Wrap a provided function to make it take a vector of Ptr and return a + // single Ptr representing the result. + template + 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, *this); + } else { + using index_t = emp::ValPackCount; + return WrapFunction_impl::ConvertFun(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 + 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 ); + + using helper_t = WrapFunction_impl::fun_t, index_t>; + return helper_t::ConvertMemberFun(name, fun, *this); + } + + }; + +} + +#endif diff --git a/source/Emplode/Symbol_Function.hpp b/source/Emplode/Symbol_Function.hpp new file mode 100644 index 00000000..be36dfda --- /dev/null +++ b/source/Emplode/Symbol_Function.hpp @@ -0,0 +1,80 @@ +/** + * @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 "SymbolTableBase.hpp" + +namespace emplode { + + class Symbol_Function : public Symbol { + private: + using this_t = Symbol_Function; + using symbol_ptr_t = emp::Ptr; + 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: + Symbol_Function(const std::string & _name, + std_fun_t _fun, + const std::string & _desc, + emp::Ptr _scope, + emp::TypeID _ret_type) + : Symbol(_name, _desc, _scope), fun(_fun), return_type(_ret_type) + { + } + + 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(); } + + /// 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; + return_type = in_fun.return_type; + + return true; + } + + + symbol_ptr_t Call( const emp::vector & args ) override { return fun(args); } + }; + +} + +#endif diff --git a/source/Emplode/Symbol_Linked.hpp b/source/Emplode/Symbol_Linked.hpp new file mode 100644 index 00000000..f062d6c2 --- /dev/null +++ b/source/Emplode/Symbol_Linked.hpp @@ -0,0 +1,121 @@ +/** + * @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_Linked.hpp + * @brief Manages a configuration entry linked to another variable or functions. + * @note Status: BETA + */ + +#ifndef EMPLODE_SYMBOL_LINKED_HPP +#define EMPLODE_SYMBOL_LINKED_HPP + +#include + +#include "Symbol.hpp" + +namespace emplode { + + /// Symbol can be linked directly to a real variable. + template + class Symbol_Linked : public Symbol { + private: + T & var; + public: + using this_t = Symbol_Linked; + + template + 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 "[LinkedValue]"; + else return "[Error:InvalidLinkedType]"; + } + + 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); } + Symbol & SetValue(double in) override { var = (T) in; return *this; } + Symbol & SetString(const std::string & in) override { + var = emp::from_string(in); + return *this; + } + + bool IsNumeric() const override { return std::is_scalar_v; } + + bool CopyValue(const Symbol & in) override { var = in.AsDouble(); return true; } + }; + + /// Specialization for Symbol linked to a string variable. + template <> + class Symbol_Linked : public Symbol { + private: + std::string & var; + public: + using this_t = Symbol_Linked; + + template + 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 "[LinkedString]"; } + + 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; } + 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 Symbol & in) override { var = in.AsString(); return true; } + }; + + /// Symbol can be linked to a pair of (Get and Set) functions + /// rather than as direct variable. + template + class Symbol_LinkedFunctions : public Symbol { + private: + std::function get_fun; + std::function set_fun; + public: + using this_t = Symbol_LinkedFunctions; + + template + Symbol_LinkedFunctions(const std::string & in_name, + std::function in_get, + std::function in_set, + ARGS &&... args) + : Symbol(in_name, std::forward(args)...) + , get_fun(in_get) + , set_fun(in_set) + { ; } + Symbol_LinkedFunctions(const this_t &) = default; + + std::string GetTypename() const override { return "[Symbol_LinkedFunctions]"; } + + 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() ); } + 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; + } + + bool IsNumeric() const override { return std::is_scalar_v; } + bool IsString() const override { return std::is_same(); } + + bool CopyValue(const Symbol & in) override { SetString( in.AsString() ); return true; } + }; + +} + +#endif diff --git a/source/Emplode/Symbol_Object.hpp b/source/Emplode/Symbol_Object.hpp new file mode 100644 index 00000000..3e145ea8 --- /dev/null +++ b/source/Emplode/Symbol_Object.hpp @@ -0,0 +1,127 @@ +/** + * @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-2022. + * + * @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/error.hpp" +#include "emp/base/map.hpp" + +#include "EmplodeType.hpp" +#include "Symbol_Scope.hpp" + +namespace emplode { + + // Set of multiple config entries. + class Symbol_Object : public Symbol_Scope { + protected: + ///< Point to associated object and track ownership + emp::Ptr obj_ptr = nullptr; + emp::Ptr type_info_ptr = nullptr; + bool obj_owned = false; + + public: + Symbol_Object(const std::string & _name, + const std::string & _desc, + emp::Ptr _scope, + emp::Ptr _obj, + TypeInfo & _type_info, + bool _owned) + : Symbol_Scope(_name, _desc, _scope) + , obj_ptr(_obj), type_info_ptr(&_type_info), obj_owned(_owned) { } + + 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) + { + // 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; } + 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() const override { + if (type_info_ptr.IsNull()) return emp::GetTypeID(); + return type_info_ptr->GetTypeID(); + } + + /// Set this symbol to be a correctly-typed scope pointer. + 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; + std::cerr << "Target: " << in.DebugString() << std::endl; + emp_error("Assignment failed."); + 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 (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); + } + + /// Make a copy of this scope and all of the entries inside it. + 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); + + // 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; + } + }; + + // 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, + TypeInfo & type_info, + bool obj_owned + ) { + return Add(name, desc, this, obj_ptr, type_info, obj_owned); + } + +} +#endif diff --git a/source/Emplode/Symbol_Scope.hpp b/source/Emplode/Symbol_Scope.hpp new file mode 100644 index 00000000..be54b60b --- /dev/null +++ b/source/Emplode/Symbol_Scope.hpp @@ -0,0 +1,249 @@ +/** + * @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_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 EMPLODE_SYMBOL_SCOPE_HPP +#define EMPLODE_SYMBOL_SCOPE_HPP + +#include "emp/base/map.hpp" + +#include "Symbol.hpp" +#include "Symbol_Function.hpp" +#include "Symbol_Linked.hpp" +#include "TypeInfo.hpp" + +namespace emplode { + + class EmplodeType; + class Symbol_Object; + + // Set of multiple config entries. + class Symbol_Scope : public Symbol { + protected: + 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. + + template + T & Add(const std::string & name, ARGS &&... args) { + auto new_ptr = emp::NewPtr(name, std::forward(args)...); + emp_assert(!emp::Has(symbol_table, name), "Do not redeclare functions or variables!", + name); + symbol_table[name] = new_ptr; + return *new_ptr; + } + + template + T & AddBuiltin(const std::string & name, ARGS &&... args) { + T & result = Add(name, std::forward(args)...); + result.SetBuiltin(); + return result; + } + + public: + 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 + for (auto [name, ptr] : symbol_table) { symbol_table[name] = ptr->Clone(); } + } + Symbol_Scope(Symbol_Scope &&) = default; + + ~Symbol_Scope() { + // Clear up the symbol table. + 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! + + /// 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) { + 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(); + + // 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)) { + 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) { + 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! + return true; + } + + + /// 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 + 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->LookupSymbol(name); + } + + // Otherwise we found it! + return it->second; + } + + /// Lookup a variable, scanning outer scopes if needed (in const context!) + 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->LookupSymbol(name); + } + + // Otherwise we found it! + return it->second; + } + + /// 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 + 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); + } + + /// 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 + 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 Add>(name, get_fun, set_fun, desc, this); + } + + /// Add an internal variable of type String. + 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. + 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, + TypeInfo & type_info, + bool obj_owned=false + ); + + /// Add a new user-defined function. + template + 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, 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. + 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. + for (auto [name, ptr] : symbol_table) { + if (ptr->IsBuiltin()) continue; // Skip writing built-in entries. + ptr->Write(os, prefix, comment_offset); + } + + return *this; + } + + /// 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 + { + // If this is a built-in scope, don't print it. + 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, [](symbol_ptr_t ptr){ return !ptr->IsBuiltin(); }); + + // Only open this scope if there are contents. + 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 (has_body) { + WriteContents(os, prefix+" ", comment_offset); + os << prefix << "}\n"; // Close the scope. + } + + return *this; + } + + /// Make a copy of this scope and all of the entries inside it. + symbol_ptr_t Clone() const override { return emp::NewPtr(*this); } + }; + +} +#endif diff --git a/source/Emplode/TypeInfo.hpp b/source/Emplode/TypeInfo.hpp new file mode 100644 index 00000000..c5d19d06 --- /dev/null +++ b/source/Emplode/TypeInfo.hpp @@ -0,0 +1,118 @@ +/** + * @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 TypeInfo.hpp + * @brief Manages all of the information about a particular type in the config language. + * @note Status: BETA + */ + +#ifndef EMPLODE_TYPE_INFO_HPP +#define EMPLODE_TYPE_INFO_HPP + +#include + +#include "emp/base/assert.hpp" +#include "emp/meta/TypeID.hpp" +#include "emp/tools/string_utils.hpp" + +#include "Symbol.hpp" +#include "SymbolTableBase.hpp" + +namespace emplode { + + // Information about a member function. + struct MemberFunInfo { + using symbol_ptr_t = emp::Ptr; + using fun_t = std::function &)>; + + 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, 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. + class TypeInfo { + private: + 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; + 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; + + public: + // Constructor to allow a simple new configuration type + 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(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) + : symbol_table(_st), 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; } + const std::string & GetDesc() const { return desc; } + 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="__temp__") 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 { + if (copy_fun) return copy_fun(from, to); + return false; + } + + // 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. + template + void AddMemberFunction( + const std::string & name, + 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 TypeInfo can make use of ---- + MemberFunInfo::fun_t member_fun = symbol_table.WrapMemberFunction(type_id, name, fun); + + // Add this member function to the library we are building. + using return_t = typename emp::FunInfo::return_t; + member_funs.emplace_back(name, desc, member_fun, emp::GetTypeID()); + } + }; + +} +#endif diff --git a/source/interface/FileOutput.hpp b/source/OLD/FileOutput.hpp similarity index 93% rename from source/interface/FileOutput.hpp rename to source/OLD/FileOutput.hpp index ea00fa82..b5169231 100644 --- a/source/interface/FileOutput.hpp +++ b/source/OLD/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. @@ -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; } @@ -107,6 +108,7 @@ namespace mabe { } void BeforeUpdate(size_t ud) override { + control.Verbose("UD ", ud, ": Running FileOutput::BeforeUpdate()"); DoOutput(ud); } diff --git a/source/placement/GrowthPlacement.hpp b/source/OLD/GrowthPlacement.hpp similarity index 92% rename from source/placement/GrowthPlacement.hpp rename to source/OLD/GrowthPlacement.hpp index f42008ef..06df9671 100644 --- a/source/placement/GrowthPlacement.hpp +++ b/source/OLD/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); diff --git a/source/schema/MovePopulation.hpp b/source/OLD/MovePopulation.hpp similarity index 82% rename from source/schema/MovePopulation.hpp rename to source/OLD/MovePopulation.hpp index 7306d16b..fd10e5b3 100644 --- a/source/schema/MovePopulation.hpp +++ b/source/OLD/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. } @@ -37,7 +36,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 +63,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(), "."); } }; 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/config/Config.hpp b/source/config/Config.hpp deleted file mode 100644 index ba0eb4c0..00000000 --- a/source/config/Config.hpp +++ /dev/null @@ -1,670 +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 Config.hpp - * @brief Manages all configuration of MABE runs (full parser implementation here) - * @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; - * } - * 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. - * 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!) - * l = k[1]; // Vectors can be indexed into. - * m() = a * c; // Functions have parens after the variable name; evaluated when called. - * n(o,p) = o + p; // Functions may have arguments. - * q = 'q'; // Literal chars are translated immediately to their ascii value - * - * // use a : instead of a . to access built-in values. Note a leading colon uses current scope. - * r = k:scope_size; // = 3 (always a value) - * s = f:names; // = ["a","b","c","g","h","i","j"] (vector of strings in alphabetical order) - * t = c:string; // = "17" (convert value to string) - * u = (t+"00"):value; // = 1700 (convert string to value; can use temporaries!) - * // ALSO- :is_string, :is_value, :is_struct, :is_array (return 0 or 1) - * // :type (returns a string indicating type!) - * - * - * In practice: - * MarkovBrain Sheep = { - * outputs = 10; - * node_weights = 0.75; - * recurrance = 5; - * } - * MarkovBrain Wolves = { - * outputs = 10; - * node_weights = 0.75; - * recurrance = 3; - * } - * modules = { - * Mutations = { - * copy_prob = 0.001; - * insert_prob = 0.05; - * } - * } - */ - -#ifndef MABE_CONFIG_H -#define MABE_CONFIG_H - -#include - -#include "emp/base/assert.hpp" -#include "emp/base/map.hpp" -#include "emp/meta/TypeID.hpp" -#include "emp/tools/string_utils.hpp" - -#include "ConfigAST.hpp" -#include "ConfigEvents.hpp" -#include "ConfigFunction.hpp" -#include "ConfigLexer.hpp" -#include "ConfigScope.hpp" -#include "ConfigType.hpp" - -namespace mabe { - - class Config { - public: - struct TypeInfo { - size_t type_id; - std::string desc; - std::function init_fun; - }; - - 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? - - ConfigScope root_scope; ///< All variables from the root level. - - /// 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; - - /// A list of precedence levels for symbols. - 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 IsType(int pos) const { return HasToken(pos) && emp::Has(type_map, tokens[pos].lexeme); } - - char AsChar(int pos) const { - return (HasToken(pos) && lexer.IsSymbol(tokens[pos])) ? tokens[pos].lexeme[0] : 0; - } - const std::string & AsLexeme(int pos) const { - return HasToken(pos) ? tokens[pos].lexeme : emp::empty_string(); - } - size_t GetSize(int pos) const { return HasToken(pos) ? tokens[pos].lexeme.size() : 0; } - - std::string ConcatLexemes(size_t start_pos, size_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... - } - 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; - 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, int pos, Ts... args) const { - if (!result) { Error(pos, std::forward(args)...); } - } - template - void RequireID(int pos, Ts... args) const { - if (!IsID(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireNumber(int pos, Ts... args) const { - if (!IsNumber(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireString(int pos, Ts... args) const { - if (!IsString(pos)) { Error(pos, std::forward(args)...); } - } - template - void RequireChar(char req_char, int 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 { - 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); - - /// 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); - - /// 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(size_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); - - /// Parse an event description. - emp::Ptr ParseEvent(size_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); - - /// 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(), ")"); - auto cur_block = emp::NewPtr(); - while (pos < tokens.size() && AsChar(pos) != '}') { - // Parse each statement in the file. - emp::Ptr statement_node = ParseStatement(pos, scope); - - // 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: - Config(std::string in_filename="") - : filename(in_filename) - , root_scope("MABE", "Outer-most, global scope.", nullptr) - { - 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 }; - - // Setup operator precedence. - size_t cur_prec = 0; - 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++; - } - - // 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; - - ~Config() { } - - /// Create a new type of event that can be used in the scripting language. - ConfigEvents & AddEventType(const std::string & name) { - emp_assert(!emp::Has(events_map, name)); - Debug ("Adding event type '", 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(emp::Has(events_map, 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(emp::Has(events_map, 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(emp::Has(events_map, 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); - } - } - - /// 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) - { - 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; - } - - /// Retrieve a uniqe 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; - } - - /// 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 - /// 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); - } - - ConfigScope & GetRootScope() { return root_scope; } - const ConfigScope & GetRootScope() const { return root_scope; } - - // Load a single, specified configuration file. - 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. - file.close(); // Close the file (now that it's converted) - size_t pos = 0; // Start at the beginning of the file. - - // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, root_scope); - cur_block->Process(); - - // Store this AST onto the full set we're working with. - ast_root.AddChild(cur_block); - } - - // Sequentially load a series of configuration files. - void Load(const emp::vector & filenames) { - for ( const std::string & fn : filenames) Load(fn); - } - - // Load a single, specified configuration file. - 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. - - // Parse and run the program, starting from the outer scope. - auto cur_block = ParseStatementList(pos, root_scope); - cur_block->Process(); - - // Store this AST onto the full set we're working with. - ast_root.AddChild(cur_block); - } - - - Config & Write(std::ostream & os=std::cout) { - root_scope.WriteContents(os); - os << '\n'; - PrintEvents(os); - return *this; - } - - Config & Write(const std::string & filename) { - // If the filename is empty or "_", output to standard out. - if (filename == "" || filename == "_") return Write(); - - // Otherwise generate an output file. - std::ofstream ofile(filename); - return Write(ofile); - } - }; - - ////////////////////////////////////////////////////////// - // --== Config member function Implementations! ==-- - - - // Load a variable name from the provided scope. - emp::Ptr Config::ParseVar(size_t & pos, - ConfigScope & cur_scope, - bool create_ok, bool scan_scopes) - { - Debug("Running ParseVar(", pos, ":('", AsLexeme(pos), "'),", cur_scope.GetName(), ",", create_ok, ")"); - - // First, check for leading dots. - 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; - while (num_dots-- > 1) { - scope_ptr = scope_ptr->GetScope(); - if (scope_ptr.IsNull()) Error(pos, "Too many dots; goes beyond global scope."); - } - 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); - } - - // 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++); - - // 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 (cur_entry.IsNull()) { - Error(pos, "'", var_name, - "' does not exist as a parameter, variable, or type."); - } - - // If this variable just provided a scope, keep going. - if (IsDots(pos)) return ParseVar(pos, cur_entry->AsScope(), create_ok, false); - - // Otherwise return the variable as a leaf! - return emp::NewPtr(cur_entry); - } - - emp::Ptr MakeTempDouble(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) { - 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(size_t & pos, ConfigScope & cur_scope) { - Debug("Running ParseValue(", pos, ":('", 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); - - // 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 MakeTempDouble(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. - } - - // 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. - } - - // 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."); - return out_ast; - } - - Error(pos, "Expected a value, found: ", AsLexeme(pos)); - - return nullptr; - } - - // 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_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 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; - } - - - // 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(), ")"); - - // @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); - while ( emp::Has(precedence_map, symbol) && precedence_map[symbol] < prec_limit ) { - pos++; - // 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. - } - RequireChar(')', pos++, "Expected a ')' to end function call."); - cur_node = emp::NewPtr(cur_node, args); - } - - // Otherwise we must have a binary math operation. - else { - emp::Ptr node2 = ParseExpression(pos, scope, 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); - } - - emp_assert(!cur_node.IsNull()); - return cur_node; - } - - // Parse an the declaration of a variable. - ConfigEntry & Config::ParseDeclaration(size_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++); - - if (type_name == "String") { - return scope.AddStringVar(var_name, "Local string variable."); - } - else if (type_name == "Value") { - return scope.AddValueVar(var_name, "Local value variable."); - } - else if (type_name == "Struct") { - return scope.AddScope(var_name, "Local struct"); - } - - // 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); - 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.SetupConfig(); - - return new_scope; - } - - // Parse an event description. - emp::Ptr Config::ParseEvent(size_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++); - RequireChar('(', pos++, "Expected parentheses after '", event_name, "' for args."); - - emp::vector> args; - while (AsChar(pos) != ')') { - args.push_back( ParseExpression(pos, scope) ); - if (AsChar(pos) == ',') pos++; - } - RequireChar(')', pos++, "Event args must end in a ')'"); - - emp::Ptr action = ParseStatement(pos, scope); - - 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 Config::ParseStatement(size_t & pos, ConfigScope & scope) { - Debug("Running ParseStatement(", pos, ":('", AsLexeme(pos), "'),", scope.GetName(), ")"); - - // Allow a statement with an empty line. - if (AsChar(pos) == ';') { pos++; return nullptr; } - - // Allow a statement to be a new scope. - if (AsChar(pos) == '{') { - pos++; - // @CAO Need to add an anonymous scope (that gets written properly) - emp::Ptr out_node = ParseStatementList(pos, scope); - RequireChar('}', pos++, "Expected '}' to close scope."); - return out_node; - } - - // Allow event definitions if a statement begins with an '@' - if (AsChar(pos) == '@') return ParseEvent(pos, scope); - - // Allow this statement to be a declaration if it begins with a type. - if (IsType(pos)) { - ConfigEntry & new_entry = ParseDeclaration(pos, scope); - - // If the next symbol is a ';' this is a declaration without an assignment. - if (AsChar(pos) == ';') { - pos++; // Skip the semi-colon. - 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(), - "' 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 '}'."); - return out_node; - } - - // Otherwise rewind so that variable can be used to start an expression. - pos--; - } - - - // If we made it here, remainder should be an expression. - emp::Ptr out_node = ParseExpression(pos, scope); - - // Expressions must end in a semi-colon. - RequireChar(';', pos++, "Expected ';' at the end of a statement."); - - return out_node; - } - -} -#endif diff --git a/source/config/ConfigAST.hpp b/source/config/ConfigAST.hpp deleted file mode 100644 index 4b06e281..00000000 --- a/source/config/ConfigAST.hpp +++ /dev/null @@ -1,280 +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 ConfigAST.hpp - * @brief Manages Abstract Sytax Tree nodes for Config. - * @note Status: ALPHA - */ - -#ifndef MABE_CONFIG_AST_H -#define MABE_CONFIG_AST_H - -#include "emp/base/assert.hpp" -#include "emp/base/Ptr.hpp" -#include "emp/base/vector.hpp" - -#include "ConfigEntry.hpp" - -namespace mabe { - - /// Base class for all AST Nodes. - class ASTNode { - protected: - using entry_ptr_t = emp::Ptr; - using entry_vector_t = emp::vector; - - using node_ptr_t = emp::Ptr; - using node_vector_t = emp::vector; - - // 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() { ; } - - virtual const std::string & GetName() const = 0; - - virtual bool IsLeaf() const { return false; } - virtual bool IsInternal() const { return false; } - - virtual size_t GetNumChildren() const { return 0; } - virtual node_ptr_t GetChild(size_t /* id */) { emp_assert(false); return nullptr; } - - virtual entry_ptr_t Process() = 0; - - virtual void Write(std::ostream & /* os */=std::cout, - const std::string & /* offset */="") const { } - }; - - /// An ASTNode representing an internal node. - class ASTNode_Internal : public ASTNode { - protected: - std::string name; - node_vector_t children; - - public: - ASTNode_Internal(const std::string & _name="") : name (_name) { } - ~ASTNode_Internal() { - for (auto child : children) child.Delete(); - } - - const std::string & GetName() const override { return name; } - - bool IsInternal() const override { return true; } - - 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); } - }; - - /// 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? - - 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() { if (own_entry) entry_ptr.Delete(); } - - const std::string & GetName() const override { return entry_ptr->GetName(); } - ConfigEntry & GetEntry() { return *entry_ptr; } - - bool IsLeaf() const override { return true; } - - entry_ptr_t Process() override { return entry_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(); - - // If it is a literal, print the value. - if (output == "") { - output = entry_ptr->AsString(); - - // If the entry is a string, convert it to a string literal. - if (entry_ptr->IsString()) output = emp::to_literal(output); - } - os << output; - } - }; - - class ASTNode_Block : public ASTNode_Internal { - public: - entry_ptr_t Process() override { - for (auto node : children) { - entry_ptr_t out = node->Process(); - if (out && out->IsTemporary()) out.Delete(); - } - return nullptr; - } - - void Write(std::ostream & os, const std::string & offset) const override { - for (auto child_ptr : children) { - child_ptr->Write(os, offset+" "); - os << ";\n" << offset; - } - } - }; - - /// Unary mathematical operations. - class ASTNode_Math1 : public ASTNode_Internal { - protected: - // 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) { } - - void SetFun(std::function< double(double) > _fun) { fun = _fun; } - - entry_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 MakeTempDouble(output_value); - } - - void Write(std::ostream & os, const std::string & offset) const override { - os << name; - children[0]->Write(os, offset); - } - }; - - /// Binary mathematical operations. - class ASTNode_Math2 : public ASTNode_Internal { - protected: - // A binary operator takes in two doubles and returns a third. - std::function< double(double, double) > fun; - public: - ASTNode_Math2(const std::string & name) : ASTNode_Internal(name) { } - - void SetFun(std::function< double(double, double) > _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 - 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); - } - - void Write(std::ostream & os, const std::string & offset) const override { - children[0]->Write(os, offset); - os << " " << name << " "; - children[1]->Write(os, offset); - } - }; - - class ASTNode_Assign : public ASTNode_Internal { - public: - ASTNode_Assign(node_ptr_t lhs, node_ptr_t rhs) { - AddChild(lhs); - AddChild(rhs); - } - - entry_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. - // @CAO Should make sure that lhs is properly assignable. - lhs->CopyValue(*rhs); - if (rhs->IsTemporary()) rhs.Delete(); - return lhs; - } - - void Write(std::ostream & os, const std::string & offset) const override { - children[0]->Write(os, offset); - os << " = "; - children[1]->Write(os, offset); - } - }; - - class ASTNode_Call : public ASTNode_Internal { - public: - ASTNode_Call(node_ptr_t fun, const node_vector_t & args) { - AddChild(fun); - for (auto arg : args) AddChild(arg); - } - - entry_ptr_t Process() override { - emp_assert(children.size() >= 1); - entry_ptr_t fun = children[0]->Process(); - - // Collect all arguments and call - entry_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); - - // Cleanup and return - for (auto arg : args) if (arg->IsTemporary()) arg.Delete(); - return result; - } - - void Write(std::ostream & os, const std::string & offset) const override { - children[0]->Write(os, offset); // Function name - os << "("; - for (size_t i=1; i < children.size(); i++) { - if (i>1) os << ", "; - children[i]->Write(os, offset); - } - os << ")"; - } - }; - - class ASTNode_Event : public ASTNode_Internal { - protected: - using setup_fun_t = std::function; - 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_Internal(event_name), setup_event(in_fun) - { - AddChild(action); - for (auto arg : args) AddChild(arg); - } - - entry_ptr_t Process() override { - emp_assert(children.size() >= 1); - entry_vector_t arg_entries; - for (size_t id = 1; id < children.size(); id++) { - arg_entries.push_back( children[id]->Process() ); - } - setup_event(children[0], arg_entries); - return nullptr; - } - - void Write(std::ostream & os, const std::string & offset) const override { - os << "@" << GetName() << "("; - for (size_t i = 1; i < children.size(); i++) { - if (i>1) os << ", "; - children[i]->Write(os, offset); - } - os << ") "; - children[0]->Write(os, offset); // Action. - } - }; - -} - -#endif diff --git a/source/config/ConfigEntry.hpp b/source/config/ConfigEntry.hpp deleted file mode 100644 index bfb4f99d..00000000 --- a/source/config/ConfigEntry.hpp +++ /dev/null @@ -1,382 +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.hpp - * @brief Manages a single configuration entry (e.g., variables + base for scopes and functions). - * @note Status: ALPHA - * - * - * Development Notes: - * - 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. - */ - -#ifndef MABE_CONFIG_ENTRY_H -#define MABE_CONFIG_ENTRY_H - -#include - -#include "emp/base/assert.hpp" -#include "emp/base/Ptr.hpp" -#include "emp/base/vector.hpp" -#include "emp/math/Range.hpp" -#include "emp/meta/TypeID.hpp" -#include "emp/tools/string_utils.hpp" -#include "emp/tools/value_utils.hpp" - -namespace mabe { - - class ConfigScope; - - 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? - - 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. - - enum class Format { NONE=0, SCOPE, - BOOL, INT, UNSIGNED, DOUBLE, // Values - STRING, FILENAME, PATH, URL, ALPHABETIC, ALPHANUMERIC, NUMERIC // Strings - }; - Format format = Format::NONE; - - // If we know the constraints on this parameter we can perform better error checking. - emp::Range range; ///< Min and max values allowed for this config entry (if numerical). - bool integer_only=false; ///< Should we only allow integer values? - - // Helper functions. - - /// Write out the provided description at the comment_offset. The start_pos is where the - /// text currently is. For multi-line comments, make sure to indent properly. - void WriteDesc(std::ostream & os, size_t comment_offset, size_t start_pos) const { - // If there is no description, provide a newline and stop. - if (desc.size() == 0) { - std::cout << '\n'; - return; - } - - // Break the description at the newlines. - emp::vector lines = emp::slice(desc); - - for (const auto & line : lines) { - // Find the current line to print. - while (start_pos++ < comment_offset) os << " "; - os << "// " << line << '\n'; - start_pos = 0; - } - } - - public: - ConfigEntry(const std::string & _name, - const std::string & _desc, - 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; } - 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 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? - - 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; } - - 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 emp::Ptr AsScopePtr() { return nullptr; } - ConfigScope & AsScope() { - emp_assert(AsScopePtr()); - return *(AsScopePtr()); - } - - /// 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(); } - 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(); - } - } - - ConfigEntry & SetMin(double min) { range.SetLower(min); return *this; } - ConfigEntry & 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; } - - /// 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) { - return (in_name == "") ? this : nullptr; - } - virtual emp::Ptr - LookupEntry(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); } - - /// If this entry is a function, we should be able to call it. - virtual emp::Ptr Call( emp::vector> args ); - - /// Allocate a duplicate of this class. - virtual emp::Ptr Clone() const = 0; - - virtual const ConfigEntry & 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 (IsBuiltIn()) return *this; - - // Setup this entry. - std::string cur_line = prefix; - if (IsLocal()) cur_line += emp::to_string(GetTypename(), " ", name, " = "); - else cur_line += emp::to_string(name, " = "); - - // Print the current value of this variable; if it's a string make sure to turn it to a literal. - cur_line += IsString() ? emp::to_literal(AsString()) : AsString(); - cur_line += ";"; - os << cur_line; - - // Write out the description for this line. - WriteDesc(os, comment_offset, cur_line.size()); - - return *this; - } - }; - - /// 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; } - }; - - /// Specializatin 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. - template - class ConfigEntry_Functions : public ConfigEntry { - private: - std::function get_fun; - std::function set_fun; - public: - using this_t = ConfigEntry_Functions; - - template - ConfigEntry_Functions(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_Functions(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. - template - class ConfigEntry_Var : public ConfigEntry { - private: - T value = 0; - public: - 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(in_name, in_desc, in_scope), value(default_val) { ; } - ConfigEntry_Var(const ConfigEntry_Var &) = 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) 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 { - value = 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 IsLocal() const override { return true; } - - bool CopyValue(const ConfigEntry & in) override { SetValue(in.AsDouble()); return true; } - }; - using ConfigEntry_DoubleVar = ConfigEntry_Var; - - /// ConfigEntry as a temporary variable of type STRING. - template<> - class ConfigEntry_Var : public ConfigEntry { - private: - std::string value; - public: - using this_t = ConfigEntry_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; - - std::string GetTypename() const override { return "String"; } - - emp::Ptr 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; } - - bool IsString() const override { return true; } - bool IsLocal() const override { return true; } - - bool CopyValue(const ConfigEntry & in) override { value = in.AsString(); return true; } - }; - using ConfigEntry_StringVar = ConfigEntry_Var; - - /// A ConfigEntry to transmit an error. The description provides the error and the IsError() flag - /// is set to true. - class ConfigEntry_Error : public ConfigEntry { - private: - using this_t = ConfigEntry_Error; - public: - template - ConfigEntry_Error(ARGS &&... args) - : ConfigEntry("__Error", emp::to_string(args...), nullptr) { is_temporary = true; } - - std::string GetTypename() const override { return "[[Error]]"; } - - bool IsError() const override { return true; } - - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - }; - - - //////////////////////////////////////////////////// - // Function definitions... - - emp::Ptr ConfigEntry::Call( emp::vector> /* args */ ) { - return emp::NewPtr("Cannot call a function on non-function '", name, "'."); - } - -} - -#endif diff --git a/source/config/ConfigEvents.hpp b/source/config/ConfigEvents.hpp deleted file mode 100644 index fadb37aa..00000000 --- a/source/config/ConfigEvents.hpp +++ /dev/null @@ -1,150 +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-2020. - * - * @file ConfigEvents.hpp - * @brief Manages events for configurations. - * @note Status: ALPHA - * - * 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 MABE_CONFIG_EVENTS_H -#define MABE_CONFIG_EVENTS_H - -#include - -#include "emp/base/map.hpp" -#include "emp/base/Ptr.hpp" - -#include "ConfigAST.hpp" - -namespace mabe { - - class ConfigEvents { - 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 exectute 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 handed 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_entry = ast_action->Process(); - if (result_entry->IsTemporary()) result_entry.Delete(); - next += repeat; - - // Return "active" if we ARE repeating and the next time is stiil within range. - return (repeat != 0.0 && next <= max); - } - - 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: - ConfigEvents() { ; } - ~ConfigEvents() { - // 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 diff --git a/source/config/ConfigFunction.hpp b/source/config/ConfigFunction.hpp deleted file mode 100644 index 64d6098e..00000000 --- a/source/config/ConfigFunction.hpp +++ /dev/null @@ -1,136 +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-2020. - * - * @file ConfigFunction.hpp - * @brief Manages individual functions for config. - * @note Status: ALPHA - */ - -#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" - -namespace mabe { - - class ConfigFunction : public ConfigEntry { - private: - using this_t = ConfigFunction; - using entry_ptr_t = emp::Ptr; - using entry_vector_t = emp::vector; - using fun_t = std::function< entry_ptr_t( const emp::vector & ) >; - fun_t fun; - // size_t arg_count; - - public: - ConfigFunction(const std::string & _name, - const std::string & _desc, - emp::Ptr _scope) - : ConfigEntry(_name, _desc, _scope) { ; } - - template - ConfigFunction(const std::string & _name, - std::function _fun, - const std::string & _desc, - emp::Ptr _scope) - : ConfigEntry(_name, _desc, _scope) { SetFunction(_fun); } - - ConfigFunction(const ConfigFunction &) = default; - - emp::Ptr Clone() const override { return emp::NewPtr(*this); } - - bool IsFunction() const override { return true; } - - /// Setup a function that takes NO arguments. - template - void SetFunction( std::function in_fun ) { - // 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. - if (args.size()) { - return emp::NewPtr( - "Function '", name, "' called with ", args.size(), " args, but ZERO expected." - ); - } - - emp::Ptr 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. - template - void SetFunction_impl( std::function in_fun, emp::ValPack ) { - 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); - if (args.size() != NUM_ARGS) { - return emp::NewPtr( - "Function '", name, "' called with ", args.size(), " args, but ", NUM_ARGS, " expected." - ); - } - - RETURN_T result = in_fun((args[INDICES]->template As< std::decay_t >())...); - emp::Ptr out_entry = - emp::NewPtr>("return value", result, desc, nullptr); - out_entry->SetTemporary(); - return out_entry; - }; - } - - /// 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 (std::is_same() && - sizeof...(ARGS) == 0) { - fun = [in_fun, name=name, desc=desc](const entry_vector_t & args) -> emp::Ptr { - RETURN_T result = in_fun(args); - emp::Ptr out_entry = - emp::NewPtr>("return value", result, desc, nullptr); - out_entry->SetTemporary(); - return out_entry; - }; - } - - /// 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; - // }; - } - } - - entry_ptr_t Call( emp::vector args ) override { return fun(args); } - - }; - -} - -#endif diff --git a/source/config/ConfigLexer.hpp b/source/config/ConfigLexer.hpp deleted file mode 100644 index 3512c8bf..00000000 --- a/source/config/ConfigLexer.hpp +++ /dev/null @@ -1,54 +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-2020. - * - * @file ConfigLexer.hpp - * @brief A Lexer that tokenizes MABE config files. - **/ - -#ifndef MABE_CONFIG_LEXER_H -#define MABE_CONFIG_LEXER_H - -#include "emp/compiler/Lexer.hpp" - -namespace mabe { - - class ConfigLexer : 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_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 - - public: - ConfigLexer() { - // Whitespace and comments should always be dismissed (top priority) - IgnoreToken("Whitespace", "[ \t\n\r]+"); - IgnoreToken("//-Comments", "//.*"); - IgnoreToken("/*...*/-Comments", "/[*]([^*]|([*]+[^*/]))*[*]+/"); - - // 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_dots = AddToken("Dots", "\".\"+"); - - /// Symbol tokens should have least priority. They include any solitary character not listed - /// above, or pre-specified multi-character groups. - token_symbol = AddToken("Symbol", ".|\"::\"|\"==\"|\"!=\"|\"<=\"|\">=\"|\"->\"|\"&&\"|\"||\"|\"<<\"|\">>\"|\"++\"|\"--\""); - } - - 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; } - }; -} - -#endif diff --git a/source/config/ConfigScope.hpp b/source/config/ConfigScope.hpp deleted file mode 100644 index 393b9a0b..00000000 --- a/source/config/ConfigScope.hpp +++ /dev/null @@ -1,220 +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 ConfigScope.hpp - * @brief Manages a full scope with many conig entries (or sub-scopes). - * @note Status: ALPHA - */ - -#ifndef MABE_CONFIG_SCOPE_H -#define MABE_CONFIG_SCOPE_H - -#include "emp/base/map.hpp" - -#include "ConfigEntry.hpp" -#include "ConfigFunction.hpp" - -namespace mabe { - - // Set of multiple config entries. - class ConfigScope : 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. - - ///< If this scope represents a structure, identify the type (otherwise type is "") - const std::string type; - - template - T & Add(const std::string & name, ARGS &&... args) { - auto new_ptr = emp::NewPtr(name, std::forward(args)...); - entry_list.push_back(new_ptr); - entry_map[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); - entry_map[name] = new_ptr; - return *new_ptr; - } - public: - ConfigScope(const std::string & _name, - const std::string & _desc, - emp::Ptr _scope, - const std::string & _type="") - : ConfigEntry(_name, _desc, _scope), type(_type) { } - ConfigScope(const ConfigScope & 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; - } - } - ConfigScope(ConfigScope &&) = default; - - ~ConfigScope() { - // Clear up all entries and built-ins. - for (auto & x : entry_list) { x.Delete(); } - for (auto & x : builtin_list) { x.Delete(); } - } - - std::string GetTypename() const override { return type; } - - 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; } - - /// 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; - } - - /// Lookup a variable, scanning outer scopes if needed - entry_ptr_t LookupEntry(const std::string & in_name, bool scan_scopes=true) override { - // See if this next entry is in the var list. - auto it = entry_map.find(in_name); - - // If this name is unknown, check with the parent scope! - if (it == entry_map.end()) { - if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(in_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 { - // See if this entry is in the var list. - auto it = entry_map.find(in_name); - - // If this name is unknown, check with the parent scope! - if (it == entry_map.end()) { - if (scope.IsNull() || !scan_scopes) return nullptr; // No parent? Just fail... - return scope->LookupEntry(in_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. - template - ConfigEntry_Linked & LinkVar(const std::string & name, - VAR_T & var, - const std::string & desc) { - 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. - template - ConfigEntry_Functions & LinkFuns(const std::string & name, - std::function get_fun, - std::function set_fun, - const std::string & desc) { - return Add>(name, get_fun, set_fun, desc, this); - } - - /// Add a new 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. - 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. - ConfigScope & 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, - std::function 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 - ConfigFunction & AddBuiltinFunction(const std::string & name, - std::function fun, - const std::string & desc) { - 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="", - 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); - } - - return *this; - } - - /// Write out this scope AND it's contents to the provided stream. - const ConfigEntry & 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. - if (IsBuiltIn()) return *this; - - // Declare this scope. - std::string cur_line = prefix; - if (IsLocal()) cur_line += emp::to_string(GetTypename(), " "); - cur_line += name; - - // Only open this scope if there are contents. - cur_line += entry_list.size() ? " { " : ";"; - 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()) { - WriteContents(os, prefix+" ", comment_offset); - os << prefix << "}\n"; // Close the scope. - } - - return *this; - } - - /// Make a copy of this scope and all of the entries inside it. - entry_ptr_t Clone() const override { return emp::NewPtr(*this); } - }; - -} -#endif diff --git a/source/config/ConfigType.hpp b/source/config/ConfigType.hpp deleted file mode 100644 index 7f856a66..00000000 --- a/source/config/ConfigType.hpp +++ /dev/null @@ -1,121 +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-2020. - * - * @file ConfigType.hpp - * @brief Setup types for use in scripting. - * @note Status: ALPHA - */ - -#ifndef MABE_CONFIG_TYPE_H -#define MABE_CONFIG_TYPE_H - -#include "emp/base/assert.hpp" - -#include "ConfigEntry.hpp" -#include "ConfigScope.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. - - // ---== 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. - template - ConfigEntry_Linked & LinkVar(VAR_T & var, - const std::string & name, - const std::string & desc) { - return GetScope().LinkVar(name, var, desc); - } - - /// 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, - std::function set_fun, - const std::string & name, - const std::string & desc) { - return GetScope().LinkFuns(name, get_fun, set_fun, desc); - } - - // Helper functions and info. - template - struct MenuEntry { - VAR_T value; - std::string name; - std::string desc; - - MenuEntry(VAR_T v, const std::string & n, const std::string & d) - : value(v), name(n), desc(d) {} - }; - - /// Link a set of menu option to a variable value. - /// Each option should include three arguments: - /// The return value, the option name, and the option description. - template - ConfigEntry_Functions & LinkMenu(VAR_T & var, - const std::string & name, - const std::string & desc, - const Ts &... entries) { - auto menu = emp::BuildObjVector, 3>(entries...); - - // Build the "get" function: take the current value of the menu and return the name. - std::function get_fun = - [&var,menu](){ - for (const MenuEntry & entry : menu) { - if (var == entry.value) return entry.name; - } - return std::string("UNKNOWN"); - }; - - // Build the "set" function: take the name of the menu option and update variable.. - std::function set_fun = - [&var,name,menu](const std::string & entry_name){ - for (const MenuEntry & entry : menu) { - if (entry_name == entry.name) { var = entry.value; return; } - }; - // AddError("Trying to set menu '", name, "' to '", entry_name, "'; does not exist."); - }; - - // Update the description to list all of the menu options. - std::stringstream new_desc; - - // Start with the input description and add the description for each menu option. - new_desc << desc; - for (const MenuEntry & entry : menu) { - new_desc << "\n " << entry.name << ": " << entry.desc; - } - - return GetScope().LinkFuns(name, get_fun, set_fun, new_desc.str()); - } - - public: - virtual void SetupScope(ConfigScope & 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; } - }; -} - -#endif diff --git a/source/core/Collection.hpp b/source/core/Collection.hpp index f9df45a2..34ad03fc 100644 --- a/source/core/Collection.hpp +++ b/source/core/Collection.hpp @@ -8,10 +8,15 @@ * * 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 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 @@ -119,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. @@ -162,13 +168,25 @@ 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. + } + + 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 + // are included. using pos_map_t = std::map; pos_map_t pos_map; @@ -186,6 +204,40 @@ 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.pos_set.Has(it.Pos())) return true; + + // 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()); + + // 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 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. + + return false; + } + public: Collection() = default; Collection(const Collection &) = default; @@ -194,6 +246,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)... ); } @@ -205,7 +260,72 @@ namespace mabe { using iterator_t = CollectionIterator; using const_iterator_t = ConstCollectionIterator; - /// Calculation the total number of positions represented in this collection. + 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." + ); + } + + 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; for (auto [pop_ptr, pop_info] : pos_map) { @@ -214,17 +334,31 @@ 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); } + Organism & At(size_t org_id) override { 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); } - // @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. } @@ -237,11 +371,13 @@ 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. } + // 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); } @@ -249,8 +385,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())); } @@ -280,7 +416,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 { @@ -288,11 +426,26 @@ namespace mabe { else return pos_map.begin()->first; } + 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(); 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 population 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()); @@ -302,11 +455,13 @@ namespace mabe { // Otherwise advance to the next population, else { - ++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()); } } + template void DecPosition(T & /* it */) const { emp_error("DecPosition() not yet implemented for CollectionIterator."); @@ -320,19 +475,54 @@ 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; } /// Add a Population to this collection. template Collection & Insert(Population & pop, Ts &&... extras) { - pos_map[&pop].full_pop = true; - return Insert( std::forward(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) { + emp::Ptr pop_ptr = &pop; + pos_map[pop_ptr.ConstCast()].full_pop = true; + 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. @@ -341,17 +531,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()); } @@ -359,16 +552,23 @@ 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. + template + Collection & Set(Ts &&...args) { + Clear(); + return Insert( std::forward(args)... ); + } + // @CAO: Add: // * Remove() - works with position or population (or another collection?) // * Has() - position @@ -396,19 +596,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 colleciton. - Collection & operator &= (const Collection & in_collection) { + /// 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(); @@ -420,7 +621,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. @@ -437,10 +638,11 @@ namespace mabe { return *this; } + static std::string EMPGetTypeName() { return "mabe::Collection"; } }; // ------------------------------------------------------- - // Implementations of CollectionItertor member functions + // Implementations of CollectionIterator member functions // ------------------------------------------------------- template @@ -469,21 +671,34 @@ 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) : 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. + /// 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 diff --git a/source/core/DeveloperNotes.md b/source/core/DeveloperNotes.md index 10b8f93f..a25fe407 100644 --- a/source/core/DeveloperNotes.md +++ b/source/core/DeveloperNotes.md @@ -1,35 +1,43 @@ 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. 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 dependencies (indicated by indentation below the requirement). data_collect.hpp - Tools to extract data from elements in a container. 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. 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. +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. 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 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 -## Organisms +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. -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. +## Adding Modules -## Modules +## Adding Managed Configuration Types # Core MABE Development @@ -40,23 +48,28 @@ 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 virtual *_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 -* 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. @@ -65,5 +78,3 @@ MABE.h: * 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/EmptyOrganism.hpp b/source/core/EmptyOrganism.hpp index d2f8035f..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()"); } @@ -29,17 +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 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; } + 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; } }; } diff --git a/source/core/Genome.hpp b/source/core/Genome.hpp new file mode 100644 index 00000000..54bc14ac --- /dev/null +++ b/source/core/Genome.hpp @@ -0,0 +1,220 @@ +/** + * @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 + +#include "emp/base/error.hpp" +#include "emp/math/Random.hpp" +#include "emp/meta/TypeID.hpp" + +namespace mabe { + + // 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. + + virtual emp::Ptr Clone() = 0; // Make an exact copy of this genome. + virtual emp::Ptr CloneProtocol() = 0; // Copy everything in this genome except sequence. + + 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. + + 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. + + virtual void Randomize(emp::Random &, size_t /*pos*/) = 0; // Randomize only at one locus + + // 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; + + 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); + + double alphabet_size = 4.0; + + public: + TypedGenome() { } + TypedGenome(this_t &) = default; + + 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; + } + + 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 diff --git a/source/core/MABE.hpp b/source/core/MABE.hpp index 11ce365e..fc3d763c 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 @@ -23,18 +23,21 @@ #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" -#include "emp/math/Random.hpp" +#include "emp/data/DataMapParser.hpp" #include "emp/datastructs/vector_utils.hpp" +#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" -#include "ErrorManager.hpp" #include "MABEBase.hpp" +#include "MABEScript.hpp" #include "ModuleBase.hpp" #include "Population.hpp" #include "SigListener.hpp" @@ -44,23 +47,17 @@ 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: 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? - 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 show_help = false; ///< Should we show "help" before exiting? + std::string help_topic=""; ///< What topic should we give help about? /// Populations used; generated in the configuration file. emp::vector< emp::Ptr > pops; @@ -68,24 +65,18 @@ namespace mabe { /// 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::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? - + TraitManager trait_man; ///< Manage consistent read/write access to traits // --- 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 &)>; @@ -101,130 +92,51 @@ 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. - emp::Ptr cur_scope; ///< Which config scope are we currently using? - - - // ----------- 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" - << "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(); - } + MABEScript config_script; ///< Configuration information for this run. - /// 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(); - } - /// 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 implment responses to those signals. - 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; - } - } + 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. 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. + 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(); + } + // Delete the empty organism AFTER clearing the populations, so it's not still used. + if (empty_org) empty_org.Delete(); + } + + /// 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; } - // --- Basic accessors --- - emp::Random & GetRandom() { return random; } - size_t GetUpdate() const noexcept { return update; } - mabe::ErrorManager & GetErrorManager() { return error_man; } + 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(); - /// 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.MakeOrganism(); - } + /// 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) { - config.TriggerEvents("start"); - for (size_t ud = 0; ud < num_updates && !exit_now; ud++) { - Update(); - } - Exit(); - } - - // -- World Structure -- - - OrgPosition FindBirthPosition(Organism & offspring, OrgPosition ppos, Population & pop) { - return do_place_birth_sig.FindPosition(offspring, ppos, pop); - } - OrgPosition FindInjectPosition(Organism & new_org, Population & pop) { - return do_place_inject_sig.FindPosition(new_org, pop); - } - OrgPosition FindNeighbor(OrgPosition pos) { - return do_find_neighbor_sig.FindPosition(pos); - } - + void Update(size_t num_updates=1); // --- Population Management --- @@ -232,26 +144,9 @@ 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 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. - } - - /// 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]; - } + const Population & GetPopulation(size_t id) const { return *pops[id]; } + 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. @@ -260,71 +155,27 @@ 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(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.Clone(); - 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; - } + /// 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. - 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; - } + /// 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(const std::string & type_name, Population & pop, size_t copy_count=1) { - 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. - } - return pos; // Return last position injected. - } + /// 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 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. - } + Collection 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) { 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); } @@ -332,62 +183,48 @@ 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) { - 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.MakeOffspring(random) : org.Clone(); - - // 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; - } + 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) { - emp_assert(org.IsEmpty() == false); // Empty cells cannot reproduce. - emp_assert(target_pos.IsValid()); // Target positions must already be valid. + Collection DoBirth(const Organism & org, + OrgPosition ppos, + OrgPosition target_pos, + bool do_mutations=true); - before_repro_sig.Trigger(ppos); - emp::Ptr new_org = do_mutations ? org.MakeOffspring(random) : org.Clone(); - on_offspring_ready_sig.Trigger(*new_org, ppos, target_pos.Pop()); - AddOrgAt(new_org, target_pos, ppos); - - return target_pos; - } - - - /// 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); } - /// 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); - } + /// 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); + } + /// Resize a population while clearing all of the organisms in it. + void EmptyPop(Population & pop, size_t new_size=0) { + ClearPop(pop); 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) override { + 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)); + } + } + + /// Move all organisms from one population to another. + void MoveOrgs(Population & from_pop, Population & to_pop, bool reset_to) override; + /// Return a ramdom position from a desginated population. OrgPosition GetRandomPos(Population & pop) { emp_assert(pop.GetSize() > 0); @@ -398,13 +235,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)); } @@ -412,20 +243,8 @@ namespace mabe { // --- Collection Management --- - std::string ToString(const mabe::Collection & collect) const { - 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; - } + std::string ToString(const mabe::Collection & collect) const { return collect.ToString(); } + Collection ToCollection(const std::string & load_str); Collection GetAlivePopulation(size_t id) { Collection col(GetPopulation(id)); @@ -464,95 +283,24 @@ namespace mabe { // --- Deal with Organism TRAITS --- TraitManager & GetTraitManager() { return trait_man; } - /// 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. - - 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: - // (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; + /// 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); } - // --- Manage configuration scope --- - - /// Access to the current configuration scope. - ConfigScope & GetCurScope() { return *cur_scope; } - - /// Add a new scope under the current one. - ConfigScope & AddScope(const std::string & name, const std::string & desc) { - cur_scope = &(cur_scope->AddScope(name, desc)); - return *cur_scope; + /// Build a trait equations for organisms in a given population. + auto BuildTraitEquation(const Population & pop, const std::string & equation) { + return BuildTraitEquation(pop.GetDataLayout(), equation); } - /// Move up one level of scope. - ConfigScope & LeaveScope() { return *(cur_scope = cur_scope->GetScope()); } - - /// Return to the root scope. - ConfigScope & ResetScope() { return *(cur_scope = &(config.GetRootScope())); } - - /// Setup the configuration options for MABE, including for each module. - void SetupConfig(); - - /// Sanity checks for debugging - bool OK(); + const std::set & GetEquationTraits(const std::string & equation) { + return config_script.GetEquationTraits(equation); + } + 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; }; @@ -568,132 +316,57 @@ 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; }; - 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; }; }; // ========================== OUT-OF-CLASS DEFINITIONS! ========================== - 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) - , args(emp::cl::args_to_strings(argc, argv)) - , 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); - }; - config.AddType("Population", "Collection of organisms", pop_init_fun); - - // 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); - }; - config.AddType(mod.name, mod.desc, mod_init_fun); - } - - - // Add other built-in functions to the config file. - - // 'exit' should terminate a run. - std::function exit_fun = [this](){ Exit(); return 0; }; - config.AddFunction("exit", exit_fun, "Exit from this MABE run."); - + // ---------------- PRIVATE MEMBER FUNCTIONS ----------------- - // '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)."); - - // '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."); - - // 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. - } + /// Print information on how to run the software. + void MABE::ShowHelp() { + std::cout << "MABE v" << VERSION << "\n"; + on_help_sig.Trigger(); - 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') - if (exit_now) return false; - - // 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 - } - - if (config_settings.size()) { - std::cout << "Loading command-line settings." << std::endl; - config.LoadStatements(config_settings); + 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"; + } - // 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); - Exit(); } - - // If any of the inital flags triggered an 'exit_now', do so. - if (exit_now) return false; - - // 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. - - 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); + exit_now = true; } - /// 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); + /// 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 & [type_name,mod] : GetModuleMap()) { + std::cout << " " << type_name << " : " << mod.desc << "\n"; + } + exit_now = true;; } void MABE::ProcessArgs() { @@ -703,19 +376,23 @@ 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 + // 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."); - Exit(); + 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]; } }); 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", @@ -726,7 +403,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; } ); @@ -752,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; } @@ -772,7 +449,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 @@ -784,16 +461,13 @@ 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); // 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); @@ -804,29 +478,277 @@ namespace mabe { rescan_signals = false; } - void MABE::SetupConfig() { - emp_assert(cur_scope); - emp_assert(cur_scope.Raw() == &(config.GetRootScope()), - cur_scope->GetName(), - 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); + // ---------------- PUBLIC MEMBER FUNCTIONS ----------------- + + + MABE::MABE(int argc, char* argv[]) + : args(emp::cl::args_to_strings(argc, argv)) + , config_script(*this) + { + // 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."); + + // 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_script.AddType(type_name, mod.desc, mod_init_fun, nullptr, mod.type_id); + mod.type_init_fun(type_info); // Setup functions for this module. + } } + bool MABE::Setup() { + ProcessArgs(); // Deal with command-line inputs. - bool MABE::OK() { - bool result = true; + // Sometimes command-line arguments will require an immediate exit (such as after '--help') + if (exit_now) return false; - // Make sure the populations are all OK. - for (size_t pop_id = 0; pop_id < pops.size(); pop_id++) { - result &= pops[pop_id]->OK(); + // 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_script.Load(config_filenames); // Load files } - // @CAO: Should check to make sure modules are okay too. + if (config_settings.size()) { + std::cout << "Loading command-line settings." << std::endl; + 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_script.Write(gen_filename); + exit_now = true; + } + + // If any of the inital flags triggered an 'exit_now', do so. + if (exit_now) return false; + + // 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. + UpdateSignals(); // Setup the appropriate modules to be linked with each signal. + SetupBase(); // Call Setup on MABEBase (which will report errors) + + return true; + } + + /// Update MABE world. + void MABE::Update(size_t num_updates) { + 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.Trigger("UPDATE", update); // Trigger any updated-based events + } + } + + /// 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. + + empty_org = empty_manager.template Make(); + } + + /// New populations must be given a name and an optional size. + Population & MABE::AddPopulation(const std::string & name, size_t pop_size) { + 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 placement 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; + } + + /// 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) { + emp_assert(org.GetDataMap().SameLayout(org_data_map)); + 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); + OrgPosition pos = pop.PlaceInject(*inject_org); + if (pos.IsValid()) { + AddOrgAt( inject_org, pos); + placement_set.Insert(pos); + } else { + inject_org.Delete(); + emp::notify::Error("Invalid position; failed to inject organism ", i, "!"); + } + } + return placement_set; + } + + /// 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(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 = pop.PlaceInject(*org_ptr); + if (pos.IsValid()) AddOrgAt( org_ptr, pos); + else { + org_ptr.Delete(); + emp::notify::Error("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. + 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. + 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. + OrgPosition pos = InjectInstance(pop, org_ptr); // ...Inject it into the population. + placement_set.Insert(pos); // ...Record the position. + } + + 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.) + 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) { + 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. + } + + /// 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. + 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; + 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. + on_offspring_ready_sig.Trigger(*new_org, ppos, target_pop); + pos = target_pop.PlaceBirth(*new_org, ppos); + + // If this placement is valid, do so. Otherwise delete the organism. + if (pos.IsValid()) { + AddOrgAt(new_org, pos, ppos); + birth_list.Insert(pos); + } + else new_org.Delete(); + } + return birth_list; + } + + 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. + + 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; + } + + 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) { + emp_assert(pop.GetNumOrgs() > 0, "GetRandomOrgPos cannot be called if there are no orgs."); + OrgPosition pos = GetRandomPos(pop); + while (pos.IsEmpty()) pos = GetRandomPos(pop); + return pos; + } + + + // --- Collection Management --- + + Collection MABE::ToCollection(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) emp::notify::Error("Unknown population: ", name); + else out.Insert(GetPopulation(pop_id)); + } + return out; + } + + bool MABE::OK() { + bool result = true; + 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; } diff --git a/source/core/MABEBase.hpp b/source/core/MABEBase.hpp index 0ff0d922..718b7719 100644 --- a/source/core/MABEBase.hpp +++ b/source/core/MABEBase.hpp @@ -13,6 +13,7 @@ #include #include "emp/base/array.hpp" +#include "emp/base/notify.hpp" #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" @@ -29,6 +30,11 @@ namespace mabe { class MABEBase { protected: + 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; @@ -63,25 +69,14 @@ 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; - // OnError(const std::string & msg) - SigListener on_error_sig; - // OnWarning(const std::string & msg) - SigListener on_warning_sig; + SigListener on_pop_resize_sig; // BeforeExit() SigListener before_exit_sig; // OnHelp() SigListener on_help_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 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; @@ -102,16 +97,24 @@ 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) - , 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: + virtual ~MABEBase() { } + + void SetupBase() { + emp::notify::Unpause(); + } + + // --- Basic accessors --- + emp::Random & GetRandom() { return random; } + size_t GetUpdate() const noexcept { return update; } + bool GetVerbose() const { return verbose; } + + /// 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; } @@ -121,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 relavant position is already empty, nothing happens. + /// 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. @@ -176,6 +180,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; }; } diff --git a/source/core/MABEScript.hpp b/source/core/MABEScript.hpp new file mode 100644 index 00000000..9f834cf6 --- /dev/null +++ b/source/core/MABEScript.hpp @@ -0,0 +1,368 @@ +/** + * @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-2022. + * + * @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 "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 + + 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) { + 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()); }; + } + + /// 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. + 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 < 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 (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(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 pp_out; + } + + + /// 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." ); + + // Pre-process the trait function to allow for use of regular config variables. + trait_fun = Preprocess(trait_fun).result; + + // 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 = BuildCollectFun(mode, get_fun); + + // 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 = 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 Symbol_Var(0); }; + } + + // Go through all combinations of TO/FROM to return the correct types. + if constexpr (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) { + return [this,fun_type](FROM_T & pop, const std::string & equation) { + 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).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. + AddSignal("START"); // Triggered at the beginning of a run. + AddSignal("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 diff --git a/source/core/ManagerModule.hpp b/source/core/ManagerModule.hpp new file mode 100644 index 00000000..7faa7102 --- /dev/null +++ b/source/core/ManagerModule.hpp @@ -0,0 +1,136 @@ +/** + * @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 ManagerModule.hpp + * @brief Base module to manage a selection of objects that share a common configuration. + */ + +#ifndef MABE_MANAGER_MODULE_H +#define MABE_MANAGER_MODULE_H + +#include "emp/meta/TypeID.hpp" + +#include "MABE.hpp" +#include "Module.hpp" + +namespace mabe { + + // Pre-declarations... + class MABE; + 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 + class ProductTemplate : public BASE_T { + public: + ProductTemplate(ModuleBase & _man) : BASE_T(_man) { ; } + + using managed_t = MANAGED_T; + using manager_t = ManagerModule; + + /// 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 MANAGED_T the type of object type being managed. + /// @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. + friend class ProductTemplate; + + private: + /// Locate the specification for the data that we need for management in the manager module. + using data_t = typename MANAGED_T::ManagerData; + + /// Shared data across all objects that use the same manager. + data_t data; + + /// Maintain a prototype for the objects being created. + emp::Ptr obj_prototype; + + public: + 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 ~ManagerModule() { obj_prototype.Delete(); } + + /// Save the type that uses this manager. + using managed_t = MANAGED_T; + + /// 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_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_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_impl(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 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; + 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); }; + GetModuleMap()[type_name] = new_info; + } + }; + + + /// MACRO for quickly adding new manager modules. + #define MABE_REGISTER_MANAGER_MODULE(TYPE, BASE_TYPE, DESC) \ + mabe::ManagerModuleRegistrar> MABE_ ## TYPE ## _Registrar(#TYPE, DESC) + +} + +#endif diff --git a/source/core/Module.hpp b/source/core/Module.hpp index e4464a4b..07178c36 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" @@ -41,68 +39,78 @@ 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; 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_Functions & LinkPop(int & var, - const std::string & name, - const std::string & desc) { + emplode::Symbol_LinkedFunctions & LinkPop( + int & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.GetPopulation(var).GetName(); }; 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 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. - ConfigEntry_Functions & LinkCollection(mabe::Collection & var, - const std::string & name, - const std::string & desc) { + emplode::Symbol_LinkedFunctions & LinkCollection( + mabe::Collection & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.ToString(var); }; 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); + return AsScope().LinkFuns(name, get_fun, set_fun, desc); } /// 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) { + emplode::Symbol_LinkedFunctions & LinkModule( + int & var, + const std::string & name, + const std::string & desc + ) { std::function get_fun = [this,&var](){ return control.GetModule(var).GetName(); }; 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 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. - ConfigEntry_Functions & LinkRange(int & start_var, - int & step_var, - int & stop_var, - const std::string & name, - const std::string & desc) { + emplode::Symbol_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) @@ -117,7 +125,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: @@ -129,7 +137,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); } @@ -167,15 +177,24 @@ 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. // 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 +202,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 +210,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 +218,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 +226,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(); @@ -218,7 +241,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; @@ -227,6 +250,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 +258,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 +266,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 +274,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 +282,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 +290,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,25 +298,12 @@ 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(); } - // Format: OnError(const std::string & msg) - // Trigger: An error has occurred and the user should be notified. - 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. - 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 { @@ -303,38 +319,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 querried 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. - // Return: Position to place offspring or an invalid position if failed. - - OrgPosition DoPlaceBirth(Organism &, OrgPosition, Population &) 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. - - OrgPosition DoPlaceInject(Organism &, Population &) 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(); @@ -361,26 +345,26 @@ 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); }; - 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) 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; - new_info.init_fun = [desc](MABE & control, const std::string & name) -> ConfigType & { - return control.AddModule(name, desc); + new_info.obj_init_fun = [desc](MABE & control, const std::string & name) -> emp::Ptr { + return &control.AddModule(name, desc); }; - GetModuleInfo().insert(new_info); + new_info.type_init_fun = [](emplode::TypeInfo & info){ T::InitType(info); }; + new_info.type_id = emp::GetTypeID(); + GetModuleMap()[type_name] = new_info; } }; diff --git a/source/core/ModuleBase.hpp b/source/core/ModuleBase.hpp index ee9d663d..f6ab5d83 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) @@ -48,23 +48,11 @@ * : 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() * : Run when the --help option is called at startup. * ... - * - * - Various Do* functions run in modules until one of them returns a valid answer. - * DoPlaceBirth(Organism & offspring, OrgPosition parent_pos, Population & target_pop) - * : Place a new offspring about to be born. - * DoPlaceInject(Organism & new_org, Population & pop) - * : 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 @@ -74,24 +62,27 @@ #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" #include "emp/datastructs/reference_vector.hpp" -#include "../config/Config.hpp" +#include "../Emplode/Emplode.hpp" -#include "ErrorManager.hpp" #include "TraitInfo.hpp" namespace mabe { class MABE; + class OrgType; class Organism; 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. @@ -99,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. @@ -120,8 +108,8 @@ 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. + using value_fun_t = std::function; + using string_fun_t = std::function; public: // Setup each signal with a unique ID number @@ -140,13 +128,8 @@ namespace mabe { SIG_OnSwap, SIG_BeforePopResize, SIG_OnPopResize, - SIG_OnError, - SIG_OnWarning, SIG_BeforeExit, SIG_OnHelp, - SIG_DoPlaceBirth, - SIG_DoPlaceInject, - SIG_DoFindNeighbor, NUM_SIGNALS, SIG_UNKNOWN }; @@ -155,12 +138,22 @@ 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."); + 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: @@ -176,6 +169,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; } @@ -186,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"); } @@ -202,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); } @@ -235,15 +232,9 @@ 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; - virtual OrgPosition DoPlaceBirth(Organism &, OrgPosition, Population &) = 0; - virtual OrgPosition DoPlaceInject(Organism &, Population &) = 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. @@ -261,82 +252,52 @@ 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; - 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 ===--- - 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 ManagerModule."); return emp::TypeID(); } - virtual emp::Ptr CloneOrganism(const Organism &) { - emp_assert(false, "CloneOrganism() must be overridden for either Organism or OrganismManager module."); - return nullptr; - } - virtual emp::Ptr CloneOrganism(const Organism &, emp::Random &) { - emp_assert(false, "CloneOrganism() must be overridden for either Organism or OrganismManager module."); - return nullptr; - } - virtual emp::Ptr MakeOrganism() { - emp_assert(false, "MakeOrganism() must be overridden for either Organism or OrganismManager module."); - return nullptr; - } - virtual emp::Ptr MakeOrganism(emp::Random &) { - emp_assert(false, "MakeOrganism() must be overridden for either Organism or OrganismManager module."); - return nullptr; + template + emp::Ptr CloneObject(const OBJ_T & in_obj) { + return CloneObject_impl(in_obj).template DynamicCast(); } - virtual std::string OrgToString(const Organism &) const { - emp_assert(false, "OrgToString() must be overridden for either Organism or OrganismManager module."); - return ""; + template + emp::Ptr CloneObject(const OBJ_T & in_obj, emp::Random & random) { + return CloneObject_impl(in_obj, random).template DynamicCast(); } - virtual std::ostream & PrintOrganism(Organism &, std::ostream & is) const { - emp_assert(false, "Print() must be overridden for either Organism or OrganismManager module."); - return is; + template + emp::Ptr Make() { + return Make_impl().template DynamicCast(); } - virtual size_t Mutate(Organism &, emp::Random &) const { - emp_assert(false, "Mutate() must be overridden for either Organism or OrganismManager module."); - return 0; + template + emp::Ptr Make(emp::Random & random) { + return Make_impl(random).template DynamicCast(); } - 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() { } }; struct ModuleInfo { std::string name; std::string desc; - std::function 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; } }; - 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; } } } diff --git a/source/core/OrgIterator.hpp b/source/core/OrgIterator.hpp index b850aa85..4045fc25 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. @@ -24,19 +24,18 @@ #include "emp/base/Ptr.hpp" #include "emp/base/vector.hpp" -#include "../config/ConfigType.hpp" - #include "Organism.hpp" namespace mabe { /// Base class for all organsim containers, including population. - struct OrgContainer { + struct OrgContainer : public EmplodeType { virtual ~OrgContainer() { } 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; @@ -96,6 +95,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(); @@ -139,6 +140,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(); } @@ -203,7 +207,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()); } diff --git a/source/core/OrgType.hpp b/source/core/OrgType.hpp new file mode 100644 index 00000000..59349e09 --- /dev/null +++ b/source/core/OrgType.hpp @@ -0,0 +1,140 @@ +/** + * @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 organisms + /// 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 equivalent 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() { ; } + }; + +} + +#endif diff --git a/source/core/Organism.hpp b/source/core/Organism.hpp index 7f3c6276..24b2f55c 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 * @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::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. * @@ -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. * */ @@ -28,222 +29,98 @@ #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/data/AnnotatedType.hpp" #include "emp/tools/string_utils.hpp" -#include "ModuleBase.hpp" +#include "OrgType.hpp" namespace mabe { - class Module; + class Population; - class Organism { + class Organism : public OrgType, public emp::AnnotatedType { private: - emp::DataMap data_map; ///< Dynamic variables assigned to organism - ModuleBase & manager; ///< Manager for the specific organism type - + emp::Ptr pop_ptr = nullptr; public: - Organism(ModuleBase & _man) : manager(_man) { ; } - virtual ~Organism() { ; } - - /// 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 { - }; - - 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 { - 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 - 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 - 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); + Organism(ModuleBase & _man) : OrgType(_man) { ; } + virtual ~Organism() { + emp_assert( + pop_ptr.IsNull(), + "Organisms must be removed from populations before deletion; use MABE::ClearOrgAt()." + ); } - - /// Test if this organism represents an empy cell. + /// 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; } - // ------------------------------------------ - // ------ 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, try to the equivilent function in the organism manager. - [[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); } + /// Specialty version of Clone to return an Organism type. + [[nodiscard]] emp::Ptr CloneOrganism() const { + return OrgType::Clone().DynamicCast(); + } - /// 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); + [[nodiscard]] emp::Ptr + RecombineOrganisms(emp::Ptr parent2, emp::Random & random) const { + return OrgType::Recombine(parent2, random).DynamicCast(); } - /// 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 { - return manager.Recombine(*this, other_parents, random); + // @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]] virtual emp::Ptr MakeOffspring(emp::Random & random) const { - emp::Ptr offspring = Clone(); - offspring->Mutate(random); - return offspring; + /// 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(). - [[nodiscard]] virtual emp::Ptr - MakeOffspring(emp::Ptr parent2, emp::Random & random) const { - emp::Ptr offspring = Recombine(parent2, random); - offspring->Mutate(random); - return offspring; + /// 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(); } + // @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]] 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; + [[nodiscard]] emp::vector> + MakeOffspringOrganisms(emp::vector> other_parents, emp::Random & random) const { + return OrgType::MakeOffspring(other_parents, random); } - /// 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); } - - /// Completely randomize a new organism (typically for initialization) - virtual void Randomize(emp::Random & random) { manager.Randomize(*this, random); } - - /// Setup a new organism from scratch; by default just randomize. - virtual void Initialize(emp::Random & random) { manager.Randomize(*this, 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() { ; } + // -- Also deal with some deprecated functionality... -- - }; - - - // 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) { ; } + [[deprecated("Use OrgType::HasTrait() instead of OrgType::HasVar()")]] + 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 GetTrait(name); } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + 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 GetTrait(id); } + template + [[deprecated("Use OrgType::GetTrait() instead of OrgType::GetVar()")]] + const T & GetVar(size_t id) const { return GetTrait(id); } - using org_t = ORG_T; - using manager_t = OrganismManager; + template + [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] + void SetVar(const std::string & name, const T & value) { SetTrait(name, value); } - /// 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(); - } + template + [[deprecated("Use OrgType::SetTrait() instead of OrgType::SetVar()")]] + void SetVar(size_t id, const T & value) { SetTrait(id, value); } - auto & SharedData() { return GetManager().data; } - const auto & SharedData() const { return GetManager().data; } }; } diff --git a/source/core/OrganismManager.hpp b/source/core/OrganismManager.hpp index c78f1e85..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. @@ -10,116 +10,20 @@ #ifndef MABE_ORGANISM_MANAGER_H #define MABE_ORGANISM_MANAGER_H -#include "emp/meta/TypeID.hpp" - -#include "../config/Config.hpp" - -#include "MABE.hpp" -#include "Module.hpp" +#include "ManagerModule.hpp" namespace mabe { - class Organism; - class MABE; - + // Setup an OrganismManager as a standard ManagerModule that builds different kinds of Organisms 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) - { - 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(); } - - /// 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; - } - - /// Create a clone of the provided organism; default to using copy constructor. - emp::Ptr CloneOrganism(const Organism & org) override { - return emp::NewPtr( ConvertOrg(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; - } + using OrganismManager = ManagerModule; - /// 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; - } - - /// 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(); - } - - 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) + // Setup OrganismTemplate as a quick way to build new organism types. + template + using OrganismTemplate = ProductTemplate; + #define MABE_REGISTER_ORG_TYPE(TYPE, DESC) MABE_REGISTER_MANAGER_MODULE(TYPE, mabe::Organism, DESC) } + #endif diff --git a/source/core/Population.hpp b/source/core/Population.hpp index 1c52b2e5..fe0047ea 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,24 +81,30 @@ 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 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? - size_t max_orgs = (size_t) -1; ///< Maximum number of orgs allowed in 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? + + /// 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; - 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; - public: Population() { emp_assert(false, "Do not use default constructor on Population!"); } Population(const std::string & in_name, size_t in_id, @@ -106,40 +114,42 @@ 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]->Clone(); - } - } - emp_assert(OK()); - } - // 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() { 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; } 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(); } 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; } + 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]); } @@ -153,16 +163,24 @@ 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. - void SetupConfig() override { } + 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) { 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); + if (!data_layout_ptr) data_layout_ptr = &org_ptr->GetDataMap().GetLayout(); + + 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++; } @@ -172,7 +190,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; } @@ -202,43 +223,70 @@ namespace mabe { void SetEmpty(emp::Ptr in_empty) { empty_org = in_empty; } public: + // Setup member functions associated with population. + static void InitType(emplode::TypeInfo & info) { + 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."); + } + + // ------ DEBUG FUNCTIONS ------ bool OK() const { - if (pop_id < 0) { - std::cout << "WARNING: Invalid Population ID (pop_id = " << pop_id << ")" << std::endl; + // We may have a handful of populations, but assume error if we have more than a million. + if (pop_id > 1000000) { + 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; } + // 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 + std::cerr << "ERROR: Population " << pop_id << " as position " << pos << " has null pointer instead of an organism." << std::endl; return false; } - // Double check the organism count. + // 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 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 + std::cerr << "ERROR: Population " << pop_id << " has num_orgs = " << num_orgs << ", but audit counts " << org_count << " orgs." << std::endl; 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; } + + static std::string EMPGetTypeName() { return "mabe::Population"; } }; diff --git a/source/core/TraitInfo.hpp b/source/core/TraitInfo.hpp index 05502712..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 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 { @@ -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? @@ -114,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). @@ -134,11 +135,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, 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 +239,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; } diff --git a/source/core/TraitManager.hpp b/source/core/TraitManager.hpp index b07b013d..6615b463 100644 --- a/source/core/TraitManager.hpp +++ b/source/core/TraitManager.hpp @@ -37,11 +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. @@ -54,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) { @@ -84,16 +79,17 @@ 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()."); + 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. @@ -115,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, "'."); } @@ -133,13 +129,15 @@ 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 '", - 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()); } } @@ -147,21 +145,118 @@ namespace mabe { cur_trait->SetAltTypes(intersect_types); } - // Add this modules access to the trait. - cur_trait->AddAccess(mod_name, mod_ptr, access); + // Add this module's access to the trait. + bool is_manager = mod_ptr->IsManageMod(); + cur_trait->AddAccess(mod_name, mod_ptr, access, is_manager); 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()) { + emp::notify::Error("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. + 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.]"; + emp::notify::Error(error_msg.str()); + return false; + } + + if (trait_ptr->GetPrivateCount() && trait_ptr->GetModuleCount() > 1) { + 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; + } + + 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.]"; + 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()); + 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()), + "[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.]"); + 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()) { + 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", + " SHARED or OWNED).]"); + return false; + } + + // A GENERATED trait requires another module to read (REQUIRE) it. + else if (trait_ptr->IsGenerated() && !trait_ptr->IsRequired()) { + 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; + } + + return true; + } /// 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"; } - 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) { @@ -175,90 +270,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!)"); - error_count++; - 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()); + if (!VerifyValid(trait_name, trait_ptr) || + !VerifyPrivacy(trait_name, trait_ptr) || + !VerifyOwnership(trait_name, trait_ptr) || + !VerifyRequirements(trait_name, trait_ptr)) { 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; - } + } } + + return error_count; } }; - } #endif \ No newline at end of file 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 07c34ce9..01d2c74f 100644 --- a/source/core/data_collect.hpp +++ b/source/core/data_collect.hpp @@ -1,12 +1,12 @@ /** * @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. * - * 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 @@ -20,34 +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; - // 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) { - return emp::to_string( get_fun( container.At(index) ) ); - }; - } + // Return the value at a specified index. + template + 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 BuildCollectFun_Unique(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + // Count up the number of distinct values. + template + 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 BuildCollectFun_Mode(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + Symbol_Var Mode(const CONTAIN_T & container, FUN_T get_fun) { std::map vals; for (const auto & entry : container) { vals[ get_fun(entry) ]++; @@ -61,13 +60,11 @@ namespace emp { mode_val = cur_val; } } - return emp::to_string(mode_val); - }; - } + return mode_val; + } - template - auto BuildCollectFun_Min(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + 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(); @@ -79,28 +76,59 @@ 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 BuildCollectFun_Max(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + Symbol_Var Max(const CONTAIN_T & container, FUN_T get_fun) { 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); if (cur_val > max) max = cur_val; } - return emp::to_string(max); - }; - } + return max; + } - template - auto BuildCollectFun_Mean(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + 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(); + } + 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 min_id; + } + + template + 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(); + } + 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 max_id; + } + + template + 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; @@ -108,28 +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 BuildCollectFun_Median(FUN_T get_fun) { - // return [get_fun](const CONTAIN_T & container) { - // 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 + 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 values[count/2]; + } - template - auto BuildCollectFun_Variance(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + 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(); @@ -143,15 +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 BuildCollectFun_StandardDeviation(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + 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(); @@ -165,29 +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 BuildCollectFun_Sum(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + 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 BuildCollectFun_Entropy(FUN_T get_fun) { - return [get_fun](const CONTAIN_T & container) { + template + Symbol_Var Entropy(const CONTAIN_T & container, FUN_T get_fun) { std::map vals; for (const auto & entry : container) { vals[ get_fun(entry) ]++; @@ -198,76 +216,112 @@ 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 - BuildCollectFun(std::string type, FUN_T get_fun) { + 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. - 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); - return emp::BuildCollectFun_Index(get_fun, index); + 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") { - return emp::BuildCollectFun_Unique(get_fun); + 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") { - return emp::BuildCollectFun_Mode(get_fun); + 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") { - return emp::BuildCollectFun_Min(get_fun); + 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") { - return emp::BuildCollectFun_Max(get_fun); + else if (action == "max") { + return [get_fun](const CONTAIN_T & container) { + return DataCollect::Max(container, get_fun); + }; + } + + // Return the lowest trait value. + 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 (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") { - return emp::BuildCollectFun_Mean(get_fun); + 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") { - // return emp::BuildCollectFun_Median(get_fun); - //} + 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") { - return emp::BuildCollectFun_Variance(get_fun); + 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") { - return emp::BuildCollectFun_StandardDeviation(get_fun); + 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") { - return emp::BuildCollectFun_Sum(get_fun); + 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") { - return emp::BuildCollectFun_Entropy(get_fun); + else if (action == "entropy") { + return [get_fun](const CONTAIN_T & container) { + return DataCollect::Entropy(container, get_fun); + }; } - return std::function(); + return std::function(); } -}; +} #endif diff --git a/source/evaluate/games/EvalMancala.hpp b/source/evaluate/games/EvalMancala.hpp new file mode 100644 index 00000000..68c0a8fd --- /dev/null +++ b/source/evaluate/games/EvalMancala.hpp @@ -0,0 +1,273 @@ +/** + * @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: + 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 { + 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.") + : Module(control, name, desc) + { + 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 { + 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."); + 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", + 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(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); + } + + + // Determine the next move of an organism. + size_t EvalMove(emp::Mancala & game, Organism & org) { + // Setup the hardware with proper inputs. + org.GetTrait>(input_trait) = game.AsVectorInput(game.GetCurPlayer()); + + // Run the code. + org.GenerateOutput(); + + emp::vector results = org.GetTrait>(output_trait); + + // Determine the chosen move. + size_t best_move = 0; + 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; } + } + + 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'); + } + + /// 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 + /// @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) + 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; + 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) { + os << "round = " << round++ << " errors = " << errors << std::endl; + game.Print(os); + char move_sym = (char) ('A' + best_move); + os << "Move = " << move_sym; + if (game.GetCurSide()[best_move] == 0) { + os << " (illegal!)"; + } + os << 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) { + os << "Final scores -- A: " << game.ScoreA() + << " B: " << game.ScoreB() + << std::endl; + } + + return Results{ game.ScoreA(), game.ScoreB(), errors }; + } + + /// 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); }; + } + + /// 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) + 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); + } + + /// 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) + 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); + while (!game.IsMoveValid(move_id)) move_id = random.GetUInt(6); + return move_id; + }; + return EvalGame(ToOrgFun(org), rand_fun, start_player, verbose, os); + } + + /// 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) + 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); + }; + 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) { + EvalGame(org, control.GetRandom(), 0, true, os); + } + + 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( 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); + 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(); + + 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."); +} + +#endif diff --git a/source/evaluate/static/EvalCountBits.hpp b/source/evaluate/static/EvalCountBits.hpp index 7be417be..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.GetVar(bits_trait); - double fitness = (double) bits.CountOnes(); + const emp::BitVector & bits = org.GetTrait(bits_trait); + 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.SetVar(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; } }; diff --git a/source/evaluate/static/EvalDiagnostic.hpp b/source/evaluate/static/EvalDiagnostic.hpp index 1bd48504..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. @@ -38,12 +36,11 @@ 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") : 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,23 +77,21 @@ 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(); // 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()); @@ -162,6 +165,7 @@ namespace mabe { max_org = &org; } } + return max_total; } }; diff --git a/source/evaluate/static/EvalMatchBits.hpp b/source/evaluate/static/EvalMatchBits.hpp index 59e908cd..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].SetVar(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.GetVar(bits_trait); - const emp::BitVector & bits2 = org2.GetVar(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.SetVar(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.SetVar(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].SetVar(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; } }; diff --git a/source/evaluate/static/EvalNK.hpp b/source/evaluate/static/EvalNK.hpp index 69b44860..e84bcabe 100644 --- a/source/evaluate/static/EvalNK.hpp +++ b/source/evaluate/static/EvalNK.hpp @@ -23,7 +23,6 @@ namespace mabe { size_t N; size_t K; NKLandscape landscape; - mabe::Collection target_collect; std::string bits_trait; std::string fitness_trait; @@ -35,7 +34,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 +41,17 @@ namespace mabe { } ~EvalNK() { } + // Setup member functions associated with this class. + static void InitType(emplode::TypeInfo & info) { + 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; }, + "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,23 +67,21 @@ namespace mabe { landscape.Config(N, K, control.GetRandom()); // Setup the fitness landscape. } - void OnUpdate(size_t /* update */) override { - emp_assert(control.GetNumPopulations() >= 1); - + 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_collect( target_collect.GetAlive() ); - for (Organism & org : alive_collect) { + mabe::Collection alive_orgs( orgs.GetAlive() ); + for (Organism & org : alive_orgs) { 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()); + emp::notify::Error("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; @@ -84,8 +89,14 @@ namespace mabe { } } - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; + 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."); diff --git a/source/evaluate/static/EvalPacking.hpp b/source/evaluate/static/EvalPacking.hpp index ad52356c..92e48f57 100644 --- a/source/evaluate/static/EvalPacking.hpp +++ b/source/evaluate/static/EvalPacking.hpp @@ -4,12 +4,6 @@ * @date 2021. * * @file EvalPacking.hpp -<<<<<<< HEAD - * @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). -======= * @brief MABE Evaluation module for counting the number of successful packages that have been packed. * * Note : A package is comprised of three sections: front padding of 0's followed by a package of 1's @@ -18,7 +12,6 @@ * z 0's on both sides. * For example, if p = 3, z = 2, a successfull package would be 0011100. * Packages can have overlapping buffers. Thus with p = 3, z = 2, 001110011100 counts as two packages. ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f */ #ifndef MABE_EVAL_PACKING_H @@ -38,15 +31,6 @@ namespace mabe { std::string bits_trait; std::string fitness_trait; -<<<<<<< HEAD - size_t brick_size = 6; - size_t packing_size = 3; - - public: - EvalPacking(mabe::MABE & control, - const std::string & name="EvalPacking", - const std::string & desc="Evaluate bitstrings by counting correctly packed bricks.") -======= size_t num_ones = 3; size_t num_zeros = 2; @@ -55,7 +39,6 @@ namespace mabe { const std::string & name="EvalPacking", const std::string & desc="Evaluate bitstrings using the Royal Road" ) ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f : Module(control, name, desc) , target_collect(control.GetPopulation(0)) , bits_trait("bits") @@ -68,88 +51,13 @@ namespace mabe { 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 - 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(packing_size, "packing_size", "Minimum nubmer of zeros to surround bricks of ones."); -======= LinkVar(fitness_trait, "fitness_trait", "Which trait should we store the fitness in?"); LinkVar(num_ones, "num_ones", "Number of ones to be packaged together."); LinkVar(num_zeros, "num_zeros", "Number of zeros to buffer each side of ones package."); ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f } void SetupModule() override { AddRequiredTrait(bits_trait); -<<<<<<< HEAD - AddOwnedTrait(fitness_trait, "Packing fitness value", 0.0); - } - - size_t evaluate(size_t b_s, size_t p_s, const emp::BitVector bits) { - size_t brick_size = b_s; - size_t packing_size = p_s; - - if (bits.GetSize() < brick_size) { - return 0; - } - - size_t packed = 0; // number of correctly packed bricks - - size_t ones_count = 0; - size_t zeros_count = 0; - - int check_step = 1; // 0 = count front packing, 1 = count brick, 2 = count back packing, 3 = all elements found - - for (size_t i = 0; i < bits.size(); i++) { - if (check_step == 0 || check_step == 2) { - if (bits[i] == 0) { - zeros_count++; - } - if (zeros_count == packing_size) { - zeros_count = 0; - check_step++; - } - // one found, restart search for front packing - else if (bits[i] == 1) { - zeros_count = 0; - check_step = 0; - } - } - // looking for brick - else if (check_step == 1) { - if (bits[i] == 1) { - ones_count++; - // full brick found, begin looking for zeros - if (ones_count == brick_size) { - ones_count = 0; - if (packing_size == 0) { - check_step = 3; - } else { - check_step = 2; - } - } - } - // zero found, begin looking for front packing - else if (bits[i] == 0) { - ones_count = 0; - zeros_count = 1; - check_step = 0; - } - } - if (check_step == 3) { - packed++; - check_step = 1; - } - } - - return packed; - } - - 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(); -======= AddOwnedTrait(fitness_trait, "Royal Road Fitness Value", 0.0); } @@ -217,96 +125,10 @@ namespace mabe { double max_fitness = 0.0; mabe::Collection alive_collect( target_collect.GetAlive() ); ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f for (Organism & org : alive_collect) { // Make sure this organism has its bit sequence ready for us to access. org.GenerateOutput(); -<<<<<<< HEAD - // Count the number of ones in the bit sequence. - const emp::BitVector & bits = org.GetVar(bits_trait); - - size_t fitness = evaluate(brick_size, packing_size, bits); - // Store the count on the organism in the fitness trait. - - org.SetVar(fitness_trait, fitness); - - if (fitness > max_fitness) { - max_fitness = fitness; - } - - std::cout << "Max " << fitness_trait << " = " << max_fitness << std::endl; - } - } - }; - - MABE_REGISTER_MODULE(EvalPacking, "Evaluate bitstrings by counting correctly packed bricks."); -} - -#endif - -/* - while (i < bits.size()) { - // reset brick and back_packing to not found - brick_found = false; - back_packing_found = false; - // check for full brick - size_t j = 0; - for (j = 0; j < brick_size; j++) { - if (bits[i+j] == 0) { - brick_found = false; - break; - } else { - brick_found = true; - } - } - // brick not found, check if zero found makes full front_packing - if (!brick_found && bits[i + j] == 0) { - i += j + 1; - // check for front_packing - size_t k = 0; - for (k = 0; k < packing_size; k ++) { - if (bits[i+k] == 1) { - front_packing_found = false; - break; - } else { - front_packing_found = true; - } - } - // move beyond font_packing or to first one found - if (front_packing_found) { - i += k + 1; - } else { - i += k; - } - continue; - } - // check that no more ones follow full brick, if so front_packing is incorrect, continue - if (brick_found && bits[i + j + 1] == 1) { - i += j + 1; - front_packing_found = false; - continue; - } - // check for full back_packing - i += j + 1; - size_t k = 0; - for (k = 0; k < packing_size; k ++) { - if (bits[i+k] == 1) { - back_packing_found = false; - break; - } else { - back_packing_found = true; - } - } - if (front_packing_found && brick_found && back_packing_found) { - packed++; - } - // back_packing of current brick is fornt_packing of next brick - front_packing_found = back_packing_found; - i += k + 1; - } -*/ -======= // Get the bits_traits of the orgnism. const emp::BitVector & bits = org.GetVar(bits_trait); @@ -331,4 +153,3 @@ namespace mabe { } #endif ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f diff --git a/source/evaluate/static/EvalRoyalRoad.hpp b/source/evaluate/static/EvalRoyalRoad.hpp index efa73045..1685d591 100644 --- a/source/evaluate/static/EvalRoyalRoad.hpp +++ b/source/evaluate/static/EvalRoyalRoad.hpp @@ -4,14 +4,10 @@ * @date 2021. * * @file EvalRoyalRoad.hpp -<<<<<<< HEAD * @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). -======= - * @brief MABE Evaluation module for counting the number of ones (or zeros) in an output. ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f */ #ifndef MABE_EVAL_ROYAL_ROAD_H @@ -26,77 +22,53 @@ namespace mabe { class EvalRoyalRoad : public Module { private: - Collection target_collect; - std::string bits_trait; - std::string fitness_trait; + std::string score_trait; -<<<<<<< HEAD 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 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).") : 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?"); -<<<<<<< HEAD - 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."); -======= - 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 } void SetupModule() override { AddRequiredTrait(bits_trait); -<<<<<<< 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(); -======= - 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() ); ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f + 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(); -<<<<<<< HEAD // 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; @@ -105,48 +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); -======= - 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++; - - } + // 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); - // 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); - - if (fitness > max_fitness) { - max_fitness = fitness; + if (score > max_score) { + max_score = score; } -<<<<<<< HEAD -======= - ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f } - 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."); } -<<<<<<< HEAD -#endif -======= #endif ->>>>>>> 8abdd5ce85047abf742e14caf5219383fcaa9e1f diff --git a/source/interface/CommandLine.hpp b/source/interface/CommandLine.hpp index abdeb2cb..d99fbdc4 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.BuildTraitSummary(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 { diff --git a/source/modules.hpp b/source/modules.hpp index 438b55d7..8b9ab501 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" @@ -15,24 +16,15 @@ #include "evaluate/static/EvalPacking.hpp" #include "evaluate/static/EvalRoyalRoad.hpp" -#include "evaluate/static/EvalRoyalRoad.hpp" -#include "evaluate/static/EvalPacking.hpp" - // Interface Modules -#include "interface/CommandLine.hpp" -#include "interface/FileOutput.hpp" // Placement Modules -#include "placement/GrowthPlacement.hpp" // Selection Modules #include "select/SelectElite.hpp" -#include "select/SelectTournament.hpp" #include "select/SelectLexicase.hpp" - -// Other schema -#include "schema/MovePopulation.hpp" -#include "schema/Mutate.hpp" +#include "select/SelectRoulette.hpp" +#include "select/SelectTournament.hpp" // Organism Types #include "orgs/AvidaGPOrg.hpp" diff --git a/source/orgs/AvidaGPOrg.hpp b/source/orgs/AvidaGPOrg.hpp index 5f4a4e53..9c6c1b44 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" @@ -38,7 +39,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. @@ -81,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(GetTrait>(SharedData().input_name)); // Run the code. hardware.Process(SharedData().eval_time); // Store the results. - SetVar>(SharedData().output_name, hardware.GetOutputs()); + SetTrait>(SharedData().output_name, emp::ToVector(hardware.GetOutputs())); } /// Setup this organism type to be able to load from config. @@ -106,19 +107,25 @@ 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. 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()); - // 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()); + emp::vector()); } }; 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..346af584 100644 --- a/source/select/SelectElite.hpp +++ b/source/select/SelectElite.hpp @@ -20,52 +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(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? + 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->GetVar(trait)); - //std::cout << "Measuring fit " << it->GetVar(trait) << std::endl; - } - - // 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); - } + AddRequiredEquation(fit_equation); // The fitness traits must be set by another module. } }; diff --git a/source/select/SelectLexicase.hpp b/source/select/SelectLexicase.hpp index c1cc9315..8c1a21d5 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()) { + emp::notify::Error("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."); diff --git a/source/select/SelectRoulette.hpp b/source/select/SelectRoulette.hpp new file mode 100644 index 00000000..dd4a1df3 --- /dev/null +++ b/source/select/SelectRoulette.hpp @@ -0,0 +1,84 @@ +/** + * @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 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()) { + emp::notify::Error("SelectRoulette currently requires birth_pop and select_pop to be different."); + return Collection{}; + } + + 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( + 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() { } + + // 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 { + LinkVar(fit_equation, "fitness_fun", "Function used as fitness for selection?"); + } + + void SetupModule() override { + AddRequiredEquation(fit_equation); // The fitness traits must be set by another module. + } + + }; + + MABE_REGISTER_MODULE(SelectRoulette, "Randomly choose organisms to replicate weighted by fitness."); +} + +#endif diff --git a/source/select/SelectTournament.hpp b/source/select/SelectTournament.hpp index d7170d6c..63494dbf 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") @@ -18,71 +18,79 @@ 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_equation; ///< Trait function that we should select on + size_t tourny_size; ///< Number of organisms in each tournament - 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", - 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) - { - 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?"); - 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?"); - } - - void SetupModule() override { - AddRequiredTrait(trait); ///< The fitness trait must be set by another module. - } - - void OnUpdate(size_t /* update */) override { + 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; + emp::notify::Error("Trying to run Tournament Selection on an Empty Population."); + return Collection(); } - // @CAO if we have a sparse Population, we probably want to take that into account. + // Setup the fitness function - redo this each time in case it changes. + auto fit_fun = control.BuildTraitEquation(select_pop, fit_equation); + + // 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); - double best_fit = select_pop[best_id].GetVar(trait); + 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. 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 = fit_fun(select_pop[test_id]); if (test_fit > best_fit) { best_id = test_id; best_fit = test_fit; } } - // Replicat the organism that did best in this tournament. - control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); + // Replicate the organism that did best in this tournament. + placement_list += control.Replicate(select_pop.IteratorAt(best_id), birth_pop, 1); } + + 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. } }; diff --git a/source/third-party/empirical b/source/third-party/empirical index 87a9b3cb..75b16057 160000 --- a/source/third-party/empirical +++ b/source/third-party/empirical @@ -1 +1 @@ -Subproject commit 87a9b3cb8c15a16e4e40f8ce2d8d1e2e217f4944 +Subproject commit 75b16057d18e9dafeeb501101cd120fe1b02cb8d