diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 38f3e30459..91eb0a409e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,7 +10,7 @@ on: jobs: tidy: name: Enforce Tidyness - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 with: @@ -31,7 +31,7 @@ jobs: - run: ./ci/test_tidy.sh test: name: Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -50,11 +50,14 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' + - name: Set up GCC + uses: egor-tensin/setup-gcc@v1 + with: + version: 11 - run: sudo apt-get update -qq - run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - run: sudo apt-get update -qq - - run: sudo apt-get install -qq g++-8 cmake build-essential python3-pip python3-virtualenv nodejs tar gzip libpthread-stubs0-dev libc6-dbg gdb - - run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - run: sudo apt-get install -qq cmake build-essential python3-pip python3-virtualenv nodejs tar gzip libpthread-stubs0-dev libc6-dbg gdb - run: git fetch origin master:refs/remotes/origin/master - run: make install-test-dependencies - run: ${CXX} --version @@ -63,14 +66,17 @@ jobs: make ${TEST_SET} CXX=${CXX} test-web: name: Web Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 with: submodules: 'recursive' + - name: Set up GCC + uses: egor-tensin/setup-gcc@v1 + with: + version: 11 - run: sudo apt-get update -qq - - run: sudo apt-get install -qq g++-8 cmake build-essential python3-pip python3-virtualenv nodejs tar gzip libpthread-stubs0-dev libc6-dbg gdb - - run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - run: sudo apt-get install -qq cmake build-essential python3-pip python3-virtualenv nodejs tar gzip libpthread-stubs0-dev libc6-dbg gdb - run: make install-test-dependencies - name: Run headless test uses: GabrielBB/xvfb-action@v1 @@ -78,31 +84,21 @@ jobs: run: make test-web test-coverage: name: Measure Test Coverage - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 with: submodules: 'recursive' - run: sudo apt-get update -qq - - run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - run: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - - run: sudo apt-add-repository "deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main" - - run: sudo apt-get update -qq - - run: sudo apt-get install -qq g++-8 - - run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 - - run: sudo apt-get install cmake build-essential python3-virtualenv python3-pip nodejs tar gzip libclang-7-dev llvm llvm-dev libllvm7 llvm-7 llvm-7-dev clang-7 libstdc++-7-dev # might have to happen after we update g++ - - run: sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 90 - - run: sudo update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-7 90 - - run: sudo update-alternatives --install /usr/bin/llvm-profdata llvm-profdata /usr/bin/llvm-profdata-7 90 - - run: sudo update-alternatives --install /usr/bin/llvm-cov llvm-cov /usr/bin/llvm-cov-7 90 + - run: sudo apt-get install cmake build-essential python3-virtualenv python3-pip nodejs tar gzip clang llvm-dev libclang-dev - run: git fetch origin master:refs/remotes/origin/master - run: make install-test-dependencies - - run: export CXX=clang++-7 && make install-coverage-dependencies - - run: export CXX=clang++-7 && make coverage + - run: export CXX=clang++ && make install-coverage-dependencies + - run: export CXX=clang++ && make coverage - run: curl -s https://codecov.io/bash | bash test-documentation: name: Test Documentation Build - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 with: @@ -119,7 +115,7 @@ jobs: dst: stats/doc-coverage.json deploy-dockerhub: name: Deploy to DockerHub - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 if: github.ref == 'refs/heads/master' needs: - tidy diff --git a/README.md b/README.md index 1164a18eac..12e93b9e66 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ namespace, so it is simple to incorporate into existing projects. [![CI](https://github.com/devosoft/Empirical/workflows/CI/badge.svg)](https://github.com/devosoft/Empirical/actions?query=workflow%3ACI+branch%3Amaster) [![Documentation Status](https://readthedocs.org/projects/empirical/badge/?version=latest)](https://empirical.readthedocs.io/en/latest/?badge=latest) [![DOI](https://zenodo.org/badge/24824563.svg)](https://zenodo.org/badge/latestdoi/24824563) [![codecov](https://codecov.io/gh/devosoft/Empirical/branch/master/graph/badge.svg)](https://codecov.io/gh/devosoft/Empirical) [![DockerHub](https://img.shields.io/badge/DockerHub-Hosted-blue)](https://hub.docker.com/r/devosoft/empirical) ![Documentation Coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fdevosoft%2FEmpirical%2Fgh-storage%2Fstats%2Fdoc-coverage.json) +[![GitHub contributors](https://img.shields.io/github/contributors/devosoft/Empirical.svg?style=flat-square)](https://github.com/devosoft/Empirical/graphs/contributors) + See our [Built With Empirical Gallery](https://empirical.readthedocs.io/en/latest/BuiltWithEmpiricalGallery) for examples of web tools built with Empirical. diff --git a/doc/blogs/Binomial.md b/doc/blogs/Binomial.md index b096817a7c..551b6d85bf 100644 --- a/doc/blogs/Binomial.md +++ b/doc/blogs/Binomial.md @@ -40,4 +40,4 @@ before introducing the next bug? A **Poisson Distribution** is a continuous version of a Binomial Distribution, used for measuring the number of independent events that occur in a time period rather than during a specified -number of events. \ No newline at end of file +number of events. diff --git a/doc/library/Evolve/evolve.md b/doc/library/Evolve/evolve.md new file mode 100644 index 0000000000..664ce6e740 --- /dev/null +++ b/doc/library/Evolve/evolve.md @@ -0,0 +1,30 @@ +# Evolution tools + +## World + +```{eval-rst} +.. doxygenfile:: emp/Evolve/World.hpp + :project: Empirical + :no-link: +``` + +## Systematics + +```{ref} systematics +``` + +## NK + +```{eval-rst} +.. doxygenfile:: emp/Evolve/NK.hpp + :project: Empirical + :no-link: +``` + +## Selection + +```{eval-rst} +.. doxygenfile:: emp/Evolve/World_select.hpp + :project: Empirical + :no-link: +``` diff --git a/doc/library/Evolve/systematics.rst b/doc/library/Evolve/systematics.rst new file mode 100644 index 0000000000..2d1f2b8d9f --- /dev/null +++ b/doc/library/Evolve/systematics.rst @@ -0,0 +1,195 @@ +.. SystematicsDocumentation documentation master file, created by + sphinx-quickstart on Thu May 28 16:40:07 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +Documentation for Systematics +==================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + modules + +Systematics +=========== + +Systematics is a classification of organisms based on evolutionary (phylogenetic) relationships. + +*************** +Systematics.h +*************** + +This file is part of Empirical and is located in ``Empirical/source/Evolve/Systematics.h`` + +The systematics manager is used to track genotypes, species, clades, or lineages of organisms in a world. + +Systematics allows a user to generate data to form phylogenetic trees. + +The program can be run with different levels of abstraction, meaning the data can be generated by position, +phenotype, or even genotype if you have a lot of RAM. + +**Note**: You are responsible for filling in templates! Adding the template just gives you a place to store your data. + +Taxon Specifics +=============== + +* Taxon - a group of species with similar characteristics +* Genotypes are the most commonly used Taxon + +A user can see the type and number of mutations that ocurred to bring about a taxon. + +Some information that can be accessed is: + +* taxon ID# ``GetID()`` +* details of organisms in the taxon ``GetInfo()`` +* pointer to the parent group (will return a null pointer if the species was injected) ``GetParent()`` +* how many organisms currently exist in the group and how many total organisms have ever existed in the group ``GetNumOrgs()`` or ``GetTotOrgs()`` +* how many direct offspring groups exist from this group and how many total extant offspring that exist from this taxa ``GetTotalOffspring()`` +* how deep in the tree the node you are examining is ``GetDepth()`` +* when did this taxon first appear in the population ``GetOriginationTime()`` +* when did the taxon leave the population ``GetDestructionTime()`` + +New organisms are added to the taxon using ``AddOrg()``. +New offspring are added to the taxon with ``AddOffspring()`` . + +Organisms are removed with ``RemoveOrg()``. +Offspring are removed with ``RemoveOffspring()`` . + +If there are no more remaining organisms or offspring the taxon will deactivate. + + +General Systematics Data +========================= + +Things that systematics can tell you about a phylogeny and how to access them: + +* Are we tracking a synchronous population? ``GetTrackSynchronous()`` ``SetTrackSynchronous()`` +* Are we storing all taxa that are still alive in the population? ``GetStoreActive()`` ``SetStoreActive()`` +* Are we storing all taxa that are ancestors of the living organisms in the population? ``GetStoreAncestors()`` ``SetStoreAncestors()`` +* Are we storing all taxa that have died out, as have all of their descendants? ``GetStoreOutside()`` ``SetStoreOutside()`` +* Are we storing any taxa types that have died out? ``GetArchive()`` ``SetArchive()`` +* Are we storing the positions of taxa? ``GetStorePosition()`` ``SetStorePosition()`` +* How many living organisms are currently being tracked? ``GetTotalOrgs()`` +* How many independent trees are being tracked? ``GetNumRoots()`` +* What ID will the next taxon have? ``GetNextID()`` +* What is the average phylogenetic depth of organisms in the population? ``GetAveDepth()`` +* To find the most recent common ancestor (MRCA) use ``GetMRCA()`` or ``GetMRCADepth()`` to find the distance to the MRCA. + +**The systematics class tracks the relationships among all organisms bases on the INFO_TYPE +provided. If an offspring has the same value for INFO_TYPE as its parent, it is grouped into +the same taxon. Otherwise a new Taxon is created and the old one is used as its parent in +the phylogeny. If the provided INFO_TYPE is the organism's genome, a traditional phylogeny +is formed, with genotypes. If the organism's behavior/task set is used, then organisms are +grouped by phenotypes. If the organism's position is used, the evolutionary path through +space is tracked. Any other aspect of organisms can be tracked this way as well.** + + +**Generally, all living organisms' taxa should be tracked and ancestral organisms' taxa should be maintained for lineage. +However, not all dead taxa should be maintained, it gets too big.** + +*************************** +Diversity and Distinction +*************************** + +Systematics.h can also be used to find phylogenetic diversity for all extant taxa in the tree, +assuming all edges from parent to child have a length of one. + +When all branch lengths are equal, the phylogenetic diversity is the number of internal nodes plus the number of +extant taxa minus 1. + +You can also find how distinct a specific taxa is from the rest of the population +based on the amount of unique evolutionary history that it represents. + +***************************** +Synchronous Populations +***************************** + +A synchronous population is a population in which each generation is a discrete time point +and a completely new set of individual organisms is created for each generation. This means that +an organism and its parent can never exist at the same time. + +An asynchronous population is the opposite, where generations overlap and organisms reproduce +when they are ready. + +In the systematics manager, synchronicity is controlled with + +``GetTrackSynchronous()`` which returns true or false and +``SetTrackSynchronous(input true or false)`` which allows you to use a synchronous or asynchronous population. + + +Using the Systematics Manager +============================== + +The systematics.h file alone will not give you any useful information. You must use a test file in conjunction with the systematics manager +in order to see output. + +To retrieve some results we will use the file Systematics.cc +which is located in Empirical/tests/Evolve/Systematics.cc. + +To compile to code use this command in the tests directory:: + + make test-Systematics + + +********** +Output +********** + +Terminal Output:: + + AddOrg 25 (id1, no parent) + + AddOrg -10 (id2; parent id1) + + AddOrg 26 (id3, parent id1) + + AddOrg 27 (id4, parent id2) + +The first line of output shows the first organism in the examined phylogeny. This organism is added with AddOrg +and is assigned an ID of id1. The organism has no parent, as seen in the farthest column of output, meaning that +organism id1 will be the root of the phylogeny and produce offspring. + +If we then look at the first number is parenthesis, we see the second organism with and ID of id2. Id2 is a direct descendant of the id1 organism. + +Lastly, if we look at id4, we see that its parent is id2, meaning that we have created another node in the tree +as the organisms move through generations, producing new offspring. + +The terminal output should also include this section:: + + Active count: 11 [18|1,0|17] [17|1,2|11] [15|1,0|null] [12|1,1|11] [16|1,0|11] [11|1,3|null] [6|1,0|5] [19|1,0|17] [5|1,1|null] [4|1,0|null] [3|1,0|null] + + +The 11 at the front refers to the number of total taxa in the phylogeny. + +If we look at the first set of numbers: ``[18|1, 0|17]`` + +The first number in brackets, 18 in this case, is the taxon of the organism where +a mutation occurred. 1, the next number, is the number of mutations that led to this branch. +0 is the number of offspring from this organism. Lastly, 17 is the id of the parent organism. + +As for the second set ``[17|1, 2|11]`` -- this is taxon 17, one mutation occurred, +id17 had 2 offspring, and its parent is id11. + +The last portion of the output has several lines of 3 numbers. + +It should look like this: :: + + 1 : 0 : -1 + 2 : 0 : -1 + 3 : 0 : 0 + 4 : 0 : 0 + 5 : 0 : 0 + 6 : 0 : 0 + 7 : 0 : 0 + 8 : 0 : 987 + 9 : 0 : 986 + 10 : 0 : 987 + 11 : 0 : 988 + 12 : 0 : 987 + 13 : 0 : 988 + +The first number is the organism number. The second number is the position of the organism. +The third number is the fitness of the organism at position 0. diff --git a/doc/library/index.md b/doc/library/index.md index e072b1036f..642bfc67e9 100644 --- a/doc/library/index.md +++ b/doc/library/index.md @@ -11,6 +11,7 @@ compiler/compiler data/data datastructs/datastructs debug/debug +Evolve/evolve functional/functional io/io math/math diff --git a/examples/math/CombinedBinomialDistribution.cpp b/examples/math/CombinedBinomialDistribution.cpp new file mode 100644 index 0000000000..f053aba8a6 --- /dev/null +++ b/examples/math/CombinedBinomialDistribution.cpp @@ -0,0 +1,35 @@ +/** + * @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 2022-2022 + * + * @file CombinedBinomialDistribution.cpp + * @brief Some examples code for using emp::CombinedBinomialDistribution + */ + + +#include "emp/math/CombinedBinomialDistribution.hpp" +#include "emp/math/Random.hpp" + +int main(int argc, char* argv[]) +{ + if(argc != 4){ + std::cout << "Error! Expecting exactly three command line arguments: " + << "p n num_trials" << std::endl; + emp_assert(false); + } + double p = std::stod(argv[1]); + size_t n = std::stoi(argv[2]); + size_t num_trials = std::stoi(argv[3]); + + emp::Random random; + emp::CombinedBinomialDistribution distribution(p, 1); + + double mean = 0; + + for(size_t i = 0; i < num_trials; i++){ + mean += (double)distribution.PickRandom(n, random) / num_trials; + } + std::cout << "Mean after " << num_trials << " trials: " << mean << std::endl; + return 0; +} diff --git a/examples/math/Makefile b/examples/math/Makefile index 0273c03544..1fe781e7c3 100644 --- a/examples/math/Makefile +++ b/examples/math/Makefile @@ -19,7 +19,7 @@ CFLAGS_web_debug := $(CFLAGS_all) $(OFLAGS_web_debug) --js-library ../../include CFLAGS_web_opt := $(CFLAGS_all) $(OFLAGS_web_opt) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 #CFLAGS_web := $(CFLAGS_all) $(OFLAGS_web) --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s DISABLE_EXCEPTION_CATCHING=1 -s NO_EXIT_RUNTIME=1 -TARGETS := combos constants Distribution info_theory math Random Range stats +TARGETS := combos constants Distribution info_theory math Random Range stats CombinedBinomialDistribution default: native diff --git a/examples/timing/Binomial.cpp b/examples/timing/Binomial.cpp index 6392ef00a5..f69cfa2e9e 100644 --- a/examples/timing/Binomial.cpp +++ b/examples/timing/Binomial.cpp @@ -1,5 +1,14 @@ -#include +/** + * @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 2022 + * + * @file Binomial.cpp + * + */ + #include +#include #include "../../include/emp/math/Distribution.hpp" #include "../../include/emp/math/Random.hpp" @@ -186,4 +195,4 @@ int main() TestBinomial(random, 0.01, 1000, num_tests); TestBinomial(random, 0.001, 1000, num_tests); TestBinomial(random, 0.0001, 1000, num_tests); -} \ No newline at end of file +} diff --git a/include/emp/Evolve/Systematics.hpp b/include/emp/Evolve/Systematics.hpp index 323b6d780a..ae698ee172 100644 --- a/include/emp/Evolve/Systematics.hpp +++ b/include/emp/Evolve/Systematics.hpp @@ -12,8 +12,8 @@ * @todo We should provide an option to back up systematics data to a file so that it doesn't all * need to be kept in memory, especially if we're only doing post-analysis. * @todo This inheritance system makes adding new systematics-related data tracking kind of a pain. - * Over time, this will probably become a maintainability problem. We can probably make the - * whole inheritance thing go away through judicious use of signals. + * Over time, this will probably become a maintainability problem. We could make the inheritance + * go away and just use signals, but then the World could not maintain systematics managers. * @todo This does not currently handle situations where organisms change locations during their * lifetimes gracefully. */ @@ -21,11 +21,14 @@ #ifndef EMP_EVOLVE_SYSTEMATICS_HPP_INCLUDE #define EMP_EVOLVE_SYSTEMATICS_HPP_INCLUDE - +#include +#include #include #include #include #include +#include +#include #include #include "../base/Ptr.hpp" @@ -35,6 +38,7 @@ #include "../data/DataNode.hpp" #include "../datastructs/map_utils.hpp" #include "../datastructs/set_utils.hpp" +#include "../io/File.hpp" #include "../math/info_theory.hpp" #include "../math/stats.hpp" #include "../tools/string_utils.hpp" @@ -67,34 +71,41 @@ namespace emp { fitness.Add(fit); } - const double GetFitness() const { + double GetFitness() const { return fitness.GetMean(); } }; + /// Track information related to the mutational landscape + /// Maps a string representing a type of mutation to a count representing + /// the number of that type of mutation that occurred to bring about this taxon. template - struct mut_landscape_info { /// Track information related to the mutational landscape - /// Maps a string representing a type of mutation to a count representing - /// the number of that type of mutation that occurred to bring about this taxon. + struct mut_landscape_info { using phen_t = PHEN_TYPE; using has_phen_t = std::true_type; using has_mutations_t = std::true_type; using has_fitness_t = std::true_type; // using has_phenotype_t = true; - std::unordered_map mut_counts; + std::unordered_map mut_counts = {}; /// The number of mutations of each type that occurred to make this taxon DataNode fitness; /// This taxon's fitness (for assessing deleterious mutational steps) PHEN_TYPE phenotype; /// This taxon's phenotype (for assessing phenotypic change) + /// @returns this taxon's phenotype const PHEN_TYPE & GetPhenotype() const { return phenotype; } - const double GetFitness() const { + /// @returns this taxon's fitness + double GetFitness() const { return fitness.GetMean(); } - void RecordMutation(std::unordered_map muts) { + /// Adds mutations to the list of mutations that occurred to make this taxon + /// @param muts can contain as many strings (types of mutation) as desired, each accompanied + /// by a number indicating how many of that mutation occurred + /// Example: {"point_mutation":2, "insertion":1} + void RecordMutation(std::unordered_map & muts) { for (auto mut : muts) { if (Has(mut_counts, mut.first)) { mut_counts[mut.first] += mut.second; @@ -104,10 +115,14 @@ namespace emp { } } + /// Record the fitness of this taxon + /// @param fit the fitness void RecordFitness(double fit) { fitness.Add(fit); } + /// Record the phenotype of this taxon + /// @param phen the phenotype void RecordPhenotype(PHEN_TYPE phen) { phenotype = phen; } @@ -181,17 +196,25 @@ namespace emp { /// Get the number of taxanomic steps since the ancestral organism was injected into the World. size_t GetDepth() const { return depth; } + /// Get data struct associated with this taxon data_t & GetData() {return data;} + /// Get data struct associated with this taxon const data_t & GetData() const {return data;} + /// Get pointers to this taxon's offspring std::set > GetOffspring() {return offspring;} + /// Set this taxon's data struct to the given value void SetData(data_t d) {data = d;} + /// @returns this taxon's origination time double GetOriginationTime() const {return origination_time;} + /// Set this taxon's origination time void SetOriginationTime(double time) {origination_time = time;} + /// @returns this taxon's destruction time double GetDestructionTime() const {return destruction_time;} + /// Sets this taxon's destruction time void SetDestructionTime(double time) {destruction_time = time;} /// Add a new organism to this Taxon. @@ -227,6 +250,7 @@ namespace emp { return num_orgs; } + /// Remove specified taxon from this taxon's offspring list void RemoveFromOffspring(Ptr offspring_tax) { offspring.erase(offspring_tax); } @@ -253,8 +277,8 @@ namespace emp { /// A base class for Systematics, maintaining information common to all systematics managers - /// and providing virtual functions. - + /// and providing virtual functions. You probably don't want to instantiate this. It just + /// exists so that you can make containers of Systematics managers of different types. template class SystematicsBase { protected: @@ -317,6 +341,9 @@ namespace emp { /// What is the average phylogenetic depth of organisms in the population? double GetAveDepth() const { return ((double) total_depth) / (double) org_count; } + /// @returns current update/time step + size_t GetUpdate() const {return curr_update;} + /// Are we tracking organisms evolving in synchronous generations? void SetTrackSynchronous(bool new_val) {track_synchronous = new_val; } @@ -335,14 +362,19 @@ namespace emp { /// Are we storing the location of taxa? void SetStorePosition(bool new_val) { store_position = new_val; } - // Returns a reference so that capturing it in a lambda to call on update - // is less confusing. It's possible we should change it to be consistent - // with GetFitnessDataNode, though. + /// Sets the current update/time step + void SetUpdate(size_t ud) {curr_update = ud;} + + /// Add a data node to this systematics manager + /// @param name the name of the data node (so it can be found later) data_ptr_t AddDataNode(const std::string & name) { emp_assert(!data_nodes.HasNode(name)); return &(data_nodes.New(name)); } + /// Add a data node to this systematics manager + /// @param name the name of the data node (so it can be found later) + /// @param pull_set_fun a function to run when the data node is requested to pull data (returns vector of values) data_ptr_t AddDataNode(std::function()> pull_set_fun, const std::string & name) { emp_assert(!data_nodes.HasNode(name)); auto node = AddDataNode(name); @@ -350,6 +382,9 @@ namespace emp { return node; } + /// Add a data node to this systematics manager + /// @param name the name of the data node (so it can be found later) + /// @param pull_set_fun a function to run when the data node is requested to pull data (returns single value) data_ptr_t AddDataNode(std::function pull_fun, const std::string & name) { emp_assert(!data_nodes.HasNode(name)); auto node = AddDataNode(name); @@ -357,7 +392,7 @@ namespace emp { return node; } - + /// @returns a pointer to the data node with the specified name data_ptr_t GetDataNode(const std::string & name) { return &(data_nodes.Get(name)); } @@ -384,18 +419,22 @@ namespace emp { virtual int SackinIndex() const = 0; virtual double CollessLikeIndex() const = 0; virtual int GetMRCADepth() const = 0; - virtual void AddOrg(ORG && org, WorldPosition pos, int update) = 0; - virtual void AddOrg(ORG & org, WorldPosition pos, int update) = 0; - virtual bool RemoveOrg(WorldPosition pos, int time=-1) = 0; - virtual void RemoveOrgAfterRepro(WorldPosition pos, int time=-1) = 0; - // virtual bool RemoveNextOrg(WorldPosition pos, int time=-1) = 0; + virtual void AddOrg(ORG && org, WorldPosition pos) = 0; + virtual void AddOrg(ORG & org, WorldPosition pos) = 0; + virtual void AddOrg(ORG && org, WorldPosition pos, WorldPosition parent) = 0; + virtual void AddOrg(ORG & org, WorldPosition pos, WorldPosition parent) = 0; + virtual bool RemoveOrg(WorldPosition pos) = 0; + virtual void RemoveOrgAfterRepro(WorldPosition pos) = 0; virtual void PrintStatus(std::ostream & os) const = 0; virtual double CalcDiversity() const = 0; virtual void Update() = 0; - virtual void SetNextParent(int pos) = 0; - virtual void SetNextParent(WorldPosition & pos) = 0; + virtual void SetNextParent(WorldPosition pos) = 0; + virtual void SwapPositions(WorldPosition p1, WorldPosition p2) = 0; }; + // Forward-declare CollessStruct for use in calculating Colless metric + struct CollessStruct; + /// @brief A tool to track phylogenetic relationships among organisms. /// The systematics class tracks the relationships among all organisms based on the INFO_TYPE /// provided. If an offspring has the same value for INFO_TYPE as its parent, it is grouped into @@ -415,9 +454,9 @@ namespace emp { using hash_t = typename Ptr::hash_t; using fun_calc_info_t = std::function; - fun_calc_info_t calc_info_fun; - Ptr next_parent; - Ptr most_recent; + fun_calc_info_t calc_info_fun; ///< Function that takes an organism and returns the unit being tracked by systematics + Ptr next_parent; ///< The taxon that has been marked as parent for next new org + Ptr most_recent; ///< The most-recently added taxon using parent_t::store_active; using parent_t::store_ancestors; @@ -440,28 +479,20 @@ namespace emp { using parent_t::GetNumOutside; using parent_t::GetTreeSize; using parent_t::GetNumTaxa; - // using parent_t::OnNew; - // using parent_t::OnPrune; using parent_t::GetPhylogeneticDiversity; - // using parent_t::GetTaxonDistinctiveness; - // using parent_t::GetEvolutionaryDistinctiveness; using parent_t::GetMeanPairwiseDistance; using parent_t::GetSumPairwiseDistance; using parent_t::GetVariancePairwiseDistance; using parent_t::GetPairwiseDistances; - // using parent_t::GetDistanceToRoot; - // using parent_t::GetBranchesToRoot; - // using parent_t::GetMRCA; using parent_t::GetMRCADepth; using parent_t::AddOrg; using parent_t::RemoveOrg; using parent_t::RemoveOrgAfterRepro; - // using parent_t::RemoveNextOrg; - // using parent_t::Parent; using parent_t::PrintStatus; - // using parent_t::PrintLineage; using parent_t::CalcDiversity; using parent_t::Update; + using parent_t::GetUpdate; + using parent_t::SetUpdate; using parent_t::SetNextParent; using parent_t::GetDataNode; @@ -475,11 +506,12 @@ namespace emp { using parent_t::AddMutationCountDataNode; using parent_t::GetMaxDepth; + /// Struct for keeping track of what information to print out in snapshot files struct SnapshotInfo { using snapshot_fun_t = std::function; - snapshot_fun_t fun; - std::string key; - std::string desc; + snapshot_fun_t fun; ///< Function for converting taxon to string containing desired data + std::string key; ///< Column name for data calculated with this function + std::string desc; ///< Description of data in this function SnapshotInfo(const snapshot_fun_t & _fun, const std::string & _key, const std::string & _desc="") : fun(_fun), @@ -488,20 +520,19 @@ namespace emp { { ; } }; - emp::vector user_snapshot_funs; + emp::vector user_snapshot_funs; ///< Collection of all desired snapshot file columns std::unordered_set< Ptr, hash_t > active_taxa; ///< A set of all living taxa. std::unordered_set< Ptr, hash_t > ancestor_taxa; ///< A set of all dead, ancestral taxa. std::unordered_set< Ptr, hash_t > outside_taxa; ///< A set of all dead taxa w/o descendants. - Ptr to_be_removed = nullptr; - int removal_time = -1; - int removal_pos = -1; + Ptr to_be_removed = nullptr; ///< Taxon to remove org from after next call to AddOrg + emp::WorldPosition removal_pos = {0, 0}; ///< Position of taxon to next be removed - emp::vector > taxon_locations; - emp::vector > next_taxon_locations; + emp::vector > > taxon_locations; ///< Positions in this vector indicate taxon positions in world - Signal, ORG & org)> on_new_sig; ///< Trigger when any organism is pruned from tree + Signal, ORG & org)> on_new_sig; ///< Trigger when a new taxon is created + Signal)> on_extinct_sig; ///< Trigger when a taxon goes extinct Signal)> on_prune_sig; ///< Trigger when any organism is pruned from tree mutable Ptr mrca; ///< Most recent common ancestor in the population. @@ -513,7 +544,16 @@ namespace emp { void RemoveOffspring(Ptr offspring, Ptr taxon); /// Called when there are no more living members of a taxon. There may be descendants. - void MarkExtinct(Ptr taxon, int time=-1); + void MarkExtinct(Ptr taxon); + + #ifndef DOXYGEN_SHOULD_SKIP_THIS + /// Helper function for RemoveBefore + /// @returns true if a a taxon can safely be + /// removed by RemoveBefore + bool CanRemove(Ptr t, int ud); + // Helper for Colless function calculation + CollessStruct RecursiveCollessStep(Ptr curr) const; + #endif // DOXYGEN_SHOULD_SKIP_THIS @@ -544,30 +584,97 @@ namespace emp { outside_taxa.clear(); } + // ===== Functions for modifying phylogeny/systematics manager internal state ==== - void Update() { - ++curr_update; - if (track_synchronous) { - - // Clear pending removal - if (to_be_removed != nullptr) { - RemoveOrg(to_be_removed, removal_time); - taxon_locations[removal_pos] = nullptr; - to_be_removed = nullptr; - removal_pos = -1; - } + /// Switch to next update/time step + /// Useful for keeping track of taxon survival times + /// and population positions in synchronous generation worlds. + void Update(); - std::swap(taxon_locations, next_taxon_locations); - next_taxon_locations.resize(0); + ///@{ + /// Add information about a new organism, including its stored info and parent's taxon; + /// If you would like the systematics manager to track taxon age, you can also supply + /// the update at which the taxon is being added. + /// return a pointer for the associated taxon. + /// @returns a pointer for the associated taxon. + /// @param org a reference to the organism being added + /// @param pos the position of the organism being added + /// @param parent a pointer to the org's parent + void AddOrg(ORG && org, WorldPosition pos); + void AddOrg(ORG && org, WorldPosition pos, WorldPosition parent); + Ptr AddOrg(ORG && org, WorldPosition pos, Ptr parent); + Ptr AddOrg(ORG && org, Ptr parent=nullptr); + + void AddOrg(ORG & org, WorldPosition pos); + void AddOrg(ORG & org, WorldPosition pos, WorldPosition parent); + Ptr AddOrg(ORG & org, WorldPosition pos, Ptr parent); + Ptr AddOrg(ORG & org, Ptr parent=nullptr); + ///@} + + ///@{ + /// Remove an instance of an organism; track when it's gone. + /// @param pos the world position of the individual being removed + /// @param taxon a pointer to the taxon of the individual being removed + bool RemoveOrg(WorldPosition pos); + bool RemoveOrg(Ptr taxon); + ///@} + + ///@{ + /// Mark an instance of a taxon to be removed; track when it's gone. + /// This is a work-around to deal with steady state/non-synchronous + /// populations in which an organism might die as its offspring is born + /// (e.g. in a spatial world where the offspring replaces the parent). + /// If the bookkeeping is not handled correctly, we could accidentally + /// mark the taxon as extinct when it is actually continuing. + /// By using this method, the taxon won't be removed until after the + /// next org is added or the next time an org is marked for removal. + /// @param pos the world position of the individual being removed + /// @param taxon a pointer to the taxon of the individual being removed + void RemoveOrgAfterRepro(WorldPosition pos); + void RemoveOrgAfterRepro(Ptr taxon); + ///@} + + + ///@{ + /// Tell systematics manager that the parent of the next taxon added + /// will be the one specified by this function (either at the specified + /// position or the one pointed to by the given pointer) + /// Works with version of AddOrg that only takes org, position, and + /// update. + /// Will be set to null after being assigned as the parent of a taxon + void SetNextParent(WorldPosition pos) { + emp_assert(pos.IsActive() || !pos.IsValid()); + if (!pos.IsValid()) { + next_parent = nullptr; + } else { + next_parent = taxon_locations[pos.GetPopID()][pos.GetIndex()]; } } + void SetNextParent(Ptr p) { + next_parent = p; + } + ///@} + + /// Set function used to calculate taxons from organisms void SetCalcInfoFun(fun_calc_info_t f) {calc_info_fun = f;} - // Currently using raw pointers because of a weird bug in emp::Ptr. Should switch when fixed. + /// Remove all taxa that 1) went extinct before the specified update/time step, + /// and 2) only have ancestors that went extinct before the specified update/time step. + /// Warning: this function invalidates most measurements you could make about tree topology. + /// It is useful in select situations where you need to store ancestors for some period of time, + /// but cannot computationally afford to store all ancestors for your entire run. + void RemoveBefore(int ud); + + // ===== Functions for querying phylogeny/systematics manager internal state ==== + + // Currently using raw pointer because of a weird bug in emp::Ptr. Should switch when fixed. std::unordered_set< Ptr, hash_t > * GetActivePtr() { return &active_taxa; } + /// @returns set of active (extant/living) taxa0 const std::unordered_set< Ptr, hash_t > & GetActive() const { return active_taxa; } + /// @returns set of ancestor taxa (extinct, but have active descendants) const std::unordered_set< Ptr, hash_t > & GetAncestors() const { return ancestor_taxa; } + /// @returns set of outside taxa (extinct, with no active descendants) const std::unordered_set< Ptr, hash_t > & GetOutside() const { return outside_taxa; } /// How many taxa are still active in the population? @@ -585,59 +692,60 @@ namespace emp { /// How many taxa are stored in total? size_t GetNumTaxa() const { return GetTreeSize() + GetNumOutside(); } - int GetMaxDepth() { - if (max_depth != -1) { - return max_depth; - } + /// @returns the phylogenetic depth (lineage length) of the taxon with + /// the longest lineage out of all active taxa + int GetMaxDepth(); - for (auto tax : active_taxa) { - int depth = tax->GetDepth(); - if (depth > max_depth) { - max_depth = depth; - } - } - return max_depth; + /// @returns the taxon that will be used as the parent + /// of the next taxon created via the version of AddOrg + /// that does not accept a parent + Ptr GetNextParent() { + return next_parent; } - void SetNextParent(WorldPosition & pos) { - emp_assert(pos.IsActive() || !pos.IsValid()); - if (!pos.IsValid()) { - next_parent = nullptr; - } else { - next_parent = taxon_locations[pos.GetIndex()]; - } + /// @returns the most recently created taxon + Ptr GetMostRecent() { + return most_recent; } - void SetNextParent(int pos) { - emp_assert(pos < (int)taxon_locations.size(), "Invalid parent", pos, taxon_locations.size()); - if (pos == -1) { - next_parent = nullptr; - } else { - emp_assert(pos >= 0, "Invalid parent", pos); - emp_assert(taxon_locations[pos], pos); - next_parent = taxon_locations[pos]; - } - } + /// @returns a pointer to the parent of a given taxon + Ptr Parent(Ptr taxon) const; - void SetNextParent(Ptr p) { - next_parent = p; + /// @returns true if there is a taxon at specified location + bool IsTaxonAt(WorldPosition id) { + emp_assert(id.GetPopID() < taxon_locations.size(), "Invalid population id", id, taxon_locations.size()); + emp_assert(id.GetIndex() < taxon_locations[id.GetPopID()].size(), "Invalid taxon location", id, taxon_locations[id.GetPopID()].size()); + return taxon_locations[id.GetPopID()][id.GetIndex()] != nullptr; } - Ptr GetNextParent() { - return next_parent; + /// @returns pointer to taxon at specified location + Ptr GetTaxonAt(WorldPosition id) { + emp_assert(id.GetPopID() < taxon_locations.size(), "Invalid population id", id, taxon_locations.size()); + emp_assert(id.GetIndex() < taxon_locations[id.GetPopID()].size(), "Invalid taxon location", id, taxon_locations[id.GetPopID()].size()); + return taxon_locations[id.GetPopID()][id.GetIndex()]; } - Ptr GetMostRecent() { - return most_recent; - } + // ===== Functions for adding actions to systematics manager signals ==== + + /// Privide a function for Systematics to call each time a new taxon is created. + /// Trigger: New taxon is made + /// Argument: Pointer to taxon, reference to org taxon was created from + SignalKey OnNew(std::function t, ORG & org)> & fun) { return on_new_sig.AddAction(fun); } - SignalKey OnNew(std::function, ORG & org)> & fun) { return on_new_sig.AddAction(fun); } + /// Privide a function for Systematics to call each time a taxon goes extinct. + /// Trigger: Taxon is going extinct + /// Argument: Pointer to taxon + SignalKey OnExtinct(std::function t)> & fun) { return on_extinct_sig.AddAction(fun); } - /// Privide a function for Systematics to call each time a taxon is about to be pruned. + /// Privide a function for Systematics to call each time a taxon is about to be pruned (removed from ancestors). /// Trigger: Taxon is about to be killed /// Argument: Pointer to taxon SignalKey OnPrune(std::function)> & fun) { return on_prune_sig.AddAction(fun); } + // ===== Functions for adding data nodes to systematics manager ==== + + /// Add data node that records evolutionary distinctiveness when requested to pull. + /// Used by AddPhylodiversityFile in World_output.hpp virtual data_ptr_t AddEvolutionaryDistinctivenessDataNode(const std::string & name = "evolutionary_distinctiveness") { auto node = AddDataNode(name); @@ -652,6 +760,8 @@ namespace emp { return node; } + /// Add data node that records pairwise distance when requested to pull. + /// Used by AddPhylodiversityFile in World_output.hpp virtual data_ptr_t AddPairwiseDistanceDataNode(const std::string & name = "pairwise_distance") { auto node = AddDataNode(name); node->AddPullSet([this](){ @@ -660,6 +770,8 @@ namespace emp { return node; } + /// Add data node that records phylogenetic distinctiveness when requested to pull. + /// Used by AddPhylodiversityFile in World_output.hpp virtual data_ptr_t AddPhylogeneticDiversityDataNode(const std::string & name = "phylogenetic_diversity") { auto node = AddDataNode(name); node->AddPull([this](){ @@ -668,133 +780,102 @@ namespace emp { return node; } - + /// Add data node that records counts of deleterious steps along + /// lineages in this systematics manager when requested to pull. + /// Used by AddLineageMutationFile in World_output.hpp virtual data_ptr_t AddDeleteriousStepDataNode(const std::string & name = "deleterious_steps") { - return AddDeleteriousStepDataNodeImpl(1, name); - } - - data_ptr_t AddDeleteriousStepDataNodeImpl(bool decoy, const std::string & name = "deleterious_steps") { - emp_assert(false, "Calculating deleterious steps requires suitable DATA_STRUCT"); - return AddDataNode(name); - } - - template - data_ptr_t - AddDeleteriousStepDataNodeImpl(typename std::enable_if::type decoy, const std::string & name = "deleterious_steps") { auto node = AddDataNode(name); - node->AddPullSet([this](){ - emp::vector result; - for (auto tax : active_taxa) { - result.push_back(CountDeleteriousSteps(tax)); - } - return result; - }); + if constexpr (!DATA_STRUCT::has_fitness_t::value) { + emp_assert(false && + "Error: Trying to track deleterious steps in Systematics manager that doesn't track fitness" && + "Please use a DATA_STRUCT type that supports fitness tracking."); + } else { + node->AddPullSet([this](){ + emp::vector result; + for (auto tax : active_taxa) { + result.push_back(CountDeleteriousSteps(tax)); + } + return result; + }); + } return node; } + /// Add data node that phenotypic volatility (changes in phenotype) along + /// lineages in this systematics manager when requested to pull. + /// Used by AddLineageMutationFile in World_output.hpp virtual data_ptr_t AddVolatilityDataNode(const std::string & name = "volatility") { - return AddVolatilityDataNodeImpl(1, name); - } - - data_ptr_t AddVolatilityDataNodeImpl(bool decoy, const std::string & name = "volatility") { - emp_assert(false, "Calculating taxon volatility requires suitable DATA_STRUCT"); - return AddDataNode(name); - } - - template - data_ptr_t - AddVolatilityDataNodeImpl(typename std::enable_if::type decoy, const std::string & name = "volatility") { auto node = AddDataNode(name); - node->AddPullSet([this](){ - emp::vector result; - for (auto tax : active_taxa) { - result.push_back(CountPhenotypeChanges(tax)); - } - return result; - }); + if constexpr (!DATA_STRUCT::has_phen_t::value) { + emp_assert(false && + "Error: Trying to track phenotypic volatility in Systematics manager that doesn't track fitness" && + "Please use a DATA_STRUCT type that supports phenotype tracking."); + } else { + node->AddPullSet([this](){ + emp::vector result; + for (auto tax : active_taxa) { + result.push_back(CountPhenotypeChanges(tax)); + } + return result; + }); + } return node; } + /// Add data node that records counts of unique taxa along + /// lineages in this systematics manager when requested to pull. + /// Used by AddLineageMutationFile in World_output.hpp virtual data_ptr_t AddUniqueTaxaDataNode(const std::string & name = "unique_taxa") { - return AddUniqueTaxaDataNodeImpl(1, name); - } + auto node = AddDataNode(name); - data_ptr_t AddUniqueTaxaDataNodeImpl(bool decoy, const std::string & name = "unique_taxa") { - emp_assert(false, "Calculating unique taxa requires suitable DATA_STRUCT"); - return AddDataNode(name); - } + if constexpr (!DATA_STRUCT::has_phen_t::value) { + emp_assert(false && + "Error: Trying to track phenotypic volatility in Systematics manager that doesn't track fitness" && + "Please use a DATA_STRUCT type that supports phenotype tracking."); + } else { - template - data_ptr_t - AddUniqueTaxaDataNodeImpl(typename std::enable_if::type decoy, const std::string & name = "unique_taxa") { - auto node = AddDataNode(name); - node->AddPullSet([this](){ - emp::vector result; - for (auto tax : active_taxa) { - result.push_back(CountUniquePhenotypes(tax)); - } - return result; - }); + node->AddPullSet([this](){ + emp::vector result; + for (auto tax : active_taxa) { + result.push_back(CountUniquePhenotypes(tax)); + } + return result; + }); + } return node; } + /// Add data node that records counts of mutations of the specified type along + /// lineages in this systematics manager when requested to pull. + /// Used by AddLineageMutationFile in World_output.hpp virtual data_ptr_t AddMutationCountDataNode(const std::string & name = "mutation_count", const std::string & mutation = "substitution") { - return AddMutationCountDataNodeImpl(1, name, mutation); - } - - data_ptr_t AddMutationCountDataNodeImpl(bool decoy, const std::string & name = "mutation_count", const std::string & mutation = "substitution") { - emp_assert(false, "Calculating mutation count requires suitable DATA_STRUCT"); - return AddDataNode(name); - } - - template - data_ptr_t - AddMutationCountDataNodeImpl(typename std::enable_if::type decoy, const std::string & name = "mutation_count", const std::string & mutation = "substitution") { auto node = AddDataNode(name); - node->AddPullSet([this,mutation](){ - emp::vector result; - for (auto tax : active_taxa) { - result.push_back(CountMuts(tax, mutation)); - } - return result; - }); + if constexpr (!DATA_STRUCT::has_mutations_t::value) { + emp_assert(false && + "Error: Trying to track phenotypic volatility in Systematics manager that doesn't track mutations" && + "Please use a DATA_STRUCT type that supports mutation tracking."); + } else { + node->AddPullSet([this,mutation](){ + emp::vector result; + for (auto tax : active_taxa) { + result.push_back(CountMuts(tax, mutation)); + } + return result; + }); + } return node; } - /// Add a new snapshot function. - /// When a snapshot of the systematics is taken, in addition to the default - /// set of functions, all user-added snapshot functions are run. Functions - /// take a reference to a taxon as input and return the string to be dumped - /// in the file at the given key. - void AddSnapshotFun(const std::function & fun, - const std::string & key, const std::string & desc="") { - user_snapshot_funs.emplace_back(fun, key, desc); - } - - bool IsTaxonAt(int id) { - emp_assert(id < (int) taxon_locations.size(), "Invalid taxon location", id, taxon_locations.size()); - return taxon_locations[id]; - } - - Ptr GetTaxonAt(int id) { - emp_assert(id < (int) taxon_locations.size(), "Invalid taxon location", id, taxon_locations.size()); - emp_assert(taxon_locations[id], "No taxon at specified location"); - return taxon_locations[id]; - } - Ptr GetNextTaxonAt(int id) { - emp_assert(id < (int)next_taxon_locations.size(), "Invalid taxon location"); - emp_assert(next_taxon_locations[id], "No taxon at specified location"); - return next_taxon_locations[id]; - } + // ===== Functions for calculating phylogeny toplogy metrics ==== /** From (Faith 1992, reviewed in Winters et al., 2013), phylogenetic diversity is * the sum of edges in the minimal spanning tree connected the taxa you're @@ -811,92 +892,48 @@ namespace emp { int GetPhylogeneticDiversity() const { // As shown on page 5 of Faith 1992, when all branch lengths are equal the phylogenetic // diversity is the number of internal nodes plus the number of extant taxa - 1. + //int phylodiversity = ancestor_taxa.size() + active_taxa.size() -1; + return ancestor_taxa.size() + active_taxa.size() - 1; } - /** This is a metric of how distinct @param tax is from the rest of the population. + + /// @returns phylogenetic diversity if used without any arguments . + /// If you want to receive normalized data, you need to include the number of generations + /// your tree has (multiples of 10 from 10 to 100 are allowed) + /// you also need to specify a file with which to normalize your data. + /// If value is outside of the values in the file, 100th percentile will be returned + int GetPhylogeneticDiversityNormalize(int generation = 0, std::string filename = "") const; + + + /** This is a metric of how distinct \c tax is from the rest of the population. * - * (From Vane-Wright et al., 1991; reviewed in Winter et al., 2013) - */ + * (From Vane-Wright et al., 1991; reviewed in Winter et al., 2013) */ double GetTaxonDistinctiveness(Ptr tax) const {return 1.0/GetDistanceToRoot(tax);} /** This metric (from Isaac, 2007; reviewed in Winter et al., 2013) measures how - * distinct @param tax is from the rest of the population, weighted for the amount of + * distinct \c tax is from the rest of the population, weighted for the amount of * unique evolutionary history that it represents. * - * To quantify length of evolutionary history, this method needs @param time: the current + * To quantify length of evolutionary history, this method needs \c time: the current * time, in whatever units time is being measured in when taxa are added to the systematics * manager. Note that passing a time in the past will produce inacurate results (since we * don't know what the state of the tree was at that time). * - * Assumes the tree is all connected. Will return -1 if this assumption isn't met. - */ - double GetEvolutionaryDistinctiveness(Ptr tax, double time) const { - - double depth = 0; // Length (in time units) of section we're currently exploring - double total = 0; // Count up scores for each section of tree - double divisor = tax->GetTotalOffspring() + 1; // Number of extant taxa this will split into (1 for current taxa, plus its offspring) - - // We're stopping when we hit MRCA, so we need to make sure it's been calculated. - GetMRCA(); - if (tax == mrca) { - return 0; - } - - // std::cout << "Initializing divisor to " << divisor << " Offspring: " << tax->GetTotalOffspring() << std::endl; - // std::cout << "MRCA ID: " << mrca->GetID() << " Tax ID: " << tax->GetID() << " time: " << time << " Orig: " << tax->GetOriginationTime() << std::endl; - - Ptr test_taxon = tax->GetParent(); - - emp_assert(time != -1 && "Invalid time - are you passing time to rg?", time); - emp_assert(time >= tax->GetOriginationTime() - && "GetEvolutionaryDistinctiveness received a time that is earlier than the taxon's origination time.", tax->GetOriginationTime(), time); - - while (test_taxon) { - - // emp_assert(test_taxon->GetOriginationTime() != -1 && - // "Invalid time - are you passing time to rg?", time); - - depth += time - test_taxon->GetOriginationTime(); - // std::cout << "Tax: " << test_taxon->GetID() << " depth: " << depth << " time: " << time << " Orig: " << test_taxon->GetOriginationTime() << " divisor: " << divisor << std::endl; - time = test_taxon->GetOriginationTime(); - if (test_taxon == mrca || !test_taxon) { - // Stop when everything has converged or when we hit the root. - // std::cout << (int)(test_taxon == mrca) << " depth: " << depth << " divisor: " << divisor << std::endl; - total += depth/divisor; - return total; - } else if (test_taxon->GetNumOrgs() > 0) { - // If this taxon is still alive we need to update the divisor - // std::cout << "Alive point" << " depth: " << depth << " divisor: " << divisor << std::endl; - total += depth/divisor; - depth = 0; - divisor = test_taxon->GetTotalOffspring() + 1; - } else if (test_taxon->GetNumOff() > 1) { - // This is a branch point. We need to add the things on the other branch to the divisor.. - // std::cout << "Branch point" << " depth: " << depth << " divisor: " << divisor << std::endl; - total += depth/divisor; - depth = 0; - divisor = test_taxon->GetTotalOffspring(); - } - - test_taxon = test_taxon->GetParent(); - } - - return -1; - } + * Assumes the tree is all connected. Will return -1 if this assumption isn't met.*/ + double GetEvolutionaryDistinctiveness(Ptr tax, double time) const; /** Calculates mean pairwise distance between extant taxa (Webb and Losos, 2000). * This measurement is also called Average Taxonomic Diversity (Warwick and Clark, 1998) * (for demonstration of equivalence see Tucker et al, 2016). This measurement tells * you about the amount of distinctness in the community as a whole. * - * @param branch_only only counts distance in terms of nodes that represent a branch - * between two extant taxa (potentially useful for comparison to biological data, where - * non-branching nodes generally cannot be inferred). - * * This measurement assumes that the tree is fully connected. Will return -1 * if this is not the case. - * */ + * + * @param branch_only only counts distance in terms of nodes that represent a branch + * between two extant taxa (potentially useful for comparison to biological data, where + * non-branching nodes generally cannot be inferred). */ double GetMeanPairwiseDistance(bool branch_only=false) const { emp::vector dists = GetPairwiseDistances(branch_only); return (double)Sum(dists)/dists.size(); @@ -905,13 +942,12 @@ namespace emp { /** Calculates summed pairwise distance between extant taxa. Tucker et al 2017 points * out that this is a measure of phylogenetic richness. * - * @param branch_only only counts distance in terms of nodes that represent a branch - * between two extant taxa (potentially useful for comparison to biological data, where - * non-branching nodes generally cannot be inferred). - * * This measurement assumes that the tree is fully connected. Will return -1 * if this is not the case. - * */ + * + * @param branch_only only counts distance in terms of nodes that represent a branch + * between two extant taxa (potentially useful for comparison to biological data, where + * non-branching nodes generally cannot be inferred) */ double GetSumPairwiseDistance(bool branch_only=false) const { emp::vector v = GetPairwiseDistances(branch_only); return Sum(v); @@ -920,174 +956,40 @@ namespace emp { /** Calculates variance of pairwise distance between extant taxa. Tucker et al 2017 points * out that this is a measure of phylogenetic regularity. * - * @param branch_only only counts distance in terms of nodes that represent a branch - * between two extant taxa (potentially useful for comparison to biological data, where - * non-branching nodes generally cannot be inferred). - * * This measurement assumes that the tree is fully connected. Will return -1 * if this is not the case. - * */ + * + * @param branch_only only counts distance in terms of nodes that represent a branch + * between two extant taxa (potentially useful for comparison to biological data, where + * non-branching nodes generally cannot be inferred). */ double GetVariancePairwiseDistance(bool branch_only=false) const { emp::vector v = GetPairwiseDistances(branch_only); return Variance(v); } - /** Calculates a vector of all pairwise distances between extant taxa. - * - * @param branch_only only counts distance in terms of nodes that represent a branch - * between two extant taxa (potentially useful for comparison to biological data, where - * non-branching nodes generally cannot be inferred). * * This method assumes that the tree is fully connected. Will return -1 * if this is not the case. - * */ - emp::vector GetPairwiseDistances(bool branch_only=false) const { - // The overarching approach here is to start with a bunch of pointers to all - // extant organisms (since that will include all leaves). Then we trace back up - // the tree, keeping track of distances. When things meet up, we calculate - // distances between the nodes on the sides that just met up. - - emp::vector dists; - - std::map< Ptr, emp::vector> > curr_pointers; - std::map< Ptr, emp::vector> > next_pointers; - - - for (Ptr tax : active_taxa) { - curr_pointers[tax] = emp::vector>({{0}}); - } - - // std::cout << "Starting curr_pointers size: " << curr_pointers.size() << std::endl; - - while (curr_pointers.size() > 0) { - for (auto & tax : curr_pointers) { - bool alive = tax.first->GetNumOrgs() > 0; - // std::cout << tax.first << " has " << to_string(tax.second) << "and is waiting for " << tax.first->GetNumOff() + int(alive) << std::endl; - if ( tax.second.size() < tax.first->GetNumOff() + int(alive)) { - if (Has(next_pointers, tax.first)) { - // In case an earlier iteration added this node to next_pointers - for (auto vec : tax.second) { - next_pointers[tax.first].push_back(vec); - } - } else { - next_pointers[tax.first] = curr_pointers[tax.first]; - } - continue; - } - emp_assert(tax.first->GetNumOff() + int(alive) == tax.second.size(), tax.first->GetNumOff(), alive, to_string(tax.second), tax.second.size()); - - // Okay, things should have just met up. Let's compute the distances - // between everything that just met. - - if (tax.second.size() > 1) { - - for (size_t i = 0; i < tax.second.size(); i++ ) { - for (size_t j = i+1; j < tax.second.size(); j++) { - for (int disti : tax.second[i]) { - for (int distj : tax.second[j]) { - // std::cout << "Adding " << disti << " and " << distj << std::endl; - dists.push_back(disti+distj); - } - } - } - } - } - // std::cout << "dists " << to_string(dists) << std::endl; - // Increment distances and stick them in new vector - emp::vector new_dist_vec; - for (auto & vec : tax.second) { - for (int el : vec) { - new_dist_vec.push_back(el+1); - } - } - - // std::cout << "new_dist_vec " << to_string(new_dist_vec) << std::endl; - - next_pointers.erase(tax.first); - - Ptr test_taxon = tax.first->GetParent(); - while (test_taxon && test_taxon->GetNumOff() == 1 && test_taxon->GetNumOrgs() == 0) { - if (!branch_only) { - for (size_t i = 0; i < new_dist_vec.size(); i++){ - new_dist_vec[i]++; - } - } - test_taxon = test_taxon->GetParent(); - } - - if (!test_taxon) { - continue; - } else if (!Has(next_pointers, test_taxon)) { - next_pointers[test_taxon] = emp::vector >({new_dist_vec}); - } else { - next_pointers[test_taxon].push_back(new_dist_vec); - } - } - curr_pointers = next_pointers; - next_pointers.clear(); - // std::cout << curr_pointers.size() << std::endl; - } - - if (dists.size() != (active_taxa.size()*(active_taxa.size()-1))/2) { - // The tree is not connected - // It's possible we should do something different here... - return dists; - } - - // std::cout << "Total: " << total << "Dists: " << dists.size() << std::endl; - - return dists; - - } - - - /** - * Returns a vector containing all taxa from @param time_point that were * - * */ - std::set> GetCanopyExtantRoots(int time_point = 0) const { - // NOTE: This could be made faster by doing something similar to the pairwise distance - // function - - std::set< Ptr> result; - // std::cout << "starting " << time_point << std::endl; - for (Ptr tax : active_taxa) { - // std::cout << tax->GetInfo() << std::endl; - while (tax) { - // std::cout << tax->GetInfo() << " " << tax->GetOriginationTime() << " " << tax->GetDestructionTime() << std::endl; - if (tax->GetOriginationTime() <= time_point && tax->GetDestructionTime() > time_point ) { - result.insert(tax); - // std::cout << "inserting " << tax->GetInfo() << std::endl; - break; - } - tax = tax->GetParent(); - } - } - - return result; + * @param branch_only only counts distance in terms of nodes that represent a branch + * between two extant taxa (potentially useful for comparison to biological data, where + * non-branching nodes generally cannot be inferred). * */ + emp::vector GetPairwiseDistances(bool branch_only=false) const; - } + /** + * Returns a vector containing all taxa that were extant at \c time_point and + * were at that time the most recent ancestors of taxa that are now extant + * Example: Say the only current extant taxon is C, its lineage goes A -> B -> C, + * and B and C were both alive at the specified time_point. This function would + * only return B. If, however, there were another currently extant taxon that were + * descended directly from A, then this function would return both A and B. */ + std::set> GetCanopyExtantRoots(int time_point = 0) const; /** Counts the total number of ancestors between @param tax and MRCA, if there is one. If - * there is no common ancestor, distance to the root of this tree is calculated instead. - */ - int GetDistanceToRoot(Ptr tax) const { - // Now, trace the line of descent, updating the candidate as we go. - GetMRCA(); - - int depth = 0; - Ptr test_taxon = tax->GetParent(); - while (test_taxon) { - depth++; - if (test_taxon == mrca || !test_taxon) { - return depth; - } - test_taxon = test_taxon->GetParent(); - } - return depth; - } + * there is no common ancestor, distance to the root of this tree is calculated instead.*/ + int GetDistanceToRoot(Ptr tax) const ; /** Counts the number of branching points leading to multiple extant taxa * between @param tax and the most-recent common ancestor (or the root of its subtree, @@ -1095,315 +997,100 @@ namespace emp { * of stats for phylogenies are designed for phylogenies reconstructed from extant taxa. * These phylogenies generally only contain branching points, rather than every ancestor * along the way to the current taxon.*/ - int GetBranchesToRoot(Ptr tax) const { - GetMRCA(); - - int depth = 0; - Ptr test_taxon = tax->GetParent(); - while (test_taxon) { - if (test_taxon == mrca || !test_taxon) { - return depth; - } else if (test_taxon->GetNumOff() > 1) { - depth++; - } - test_taxon = test_taxon->GetParent(); - } - return depth; - } + int GetBranchesToRoot(Ptr tax) const; /** Calculate Sackin Index of this tree (Sackin, 1972; reviewed in Shao, 1990). - * Measures tree balance - */ + * Measures tree balance*/ int SackinIndex() const { int sackin = 0; - for (auto taxon : active_taxa) { sackin += GetBranchesToRoot(taxon) + 1; // Sackin index counts root as branch } - return sackin; } + /** Calculate Colless Index of this tree (Colless, 1982; reviewed in Shao, 1990). + * Measures tree balance. The standard Colless index only works for bifurcating trees, + * so this will be a Colless-like Index, as suggested in + * "Sound Colless-like balance indices for multifurcating trees" (Mir, 2018, PLoS One)*/ + double CollessLikeIndex() const { + GetMRCA(); + return RecursiveCollessStep(mrca).total; + } - // Graph ToGraph() const { - - // std::map, int> ids; - // int next_id = 0; - - // for (Ptr tax : active_taxa) { - // ids[tax] = next_id; - // next_id++; - // } - - // for (Ptr tax : ancestor_taxa) { - // ids[tax] = next_id; - // next_id++; - // } - - // for (Ptr tax : outside_taxa) { - // ids[tax] = next_id; - // next_id++; - // } - - // Graph g(next_id); - - // for (Ptr tax : active_taxa) { - // if (tax->GetParent()) { - // g.AddEdge(ids[tax->GetParent()], ids[tax]); - // } - // } - - // for (Ptr tax : ancestor_taxa) { - // if (tax->GetParent()) { - // g.AddEdge(ids[tax->GetParent()], ids[tax]); - // } - // } - - // for (Ptr tax : outside_taxa) { - // if (tax->GetParent()) { - // g.AddEdge(ids[tax->GetParent()], ids[tax]); - // } - // } - - // return g; - // } - - // Graph ToMinimalGraph() const { - // std::map, int> ids; - // int next_id = 0; - - // for (Ptr tax : active_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - // ids[tax] = next_id; - // next_id++; - // } - - // for (Ptr tax : ancestor_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - // ids[tax] = next_id; - // next_id++; - // } - - // for (Ptr tax : outside_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - // ids[tax] = next_id; - // next_id++; - // } - - // Graph g(next_id); - - // for (Ptr tax : active_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - - // Ptr parent = tax->GetParent(); - // while (parent) { - // if (parent->GetNumOff() == 1) { - // parent = parent->GetParent(); - // } else { - // g.AddEdge(ids[parent], ids[tax]); - // } - // } - // } - - // for (Ptr tax : ancestor_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - - // Ptr parent = tax->GetParent(); - // while (parent) { - // if (parent->GetNumOff() == 1) { - // parent = parent->GetParent(); - // } else { - // g.AddEdge(ids[parent], ids[tax]); - // } - // } - // } - - // for (Ptr tax : outside_taxa) { - // if (tax->GetNumOff() == 1) { - // continue; - // } - - // Ptr parent = tax->GetParent(); - // while (parent) { - // if (parent->GetNumOff() == 1) { - // parent = parent->GetParent(); - // } else { - // g.AddEdge(ids[parent], ids[tax]); - // } - // } - // } - - // return g; - // } - - struct CollessStruct { - double total = 0; - emp::vector ns; - }; - - CollessStruct RecursiveCollessStep(Ptr curr) const { - CollessStruct result; - - while (curr->GetNumOff() == 1) { - curr = *(curr->GetOffspring().begin()); - } + /// @returns a pointer to the Most-Recent Common Ancestor for the population. + Ptr GetMRCA() const; - if (curr->GetNumOff() == 0) { - result.ns.push_back(0); // Node itself is calculated at level above - return result; - } + /// @returns the depth of the Most-Recent Common Ancestor; return -1 for none. + int GetMRCADepth() const; - for (Ptr off : curr->GetOffspring()) { - // std::cout << "Recursing on ID: " << off->GetID() << " Offspring: " << off->GetTotalOffspring() << std::endl; + /// @returns a pointer to the Most-Recent Ancestor shared by two taxa. + Ptr GetSharedAncestor(Ptr t1, Ptr t2) const; - CollessStruct new_result = RecursiveCollessStep(off); - result.ns.push_back(Sum(new_result.ns) + log(off->GetOffspring().size() + exp(1))); - result.total += new_result.total; - } + /// @returns the genetic diversity of the population. + double CalcDiversity() const; - // std::cout << "Evaluating: " << curr->GetID() << std::endl; + /// @returns vector containing the lineages of the specified taxon + emp::vector> GetLineage(Ptr tax) const { + emp::vector> lineage; + lineage.push_back(tax); - double med = Median(result.ns); - double sum_diffs = 0; - // std::cout << "Median: " << med << std::endl; - for (double n : result.ns) { - // std::cout << n << std::endl; - sum_diffs += std::abs(n-med); + while (tax) { + tax = Parent(tax); + lineage.push_back(tax); } - // std::cout << "Sumdiffs: " << sum_diffs << " n: " << result.ns.size() << " average: " << sum_diffs/result.ns.size() << std::endl; - result.total += sum_diffs/result.ns.size(); - return result; + return lineage; } - /** Calculate Colless Index of this tree (Colless, 1982; reviewed in Shao, 1990). - * Measures tree balance. The standard Colless index only works for bifurcating trees, - * so this will be a Colless-like Index, as suggested in - * "Sound Colless-like balance indices for multifurcating trees" (Mir, 2018, PLoS One) - */ - double CollessLikeIndex() const { + /// @returns vector containing the lineages of the specified taxon + /// up to and including the MRCA, but not past the MRCA + emp::vector> GetLineageToMRCA(Ptr tax) const { GetMRCA(); + emp::vector> lineage; + lineage.push_back(tax); - return RecursiveCollessStep(mrca).total; + while (tax && tax != mrca) { + tax = Parent(tax); + lineage.push_back(tax); + } + return lineage; } + // ===== Output functions ==== - void RemoveBefore(int ud) { - - // @ELD: This would be such a nice way to do it - // but we can't because we need to notify offspring - // when their parents are un-tracked - // std::set> to_remove; - // for (Ptr tax : ancestor_taxa) { - // if (tax->GetDestructionTime() < ud) { - // to_remove.insert(tax); - // } - // } - - // for (Ptr tax : to_remove) { - // ancestor_taxa.erase(tax); - // tax.Delete(); - // } - - std::map, std::set>> to_remove; + /// Print details about the Systematics manager. + /// First prints setting, followed by all active, ancestor, and outside + /// taxa being stored. Format for taxa is + /// [ id | number of orgs in this taxon, number of offspring taxa of this taxon | parent taxon] + /// @param os output stream to print to + void PrintStatus(std::ostream & os=std::cout) const; - for (Ptr tax : active_taxa) { - Ptr curr = tax; + /// Print a whole lineage. Format: "Lineage:", followed by each taxon in the lineage, each on new line + /// @param taxon a pointer to the taxon to print the lineage of + /// @param os output stream to print to + void PrintLineage(Ptr taxon, std::ostream & os=std::cout) const; - while (curr && !CanRemove(curr->GetParent(), ud)) { - curr = curr->GetParent(); - } - - if (curr) { - Ptr next = curr->GetParent(); - while (next) { - to_remove[next].insert(curr); - curr = next; - next = next->GetParent(); - } - } - } - // std::cout << "About to remove " << to_remove.size() << " orgs" << std::endl; - for (std::pair, std::set>> el : to_remove) { - emp_assert(el.first->GetDestructionTime() < ud, el.first->GetDestructionTime(), ud); - if (el.first->GetNumOff() == el.second.size()) { - // Everything is account for - for (auto tax : el.second) { - tax->NullifyParent(); - } - ancestor_taxa.erase(el.first); - el.first.Delete(); - } - } - - } - - bool CanRemove(Ptr t, int ud) { - if (!t) { - return false; - } - while (t) { - if (t->GetNumOrgs() > 0 || t->GetDestructionTime() >= ud) { - return false; - } - t = t->GetParent(); - } - return true; + /// Add a new snapshot function. + /// When a snapshot of the systematics is taken, in addition to the default + /// set of functions, all user-added snapshot functions are run. Functions + /// take a reference to a taxon as input and return the string to be dumped + /// in the file at the given key. + void AddSnapshotFun(const std::function & fun, + const std::string & key, const std::string & desc="") { + user_snapshot_funs.emplace_back(fun, key, desc); } - /// Request a pointer to the Most-Recent Common Ancestor for the population. - Ptr GetMRCA() const; - - /// Request the depth of the Most-Recent Common Ancestor; return -1 for none. - int GetMRCADepth() const; - - /// Add information about a new organism, including its stored info and parent's taxon; - /// If you would like the systematics manager to track taxon age, you can also supply - /// the update at which the taxon is being added. - /// return a pointer for the associated taxon. - void AddOrg(ORG && org, WorldPosition pos, int update=-1); - Ptr AddOrg(ORG && org, WorldPosition pos, Ptr parent=nullptr, int update=-1); - Ptr AddOrg(ORG && org, Ptr parent=nullptr, int update=-1); - - void AddOrg(ORG & org, WorldPosition pos, int update=-1); - Ptr AddOrg(ORG & org, WorldPosition pos, Ptr parent=nullptr, int update=-1); - Ptr AddOrg(ORG & org, Ptr parent=nullptr, int update=-1); - - - /// Remove an instance of an organism; track when it's gone. - bool RemoveOrg(WorldPosition pos, int time=-1); - bool RemoveOrg(Ptr taxon, int time=-1); - - void RemoveOrgAfterRepro(WorldPosition pos, int time=-1); - void RemoveOrgAfterRepro(Ptr taxon, int time=-1); - - /// Remove org from next population (for use with synchronous generations) - // bool RemoveNextOrg(WorldPosition pos, int time=-1); - // bool RemoveNextOrg(Ptr taxon, int time=-1); - - /// Climb up a lineage... - Ptr Parent(Ptr taxon) const; - - /// Print details about the Systematics manager. - void PrintStatus(std::ostream & os=std::cout) const; - - /// Print whole lineage. - void PrintLineage(Ptr taxon, std::ostream & os=std::cout) const; - + /// Take a snapshot of current state of taxon phylogeny. + /// WARNING: Current, this function assumes one parent taxon per-taxon. + /// @param file_path the file to store the snapshot data in void Snapshot(const std::string & file_path) const; - /// Calculate the genetic diversity of the population. - double CalcDiversity() const; + void SwapPositions(WorldPosition p1, WorldPosition p2) { + emp::vector > & v1 = taxon_locations[p1.GetPopID()]; + emp::vector > & v2 = taxon_locations[p2.GetPopID()]; + std::swap(v1[p1.GetIndex()], v2[p2.GetIndex()]); + } }; @@ -1413,6 +1100,28 @@ namespace emp { // === === // ============================================================= + // ======= Functions for manipulating systematics manager internals + + template + void Systematics::Update() { + if (track_synchronous) { + + // Clear pending removal + if (to_be_removed != nullptr) { + RemoveOrg(to_be_removed); + taxon_locations[removal_pos.GetPopID()][removal_pos.GetIndex()] = nullptr; + to_be_removed = nullptr; + removal_pos = {0, 0}; + } + + // Assumes that synchronous worlds have two populations, with 0 + // being currently alive and 1 being the one being created + std::swap(taxon_locations[0], taxon_locations[1]); + taxon_locations[1].resize(0); + } + ++curr_update; + } + // Should be called wheneven a taxon has no organisms AND no descendants. template void Systematics::Prune(Ptr taxon) { @@ -1436,15 +1145,23 @@ namespace emp { // If the taxon is still active AND the is the current mrca AND now has only one offspring, // clear the MRCA for lazy re-evaluation later. - else if (taxon == mrca && taxon->GetNumOff() == 1) mrca = nullptr; + else if (taxon == mrca && taxon->GetNumOff() == 1) { + mrca = nullptr; + } } // Mark a taxon extinct if there are no more living members. There may be descendants. template - void Systematics::MarkExtinct(Ptr taxon, int time) { + void Systematics::MarkExtinct(Ptr taxon) { emp_assert(taxon); emp_assert(taxon->GetNumOrgs() == 0); + // Track destruction time + taxon->SetDestructionTime(curr_update); + + // Give other functions a chance to do stuff with taxon before extinction + on_extinct_sig.Trigger(taxon); + if (max_depth == (int)taxon->GetDepth()) { // We no longer know the max depth max_depth = -1; @@ -1464,105 +1181,92 @@ namespace emp { taxon.Delete(); return; } - // std::cout << "About to set destruction time " << time << std::endl; - // Only need to track destruction time if we're archiving taxa - taxon->SetDestructionTime(time); if (store_ancestors) { ancestor_taxa.insert(taxon); // Move taxon to ancestors... } + if (taxon == mrca && taxon->GetNumOff() <= 1) { + // If this taxon was mrca and has only one offspring, then the new + // mrca is somewhere farther down the chain. + // If this taxon was mrca and now has no offspring, something very + // strange has happened. + // Either way, we should mark mrca for lazy recalculation + mrca = nullptr; + } if (taxon->GetNumOff() == 0) Prune(taxon); // ...and prune from there if needed. } - - // Request a pointer to the Most-Recent Common Ancestor for the population. + // Add information about a new organism, including its stored info and parent's taxon; + // Can't return a pointer for the associated taxon because of obnoxious inheritance problems template - Ptr::taxon_t> Systematics::GetMRCA() const { - if (!mrca && num_roots == 1) { // Determine if we need to calculate the MRCA. - // First, find a candidate among the living taxa. Only taxa that have one offsrping - // can be on the line-of-descent to the MRCA, so anything else is a good start point. - // There must be at least one! Stop as soon as we find a candidate. - Ptr candidate(nullptr); - for (auto x : active_taxa) { - if (x->GetNumOff() != 1) { candidate = x; break; } - } - - // Now, trace the line of descent, updating the candidate as we go. - Ptr test_taxon = candidate->GetParent(); - while (test_taxon) { - emp_assert(test_taxon->GetNumOff() >= 1); - // If the test_taxon is dead, we only want to update candidate when we hit a new branch point - // If test_taxon is still alive, though, we always need to update it - if (test_taxon->GetNumOff() > 1 || test_taxon->GetNumOrgs() > 0) candidate = test_taxon; - test_taxon = test_taxon->GetParent(); - } - mrca = candidate; - } - return mrca; + // Ptr::taxon_t> + void Systematics::AddOrg(ORG & org, WorldPosition pos) { + emp_assert(store_position, "Trying to pass position to a systematics manager that can't use it"); + // emp_assert(next_parent, "Adding organism with no parent specified and no next_parent set"); + AddOrg(org, pos, next_parent); + next_parent = nullptr; } - // Request the depth of the Most-Recent Common Ancestor; return -1 for none. + // Add information about a new organism, including its stored info and parent's taxon; + // Can't return a pointer for the associated taxon because of obnoxious inheritance problems template - int Systematics::GetMRCADepth() const { - GetMRCA(); - if (mrca) return (int) mrca->GetDepth(); - return -1; + // Ptr::taxon_t> + void Systematics::AddOrg(ORG && org, WorldPosition pos) { + emp_assert(store_position, "Trying to pass position to a systematics manager that can't use it"); + // emp_assert(next_parent, "Adding organism with no parent specified and no next_parent set"); + AddOrg(org, pos, next_parent); + next_parent = nullptr; } - - // Add information about a new organism, including its stored info and parent's taxon; // Can't return a pointer for the associated taxon because of obnoxious inheritance problems template // Ptr::taxon_t> - void Systematics::AddOrg(ORG & org, WorldPosition pos, int update) { + void Systematics::AddOrg(ORG & org, WorldPosition pos, WorldPosition parent) { emp_assert(store_position, "Trying to pass position to a systematics manager that can't use it"); - // emp_assert(next_parent, "Adding organism with no parent specified and no next_parent set"); - AddOrg(org, pos, next_parent, update); - next_parent = nullptr; + AddOrg(org, pos, taxon_locations[parent.GetPopID()][parent.GetIndex()]); } // Add information about a new organism, including its stored info and parent's taxon; // Can't return a pointer for the associated taxon because of obnoxious inheritance problems template // Ptr::taxon_t> - void Systematics::AddOrg(ORG && org, WorldPosition pos, int update) { + void Systematics::AddOrg(ORG && org, WorldPosition pos, WorldPosition parent) { emp_assert(store_position, "Trying to pass position to a systematics manager that can't use it"); - // emp_assert(next_parent, "Adding organism with no parent specified and no next_parent set"); - AddOrg(org, pos, next_parent, update); - next_parent = nullptr; + AddOrg(org, pos, taxon_locations[parent.GetPopID()][parent.GetIndex()]); } // Version for if you aren't tracking positions template Ptr::taxon_t> - Systematics::AddOrg(ORG & org, Ptr parent, int update) { - return AddOrg(org, -1, parent, update); + Systematics::AddOrg(ORG & org, Ptr parent) { + emp_assert(!store_position && + "Trying to add org to position-tracking systematics manager without position. Either specify a valid position or turn of position tracking for systematic manager.", store_position); + return AddOrg(org, WorldPosition::invalid_id, parent); } // Version for if you aren't tracking positions template Ptr::taxon_t> - Systematics::AddOrg(ORG && org, Ptr parent, int update) { + Systematics::AddOrg(ORG && org, Ptr parent) { emp_assert(!store_position && "Trying to add org to position-tracking systematics manager without position. Either specify a valid position or turn of position tracking for systematic manager.", store_position); - return AddOrg(org, WorldPosition::invalid_id, parent, update); + return AddOrg(org, WorldPosition::invalid_id, parent); } // Add information about a new organism, including its stored info and parent's taxon; // return a pointer for the associated taxon. template Ptr::taxon_t> - Systematics::AddOrg(ORG && org, WorldPosition pos, Ptr parent, int update) { - return AddOrg(org, pos, parent, update); + Systematics::AddOrg(ORG && org, WorldPosition pos, Ptr parent) { + return AddOrg(org, pos, parent); } // Add information about a new organism, including its stored info and parent's taxon; - // return a pointer for the associated taxon. template Ptr::taxon_t> - Systematics::AddOrg(ORG & org, WorldPosition pos, Ptr parent, int update) { + Systematics::AddOrg(ORG & org, WorldPosition pos, Ptr parent) { org_count++; // Keep count of how many organisms are being tracked. ORG_INFO info = calc_info_fun(org); @@ -1580,33 +1284,29 @@ namespace emp { if (max_depth != -1 && (int)cur_taxon->GetDepth() > max_depth) { max_depth = cur_taxon->GetDepth(); } - on_new_sig.Trigger(cur_taxon, org); + if (store_active) active_taxa.insert(cur_taxon); // Store new taxon. - if (parent) parent->AddOffspring(cur_taxon); // Track tree info. + if (parent) parent->AddOffspring(cur_taxon); // Track tree info. - cur_taxon->SetOriginationTime(update); + cur_taxon->SetOriginationTime(curr_update); + on_new_sig.Trigger(cur_taxon, org); } // std::cout << "about to store poisition" << std::endl; - if (store_position && pos.GetIndex() >= 0) { - if (pos.GetPopID()) { - if (pos.GetIndex() >= next_taxon_locations.size()) { - next_taxon_locations.resize(pos.GetIndex()+1); - } - next_taxon_locations[pos.GetIndex()] = cur_taxon; - - } else { - if (pos.GetIndex() >= taxon_locations.size()) { - taxon_locations.resize(pos.GetIndex()+1); - } - taxon_locations[pos.GetIndex()] = cur_taxon; + if (store_position) { + if (pos.GetPopID() >= taxon_locations.size()) { + taxon_locations.resize(pos.GetPopID()+1); } + if (pos.GetIndex() >= taxon_locations[pos.GetPopID()].size()) { + taxon_locations[pos.GetPopID()].resize(pos.GetIndex()+1); + } + taxon_locations[pos.GetPopID()][pos.GetIndex()] = cur_taxon; } cur_taxon->AddOrg(); // Record the current organism in its taxon. total_depth += cur_taxon->GetDepth(); // Track the total depth (for averaging) if (to_be_removed) { - RemoveOrg(to_be_removed, removal_time); + RemoveOrg(to_be_removed); to_be_removed = nullptr; } @@ -1615,57 +1315,52 @@ namespace emp { } template - void Systematics::RemoveOrgAfterRepro(WorldPosition pos, int time) { + void Systematics::RemoveOrgAfterRepro(WorldPosition pos) { emp_assert(store_position, "Trying to remove org based on position from systematics manager that doesn't track it."); - if (pos.GetIndex() >= taxon_locations.size() || !taxon_locations[pos.GetIndex()]) { + if (pos.GetPopID() >= taxon_locations.size() || + pos.GetIndex() >= taxon_locations[pos.GetPopID()].size() || + !taxon_locations[pos.GetPopID()][pos.GetIndex()]) { // There's not actually a taxon here return; } - RemoveOrgAfterRepro(taxon_locations[pos.GetIndex()], time); - removal_pos = pos.GetIndex(); + RemoveOrgAfterRepro(taxon_locations[pos.GetPopID()][pos.GetIndex()]); + removal_pos = pos; } template - void Systematics::RemoveOrgAfterRepro(Ptr taxon, int time) { + void Systematics::RemoveOrgAfterRepro(Ptr taxon) { if (to_be_removed != nullptr) { - RemoveOrg(to_be_removed, removal_time); - taxon_locations[removal_pos] = nullptr; + RemoveOrg(to_be_removed); + taxon_locations[removal_pos.GetPopID()][removal_pos.GetIndex()] = nullptr; to_be_removed = nullptr; - removal_pos = -1; + removal_pos = {0, 0}; } to_be_removed = taxon; - // std::cout << "Setting remove time to " << time << std::endl; - removal_time = time; } - // Remove an instance of an organism; track when it's gone. + // Remove an instance of a taxon; track when it's gone. template - bool Systematics::RemoveOrg(WorldPosition pos, int time) { + bool Systematics::RemoveOrg(WorldPosition pos) { emp_assert(store_position, "Trying to remove org based on position from systematics manager that doesn't track it."); + emp_assert(pos.GetPopID() < taxon_locations.size(), "Invalid population requested for removal", pos.GetPopID(), taxon_locations.size()); + emp_assert(pos.GetIndex() < taxon_locations[pos.GetPopID()].size(), "Invalid position requested for removal", pos.GetIndex(), taxon_locations[pos.GetPopID()].size()); - if (pos.GetPopID() == 0) { - emp_assert(pos.GetIndex() < taxon_locations.size(), "Invalid position requested for removal", pos.GetIndex(), taxon_locations.size()); - bool active = false; - if (taxon_locations[pos.GetIndex()]) { - //TODO: Figure out how this can ever not be true - active = RemoveOrg(taxon_locations[pos.GetIndex()], time); - } - taxon_locations[pos.GetIndex()] = nullptr; - return active; - } else { - emp_assert(pos.GetIndex() < next_taxon_locations.size(), "Invalid position requested for removal", pos.GetIndex(), taxon_locations.size()); - bool active = RemoveOrg(next_taxon_locations[pos.GetIndex()], time); - next_taxon_locations[pos.GetIndex()] = nullptr; - return active; + bool active = false; + if (taxon_locations[pos.GetPopID()][pos.GetIndex()]) { + //TODO: Figure out how this can ever not be true + active = RemoveOrg(taxon_locations[pos.GetPopID()][pos.GetIndex()]); } + taxon_locations[pos.GetPopID()][pos.GetIndex()] = nullptr; + return active; } - // Remove an instance of an organism; track when it's gone. + // Remove an instance of a taxon; track when it's gone. + // @param taxon the taxon of which one instance is being removed template - bool Systematics::RemoveOrg(Ptr taxon, int time) { + bool Systematics::RemoveOrg(Ptr taxon) { emp_assert(taxon); // Update stats @@ -1674,12 +1369,58 @@ namespace emp { // emp_assert(Has(active_taxa, taxon)); const bool active = taxon->RemoveOrg(); - if (!active) MarkExtinct(taxon, time); + if (!active) MarkExtinct(taxon); return active; } - // Climb up a lineage... + // Remove all taxa that 1) went extinct before the specified update/time step, + // and 2) only have ancestors that went extinct before the specified update/time step. + // Warning: this function invalidates most measurements you could make about tree topology. + // It is useful in select situations where you need to store ancestors for some period of time, + // but cannot computationally afford to store all ancestors for your entire run. + template + void Systematics::RemoveBefore(int ud) { + + std::set> to_remove; + for (Ptr tax : ancestor_taxa) { + if (tax->GetDestructionTime() < ud && CanRemove(tax, ud)) { + to_remove.insert(tax); + } + } + + for (Ptr tax : to_remove) { + for (Ptr off : tax->GetOffspring()) { + off->NullifyParent(); + } + ancestor_taxa.erase(tax); + tax.Delete(); + } + + } + + #ifndef DOXYGEN_SHOULD_SKIP_THIS + /// Helper function for RemoveBefore + /// @returns true if a a taxon can safely be + /// removed by RemoveBefore + template + bool Systematics::CanRemove(Ptr t, int ud) { + if (!t) { + return false; + } + while (t) { + if (t->GetNumOrgs() > 0 || t->GetDestructionTime() >= ud) { + return false; + } + t = t->GetParent(); + } + return true; + } + #endif // #DOXYGEN_SHOULD_SKIP_THIS + + // ======= Functions for getting information from the systematics manager + + // @returns a pointer to the parent of a given taxon template Ptr::taxon_t> Systematics::Parent(Ptr taxon) const { emp_assert(taxon); @@ -1688,6 +1429,10 @@ namespace emp { } // Print details about the Systematics manager. + // First prints setting, followed by all active, ancestor, and outside + // taxa being stored. Format for taxa is + // [ id | number of orgs in this taxon, number of offspring taxa of this taxon | parent taxon] + // @param os output stream to print to template void Systematics::PrintStatus(std::ostream & os) const { os << "Systematics Status:\n"; @@ -1720,7 +1465,6 @@ namespace emp { os << std::endl; } - // Print whole lineage. template void Systematics::PrintLineage(Ptr taxon, std::ostream & os) const { os << "Lineage:\n"; @@ -1730,8 +1474,6 @@ namespace emp { } } - /// Take a snapshot of current state of taxon phylogeny. - /// WARNING: Current, this function assumes one parent taxon per-taxon. template void Systematics::Snapshot(const std::string & file_path) const { emp::DataFile file(file_path); @@ -1746,7 +1488,7 @@ namespace emp { // - ancestor_list: ancestor list for taxon std::function get_ancestor_list = [&cur_taxon]() -> std::string { - if (cur_taxon->GetParent() == nullptr) { return "[NONE]"; } + if (cur_taxon->GetParent() == nullptr) { return "[\"NONE\"]"; } return "[" + to_string(cur_taxon->GetParent()->GetID()) + "]"; }; file.AddFun(get_ancestor_list, "ancestor_list", "Ancestor list for this taxon."); @@ -1829,11 +1571,372 @@ namespace emp { } - // Calculate the genetic diversity of the population. + // ======= Measurements about the systematics manager + + // @returns the genetic diversity of the population. template double Systematics::CalcDiversity() const { return emp::Entropy(active_taxa, [](Ptr x){ return x->GetNumOrgs(); }, (double) org_count); } + + // @returns a pointer to the Most-Recent Common Ancestor for the population or null pointer if there isn't one + template + Ptr::taxon_t> Systematics::GetMRCA() const { + if (!mrca && num_roots == 1) { // Determine if we need to calculate the MRCA. + // First, find a candidate among the living taxa. Only taxa that have one offsrping + // can be on the line-of-descent to the MRCA, so anything else is a good start point. + // There must be at least one! Stop as soon as we find a candidate. + Ptr candidate(nullptr); + for (auto x : active_taxa) { + if (x->GetNumOff() != 1) { candidate = x; break; } + } + + // Now, trace the line of descent, updating the candidate as we go. + Ptr test_taxon = candidate->GetParent(); + while (test_taxon) { + emp_assert(test_taxon->GetNumOff() >= 1); + // If the test_taxon is dead, we only want to update candidate when we hit a new branch point + // If test_taxon is still alive, though, we always need to update it + if (test_taxon->GetNumOff() > 1 || test_taxon->GetNumOrgs() > 0) candidate = test_taxon; + test_taxon = test_taxon->GetParent(); + } + mrca = candidate; + } + return mrca; + } + + // @returns the depth of the Most-Recent Common Ancestor or -1 for none. + template + int Systematics::GetMRCADepth() const { + GetMRCA(); + if (mrca) return (int) mrca->GetDepth(); + return -1; + } + + template + Ptr::taxon_t> Systematics::GetSharedAncestor(Ptr t1, Ptr t2) const { + // Same taxon + if (t1 == t2) { + return t1; + } + + // If not same, we have to actually do work + emp::vector > lineage1 = GetLineageToMRCA(t1); + emp::vector > lineage2 = GetLineageToMRCA(t2); + + size_t l1 = lineage1.size() - 1; + size_t l2 = lineage2.size() - 1; + + emp_assert(lineage1[l1] == lineage2[l2], + "Both lineages should start with MRCA"); + + while (lineage1[l1] == lineage2[l2]) { + l1--; + l2--; + } + + return lineage1[l1+1]; + } + + #ifndef DOXYGEN_SHOULD_SKIP_THIS + // Helper for Colless function calculation + struct CollessStruct { + double total = 0; + emp::vector ns; + }; + + // Helper for Colless function calculation + template + CollessStruct Systematics::RecursiveCollessStep(Ptr curr) const { + CollessStruct result; + + while (curr->GetNumOff() == 1) { + curr = *(curr->GetOffspring().begin()); + } + + if (curr->GetNumOff() == 0) { + result.ns.push_back(0); // Node itself is calculated at level above + return result; + } + + for (Ptr off : curr->GetOffspring()) { + // std::cout << "Recursing on ID: " << off->GetID() << " Offspring: " << off->GetTotalOffspring() << std::endl; + + CollessStruct new_result = RecursiveCollessStep(off); + result.ns.push_back(Sum(new_result.ns) + log(off->GetOffspring().size() + exp(1))); + result.total += new_result.total; + } + + // std::cout << "Evaluating: " << curr->GetID() << std::endl; + + double med = Median(result.ns); + double sum_diffs = 0; + // std::cout << "Median: " << med << std::endl; + for (double n : result.ns) { + // std::cout << n << std::endl; + sum_diffs += std::abs(n-med); + } + // std::cout << "Sumdiffs: " << sum_diffs << " n: " << result.ns.size() << " average: " << sum_diffs/result.ns.size() << std::endl; + result.total += sum_diffs/result.ns.size(); + return result; + } + #endif // #DOXYGEN_SHOULD_SKIP_THIS + + template + emp::vector Systematics::GetPairwiseDistances(bool branch_only) const { + // The overarching approach here is to start with a bunch of pointers to all + // extant organisms (since that will include all leaves). Then we trace back up + // the tree, keeping track of distances. When things meet up, we calculate + // distances between the nodes on the sides that just met up. + + emp::vector dists; + + std::map< Ptr, emp::vector> > curr_pointers; + std::map< Ptr, emp::vector> > next_pointers; + + + for (Ptr tax : active_taxa) { + curr_pointers[tax] = emp::vector>({{0}}); + } + + // std::cout << "Starting curr_pointers size: " << curr_pointers.size() << std::endl; + + while (curr_pointers.size() > 0) { + for (auto & tax : curr_pointers) { + bool alive = tax.first->GetNumOrgs() > 0; + // std::cout << tax.first << " has " << to_string(tax.second) << "and is waiting for " << tax.first->GetNumOff() + int(alive) << std::endl; + if ( tax.second.size() < tax.first->GetNumOff() + int(alive)) { + if (Has(next_pointers, tax.first)) { + // In case an earlier iteration added this node to next_pointers + for (auto vec : tax.second) { + next_pointers[tax.first].push_back(vec); + } + } else { + next_pointers[tax.first] = curr_pointers[tax.first]; + } + continue; + } + emp_assert(tax.first->GetNumOff() + int(alive) == tax.second.size(), tax.first->GetNumOff(), alive, to_string(tax.second), tax.second.size()); + + // Okay, things should have just met up. Let's compute the distances + // between everything that just met. + + if (tax.second.size() > 1) { + + for (size_t i = 0; i < tax.second.size(); i++ ) { + for (size_t j = i+1; j < tax.second.size(); j++) { + for (int disti : tax.second[i]) { + for (int distj : tax.second[j]) { + // std::cout << "Adding " << disti << " and " << distj << std::endl; + dists.push_back(disti+distj); + } + } + } + } + } + // std::cout << "dists " << to_string(dists) << std::endl; + // Increment distances and stick them in new vector + emp::vector new_dist_vec; + for (auto & vec : tax.second) { + for (int el : vec) { + new_dist_vec.push_back(el+1); + } + } + + // std::cout << "new_dist_vec " << to_string(new_dist_vec) << std::endl; + + next_pointers.erase(tax.first); + + Ptr test_taxon = tax.first->GetParent(); + while (test_taxon && test_taxon->GetNumOff() == 1 && test_taxon->GetNumOrgs() == 0) { + if (!branch_only) { + for (size_t i = 0; i < new_dist_vec.size(); i++){ + new_dist_vec[i]++; + } + } + test_taxon = test_taxon->GetParent(); + } + + if (!test_taxon) { + continue; + } else if (!Has(next_pointers, test_taxon)) { + next_pointers[test_taxon] = emp::vector >({new_dist_vec}); + } else { + next_pointers[test_taxon].push_back(new_dist_vec); + } + } + curr_pointers = next_pointers; + next_pointers.clear(); + // std::cout << curr_pointers.size() << std::endl; + } + + if (dists.size() != (active_taxa.size()*(active_taxa.size()-1))/2) { + // The tree is not connected + // It's possible we should do something different here... + return dists; + } + + // std::cout << "Total: " << total << "Dists: " << dists.size() << std::endl; + + return dists; + + } + + template + double Systematics::GetEvolutionaryDistinctiveness(Ptr tax, double time) const { + + double depth = 0; // Length (in time units) of section we're currently exploring + double total = 0; // Count up scores for each section of tree + double divisor = tax->GetTotalOffspring() + 1; // Number of extant taxa this will split into (1 for current taxa, plus its offspring) + + // We're stopping when we hit MRCA, so we need to make sure it's been calculated. + GetMRCA(); + if (tax == mrca) { + return 0; + } + + // std::cout << "Initializing divisor to " << divisor << " Offspring: " << tax->GetTotalOffspring() << std::endl; + // std::cout << "MRCA ID: " << mrca->GetID() << " Tax ID: " << tax->GetID() << " time: " << time << " Orig: " << tax->GetOriginationTime() << std::endl; + + Ptr test_taxon = tax->GetParent(); + + emp_assert(time != -1 && "Invalid time - are you passing time to rg?", time); + emp_assert(time >= tax->GetOriginationTime() + && "GetEvolutionaryDistinctiveness received a time that is earlier than the taxon's origination time.", tax->GetOriginationTime(), time); + + while (test_taxon) { + + // emp_assert(test_taxon->GetOriginationTime() != -1 && + // "Invalid time - are you passing time to rg?", time); + + depth += time - test_taxon->GetOriginationTime(); + // std::cout << "Tax: " << test_taxon->GetID() << " depth: " << depth << " time: " << time << " Orig: " << test_taxon->GetOriginationTime() << " divisor: " << divisor << std::endl; + time = test_taxon->GetOriginationTime(); + if (test_taxon == mrca || !test_taxon) { + // Stop when everything has converged or when we hit the root. + // std::cout << (int)(test_taxon == mrca) << " depth: " << depth << " divisor: " << divisor << std::endl; + total += depth/divisor; + return total; + } else if (test_taxon->GetNumOrgs() > 0) { + // If this taxon is still alive we need to update the divisor + // std::cout << "Alive point" << " depth: " << depth << " divisor: " << divisor << std::endl; + total += depth/divisor; + depth = 0; + divisor = test_taxon->GetTotalOffspring() + 1; + } else if (test_taxon->GetNumOff() > 1) { + // This is a branch point. We need to add the things on the other branch to the divisor.. + // std::cout << "Branch point" << " depth: " << depth << " divisor: " << divisor << std::endl; + total += depth/divisor; + depth = 0; + divisor = test_taxon->GetTotalOffspring(); + } + + test_taxon = test_taxon->GetParent(); + } + + return -1; + } + + template + int Systematics::GetBranchesToRoot(Ptr tax) const { + GetMRCA(); + + int depth = 0; + Ptr test_taxon = tax->GetParent(); + while (test_taxon) { + if (test_taxon == mrca || !test_taxon) { + return depth; + } else if (test_taxon->GetNumOff() > 1) { + depth++; + } + test_taxon = test_taxon->GetParent(); + } + return depth; + } + + template + int Systematics::GetDistanceToRoot(Ptr tax) const { + // Now, trace the line of descent, updating the candidate as we go. + GetMRCA(); + + int depth = 0; + Ptr test_taxon = tax->GetParent(); + while (test_taxon) { + depth++; + if (test_taxon == mrca || !test_taxon) { + return depth; + } + test_taxon = test_taxon->GetParent(); + } + return depth; + } + + template + std::set::taxon_t>> Systematics::GetCanopyExtantRoots(int time_point) const { + // NOTE: This could be made faster by doing something similar to the pairwise distance + // function + using taxon_t = Systematics::taxon_t; + std::set< Ptr> result; + // std::cout << "starting " << time_point << std::endl; + for (Ptr tax : active_taxa) { + // std::cout << tax->GetInfo() << std::endl; + while (tax) { + // std::cout << tax->GetInfo() << " " << tax->GetOriginationTime() << " " << tax->GetDestructionTime() << std::endl; + if (tax->GetOriginationTime() <= time_point && tax->GetDestructionTime() > time_point ) { + result.insert(tax); + // std::cout << "inserting " << tax->GetInfo() << std::endl; + break; + } + tax = tax->GetParent(); + } + } + + return result; + + } + + template + int Systematics::GetPhylogeneticDiversityNormalize(int generation, std::string filename) const { + int gen_value = ((generation / 10) - 1); //indexes from 0, 100 generations would correspond to the 10th line in the csv + bool percent_found = false; + int phylogenetic_diversity = ancestor_taxa.size() + active_taxa.size() - 1; + + if(filename == ""){ + //std::cout << "Phylogenetic Diversity is " << phylogenetic_diversity << std::endl; + return phylogenetic_diversity; + } else{ + + emp::File generation_percentiles(filename); //opens file + emp::vector< emp::vector >percentile_data = generation_percentiles.ToData(','); //turns file contents into vector + + for(int j = 0; j <= percentile_data[gen_value].size() - 2; j++){ //searches through vector for slot where phylo diversity fits + + if((percentile_data[gen_value][j] <= phylogenetic_diversity) && (percentile_data[gen_value][j + 1] > phylogenetic_diversity)){ + // std::cout << "phylogenetic diversity is in between: " << percentile_data[gen_value][j] << "and " << percentile_data[gen_value][j+1] << std::endl; + // std::cout << "The phylogenetic diversity value " << phylogenetic_diversity << " is in the " << j << " percentile, in the " << ((gen_value + 1)* 10) << " generation" << std::endl; + return j; + } + } + } + return 100; + } + + template + int Systematics::GetMaxDepth() { + if (max_depth != -1) { + return max_depth; + } + + for (auto tax : active_taxa) { + int depth = tax->GetDepth(); + if (depth > max_depth) { + max_depth = depth; + } + } + return max_depth; + } + + } #endif // #ifndef EMP_EVOLVE_SYSTEMATICS_HPP_INCLUDE diff --git a/include/emp/Evolve/SystematicsAnalysis.hpp b/include/emp/Evolve/SystematicsAnalysis.hpp index 4574207135..6d0d71e16c 100644 --- a/include/emp/Evolve/SystematicsAnalysis.hpp +++ b/include/emp/Evolve/SystematicsAnalysis.hpp @@ -19,24 +19,27 @@ namespace emp { + /// @returns the taxon with the highest fitness out of any active taxon + /// in the given systematics manager. + /// @param s the systematics manager to search in. Must have more than 0 active taxa. template Ptr FindDominant(systematics_t & s) { - double best = -999999; - Ptr best_tax = nullptr; + emp_assert(s.GetNumActive() > 0 && "Trying to call FindDominant on empty population"); + double best = (*(s.GetActive().begin()))->GetData().GetFitness(); + Ptr best_tax = (*(s.GetActive().begin())); for (Ptr tax : s.GetActive()) { double f = tax->GetData().GetFitness(); - if (f > best) { - best = f; - best_tax = tax; + if (f > best) { + best = f; + best_tax = tax; + } } - } - return best_tax; + return best_tax; } - /// Returns the total number of times a mutation of type @param type - /// that along @param taxon 's lineage. (Different from CountMuts in - /// that CountMuts sums them whereas CountMutSteps would count two - /// simultaneous mutations of the same type as one event) + /// Returns the total number of ancestor taxa in \c taxon 's lineage. + /// Requires that taxon is a member of a systematics manager that + /// has ancestor storing turned on template int LineageLength(Ptr taxon) { int count = 0; @@ -49,10 +52,16 @@ namespace emp { return count; } - /// Returns the total number of times a mutation of type @param type - /// that along @param taxon 's lineage. (Different from CountMuts in + /// Returns the total number of times a mutation of type \c type + /// occurred along \c taxon 's lineage. (Different from CountMuts in /// that CountMuts sums them whereas CountMutSteps would count two /// simultaneous mutations of the same type as one event) + /// @param type string corresponding to a type of mutation. + /// Must be in the mut_counts dictionary (i.e. the dictionary + /// passed in when \ref mut_landscape_info::RecordMutation was called) + /// @param taxon a pointer to a taxon to count mutation steps for. + /// Must have a DATA_TYPE that supports mutation tracking + /// (e.g. mut_landscape_info) template int CountMutSteps(Ptr taxon, std::string type="substitution") { int count = 0; diff --git a/include/emp/Evolve/World.hpp b/include/emp/Evolve/World.hpp index 7e869bdee8..a4d609f086 100644 --- a/include/emp/Evolve/World.hpp +++ b/include/emp/Evolve/World.hpp @@ -14,8 +14,6 @@ * whether or not they also affect injected organisms. (Right now they always do!!) * @todo We should Specialize World so that ANOTHER world can be used as an ORG, with proper * delegation to facilitate demes, pools, islands, etc. - * @todo We should be able to have any number of systematics managers, based on various type_trait - * information a that we want to track. * @todo Add a signal for DoBirth() for when a birth fails. * @todo Add a signal for population Reset() (and possibly Clear?) * @todo Add a feature to maintain population sorted by each phenotypic trait. This will allow @@ -972,7 +970,7 @@ namespace emp { // Track the new systematics info for (Ptr > s : systematics) { - s->AddOrg(*new_org, pos, (int) update); + s->AddOrg(*new_org, pos); } SetupOrg(*new_org, pos, *random_ptr); @@ -996,7 +994,7 @@ namespace emp { } for (Ptr > s : systematics) { - s->RemoveOrgAfterRepro(pos, update); // Notify systematics about organism removal + s->RemoveOrgAfterRepro(pos); // Notify systematics about organism removal } } @@ -1489,6 +1487,13 @@ namespace emp { pop.resize(0); std::swap(pops[0], pops[1]); // Move next pop into place. + // Tell systematics manager to swap next population and population + // Needs to happen here so that you can refer to systematics in + // OnPlacement functions + for (Ptr> s : systematics) { + s->Update(); + } + // Update the active population. num_orgs = 0; for (size_t i = 0; i < pop.size(); i++) { @@ -1498,12 +1503,7 @@ namespace emp { } } - // 3. Handle systematics and any data files that need to be printed this update. - - // Tell systematics manager to swap next population and population - for (Ptr> s : systematics) { - s->Update(); - } + // 3. Handle any data files that need to be printed this update. for (auto file : files) file->Update(update); diff --git a/include/emp/base/Ptr.hpp b/include/emp/base/Ptr.hpp index eaa9633cd9..243ad88dc1 100644 --- a/include/emp/base/Ptr.hpp +++ b/include/emp/base/Ptr.hpp @@ -16,9 +16,9 @@ * * If you trip an assert, you can re-do the run a track a specific pointer by defining * EMP_ABORT_PTR_NEW or EMP_ABORT_PTR_DELETE to the ID of the pointer in question. - * + * * For example: -DEMP_ABORT_PTR_NEW=1691 - * + * * This will allow you to track the pointer more easily in a debugger. * * @todo Track information about emp::vector and emp::array objects to make sure we don't diff --git a/include/emp/base/_emscripten_error_trigger.hpp b/include/emp/base/_emscripten_error_trigger.hpp index cc32c45873..9d6ce0a0f4 100644 --- a/include/emp/base/_emscripten_error_trigger.hpp +++ b/include/emp/base/_emscripten_error_trigger.hpp @@ -12,7 +12,7 @@ #ifndef EMP_BASE__EMSCRIPTEN_ERROR_TRIGGER_HPP_INCLUDE #define EMP_BASE__EMSCRIPTEN_ERROR_TRIGGER_HPP_INCLUDE - +#include #include namespace emp { diff --git a/include/emp/compiler/DFA.hpp b/include/emp/compiler/DFA.hpp index c38936935e..fd8f6e852c 100644 --- a/include/emp/compiler/DFA.hpp +++ b/include/emp/compiler/DFA.hpp @@ -11,6 +11,7 @@ #ifndef EMP_COMPILER_DFA_HPP_INCLUDE #define EMP_COMPILER_DFA_HPP_INCLUDE +#include #include #include "../base/array.hpp" diff --git a/include/emp/datastructs/IndexMap.hpp b/include/emp/datastructs/IndexMap.hpp index ce82fe804c..853e8f5db9 100644 --- a/include/emp/datastructs/IndexMap.hpp +++ b/include/emp/datastructs/IndexMap.hpp @@ -6,13 +6,13 @@ * @file IndexMap.hpp * @brief Container that weights items and returns ID for a given weight position. * @note Status: BETA - * + * * An IndexMap is a container where each item has a specified weight (specified as a double). * The total weight of the container determines the max index point. When indexing into the * container, each item is represented by a range of values equal to it's weight. Randomly * indexing into the container will provide either item with a probability proportional to its * weight. - * + * * In this regular IndexMap, all items are kept in order (so the map starts at 0, then 1, then * 2, etc.) If order is not required, UnorderedIndexMap is slightly faster. * diff --git a/include/emp/functional/AnyFunction.hpp b/include/emp/functional/AnyFunction.hpp index 63ac2d5ba6..42ebffb489 100644 --- a/include/emp/functional/AnyFunction.hpp +++ b/include/emp/functional/AnyFunction.hpp @@ -50,6 +50,8 @@ namespace emp { /// Determine if this BaseFunction can be converted into a derived emp::Function template bool ConvertOK(); + + virtual emp::Ptr Clone() = 0; }; @@ -80,6 +82,10 @@ namespace emp { /// Get the std::function to be called. const fun_t & GetFunction() const { return fun; } + + emp::Ptr Clone() override{ + return emp::NewPtr>(fun); + } }; @@ -88,6 +94,7 @@ namespace emp { private: emp::Ptr fun = nullptr; + private: /// Helper to build a proper derived function. template auto MakePtr( T in_fun ) { @@ -101,6 +108,29 @@ namespace emp { // By default, build an empty function. AnyFunction() { ; } + AnyFunction(const AnyFunction& other){ // copy constructor + fun = other.CloneFunc(); + } + + AnyFunction(AnyFunction&& other) noexcept{ // move constructor + fun = other.CloneFunc(); + other.fun.Delete(); + other.fun = nullptr; + } + + AnyFunction& operator=(const AnyFunction& other){ // copy assignment + Clear(); + fun = other.CloneFunc(); + return *this; + } + + AnyFunction& operator=(AnyFunction&& other) noexcept{ // move assignment + Clear(); + fun = other.CloneFunc(); + other.Clear(); + return *this; + } + /// If an argument is provided, set the function. template AnyFunction(T in_fun) { @@ -111,6 +141,10 @@ namespace emp { void Clear() { if (fun) fun.Delete(); fun = nullptr; } size_t NumArgs() const { return fun ? fun->NumArgs() : 0; } + emp::Ptr CloneFunc() const{ + if(fun == nullptr) return nullptr; + return fun->Clone(); + } operator bool() { return (bool) fun; } diff --git a/include/emp/hardware/AvidaCPU_InstLib.hpp b/include/emp/hardware/AvidaCPU_InstLib.hpp index ca00e09969..e5c270c226 100644 --- a/include/emp/hardware/AvidaCPU_InstLib.hpp +++ b/include/emp/hardware/AvidaCPU_InstLib.hpp @@ -15,10 +15,8 @@ #include "InstLib.hpp" namespace emp { - /// AvidaCPU_InstLib is a pure-virtual class that defines a series of instructions that /// can be used with AvidaCPU_Base or any of its derived classes. - template struct AvidaCPU_InstLib : public InstLib { using hardware_t = HARDWARE_T; diff --git a/include/emp/hardware/AvidaGP.hpp b/include/emp/hardware/AvidaGP.hpp index 2ced1ebf7b..ec1eab47f9 100644 --- a/include/emp/hardware/AvidaGP.hpp +++ b/include/emp/hardware/AvidaGP.hpp @@ -56,7 +56,7 @@ namespace emp { using stack_t = emp::vector; using arg_set_t = emp::array; - struct Instruction { + struct Instruction : public inst_lib_t::InstructionBase { size_t id; arg_set_t args; @@ -78,6 +78,10 @@ namespace emp { void Set(size_t _id, size_t _a0=0, size_t _a1=0, size_t _a2=0) { id = _id; args[0] = _a0; args[1] = _a1; args[2] = _a2; } + + size_t GetIndex() const override{ + return id; + } }; struct ScopeInfo { diff --git a/include/emp/hardware/EventDrivenGP.hpp b/include/emp/hardware/EventDrivenGP.hpp index 1bb670e1d0..8d01889938 100644 --- a/include/emp/hardware/EventDrivenGP.hpp +++ b/include/emp/hardware/EventDrivenGP.hpp @@ -363,6 +363,7 @@ namespace emp { CEREAL_NVP(id) ); } + size_t GetIndex() const{ return id; } }; diff --git a/include/emp/hardware/InstLib.hpp b/include/emp/hardware/InstLib.hpp index fb4f40cedf..072e14d019 100644 --- a/include/emp/hardware/InstLib.hpp +++ b/include/emp/hardware/InstLib.hpp @@ -4,7 +4,7 @@ * @date 2017-2021. * * @file InstLib.hpp - * @brief This file maintains information about instructions availabel in virtual hardware. + * @brief This file maintains information about instructions available in virtual hardware. */ #ifndef EMP_HARDWARE_INSTLIB_HPP_INCLUDE @@ -22,6 +22,7 @@ namespace emp { + /// ScopeType is used for scopes that we need to do something special at the end. /// Eg: LOOP needs to go back to beginning of loop; FUNCTION needs to return to call. enum class ScopeType { NONE=0, ROOT, BASIC, LOOP, FUNCTION }; @@ -40,7 +41,14 @@ namespace emp { using fun_t = std::function; using inst_properties_t = std::unordered_set; + struct InstructionBase{ + virtual ~InstructionBase() {;} + virtual size_t GetIndex() const = 0; + }; + struct InstDef { + size_t index; + size_t id; std::string name; ///< Name of this instruction. fun_t fun_call; ///< Function to call when executing. size_t num_args; ///< Number of args needed by function. @@ -50,11 +58,11 @@ namespace emp { inst_properties_t properties; ///< Are there any generic properties associated with this inst def? char symbol; ///< Unique symbol for this instruction. - InstDef(const std::string & _n, fun_t _fun, size_t _args, const std::string & _d, - ScopeType _s_type, size_t _s_arg, + InstDef(size_t _idx, size_t _id, const std::string & _n, fun_t _fun, size_t _args, + const std::string & _d, ScopeType _s_type, size_t _s_arg, const inst_properties_t & _properties = inst_properties_t(), char _sym='?') - : name(_n), fun_call(_fun), num_args(_args), desc(_d) + : index(_idx), id(_id), name(_n), fun_call(_fun), num_args(_args), desc(_d) , scope_type(_s_type), scope_arg(_s_arg), properties(_properties), symbol(_sym) { ; } InstDef(const InstDef &) = default; }; @@ -63,6 +71,7 @@ namespace emp { emp::vector inst_lib; ///< Full definitions for instructions. emp::vector inst_funs; ///< Map of instruction IDs to their functions. std::map name_map; ///< How do names link to instructions? + std::map id_map; ///< How do identifiers link to instructions? std::map arg_map; ///< How are different arguments named? /// Symbols to use when representing individual instructions (80). @@ -71,39 +80,43 @@ namespace emp { emp::array symbol_map; ///< Map of symbols back to instruction IDs. public: - InstLib() : inst_lib(), inst_funs(), name_map(), arg_map() { ; } ///< Default Constructor + InstLib() : inst_lib(), inst_funs(), name_map(), id_map(), arg_map() { ; } ///< Default Constructor InstLib(const InstLib &) = delete; ///< Copy Constructor InstLib(InstLib &&) = delete; ///< Move Constructor - ~InstLib() { ; } ///< Destructor + virtual ~InstLib() { ; } ///< Destructor - InstLib & operator=(const InstLib &) = default; ///< Copy Operator - InstLib & operator=(InstLib &&) = default; ///< Move Operator + InstLib & operator=(const InstLib &) = default; ///< Copy Operator + InstLib & operator=(InstLib &&) = default; ///< Move Operator /// Return the name associated with the specified instruction ID. - const std::string & GetName(size_t id) const { return inst_lib[id].name; } + const std::string & GetName(size_t idx) const { return inst_lib[idx].name; } /// Return the function associated with the specified instruction ID. - const fun_t & GetFunction(size_t id) const { return inst_lib[id].fun_call; } + const fun_t & GetFunction(size_t idx) const { return inst_lib[idx].fun_call; } /// Return the number of arguments expected for the specified instruction ID. - size_t GetNumArgs(size_t id) const { return inst_lib[id].num_args; } + size_t GetNumArgs(size_t idx) const { return inst_lib[idx].num_args; } - /// Return the provided description for the provided instruction ID. - const std::string & GetDesc(size_t id) const { return inst_lib[id].desc; } + /// Return the provided description for the providxed instruction ID. + const std::string & GetDesc(size_t idx) const { return inst_lib[idx].desc; } /// What type of scope does this instruction state? ScopeType::NONE is default. - ScopeType GetScopeType(size_t id) const { return inst_lib[id].scope_type; } + ScopeType GetScopeType(size_t idx) const { return inst_lib[idx].scope_type; } - /// If this instruction alters scope, identify which argument does so. - size_t GetScopeArg(size_t id) const { return inst_lib[id].scope_arg; } + /// If this instruction alters scope, idxentify which argument does so. + size_t GetScopeArg(size_t idx) const { return inst_lib[idx].scope_arg; } - /// Return the set of properties for the provided instruction ID. - const inst_properties_t & GetProperties(size_t id) const { return inst_lib[id].properties; } + /// Return the set of properties for the providxed instruction ID. + const inst_properties_t & GetProperties(size_t idx) const { + return inst_lib[idx].properties; + } - char GetSymbol(size_t id) const { return inst_lib[id].symbol; } + char GetSymbol(size_t idx) const { return inst_lib[idx].symbol; } /// Does the given instruction ID have the given property value? - bool HasProperty(size_t id, std::string property) const { return inst_lib[id].properties.count(property); } + bool HasProperty(size_t idx, std::string property) const { + return inst_lib[idx].properties.count(property); + } /// Get the number of instructions in this set. size_t GetSize() const { return inst_lib.size(); } @@ -112,10 +125,13 @@ namespace emp { return Has(name_map, name); } + size_t GetID(const size_t idx) const { + return inst_lib[idx].id; + } /// Return the ID of the instruction that has the specified name. size_t GetID(const std::string & name) const { emp_assert(Has(name_map, name), name); - return Find(name_map, name, (size_t) -1); + return inst_lib[Find(name_map, name, (size_t) -1)].id; } /// Return the ID of the instruction associated with the specified symbol. @@ -124,6 +140,17 @@ namespace emp { return symbol_map[(size_t) symbol]; } + /// Return the ID of the instruction that has the specified name. + size_t GetIndex(const std::string & name) const { + emp_assert(Has(name_map, name), name); + return Find(name_map, name, (size_t) -1); + } + /// Return the ID of the instruction that has the specified name. + size_t GetIndex(const size_t id) const { + emp_assert(Has(id_map, id), id); + return Find(id_map, id, (size_t) -1); + } + /// Return the argument value associated with the provided keyword. arg_t GetArg(const std::string & name) { emp_assert(Has(arg_map, name)); @@ -144,14 +171,20 @@ namespace emp { const std::string & desc="", ScopeType scope_type=ScopeType::NONE, size_t scope_arg=(size_t) -1, - const inst_properties_t & inst_properties=inst_properties_t()) + const inst_properties_t & inst_properties=inst_properties_t(), + int _id = -1) { - const size_t id = inst_lib.size(); + const size_t idx = inst_lib.size(); + const size_t id = (_id >= 0) ? _id : inst_lib.size(); + emp_assert(!Has(id_map, id), "ID is already in use!", id); const char symbol = (id < symbol_defaults.size()) ? symbol_defaults[id] : '+'; - inst_lib.emplace_back(name, fun_call, num_args, desc, scope_type, scope_arg, inst_properties, symbol); + inst_lib.emplace_back(idx, id, name, fun_call, num_args, desc, scope_type, scope_arg, + inst_properties, symbol); inst_funs.emplace_back(fun_call); - name_map[name] = id; + name_map[name] = idx; + id_map[id] = idx; symbol_map[(size_t) symbol] = id; + std::cout << "Registered instruction: " << name << " index: " << idx << "; id: " << id << "; symbol: " << symbol << std::endl; } /// Specify a keyword and arg value. @@ -161,18 +194,17 @@ namespace emp { } /// Process a specified instruction in the provided hardware. - void ProcessInst(hardware_t & hw, const inst_t & inst) const { - inst_funs[inst.id](hw, inst); + virtual void ProcessInst(hardware_t & hw, const inst_t & inst) const { + inst_funs[inst.GetIndex()](hw, inst); } /// Process a specified instruction on hardware that can be converted to the correct type. template void ProcessInst(emp::Ptr hw, const inst_t & inst) const { emp_assert( dynamic_cast(hw.Raw()) ); - inst_funs[inst.id](*(hw.template Cast()), inst); + inst_funs[inst.GetIndex()](*(hw.template Cast()), inst); } - /// Write out a full genome to the provided ostream. void WriteGenome(const genome_t & genome, std::ostream & os=std::cout) const { for (const inst_t & inst : genome) { @@ -188,9 +220,10 @@ namespace emp { /// Read the instruction in the provided info and append it to the provided genome. void ReadInst(genome_t & genome, std::string info) const { std::string name = emp::string_pop_word(info); - size_t id = GetID(name); - genome.emplace_back(id); - size_t num_args = GetNumArgs(id); + size_t idx = GetIndex(name); + size_t id = GetID(idx); + genome.emplace_back(idx, id); + size_t num_args = GetNumArgs(idx); for (size_t i = 0; i < num_args; i++) { std::string arg_name = emp::string_pop_word(info); // @CAO: Should check to make sure arg name is real. diff --git a/include/emp/hardware/VirtualCPU.hpp b/include/emp/hardware/VirtualCPU.hpp new file mode 100644 index 0000000000..0b69aaa9cb --- /dev/null +++ b/include/emp/hardware/VirtualCPU.hpp @@ -0,0 +1,794 @@ +/** + * @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 2021-2022. + * + * @file VirtualCPU.hpp + * @brief A simple virtual CPU styled after the original and extended Avidian architectures. + * + * @TODO + * - Expanded heads? + * - expanded_nop_args useful? + * - Consider changing default return value for search functions + * - Consider switching to (or adding an optional mode) where nops are only curated + * as-needed instead of all at once + * + */ + +#ifndef EMP_HARDWARE_VIRTUALCPU_HPP_INCLUDE +#define EMP_HARDWARE_VIRTUALCPU_HPP_INCLUDE + +#include +#include +#include + +#include "../base/array.hpp" +#include "../base/Ptr.hpp" +#include "../base/unordered_map.hpp" +#include "../base/vector.hpp" +#include "../datastructs/map_utils.hpp" +#include "../datastructs/vector_utils.hpp" +#include "../io/File.hpp" +#include "../math/Random.hpp" +#include "../tools/string_utils.hpp" + +#include "Genome.hpp" +#include "VirtualCPU_InstLib.hpp" + +namespace emp{ + /// \brief A simple virtual CPU styled after those seen in Avida + /// + /// This class represents a single virtual CPU following a genome of assembly-level + /// instructions. + /// By defaullt, eaach CPU features four heads, two stacks, multiple registers, and + /// a circular genome. + /// Both the original and extended architectures are supported. + template + class VirtualCPU{ + public: + static constexpr size_t NUM_STACKS = 2; ///< Number of stacks in this CPU (currently 2) + static constexpr size_t MAX_NOPS = 23; ///< Maximum number of nop instructions supported + struct Instruction; + + using derived_t = DERIVED; + using data_t = uint32_t; + using inst_t = Instruction; + using inst_lib_t = VirtualCPU_InstLib; + using genome_t = Genome; + using nop_vec_t = emp::vector; + using stack_t = emp::vector; + + /// \brief Representation of a single instruction in the CPU's genome + /// + /// Only contains the necessary information for which instruction is being represented + /// as well as any data it needs in the genome. + /// Does NOT contain the actual logic of the instruction, nor the name. + /// These are handled by the instruction library itself. + struct Instruction : public inst_lib_t::InstructionBase { + size_t idx; /// Index of the instruction in the instruction library + size_t id; /// Identifier for the instruction that gives the user + /// flexibility over the instruction (e.g., what symbol + /// it should use in a string representation) + emp::vector nop_vec; /// Representation of the contiguous sequence of NOP + /// instructions following this instruction in the genome + bool has_been_executed = false; /// Has this instruction been executed? + bool has_been_copied = false; // Has this instruction been copied to an offspring? + + Instruction() = delete; + Instruction(size_t _idx, size_t _id=0, emp::vector _nop_vec = {}) + : idx(_idx), id(_id), nop_vec(_nop_vec) { ; } + Instruction(const Instruction &) = default; + Instruction(Instruction &&) = default; + + Instruction & operator=(const Instruction &) = default; + Instruction & operator=(Instruction &&) = default; + bool operator<(const Instruction & in) const { + return id < in.id; + } + bool operator==(const Instruction & in) const { return id == in.id; } + bool operator!=(const Instruction & in) const { return !(*this == in); } + bool operator>(const Instruction & in) const { return in < *this; } + bool operator>=(const Instruction & in) const { return !(*this < in); } + bool operator<=(const Instruction & in) const { return !(in < *this); } + + void Set(size_t _idx, size_t _id, emp::vector _nop_vec = {}) + { idx = _idx; id = _id; nop_vec=_nop_vec;} + + size_t GetIndex() const override { return idx; } + }; + + + protected: + size_t num_regs = 0; ///< Number of registers found in this CPU + size_t num_nops = 0; ///< Number of NOP instructions found in this CPU's library + + public: + //////// FLAGS + bool are_nops_counted = false; ///< Flag detailing if the number of NOP instructions + ///< in the CPU's library have been counted + bool are_regs_expanded = false; ///< Flag signaling if the number of registers have + ///< been expanded to accomodate the number of NOP + ///< instructions in the library + bool nops_need_curated = true; ///< Flag signaling that NOP instructions need curated + bool expanded_nop_args = false; ///< Flag signaling that CPU is used the expanded + + //////// CPU COMPONENTS + emp::vector regs; ///< Vector of registers + std::unordered_map inputs; ///< Map of all available inputs + ///< (position -> value) + std::unordered_map outputs; ///< Map of all outputs (position -> value) + emp::array stacks; ///< Array of stacks for this CPU + size_t inst_ptr; ///< Instruction pointer, signifies next + ///< instruction to be executed + size_t flow_head; ///< Flow head, used for moving heads and + ///< values + size_t read_head; ///< Read head, signals what instruction to + ///< copy next + size_t write_head; ///< Write head, signals where to copy next + ///< instruction + //////// HELPER CONSTRUCTS + emp::unordered_map nop_id_map;/**< NOP inst id -> Nop index + (e.g., NopA -> 0, NopB -> 1, + NopE -> 5) */ + emp::vector label_idx_vec; ///< Vector of LABEL instructions indices in genome + //////// GENOME + genome_t genome; ///< Preserved copy of genome from organism creation/birth + ///< that should not change in any way + genome_t genome_working; ///< Working copy of genome that can mutate, resize, and change + //////// BOOKKEEPING + size_t active_stack_idx = 0; ///< Index of CPU's active stack + emp::vector copied_inst_id_vec; /**< Vector of instructions that have been + copied */ + size_t num_insts_executed = 0; ///< Number of instructions that have been executed + + + //////// CONSTRUCTORS / DESTRUCTOR + /// Create a new VirtualCPU with the same genome (and thus instruction library) + VirtualCPU(const genome_t & in_genome) + : regs(), inputs(), outputs(), + inst_ptr(0), flow_head(0), read_head(0), write_head(0), + genome(in_genome), genome_working(in_genome){ + Initialize(); + ResetHardware(); + } + /// Create a default VirtualCPU (no genome sequence, default instruction set) + VirtualCPU() : + VirtualCPU(genome_t(inst_lib_t::DefaultInstLib())) { + Initialize(); + ResetHardware(); + } + /// Create a perfect copy of passed VirtualCPU + VirtualCPU(const VirtualCPU &) = default; + /// Default move constructor + VirtualCPU(VirtualCPU &&) = default; + /// Default destructor + virtual ~VirtualCPU() { ; } + + + //////// GETTERS + /// Return size of original genome + size_t GetGenomeSize() const { return genome.GetSize(); } + /// Return size of working genome + size_t GetWorkingGenomeSize() const { return genome_working.GetSize(); } + /// Return the number of registers in the CPU + size_t GetNumRegs() const { return num_regs; } + /// Return the number of NOP instructions found in the CPU's instruction library + size_t GetNumNops() const { return num_nops; } + /// Return the outputs of the CPU + const std::unordered_map & GetOutputs() const { return outputs; } + /// Return a pointer to the CPU's instruction library + Ptr GetInstLib() const { return genome.GetInstLib(); } + /// Return the number of instructions that have been executed + size_t GetNumInstsExecuted() const{ + size_t count = 0; + for(auto inst : genome_working){ + if(inst.has_been_executed) count++; + } + return count; + } + /// Return the number of instructions that have been copied + size_t GetNumInstsCopied() const{ + size_t count = 0; + for(auto inst : genome_working){ + if(inst.has_been_copied) count++; + } + return count; + } + + + + //////// SETTERS + /// Copies passed vector into input map + void SetInputs(const emp::vector & vals) { + inputs = emp::ToUMap(vals); + } + + + //////// GENOME & INSTRUCTION MANIPULATION + /// Load instructions from input stream + bool Load(std::istream & input) { + ClearGenome(); + File file(input); + file.RemoveComments("//"); // Remove all C++ style comments + file.RemoveComments("#"); // Remove all bash/Python/R style comments + file.CompressWhitespace(); // Trim down remaining whitespace. + file.RemoveEmpty(); + if(file.GetNumLines() == 0){ + emp_error("Error! VirtualCPU trying to load a genome from an empty stream!"); + } + file.Apply( [this](std::string & info){ PushInst(info); } ); + nops_need_curated = true; + return true; + } + /// Load instructions from file + bool Load(const std::string & filename) { + std::ifstream is(filename); + if(is.is_open()){ + return Load(is); + } + emp_error("Error! VirtualCPU genome file is either empty or missing: ", filename); + return false; + } + /// Add a new instruction to the end of the genome, by index in the instruction library + void PushInst(size_t idx){ + const size_t id = GetInstLib()->GetID(idx); + genome.emplace_back(idx, id); + genome_working.emplace_back(idx, id); + nops_need_curated = true; + } + /// Add a new instruction to the end of the genome, by name + void PushInst(const std::string & name) { + PushInst(GetInstLib()->GetIndex(name)); + nops_need_curated = true; + } + /// Add a specified new instruction to the end of the genome + void PushInst(const inst_t & inst) { + genome.emplace_back(inst); + genome_working.emplace_back(inst); + nops_need_curated = true; + } + /// Add multiple copies of a specified instruction to the end of the genome + void PushInst(const inst_t & inst, size_t count) { + genome.reserve(genome.size() + count); + for (size_t i = 0; i < count; i++) genome.emplace_back(inst); + genome_working.reserve(genome.size() + count); + for (size_t i = 0; i < count; i++) genome_working.emplace_back(inst); + nops_need_curated = true; + } + /// Return the first instruction in the instruction library + inst_t GetDefaultInst() const{ + return inst_t(GetInstLib()->GetIndex(0), 0); + } + /// Add one or more default instructions to the end of the genome + void PushDefaultInst(size_t count=1) { + PushInst( inst_t(GetInstLib()->GetIndex(0), 0), count ); + nops_need_curated = true; + } + /// Return a random instruction from the instruction library + inst_t GetRandomInst(Random & rand) { + size_t id = rand.GetUInt(GetInstLib()->GetSize()); + size_t idx = GetInstLib()->GetIndex(id); + //size_t idx = rand.GetUInt(GetInstLib()->GetSize()); + //size_t id = GetInstLib()->GetID(idx); + return inst_t(idx, id); + } + /// Overwrite the instruction at the given genome index with passed instruction + void SetInst(size_t pos, const inst_t & inst) { + genome[pos] = inst; + genome_working[pos] = inst; + nops_need_curated = true; + } + /// Overwrite the instruction at the given genome index with a random instruction + void RandomizeInst(size_t pos, Random & rand) { + SetInst(pos, GetRandomInst(rand) ); + nops_need_curated = true; + } + /// Add a random instruction from the instruction library to the end of the genome + void PushRandomInst(Random & random, const size_t count=1) { + for (size_t i = 0; i < count; i++) { + PushInst(GetRandomInst(random)); + } + nops_need_curated = true; + } + /// Insert the given instruction at the specified genome position + void InsertInst(const inst_t& inst, const size_t idx){ + genome.emplace(genome.begin() + idx, inst); + genome_working.emplace(genome_working.begin() + idx, inst); + nops_need_curated = true; + } + /// Inserts a random instruction at the given genome position + void InsertRandomInst(const size_t idx, emp::Random& random){ + InsertInst(GetRandomInst(random), idx); + } + /// Remove the instruction at the specified genome position + void RemoveInst(const size_t idx){ + genome.erase(genome.begin() + idx); + genome_working.erase(genome_working.begin() + idx); + nops_need_curated = true; + } + + + + //////// HEAD MANIPULATION + /// Move the instruction pointer to the beginning of the genome + void ResetIP(){ + inst_ptr = 0; + } + /// Move the read head to the beginning of the genome + void ResetRH(){ + read_head = 0; + } + /// Move the write head to the beginning of the genome + void ResetWH(){ + write_head = 0; + } + /// Move the flow head to the beginning of the genome + void ResetFH(){ + flow_head = 0; + } + /// Advance the instruction pointer so many steps and wrap around the end of the genome + void AdvanceIP(size_t steps=1){ + inst_ptr += steps; + inst_ptr = (genome_working.size() > 0 ? inst_ptr % genome_working.size() : 0); + } + /// Advance the read head so many steps and wrap around the end of the genome + void AdvanceRH(size_t steps=1){ + read_head += steps; + read_head = (genome_working.size() > 0 ? read_head % genome_working.size() : 0); + } + /// Advance the write head so many steps and wrap around the end of the genome + void AdvanceWH(size_t steps=1){ + write_head += steps; + write_head = (genome_working.size() > 0 ? write_head % genome_working.size() : 0); + } + /// Advance the flow head so many steps and wrap around the end of the genome + void AdvanceFH(size_t steps=1){ + flow_head += steps; + flow_head = (genome_working.size() > 0 ? flow_head % genome_working.size() : 0); + } + /// Set the instruction pointer to the genome index, wrap around the end of the genome + void SetIP(size_t pos){ + inst_ptr = pos; + inst_ptr %= genome_working.size(); + } + /// Set the read head to the genome index, wrap around the end of the genome + void SetRH(size_t pos){ + read_head = pos; + read_head %= genome_working.size(); + } + /// Set the write head to the genome index, wrap around the end of the genome + void SetWH(size_t pos){ + write_head = pos; + write_head %= genome_working.size(); + } + /// Set the flow head to the genome index, wrap around the end of the genome + void SetFH(size_t pos){ + flow_head = pos; + flow_head %= genome_working.size(); + } + /// Set the specified head (which can wrap) to the beginning of the genom, + void ResetModdedHead(size_t head_idx){ + size_t modded_idx = head_idx % 4; + if(modded_idx == 0) SetIP(0); + else if(modded_idx == 1) SetRH(0); + else if(modded_idx == 2) SetWH(0); + else if(modded_idx == 3) SetFH(0); + } + /// Set the specified head (which can wrap) to the given genome position, + /// wrap around the end of the genome + void SetModdedHead(size_t head_idx, size_t pos){ + size_t modded_idx = head_idx % 4; + if(modded_idx == 0) SetIP(pos); + else if(modded_idx == 1) SetRH(pos); + else if(modded_idx == 2) SetWH(pos); + else if(modded_idx == 3) SetFH(pos); + } + /// Advance the specified head (which can wrap) the given number of instructions, + /// wrap around the end of the genome + void AdvanceModdedHead(size_t head_idx, size_t steps=1){ + size_t modded_idx = head_idx % 4; + if(modded_idx == 0) AdvanceIP(steps); + else if(modded_idx == 1) AdvanceRH(steps); + else if(modded_idx == 2) AdvanceWH(steps); + else if(modded_idx == 3) AdvanceFH(steps); + } + /// Return the head POSITION of the specified head (can wrap) + size_t GetModdedHead(size_t head_idx){ + size_t modded_idx = head_idx % 4; + if(modded_idx == 0) return inst_ptr; + else if(modded_idx == 1) return read_head; + else if(modded_idx == 2) return write_head; + else if(modded_idx == 3) return flow_head; + return inst_ptr; + } + + + //////// HARDWARE MANIPULATION + /// Initializes the CPU by counting the number of NOP instructions in the instruction + /// library and expanding the number of registers to match + void Initialize(){ + CountNops(); + ExpandRegisters(); + ResetHardware(); + } + /// Reset all heads + void ResetHeads(){ + ResetIP(); + ResetRH(); + ResetWH(); + ResetFH(); + } + /// Reset all inputs and outputs + void ResetIO(){ + inputs.clear(); + outputs.clear(); + } + /// Reset all memory/data + void ResetMemory(){ + // Initialize registers to their position. So Reg0 = 0 and Reg11 = 11. + for (size_t i = 0; i < num_regs; i++) { + regs[i] = (data_t) i; + } + for(size_t i = 0; i < NUM_STACKS; ++i){ + stacks[i].resize(0); + } + active_stack_idx = 0; + } + /// Reset all bookkeeping variables + void ResetBookkeeping(){ + copied_inst_id_vec.clear(); + num_insts_executed = 0; + } + /// Reset the working genome back to the original genome + void ResetWorkingGenome(){ + genome_working = genome; + label_idx_vec.clear(); + nops_need_curated = true; + } + /// Reset just the CPU hardware, but keep the original genome + void ResetHardware() { + ResetHeads(); + ResetMemory(); + ResetIO(); + ResetBookkeeping(); + } + /// Clear the main genome of the organism and reset all hardware + void ClearGenome() { + genome.resize(0,0); // Clear out genome + genome_working.resize(0,0); // Clear out working genome + label_idx_vec.clear(); // No labels if genome is empty + nops_need_curated = true; + ResetHardware(); // Reset the full hardware + } + /// Compile NOP instructions in genome into useful nop vectors for each instruction, + /// and records the position of all LABEL instructions + void CurateNops(){ + if(genome_working.size() == 0){ + nops_need_curated = false; + return; + } + bool label_inst_present = GetInstLib()->IsInst("Label"); + size_t label_inst_id = label_inst_present ? GetInstLib()->GetID("Label") : 0; + + if(!are_nops_counted) CountNops(); + label_idx_vec.clear(); + // Start by filling the nop vector of the last instruction + for(size_t inst_idx = 0; inst_idx < genome_working.GetSize() - 1; ++inst_idx){ + if(emp::Has(nop_id_map, genome_working[inst_idx].id)){ + genome_working[genome_working.size() - 1].nop_vec.push_back( + nop_id_map[genome_working[inst_idx].id]); + } + else break; + } + // If the last index is a label, record it! + if(label_inst_present && + (genome_working[genome_working.size() - 1].id == label_inst_id)) + label_idx_vec.push_back(genome_working.size() - 1); + // Now iterate backward over the genome, filling in each instruction's nop vector + // Example, our genome looks like xyzabc where only a, b, and c are nops + // If we are on index 2 (z), we see it is followed by a nop. + // Thus, we copy the next instruction into the nop vector [a] + // Then we copy THAT instruction's nop vector, too: [a,b,c] + // By going in reverse order, all following instructions already have a nop vec + for(auto it = genome_working.rbegin() + 1; it != genome_working.rend(); ++it){ + if(emp::Has(nop_id_map, (it - 1)->id)){ + it->nop_vec.resize( (it - 1)->nop_vec.size() + 1 ); + it->nop_vec[0] = nop_id_map[(it - 1)->id]; + std::copy( + (it - 1)->nop_vec.begin(), + (it - 1)->nop_vec.end(), + it->nop_vec.begin() + 1); + } + } + for(size_t inst_idx = 0; inst_idx < genome_working.size(); ++inst_idx){ + if(genome_working[inst_idx].id == label_inst_id) // Record pos if inst is label + label_idx_vec.push_back(inst_idx); + } + nops_need_curated = false; + } + /// Determine the number of sequential NOP instructions in the instruction library + /// + /// Starts at NopA and continues from there. Any missing instructions force count to + /// stop. Last possible NOP instruction is NopW, as NopX is a special case in Avida. + void CountNops(){ + num_nops = 0; + nop_id_map.clear(); + are_nops_counted = true; + for(size_t idx = 0; idx < MAX_NOPS ; ++idx){ // Stop before X! + std::string nop_name = (std::string)"Nop" + (char)('A' + idx); + if(GetInstLib()->IsInst(nop_name)){ + num_nops++; + size_t id = GetInstLib()->GetID(nop_name); + nop_id_map[id] = idx; + } + else return; + } + } + /// Expand the CPU's registers to match the number of NOP instructions in the + /// instruction library + void ExpandRegisters(){ + if(!are_nops_counted) CountNops(); + are_regs_expanded = true; + num_regs = num_nops; + regs.resize(num_regs); + } + + //////// NOP SEQUENCE METHODS + /// For a given NOP instruction (as an index), return its complement index + size_t GetComplementNop(size_t idx){ + if(idx >= num_nops - 1) return 0; + else return idx + 1; + } + /// For a vector of NOP instructions (as indices), return a vector of complement indices + /// in the same order + nop_vec_t GetComplementNopSequence(const nop_vec_t& nop_vec){ + nop_vec_t res_vec; + for(size_t nop : nop_vec){ + res_vec.push_back(GetComplementNop(nop)); + } + return res_vec; + } + /// Check if a vector of NOP instructions is the same as the START of another vector + bool CompareNopSequences(const nop_vec_t& search_vec, const nop_vec_t& compare_vec){ + if(search_vec.size() > compare_vec.size()) return false; + if(search_vec.size() == 0 || compare_vec.size() == 0) return false; + for(size_t idx = 0; idx < search_vec.size(); ++idx){ + if(search_vec[idx] != compare_vec[idx]) return false; + } + return true; + } + /// Check if the given vector of NOP instructions (as indices) were the last + /// instructions to be copied by the CPU + bool CheckIfLastCopied(const nop_vec_t& label){ + if(label.size() > copied_inst_id_vec.size()) return false; + if(label.size() == 0) return false; + int idx = label.size() - 1; + for(auto copied_it = copied_inst_id_vec.rbegin(); copied_it != copied_inst_id_vec.rend(); copied_it++){ + if(*copied_it != label[idx]) + return false; + idx--; + if(idx < 0) break; + + } + return true; + } + /// Search up the genome (backward) for a sequence of NOP instructions following a LABEL + /// instruction that match the NOP sequence following the current instruction + /// + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + size_t FindLabel_Reverse(bool start_local){ + const nop_vec_t search_vec = genome_working[inst_ptr].nop_vec; + size_t start_label_vec_idx = label_idx_vec.size() - 1; + if(start_local){ + bool start_found = false; + for(size_t offset = 0; offset < label_idx_vec.size(); ++offset){ + if(label_idx_vec[label_idx_vec.size() - offset - 1] < inst_ptr){ + start_label_vec_idx = label_idx_vec.size() - offset - 1; + start_found = true; + break; + } + } + if(!start_found) start_label_vec_idx = label_idx_vec.size() - 1; + } + for(size_t offset = 0; offset < label_idx_vec.size(); ++offset){ + const size_t idx = + label_idx_vec[ + (start_label_vec_idx - offset + label_idx_vec.size()) % label_idx_vec.size() + ]; + if(CompareNopSequences(search_vec, genome_working[idx].nop_vec)) return idx; + } + return inst_ptr; + } + /// Search the genome for a sequence of NOP instructions following a LABEL + /// instruction that match the NOP sequence following the current instruction + /// + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + /// @param reverse If true, traverse the genome backward. If false, traverse forward + size_t FindLabel(bool start_local, bool reverse = false){ + if(reverse) return FindLabel_Reverse(start_local); + const nop_vec_t search_vec = genome_working[inst_ptr].nop_vec; + size_t start_label_vec_idx = 0; + if(start_local){ + bool start_found = false; + for(; start_label_vec_idx < label_idx_vec.size(); ++start_label_vec_idx){ + if(label_idx_vec[start_label_vec_idx] > inst_ptr){ + start_found = true; + break; + } + } + if(!start_found) start_label_vec_idx = 0; + } + for(size_t offset = 0; offset < label_idx_vec.size(); ++offset){ + const size_t idx = label_idx_vec[(start_label_vec_idx + offset) % label_idx_vec.size()]; + if(CompareNopSequences(search_vec, genome_working[idx].nop_vec)) return idx; + } + return inst_ptr; + } + /// Search up the genome (backward) for a sequence of NOP instructions + /// that match the given NOP sequence + /// + /// @param search_vec The sequence of NOP instructions to search for + /// @param start_idx Position in the genom to start the search + size_t FindNopSequence_Reverse(const nop_vec_t& search_vec, size_t start_idx){ + for(size_t offset = 1; offset < genome_working.size() + 1; ++offset){ + const size_t idx = (start_idx - offset + genome_working.size()) % genome_working.size(); + if(CompareNopSequences(search_vec, genome_working[idx].nop_vec)) return idx; + } + return inst_ptr; + } + /// Search up the genome (backward) for a sequence of NOP instructions + /// that match the given NOP sequence + /// + /// @param search_vec The sequence of NOP instructions to search for + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + size_t FindNopSequence_Reverse(const nop_vec_t& search_vec, bool start_local){ + size_t start_idx = 0; + if(start_local && inst_ptr != 0) start_idx = inst_ptr; + return FindNopSequence_Reverse(search_vec, start_idx); + } + /// Search up the genome (backward) for a sequence of NOP instructions + /// that match the NOP sequence following the current instruction + /// + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + size_t FindNopSequence_Reverse(bool start_local){ + const nop_vec_t search_vec = genome_working[inst_ptr].nop_vec; + return FindNopSequence_Reverse(search_vec, start_local); + } + /// Search the genome for a sequence of NOP instructions that match the given + /// NOP sequence + /// + /// @param search_vec The sequence of NOP instructions to search for + /// @param start_idx Position in the genom to start the search + size_t FindNopSequence(const nop_vec_t& search_vec, size_t start_idx, + bool reverse = false){ + if(reverse) return FindNopSequence_Reverse(search_vec, start_idx); + for(size_t offset = 1; offset < genome_working.size() + 1; ++offset){ + const size_t idx = (start_idx + offset) % genome_working.size(); + if(CompareNopSequences(search_vec, genome_working[idx].nop_vec)) return idx; + } + return inst_ptr; + } + /// Search the genome for a sequence of NOP instructions that match the given + /// NOP sequence + /// + /// @param search_vec The sequence of NOP instructions to search for + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + /// @param reverse If true, traverse the genome backward. If false, traverse forward + size_t FindNopSequence(const nop_vec_t& search_vec, bool start_local, + bool reverse = false){ + size_t start_idx = genome_working.size() - 1; + if(start_local) start_idx = inst_ptr; + return FindNopSequence(search_vec, start_idx, reverse); + } + /// Search up the genome (backward) for a sequence of NOP instructions + /// that match the NOP sequence following the current instruction + /// + /// @param start_local If true, search from instruction pointer. If false, search from + /// start of the genome + /// @param reverse If true, traverse the genome backward. If false, traverse forward + size_t FindNopSequence(bool start_local, bool reverse = false){ + const nop_vec_t search_vec = genome_working[inst_ptr].nop_vec; + return FindNopSequence(search_vec, start_local, reverse); + } + + + //////// STACK MANIPULATION + /// Push the value in the specified register on top of the active stack + void StackPush(size_t reg_idx){ + stacks[active_stack_idx].push_back(regs[reg_idx]); + } + /// Remove the value from the top of the active stack and store it in the + /// specified register + void StackPop(size_t reg_idx){ + if(stacks[active_stack_idx].size()){ + regs[reg_idx] = *stacks[active_stack_idx].rbegin(); + stacks[active_stack_idx].pop_back(); + } + } + /// Swap which stack is active + void StackSwap(){ + active_stack_idx++; + if(active_stack_idx >= NUM_STACKS) active_stack_idx = 0; + } + /// Fetch the nth value of the specified stack + data_t GetStackVal(size_t stack_idx, size_t val_idx){ + emp_assert(stack_idx < NUM_STACKS); + emp_assert(val_idx < stacks[stack_idx].size()); + size_t reverse_idx = stacks[stack_idx].size() - val_idx - 1; + return stacks[stack_idx][reverse_idx]; + } + + + //////// PROCESSING + /// Process the next instruction pointed to be the instruction pointer + void SingleProcess(bool verbose = true) { + emp_assert(genome_working.GetSize() > 0); // A genome must exist to be processed. + if(!are_regs_expanded) ExpandRegisters(); + if(nops_need_curated) CurateNops(); + if(verbose){ + GetInstLib()->GetName(genome_working[inst_ptr].idx); + PrintDetails(); + } + genome_working[inst_ptr].has_been_executed = true; + GetInstLib()->ProcessInst(ToPtr(this), genome_working[inst_ptr]); + AdvanceIP(); + num_insts_executed++; + } + /// Process the next SERIES of instructions, directed by the instruction pointer. + void Process(size_t num_inst = 1, bool verbose = true) { + for (size_t i = 0; i < num_inst; i++) SingleProcess(verbose); + } + + + //////// STATE -> STRING FUNCTIONS + /// Return the working genome in string form. + /// + /// Each instruction is represented by a single character, dictated by the + /// instruction's ID. + std::string GetWorkingGenomeString() const{ + std::stringstream sstr; + sstr << "[" << genome_working.size() << "]"; + for(size_t idx = 0; idx < genome_working.size(); idx++){ + unsigned char c = 'a' + genome_working[idx].id; + if(genome_working[idx].id > 25) c = 'A' + genome_working[idx].id - 26; + sstr << c; + } + return sstr.str(); + } + /// Return the original genome in string form. + /// + /// Each instruction is represented by a single character, dictated by the + /// instruction's ID. + std::string GetGenomeString() const{ + std::stringstream sstr; + sstr << "[" << genome.size() << "]"; + for(size_t idx = 0; idx < genome.size(); idx++){ + unsigned char c = 'a' + genome[idx].id; + if(genome[idx].id > 25) c = 'A' + genome[idx].id - 26; + sstr << c; + } + return sstr.str(); + } + /// Output the state of the CPU's heads and registers to the specified output stream + void PrintDetails(std::ostream& ostr = std::cout){ + ostr << "IP: " << inst_ptr; + ostr << " RH: " << read_head; + ostr << " WH: " << write_head; + ostr << " FH: " << flow_head; + ostr << "(nops: " << num_nops << "; regs: " << num_regs << ")" << std::endl; + for(size_t reg_idx = 0; reg_idx < regs.size(); ++reg_idx){ + ostr << "[" << reg_idx << "] " << regs[reg_idx] << std::endl; + } + } + + }; // End VirtualCPU class +} // End namespace + + + +#endif // #ifndef EMP_HARDWARE_VIRTUALCPU_HPP_INCLUDE diff --git a/include/emp/hardware/VirtualCPU_InstLib.hpp b/include/emp/hardware/VirtualCPU_InstLib.hpp new file mode 100644 index 0000000000..1ea720b8cf --- /dev/null +++ b/include/emp/hardware/VirtualCPU_InstLib.hpp @@ -0,0 +1,295 @@ +/** + * @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 2021-2022 + * + * @file VirtualCPU_InstLib.hpp + * @brief A specialized version of InstLib to handle VirtualCPU instructions. + */ + +#ifndef EMP_HARDWARE_VIRTUALCPU_INSTLIB_HPP_INCLUDE +#define EMP_HARDWARE_VIRTUALCPU_INSTLIB_HPP_INCLUDE + +#include "../base/error.hpp" +#include "../math/math.hpp" + +#include "InstLib.hpp" + +namespace emp { + + /// \brief A pure-virtual class that defines a series of instructions for VirtualCPU_Base or any of its derived classes. + template + struct VirtualCPU_InstLib : public InstLib { + using hardware_t = HARDWARE_T; + using inst_lib_t = InstLib; + using arg_t = ARG_T; + using this_t = VirtualCPU_InstLib; + using inst_t = typename hardware_t::inst_t; + using nop_vec_t = typename hardware_t::nop_vec_t; + + // Instructions + static void Inst_NopA(hardware_t & hw, const inst_t & inst) { ; } + static void Inst_NopB(hardware_t & hw, const inst_t & inst) { ; } + static void Inst_NopC(hardware_t & hw, const inst_t & inst) { ; } + static void Inst_Inc(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + ++hw.regs[idx]; + } + static void Inst_Dec(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + --hw.regs[idx]; + } + static void Inst_If_Not_Equal(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t idx_op_1 = inst.nop_vec.size() < 1 ? 1 : inst.nop_vec[0]; + size_t idx_op_2 = inst.nop_vec.size() < 2 ? hw.GetComplementNop(idx_op_1) : inst.nop_vec[1]; + if(hw.regs[idx_op_1] == hw.regs[idx_op_2]) + hw.AdvanceIP(1); + hw.AdvanceIP(inst.nop_vec.size()); + } + else{ + size_t idx_1 = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_2 = hw.GetComplementNop(idx_1); + if(hw.regs[idx_1] == hw.regs[idx_2]) + hw.AdvanceIP(1); + if(inst.nop_vec.size()) hw.AdvanceIP(1); + } + } + static void Inst_If_Less(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t idx_op_1 = inst.nop_vec.size() < 1 ? 1 : inst.nop_vec[0]; + size_t idx_op_2 = inst.nop_vec.size() < 2 ? hw.GetComplementNop(idx_op_1) : inst.nop_vec[1]; + if(hw.regs[idx_op_1] >= hw.regs[idx_op_2]) + hw.AdvanceIP(1); + hw.AdvanceIP(inst.nop_vec.size()); + } + else{ + size_t idx_1 = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_2 = hw.GetComplementNop(idx_1); + if(hw.regs[idx_1] >= hw.regs[idx_2]) + hw.AdvanceIP(1); + if(inst.nop_vec.size()) hw.AdvanceIP(1); + } + } + static void Inst_Pop(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + hw.StackPop(idx); + } + static void Inst_Push(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + hw.StackPush(idx); + } + static void Inst_Swap_Stack(hardware_t & hw, const inst_t & inst) { + hw.StackSwap(); + } + static void Inst_Shift_Right(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + hw.regs[idx] >>= 1; + } + static void Inst_Shift_Left(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + hw.regs[idx] <<= 1; + } + static void Inst_Add(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t idx_res = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_op_1 = inst.nop_vec.size() < 2 ? idx_res : inst.nop_vec[1]; + size_t idx_op_2 = inst.nop_vec.size() < 3 ? hw.GetComplementNop(idx_op_1) : inst.nop_vec[2]; + hw.regs[idx_res] = hw.regs[idx_op_1] + hw.regs[idx_op_2]; + } + else{ + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_2 = hw.GetComplementNop(idx); + hw.regs[idx] = hw.regs[idx] + hw.regs[idx_2]; + } + } + static void Inst_Sub(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t idx_res = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_op_1 = inst.nop_vec.size() < 2 ? idx_res : inst.nop_vec[1]; + size_t idx_op_2 = inst.nop_vec.size() < 3 ? hw.GetComplementNop(idx_op_1) : inst.nop_vec[2]; + hw.regs[idx_res] = hw.regs[idx_op_1] - hw.regs[idx_op_2]; + } + else{ + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_2 = hw.GetComplementNop(idx); + hw.regs[idx] = hw.regs[idx] - hw.regs[idx_2]; + } + } + static void Inst_Nand(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t idx_res = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_op_1 = inst.nop_vec.size() < 2 ? idx_res : inst.nop_vec[1]; + size_t idx_op_2 = inst.nop_vec.size() < 3 ? hw.GetComplementNop(idx_op_1) : inst.nop_vec[2]; + hw.regs[idx_res] = ~(hw.regs[idx_op_1] & hw.regs[idx_op_2]); + } + else{ + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + size_t idx_2 = hw.GetComplementNop(idx); + hw.regs[idx] = hw.regs[idx] + hw.regs[idx_2]; + hw.regs[idx] = ~(hw.regs[idx] & hw.regs[idx_2]); + } + } + static void Inst_IO(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 1 : inst.nop_vec[0]; + std::cout << "Output: " << hw.regs[idx] << std::endl; + // TODO: Handle input + } + static void Inst_H_Alloc(hardware_t & hw, const inst_t & inst) { + hw.genome_working.resize(hw.genome.size() * 2, hw.GetDefaultInst()); + hw.regs[0] = hw.genome.size(); + } + static void Inst_H_Divide(hardware_t & hw, const inst_t & inst) { + if(hw.read_head >= hw.genome.size()){ + hw.genome_working.resize(hw.read_head, 0); + hw.ResetHardware(); + hw.inst_ptr = hw.genome.size() - 1; + std::cout << "Divide!" << std::endl; + } + } + static void Inst_H_Copy(hardware_t & hw, const inst_t & inst) { + hw.genome_working[hw.write_head] = hw.genome_working[hw.read_head]; + hw.copied_inst_id_vec.push_back(hw.genome_working[hw.write_head].id); + hw.read_head++; + while(hw.read_head >= hw.genome_working.size()) hw.read_head -= hw.genome_working.size(); + hw.write_head++; + while(hw.write_head >= hw.genome_working.size()) hw.write_head -= hw.genome_working.size(); + // TODO: Mutation + } + static void Inst_H_Search(hardware_t & hw, const inst_t & inst) { + size_t res = hw.FindNopSequence(hw.GetComplementNopSequence(inst.nop_vec), hw.inst_ptr); + if(inst.nop_vec.size() == 0 || res == hw.inst_ptr){ + hw.regs[1] = 0; + hw.regs[2] = 0; + hw.SetFH(hw.inst_ptr + 1); + } + else{ + hw.regs[1] = (res - hw.inst_ptr) > 0 ? res - hw.inst_ptr : res + hw.genome_working.size() - res + hw.inst_ptr; + hw.regs[2] = inst.nop_vec.size(); + hw.SetFH(res + inst.nop_vec.size() + 1); + } + } + static void Inst_Mov_Head(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t dest_idx = hw.flow_head; + if(inst.nop_vec.size() >= 2) dest_idx = hw.GetModdedHead(inst.nop_vec[1]); + if(!inst.nop_vec.empty()) hw.SetModdedHead(inst.nop_vec[0], dest_idx); + else hw.SetIP(dest_idx); + } + else{ + if(!inst.nop_vec.empty()){ + // IP is a special case because it auto advances! + if(inst.nop_vec[0] % 4 == 0) hw.SetIP(hw.flow_head - 1); + else hw.SetModdedHead(inst.nop_vec[0], hw.flow_head); + } + else hw.SetIP(hw.flow_head - 1); + } + } + static void Inst_Jmp_Head(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t jump_dist = hw.regs[1]; + if(inst.nop_vec.size() >= 2) jump_dist = hw.regs[inst.nop_vec[1]]; + if(!inst.nop_vec.empty()) hw.AdvanceModdedHead(inst.nop_vec[0], jump_dist); + else hw.AdvanceIP(jump_dist); + } + else{ + if(!inst.nop_vec.empty()) hw.AdvanceModdedHead(inst.nop_vec[0], hw.regs[2]); + else hw.AdvanceIP(hw.regs[2]); + } + } + static void Inst_Get_Head(hardware_t & hw, const inst_t & inst) { + if(hw.expanded_nop_args){ + size_t head_val = inst.nop_vec.empty() ? hw.inst_ptr : hw.GetModdedHead(inst.nop_vec[0]); + if(inst.nop_vec.size() < 2) hw.regs[2] = head_val; + else hw.regs[inst.nop_vec[1]] = head_val; + } + else{ + if(inst.nop_vec.empty()) hw.regs[2] = hw.inst_ptr; + else hw.regs[2] = hw.GetModdedHead(inst.nop_vec[0]); + } + } + static void Inst_If_Label(hardware_t & hw, const inst_t & inst) { + hw.AdvanceIP(inst.nop_vec.size()); + if(!hw.CheckIfLastCopied(hw.GetComplementNopSequence(inst.nop_vec))) hw.AdvanceIP(); + } + static void Inst_Set_Flow(hardware_t & hw, const inst_t & inst) { + size_t idx = inst.nop_vec.empty() ? 2 : inst.nop_vec[0]; + hw.SetFH(hw.regs[idx]); + } + + /// Maintain and return a singleton of default instructions + static const this_t & DefaultInstLib() { + static this_t inst_lib; + if (inst_lib.GetSize() == 0) { + inst_lib.AddInst("NopA", Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("IfNEq", Inst_If_Not_Equal, 1, + "Skip next inst unless register values match"); + inst_lib.AddInst("IfLess", Inst_If_Less, 1, + "Skip next inst unless focal register is less than its complement"); + inst_lib.AddInst("Inc", Inst_Inc, 1, "Increment value in reg Arg1"); + inst_lib.AddInst("Dec", Inst_Dec, 1, "Decrement value in reg Arg1"); + inst_lib.AddInst("Pop", Inst_Pop, 1, "Pop value from active stack into register"); + inst_lib.AddInst("Push", Inst_Push, 1, "Add register's value to active stack"); + inst_lib.AddInst("Swap-Stk", Inst_Swap_Stack, 1, "Swap which stack is active"); + inst_lib.AddInst("ShiftR", Inst_Shift_Right, 1, "Shift register value right by one bit"); + inst_lib.AddInst("ShiftL", Inst_Shift_Left, 1, "Shift register value left by one bit"); + inst_lib.AddInst("Add", Inst_Add, 1, + "Add values in registers B and C, then store result in given register"); + inst_lib.AddInst("Sub", Inst_Sub, 1, + "Sub values in registers B and C, then store result in given register"); + inst_lib.AddInst("Nand", Inst_Nand, 1, + "NAND values in registers B and C, then store result in given register"); + inst_lib.AddInst("IO", Inst_IO, 1, + "Output value in given register and then place new input in that register"); + inst_lib.AddInst("HAlloc", Inst_H_Alloc, 1, "Allocate memory for offspring"); + inst_lib.AddInst("HDivide", Inst_H_Divide, 1, "Attempt to split offspring"); + inst_lib.AddInst("HCopy", Inst_H_Copy, 1, "Copy instruction from read head to write head"); + inst_lib.AddInst("HSearch", Inst_H_Search, 1, "Search for label complement"); + inst_lib.AddInst("MovHead", Inst_Mov_Head, 1, "Move a given head to a postiion"); + inst_lib.AddInst("JmpHead", Inst_Jmp_Head, 1, "Move a given head by a relative amount"); + inst_lib.AddInst("GetHead", Inst_Get_Head, 1, "Get location of head"); + inst_lib.AddInst("IfLabel", Inst_If_Label, 1, + "Execute next instruction if label was the last thing copied"); + inst_lib.AddInst("SetFlow", Inst_Set_Flow, 1, "Set flow head to register value"); + /* + inst_lib.AddInst("Dec", Inst_Dec, 1, "Decrement value in reg Arg1"); + inst_lib.AddInst("Not", Inst_Not, 1, "Logically toggle value in reg Arg1"); + inst_lib.AddInst("SetReg", Inst_SetReg, 2, "Set reg Arg1 to numerical value Arg2"); + inst_lib.AddInst("Add", Inst_Add, 3, "regs: Arg3 = Arg1 + Arg2"); + inst_lib.AddInst("Sub", Inst_Sub, 3, "regs: Arg3 = Arg1 - Arg2"); + inst_lib.AddInst("Mult", Inst_Mult, 3, "regs: Arg3 = Arg1 * Arg2"); + inst_lib.AddInst("Div", Inst_Div, 3, "regs: Arg3 = Arg1 / Arg2"); + inst_lib.AddInst("Mod", Inst_Mod, 3, "regs: Arg3 = Arg1 % Arg2"); + inst_lib.AddInst("TestEqu", Inst_TestEqu, 3, "regs: Arg3 = (Arg1 == Arg2)"); + inst_lib.AddInst("TestNEqu", Inst_TestNEqu, 3, "regs: Arg3 = (Arg1 != Arg2)"); + inst_lib.AddInst("TestLess", Inst_TestLess, 3, "regs: Arg3 = (Arg1 < Arg2)"); + inst_lib.AddInst("If", Inst_If, 2, "If reg Arg1 != 0, scope -> Arg2; else skip scope", ScopeType::BASIC, 1); + inst_lib.AddInst("While", Inst_While, 2, "Until reg Arg1 != 0, repeat scope Arg2; else skip", ScopeType::LOOP, 1); + inst_lib.AddInst("Countdown", Inst_Countdown, 2, "Countdown reg Arg1 to zero; scope to Arg2", ScopeType::LOOP, 1); + inst_lib.AddInst("Break", Inst_Break, 1, "Break out of scope Arg1"); + inst_lib.AddInst("Scope", Inst_Scope, 1, "Enter scope Arg1", ScopeType::BASIC, 0); + inst_lib.AddInst("Define", Inst_Define, 2, "Build function Arg1 in scope Arg2", ScopeType::FUNCTION, 1); + inst_lib.AddInst("Call", Inst_Call, 1, "Call previously defined function Arg1"); + inst_lib.AddInst("Push", Inst_Push, 2, "Push reg Arg1 onto stack Arg2"); + inst_lib.AddInst("Pop", Inst_Pop, 2, "Pop stack Arg1 into reg Arg2"); + inst_lib.AddInst("Input", Inst_Input, 2, "Pull next value from input Arg1 into reg Arg2"); + inst_lib.AddInst("Output", Inst_Output, 2, "Push reg Arg1 into output Arg2"); + inst_lib.AddInst("CopyVal", Inst_CopyVal, 2, "Copy reg Arg1 into reg Arg2"); + inst_lib.AddInst("ScopeReg", Inst_ScopeReg, 1, "Backup reg Arg1; restore at end of scope"); + */ + + //for (size_t i = 0; i < hardware_t::NUM_REGS; i++) { + // inst_lib.AddArg(to_string((int)i), i); // Args can be called by value + // inst_lib.AddArg(to_string("Reg", 'A'+(char)i), i); // ...or as a register. + //} + } + + return inst_lib; + } + }; + +} + +#endif // #ifndef EMP_HARDWARE_VIRTUALCPU_INSTLIB_HPP_INCLUDE diff --git a/include/emp/math/CombinedBinomialDistribution.hpp b/include/emp/math/CombinedBinomialDistribution.hpp new file mode 100644 index 0000000000..a8b5786d71 --- /dev/null +++ b/include/emp/math/CombinedBinomialDistribution.hpp @@ -0,0 +1,85 @@ +/** + * @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 2018-2022. + * + * @file CombinedBinomialDistribution.hpp + * @brief A means of quickly generating binomial random variables while only storing a small number of distributions. + * @note Status: ALPHA + * + * Quick check for theory: https://math.stackexchange.com/questions/1176385/sum-of-two-independent-binomial-variables + * + * If we want to generate binomial random variables of various trial counts (n's) using the + * Distribution class, we'd have to create a new Distribution for each unique trial count. + * + * This class leverages the fact that B(n, p) + B(m, p) = B(n + m, p) to calculate binomial + * draws with arbitrary trail counts without storing N distributions. + * By storing distributions for powers of 2, we only store log_2(N) distributions. + * + * Developor Notes: + * - We should come up with a more informative name for the file/class + */ + +#ifndef EMP_MATH_COMBINEDBINOMIALDISTRIBUTION_HPP_INCLUDE +#define EMP_MATH_COMBINEDBINOMIALDISTRIBUTION_HPP_INCLUDE + +#include "./Distribution.hpp" + +namespace emp{ + /// \brief A collection of distributions that allows for pulls from a binomial distribution with arbitrary N while only storing log_2(N) distributions + class CombinedBinomialDistribution{ + protected: + emp::vector distribution_vec; /**< The collection of binomial distributions + used to construct any N */ + double p; ///< The success probability of a single Bernoulli trial + size_t cur_max_power; /**< The maximum power of two currently supported by our + distributions */ + + /// Fetch the smallest power of two that is larger than N + size_t GetMaxPower(size_t n) const { + size_t power = 0; + for(size_t val = 1; val < n; val <<= 1, ++power){ ; } + return power; + } + + public: + CombinedBinomialDistribution() : p(0), cur_max_power(0){ ; } + CombinedBinomialDistribution(double _p, size_t _starting_n) : p(_p), cur_max_power(0){ + Expand(_starting_n); + } + + /// Sample a binomial distribution with n events + size_t PickRandom(size_t n, Random & random){ + size_t local_max_power = GetMaxPower(n); + size_t result = 0; + if(local_max_power > cur_max_power) Expand(n); + for(size_t power = 0; power <= local_max_power; ++power){ + if( (n & (1 << power)) != 0){ + result += distribution_vec[power].PickRandom(random); + } + } + return result; + } + + /// Reset the distribution with a new probability, p, and a starting n value + void Setup(double _p, size_t _n){ + distribution_vec.clear(); + cur_max_power = 0; + p = _p; + if(_n > (1ull << cur_max_power)) Expand(_n); + } + + /// Create more distributions to handle the given value of n + void Expand(size_t max_n){ + cur_max_power = GetMaxPower(max_n); + for(size_t power = distribution_vec.size(); power <= cur_max_power; ++power){ + distribution_vec.emplace_back(p, 1 << power); + } + } + + /// Fetch the current maximum power handled by this combined distribution + size_t GetCurMaxPower(){ return cur_max_power; } + }; +} + +#endif // #ifndef EMP_MATH_COMBINEDBINOMIALDISTRIBUTION_HPP_INCLUDE diff --git a/include/emp/math/DistributionSet.hpp b/include/emp/math/DistributionSet.hpp index 9bfa9bdede..5fdcb0c188 100644 --- a/include/emp/math/DistributionSet.hpp +++ b/include/emp/math/DistributionSet.hpp @@ -12,8 +12,8 @@ * */ -#ifndef EMP_MATH_DISTRIBUTION_SET_HPP_INCLUDE -#define EMP_MATH_DISTRIBUTION_SET_HPP_INCLUDE +#ifndef EMP_MATH_DISTRIBUTIONSET_HPP_INCLUDE +#define EMP_MATH_DISTRIBUTIONSET_HPP_INCLUDE #include "Distribution.hpp" @@ -32,7 +32,7 @@ namespace emp { class DistributionSet { private: /// Map parameters to pre-calculated distributions. - unordered_map< std::tuple, DIST_T, emp::TupleHash> dist_map; + unordered_map< std::tuple, DIST_T, emp::TupleHash> dist_map; public: size_t PickRandom(Random & random, Ts... args) { @@ -42,9 +42,9 @@ namespace emp { } }; - using BinomialSet = emp::DistributionSet; - using NegativeBinomialSet = emp::DistributionSet; + using BinomialSet = emp::DistributionSet; + using NegativeBinomialSet = emp::DistributionSet; } -#endif // #ifndef EMP_MATH_DISTRIBUTION_HPP_INCLUDE +#endif // #ifndef EMP_MATH_DISTRIBUTIONSET_HPP_INCLUDE diff --git a/include/emp/math/Random.hpp b/include/emp/math/Random.hpp index 18e4e90cd3..850a3946a3 100644 --- a/include/emp/math/Random.hpp +++ b/include/emp/math/Random.hpp @@ -438,13 +438,19 @@ namespace emp { /// Generate a random variable drawn from an exponential distribution. inline double GetExponential(double p) { - emp_assert(p > 0.0 && p < 1.0, p); + emp_assert(p >= 0.0 && p <= 1.0, p); + if (p == 0) { + return std::numeric_limits::infinity(); + } return std::log(GetDouble()) / std::log(1.0 - p); } /// Generate a random variable drawn from a geometric distribution. inline uint32_t GetGeometric(double p) { - emp_assert(p > 0.0 && p < 1.0, p); + emp_assert(p >= 0.0 && p <= 1.0, p); + if (p == 0) { + return std::numeric_limits::infinity(); + } return static_cast( GetExponential(p) ) + 1; } diff --git a/include/emp/meta/TypeID.hpp b/include/emp/meta/TypeID.hpp index acc2dffaff..bbe5c92a90 100644 --- a/include/emp/meta/TypeID.hpp +++ b/include/emp/meta/TypeID.hpp @@ -119,6 +119,7 @@ namespace emp { virtual bool IsTrivial() const { return false; } virtual bool IsVoid() const { return false; } virtual bool IsVolatile() const { return false; } + virtual bool IsFunction() const { return false; } virtual bool IsTypePack() const { return false; } @@ -170,6 +171,7 @@ namespace emp { bool IsTrivial() const override { return std::is_trivial(); } bool IsVoid() const override { return std::is_same(); } bool IsVolatile() const override { return std::is_volatile(); } + bool IsFunction() const override { return std::is_function(); } bool IsTypePack() const override { return emp::is_TypePack(); } @@ -221,6 +223,7 @@ namespace emp { size_t GetSize() const override { if constexpr (std::is_void()) return 0; + else if constexpr (std::is_function()) return 0; else return sizeof(T); } diff --git a/include/emp/tools/string_utils.hpp b/include/emp/tools/string_utils.hpp index db6324b6d4..fabd8f7c44 100644 --- a/include/emp/tools/string_utils.hpp +++ b/include/emp/tools/string_utils.hpp @@ -740,7 +740,7 @@ namespace emp { } /// Pre-pend and post-pend specified sequences to all strings provided. - [[nodiscard]] static inline string_vec_t + [[nodiscard]] static inline string_vec_t quote_strings(const string_vec_t & in_strings, const std::string open_quote, const std::string close_quote) { @@ -1559,7 +1559,7 @@ namespace emp { result); return result; // Stop where we are... No end brace found! } - + std::string key = result.substr(i+2, end_pos-i-2); auto replacement_it = var_map.find(key); if (replacement_it == var_map.end()) { diff --git a/tests/Evolve/Systematics.cpp b/tests/Evolve/Systematics.cpp index f2d40ac3a1..686a7dc58b 100644 --- a/tests/Evolve/Systematics.cpp +++ b/tests/Evolve/Systematics.cpp @@ -14,6 +14,7 @@ #ifndef NDEBUG #define TDEBUG #endif + #include "emp/base/vector.hpp" #include "emp/Evolve/SystematicsAnalysis.hpp" #include "emp/Evolve/Systematics.hpp" @@ -21,83 +22,88 @@ #include "emp/Evolve/World_output.hpp" #include "emp/hardware/AvidaGP.hpp" - -TEST_CASE("Test Systematics", "[Evolve]") -{ - +TEST_CASE("Test Systematics", "[Evolve]") { // Taxon emp::Taxon tx(0, "a"); - REQUIRE(tx.GetID() == 0); - REQUIRE(tx.GetParent() == nullptr); - REQUIRE(tx.GetInfo() == "a"); - REQUIRE(tx.GetNumOrgs() == 0); - REQUIRE(tx.GetTotOrgs() == 0); + CHECK(tx.GetID() == 0); + CHECK(tx.GetParent() == nullptr); + CHECK(tx.GetInfo() == "a"); + CHECK(tx.GetNumOrgs() == 0); + CHECK(tx.GetTotOrgs() == 0); tx.AddOrg(); - REQUIRE(tx.GetNumOrgs() == 1); + CHECK(tx.GetNumOrgs() == 1); tx.RemoveOrg(); - REQUIRE(tx.GetNumOrgs() == 0); - REQUIRE(tx.GetTotOrgs() == 1); - REQUIRE(tx.GetTotalOffspring() == 0); + CHECK(tx.GetNumOrgs() == 0); + CHECK(tx.GetTotOrgs() == 1); + CHECK(tx.GetTotalOffspring() == 0); emp::Ptr< emp::Taxon > parentPtr(&tx); emp::Taxon tx_1(1, "b", parentPtr); - REQUIRE(tx_1.GetParent() == parentPtr); + CHECK(tx_1.GetParent() == parentPtr); tx_1.AddTotalOffspring(); - REQUIRE(tx_1.GetTotalOffspring() == 1); - REQUIRE(tx.GetTotalOffspring() == 1); + CHECK(tx_1.GetTotalOffspring() == 1); + CHECK(tx.GetTotalOffspring() == 1); // Systematics std::function calc_taxon = [](double & o){ return o > 50.0 ? "large" : "small"; }; emp::Systematics sys1(calc_taxon); - REQUIRE(sys1.GetTrackSynchronous() == false); - REQUIRE(sys1.GetNumAncestors() == 0); - REQUIRE(sys1.GetNumActive() == 0); - REQUIRE(sys1.GetNumOutside() == 0); - REQUIRE(sys1.GetTreeSize() == 0); - REQUIRE(sys1.GetNumTaxa() == 0); + CHECK(sys1.GetTrackSynchronous() == false); + CHECK(sys1.GetNumAncestors() == 0); + CHECK(sys1.GetNumActive() == 0); + CHECK(sys1.GetNumOutside() == 0); + CHECK(sys1.GetTreeSize() == 0); + CHECK(sys1.GetNumTaxa() == 0); sys1.SetTrackSynchronous(true); - sys1.AddOrg(15.0, {0,0}, 0); - REQUIRE(sys1.GetNumActive() == 1); - REQUIRE(sys1.GetTaxonAt(0)->GetInfo() == "small"); - sys1.AddOrg(56.0, {1,1}, 0); - REQUIRE(sys1.GetNumActive() == 2); - REQUIRE(sys1.GetNextTaxonAt(1)->GetInfo() == "large"); + CHECK(sys1.GetTrackSynchronous() == true); + sys1.AddOrg(15.0, {0,0}); + CHECK(sys1.GetNumActive() == 1); + CHECK(sys1.GetTaxonAt({0,0})->GetInfo() == "small"); + CHECK(sys1.IsTaxonAt({0,0})); + sys1.AddOrg(56.0, {1,1}); + CHECK(sys1.GetNumActive() == 2); + CHECK(sys1.GetTaxonAt({1,1})->GetInfo() == "large"); + CHECK(sys1.IsTaxonAt({1,1})); sys1.RemoveOrg({1,1}); - REQUIRE(sys1.GetNumActive() == 1); + CHECK(!sys1.IsTaxonAt({1,1})); + CHECK(sys1.GetNumActive() == 1); + sys1.AddOrg(56.0, {1,0}); + CHECK(sys1.IsTaxonAt({1,0})); + CHECK(!sys1.RemoveOrg({1,0})); + CHECK(!sys1.IsTaxonAt({1,0})); // Base setters and getters - REQUIRE(sys1.GetStoreActive() == true); - REQUIRE(sys1.GetStoreAncestors() == true); - REQUIRE(sys1.GetStoreOutside() == false); - REQUIRE(sys1.GetArchive() == true); - REQUIRE(sys1.GetStorePosition() == true); + CHECK(sys1.GetStoreActive() == true); + CHECK(sys1.GetStoreAncestors() == true); + CHECK(sys1.GetStoreOutside() == false); + CHECK(sys1.GetArchive() == true); + CHECK(sys1.GetStorePosition() == true); sys1.SetStoreActive(false); - REQUIRE(sys1.GetStoreActive() == false); + CHECK(sys1.GetStoreActive() == false); sys1.SetStoreAncestors(false); - REQUIRE(sys1.GetStoreAncestors() == false); + CHECK(sys1.GetStoreAncestors() == false); sys1.SetStoreOutside(true); - REQUIRE(sys1.GetStoreOutside() == true); + CHECK(sys1.GetStoreOutside() == true); sys1.SetArchive(false); - REQUIRE(sys1.GetArchive() == false); + CHECK(sys1.GetArchive() == false); sys1.SetStorePosition(false); - REQUIRE(sys1.GetStorePosition() == false); + CHECK(sys1.GetStorePosition() == false); #ifndef NDEBUG - sys1.AddDeleteriousStepDataNodeImpl(true); - REQUIRE(emp::assert_last_fail); + sys1.AddDeleteriousStepDataNode(); + CHECK(emp::assert_last_fail); emp::assert_clear(); - sys1.AddVolatilityDataNodeImpl(true); - REQUIRE(emp::assert_last_fail); + sys1.AddVolatilityDataNode(); + CHECK(emp::assert_last_fail); emp::assert_clear(); - sys1.AddUniqueTaxaDataNodeImpl(true); - REQUIRE(emp::assert_last_fail); + sys1.AddUniqueTaxaDataNode(); + CHECK(emp::assert_last_fail); emp::assert_clear(); - sys1.AddMutationCountDataNodeImpl(true); - REQUIRE(emp::assert_last_fail); + sys1.AddMutationCountDataNode(); + CHECK(emp::assert_last_fail); emp::assert_clear(); #endif @@ -106,138 +112,157 @@ TEST_CASE("Test Systematics", "[Evolve]") //emp::Systematics sys2(calc_taxon) my_taxon taxon1(1, "medium"); emp::Ptr ptr1 = &taxon1; - REQUIRE(emp::LineageLength(ptr1) == 1); + CHECK(emp::LineageLength(ptr1) == 1); my_taxon taxon2(1, "medium", ptr1); emp::Ptr ptr2 = &taxon2; - REQUIRE(emp::LineageLength(ptr1) == 1); - REQUIRE(emp::LineageLength(ptr2) == 2); + CHECK(emp::LineageLength(ptr1) == 1); + CHECK(emp::LineageLength(ptr2) == 2); std::unordered_map muts; muts["short"] = 12; muts["tall"] = 3; taxon2.GetData().RecordMutation(muts); - REQUIRE(taxon2.GetData().mut_counts.size() == 2); - REQUIRE(taxon2.GetData().mut_counts["tall"] == 3); + CHECK(taxon2.GetData().mut_counts.size() == 2); + CHECK(taxon2.GetData().mut_counts["tall"] == 3); emp::vector types; types.push_back("tall"); types.push_back("short"); - REQUIRE(emp::CountMuts(ptr2, types) == 15); - REQUIRE(emp::CountMutSteps(ptr2, types) == 2); - REQUIRE(emp::CountMutSteps(ptr2, "short") == 1); + CHECK(emp::CountMuts(ptr2, types) == 15); + CHECK(emp::CountMutSteps(ptr2, types) == 2); + CHECK(emp::CountMutSteps(ptr2, "short") == 1); muts["short"] = 4; taxon1.GetData().RecordMutation(muts); - REQUIRE(emp::CountMuts(ptr1, "short") == 4); - REQUIRE(emp::CountMuts(ptr2, "short") == 16); - REQUIRE(emp::CountMutSteps(ptr1, "short") == 1); - REQUIRE(emp::CountMutSteps(ptr2, "short") == 2); - - emp::Systematics sys([](const int & i){return i;}, true, true, true, false); - - std::cout << "\nAddOrg 25 (id1, no parent)\n"; - auto id1 = sys.AddOrg(25, nullptr, 0); - std::cout << "\nAddOrg -10 (id2; parent id1)\n"; - auto id2 = sys.AddOrg(-10, id1, 6); - std::cout << "\nAddOrg 26 (id3; parent id1)\n"; - auto id3 = sys.AddOrg(26, id1, 10); - std::cout << "\nAddOrg 27 (id4; parent id2)\n"; - auto id4 = sys.AddOrg(27, id2, 25); - std::cout << "\nAddOrg 28 (id5; parent id2)\n"; - auto id5 = sys.AddOrg(28, id2, 32); - std::cout << "\nAddOrg 29 (id6; parent id5)\n"; - auto id6 = sys.AddOrg(29, id5, 39); - std::cout << "\nAddOrg 30 (id7; parent id1)\n"; - auto id7 = sys.AddOrg(30, id1, 6); - - - std::cout << "\nRemoveOrg (id2)\n"; + CHECK(emp::CountMuts(ptr1, "short") == 4); + CHECK(emp::CountMuts(ptr2, "short") == 16); + CHECK(emp::CountMutSteps(ptr1, "short") == 1); + CHECK(emp::CountMutSteps(ptr2, "short") == 2); + + emp::Systematics sys([](const int & i){return i;}, true, true, true, false); + + // std::cout << "\nAddOrg 25 (id1, no parent)\n"; + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, nullptr); + // std::cout << "\nAddOrg -10 (id2; parent id1)\n"; + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, id1); + // std::cout << "\nAddOrg 26 (id3; parent id1)\n"; + sys.SetUpdate(10); + auto id3 = sys.AddOrg(26, id1); + // std::cout << "\nAddOrg 27 (id4; parent id2)\n"; + sys.SetUpdate(25); + auto id4 = sys.AddOrg(27, id2); + // std::cout << "\nAddOrg 28 (id5; parent id2)\n"; + sys.SetUpdate(32); + auto id5 = sys.AddOrg(28, id2); + // std::cout << "\nAddOrg 29 (id6; parent id5)\n"; + sys.SetUpdate(39); + auto id6 = sys.AddOrg(29, id5); + // std::cout << "\nAddOrg 30 (id7; parent id1)\n"; + sys.SetUpdate(6); + auto id7 = sys.AddOrg(30, id1); + + CHECK(*id1 < *id2); + CHECK(sys.Parent(id2) == id1); + + // std::cout << "\nRemoveOrg (id2)\n"; sys.RemoveOrg(id1); sys.RemoveOrg(id2); double mpd = sys.GetMeanPairwiseDistance(); - std::cout << "MPD: " << mpd <GetNumOff() == 0); CHECK(outside_taxon->GetParent()->GetID() == 8); + CHECK(sys.GetMaxDepth() == 8); + auto active = sys.GetActive(); emp::vector>> active_vec(active.begin(), active.end()); emp::Sort(active_vec, [](emp::Ptr> & a, emp::Ptr> & b){ @@ -368,80 +395,92 @@ TEST_CASE("Test Systematics", "[Evolve]") CHECK(active_vec[10]->GetNumOrgs() == 1); CHECK(active_vec[10]->GetNumOff() == 0); CHECK(active_vec[10]->GetParent()->GetID() == 17); - } -TEST_CASE("Test not tracking ancestors", "[Evolve]") -{ +TEST_CASE("Test not tracking ancestors", "[Evolve]") { emp::Systematics sys([](const int & i){return i;}, true, false, false, false); - std::cout << "\nAddOrg 25 (id1, no parent)\n"; - auto id1 = sys.AddOrg(25, nullptr, 0); - std::cout << "\nAddOrg -10 (id2; parent id1)\n"; - auto id2 = sys.AddOrg(-10, id1, 6); - std::cout << "\nAddOrg 26 (id3; parent id1)\n"; - auto id3 = sys.AddOrg(26, id1, 10); - std::cout << "\nAddOrg 27 (id4; parent id2)\n"; - auto id4 = sys.AddOrg(27, id2, 25); - std::cout << "\nAddOrg 28 (id5; parent id2)\n"; - auto id5 = sys.AddOrg(28, id2, 32); - std::cout << "\nAddOrg 29 (id6; parent id5)\n"; - auto id6 = sys.AddOrg(29, id5, 39); - std::cout << "\nAddOrg 30 (id7; parent id1)\n"; - auto id7 = sys.AddOrg(30, id1, 6); - - - std::cout << "\nRemoveOrg (id2)\n"; + // std::cout << "\nAddOrg 25 (id1, no parent)\n"; + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, nullptr); + // std::cout << "\nAddOrg -10 (id2; parent id1)\n"; + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, id1); + // std::cout << "\nAddOrg 26 (id3; parent id1)\n"; + sys.SetUpdate(10); + auto id3 = sys.AddOrg(26, id1); + // std::cout << "\nAddOrg 27 (id4; parent id2)\n"; + sys.SetUpdate(25); + auto id4 = sys.AddOrg(27, id2); + // std::cout << "\nAddOrg 28 (id5; parent id2)\n"; + sys.SetUpdate(32); + auto id5 = sys.AddOrg(28, id2); + // std::cout << "\nAddOrg 29 (id6; parent id5)\n"; + sys.SetUpdate(39); + auto id6 = sys.AddOrg(29, id5); + // std::cout << "\nAddOrg 30 (id7; parent id1)\n"; + sys.SetUpdate(6); + auto id7 = sys.AddOrg(30, id1); + + + // std::cout << "\nRemoveOrg (id2)\n"; sys.RemoveOrg(id1); sys.RemoveOrg(id2); - double mpd = sys.GetMeanPairwiseDistance(); - std::cout << "Mean Pairwise Distance = " << mpd << "\n"; - - std::cout << "\nAddOrg 31 (id8; parent id7)\n"; - auto id8 = sys.AddOrg(31, id7, 11); - std::cout << "\nAddOrg 32 (id9; parent id8)\n"; - auto id9 = sys.AddOrg(32, id8, 19); + // std::cout << "\nAddOrg 31 (id8; parent id7)\n"; + sys.SetUpdate(11); + auto id8 = sys.AddOrg(31, id7); + // std::cout << "\nAddOrg 32 (id9; parent id8)\n"; + sys.SetUpdate(19); + auto id9 = sys.AddOrg(32, id8); - std::cout << "\nAddOrg 33 (id10; parent id8)\n"; - auto id10 = sys.AddOrg(33, id8, 19); + // std::cout << "\nAddOrg 33 (id10; parent id8)\n"; + auto id10 = sys.AddOrg(33, id8); sys.RemoveOrg(id7); sys.RemoveOrg(id8); sys.RemoveOrg(id10); - - std::cout << "\nAddOrg 34 (id11; parent id9)\n"; - auto id11 = sys.AddOrg(34, id9, 22); - std::cout << "\nAddOrg 35 (id12; parent id10)\n"; - auto id12 = sys.AddOrg(35, id11, 23); + // std::cout << "\nAddOrg 34 (id11; parent id9)\n"; + sys.SetUpdate(22); + auto id11 = sys.AddOrg(34, id9); + // std::cout << "\nAddOrg 35 (id12; parent id10)\n"; + sys.SetUpdate(23); + auto id12 = sys.AddOrg(35, id11); sys.RemoveOrg(id9); - std::cout << "\nAddOrg 36 (id13; parent id12)\n"; - auto id13 = sys.AddOrg(36, id12, 27); - std::cout << "\nAddOrg 37 (id14; parent id13)\n"; - auto id14 = sys.AddOrg(37, id13, 30); + // std::cout << "\nAddOrg 36 (id13; parent id12)\n"; + sys.SetUpdate(27); + auto id13 = sys.AddOrg(36, id12); + // std::cout << "\nAddOrg 37 (id14; parent id13)\n"; + sys.SetUpdate(30); + auto id14 = sys.AddOrg(37, id13); sys.RemoveOrg(id13); - std::cout << "\nAddOrg 38 (id15; parent id14)\n"; - auto id15 = sys.AddOrg(38, id14, 33); + // std::cout << "\nAddOrg 38 (id15; parent id14)\n"; + sys.SetUpdate(33); + auto id15 = sys.AddOrg(38, id14); sys.RemoveOrg(id14); - std::cout << "\nAddOrg 39 (id16; parent id11)\n"; - auto id16 = sys.AddOrg(39, id11, 35); - std::cout << "\nAddOrg 40 (id17; parent id11)\n"; - auto id17 = sys.AddOrg(40, id11, 35); + // std::cout << "\nAddOrg 39 (id16; parent id11)\n"; + sys.SetUpdate(35); + auto id16 = sys.AddOrg(39, id11); + // std::cout << "\nAddOrg 40 (id17; parent id11)\n"; + auto id17 = sys.AddOrg(40, id11); - std::cout << "\nAddOrg 41 (id18; parent id17)\n"; - auto id18 = sys.AddOrg(41, id17, 36); + // std::cout << "\nAddOrg 41 (id18; parent id17)\n"; + sys.SetUpdate(36); + auto id18 = sys.AddOrg(41, id17); std::cout << "\nAddOrg 42 (id19; parent id17)\n"; - auto id19 = sys.AddOrg(42, id17, 37); - REQUIRE(id17->GetTotalOffspring() > 0); + sys.SetUpdate(37); + auto id19 = sys.AddOrg(42, id17); + + CHECK(id17->GetTotalOffspring() > 0); std::cout << "id3 = " << id3 << std::endl; std::cout << "id4 = " << id4 << std::endl; @@ -531,11 +570,14 @@ TEST_CASE("Pointer to systematics", "[evo]") { sys.Delete(); } -TEST_CASE("Test Data Struct", "[evo]") -{ +TEST_CASE("Test Data Struct", "[evo]") { emp::Ptr >> sys; sys.New([](const int & i){return i;}, true, true, true, false); + sys->AddMutationCountDataNode(); + sys->AddVolatilityDataNode(); + sys->AddUniqueTaxaDataNode(); + auto id1 = sys->AddOrg(1, nullptr); id1->GetData().fitness.Add(2); id1->GetData().phenotype = 6; @@ -557,36 +599,79 @@ TEST_CASE("Test Data Struct", "[evo]") id4->GetData().phenotype = 3; auto id5 = sys->AddOrg(5, id4); - id5->GetData().mut_counts["substitution"] = 1; - id5->GetData().fitness.Add(2); - id5->GetData().phenotype = 6; + std::unordered_map muts; + muts["substitution"] = 1; + id5->GetData().RecordMutation(muts); + id5->GetData().RecordFitness(2); + id5->GetData().RecordPhenotype(6); + CHECK(id5->GetData().GetPhenotype() == 6); + CHECK(id5->GetData().GetFitness() == 2); CHECK(CountMuts(id4) == 3); CHECK(CountDeleteriousSteps(id4) == 1); CHECK(CountPhenotypeChanges(id4) == 1); CHECK(CountUniquePhenotypes(id4) == 2); + CHECK(LineageLength(id4) == 3); CHECK(CountMuts(id3) == 5); CHECK(CountDeleteriousSteps(id3) == 1); CHECK(CountPhenotypeChanges(id3) == 0); CHECK(CountUniquePhenotypes(id3) == 1); + CHECK(LineageLength(id3) == 2); CHECK(CountMuts(id5) == 4); CHECK(CountDeleteriousSteps(id5) == 2); CHECK(CountPhenotypeChanges(id5) == 2); CHECK(CountUniquePhenotypes(id5) == 2); + CHECK(LineageLength(id5) == 4); + + CHECK(FindDominant(*sys) == id4); + + sys->GetDataNode("mutation_count")->PullData(); + CHECK(sys->GetDataNode("mutation_count")->GetMean() == Approx(2.8)); + + sys->GetDataNode("volatility")->PullData(); + CHECK(sys->GetDataNode("volatility")->GetMean() == Approx(0.6)); + + sys->GetDataNode("unique_taxa")->PullData(); + CHECK(sys->GetDataNode("unique_taxa")->GetMean() == Approx(1.4)); + sys.Delete(); -} + emp::Ptr> sys2; + sys2.New([](const int & i){return i;}, true, true, true, false); + sys2->AddDeleteriousStepDataNode(); + auto new_tax = sys2->AddOrg(1, nullptr); + new_tax->GetData().RecordFitness(2); + CHECK(new_tax->GetData().GetFitness() == 2); + new_tax->GetData().RecordFitness(4); + CHECK(new_tax->GetData().GetFitness() == 3); + + emp::datastruct::fitness fit_data; + fit_data.RecordFitness(5); + new_tax->SetData(fit_data); + CHECK(new_tax->GetData().GetFitness() == 5); + + auto tax2 = sys2->AddOrg(2, new_tax); + tax2->GetData().RecordFitness(1); + + sys->GetDataNode("deleterious_steps")->PullData(); + CHECK(sys->GetDataNode("deleterious_steps")->GetMean() == Approx(.5)); + + + sys2.Delete(); + + +} TEST_CASE("World systematics integration", "[evo]") { - // std::function, emp::datastruct::mut_landscape_info>>)> setup_phenotype = [](emp::Ptr, emp::datastruct::mut_landscape_info>> tax){ - // tax->GetData().phenotype = emp::Sum(tax->GetInfo()); - // }; + std::function, emp::datastruct::mut_landscape_info>>, emp::vector &)> setup_phenotype = [](emp::Ptr, emp::datastruct::mut_landscape_info>> tax, emp::vector & org){ + tax->GetData().phenotype = emp::Sum(tax->GetInfo()); + }; using systematics_t = emp::Systematics< emp::vector, @@ -601,13 +686,14 @@ TEST_CASE("World systematics integration", "[evo]") { world.SetMutFun([](emp::vector & org, emp::Random & r){return 0;}); - // world.GetSystematics().OnNew(setup_phenotype); + sys->OnNew(setup_phenotype); world.InjectAt(emp::vector({1,2,3}), 0); - sys->GetTaxonAt(0)->GetData().RecordPhenotype(6); - sys->GetTaxonAt(0)->GetData().RecordFitness(2); + CHECK(sys->GetTaxonAt(0)->GetData().phenotype == 6); + sys->GetTaxonAt(0)->GetData().RecordPhenotype(10); + CHECK(sys->GetTaxonAt(0)->GetData().phenotype == 10); - REQUIRE(sys->GetTaxonAt(0)->GetData().phenotype == 6); + sys->GetTaxonAt(0)->GetData().RecordFitness(2); std::unordered_map mut_counts; mut_counts["substitution"] = 3; @@ -616,10 +702,10 @@ TEST_CASE("World systematics integration", "[evo]") { auto old_taxon = sys->GetTaxonAt(0); world.DoBirth(new_org,0); - REQUIRE(old_taxon->GetNumOrgs() == 0); - REQUIRE(old_taxon->GetNumOff() == 1); - REQUIRE(sys->GetTaxonAt(0)->GetParent()->GetData().phenotype == 6); - REQUIRE((*sys->GetActive().begin())->GetNumOrgs() == 1); + CHECK(old_taxon->GetNumOrgs() == 0); + CHECK(old_taxon->GetNumOff() == 1); + CHECK(sys->GetTaxonAt(0)->GetParent()->GetData().phenotype == 10); + CHECK((*sys->GetActive().begin())->GetNumOrgs() == 1); } @@ -628,35 +714,64 @@ emp::DataFile AddDominantFile(WORLD_TYPE & world){ using mut_count_t [[maybe_unused]] = std::unordered_map; using data_t = emp::datastruct::mut_landscape_info>; using org_t = emp::AvidaGP; - using systematics_t = emp::Systematics; + using systematics_t = emp::Systematics; - auto & file = world.SetupFile("dominant.csv"); - - std::function get_update = [&world](){return world.GetUpdate();}; - std::function dom_mut_count = [&world](){ - return CountMuts(dynamic_cast>(world.GetSystematics(0))->GetTaxonAt(0)); - }; - std::function dom_del_step = [&world](){ - return CountDeleteriousSteps(dynamic_cast>(world.GetSystematics(0))->GetTaxonAt(0)); - }; - std::function dom_phen_vol = [&world](){ - return CountPhenotypeChanges(dynamic_cast>(world.GetSystematics(0))->GetTaxonAt(0)); - }; - std::function dom_unique_phen = [&world](){ - return CountUniquePhenotypes(dynamic_cast>(world.GetSystematics(0))->GetTaxonAt(0)); - }; + auto & file = world.SetupFile("dominant.csv"); - - file.AddFun(get_update, "update", "Update"); - file.AddFun(dom_mut_count, "dominant_mutation_count", "sum of mutations along dominant organism's lineage"); - file.AddFun(dom_del_step, "dominant_deleterious_steps", "count of deleterious steps along dominant organism's lineage"); - file.AddFun(dom_phen_vol, "dominant_phenotypic_volatility", "count of changes in phenotype along dominant organism's lineage"); - file.AddFun(dom_unique_phen, "dominant_unique_phenotypes", "count of unique phenotypes along dominant organism's lineage"); - file.PrintHeaderKeys(); - return file; + std::function get_update = [&world](){return world.GetUpdate();}; + std::function dom_mut_count = [&world](){ + emp::Ptr> sys = world.GetSystematics(0); + emp::Ptr full_sys = sys.DynamicCast(); + if (full_sys->GetNumActive() > 0) { + return emp::CountMuts(emp::FindDominant(*full_sys)); + } + return 0; + }; + std::function dom_del_step = [&world](){ + emp::Ptr> sys = world.GetSystematics(0); + emp::Ptr full_sys = sys.DynamicCast(); + if (full_sys->GetNumActive() > 0) { + return emp::CountDeleteriousSteps(emp::FindDominant(*full_sys)); + } + return 0; + }; + std::function dom_phen_vol = [&world](){ + emp::Ptr> sys = world.GetSystematics(0); + emp::Ptr full_sys = sys.DynamicCast(); + if (full_sys->GetNumActive() > 0) { + return emp::CountPhenotypeChanges(emp::FindDominant(*full_sys)); + } + return 0; + }; + std::function dom_unique_phen = [&world](){ + emp::Ptr> sys = world.GetSystematics(0); + emp::Ptr full_sys = sys.DynamicCast(); + if (full_sys->GetNumActive() > 0) { + return emp::CountUniquePhenotypes(emp::FindDominant(*full_sys)); + } + return 0; + }; + std::function lin_len = [&world](){ + emp::Ptr> sys = world.GetSystematics(0); + emp::Ptr full_sys = sys.DynamicCast(); + if (full_sys->GetNumActive() > 0) { + return emp::LineageLength(emp::FindDominant(*full_sys)); + } + return 0; + }; + + file.AddFun(get_update, "update", "Update"); + file.AddFun(dom_mut_count, "dominant_mutation_count", "sum of mutations along dominant organism's lineage"); + file.AddFun(dom_del_step, "dominant_deleterious_steps", "count of deleterious steps along dominant organism's lineage"); + file.AddFun(dom_phen_vol, "dominant_phenotypic_volatility", "count of changes in phenotype along dominant organism's lineage"); + file.AddFun(dom_unique_phen, "dominant_unique_phenotypes", "count of unique phenotypes along dominant organism's lineage"); + file.AddFun(lin_len, "lineage_length", "number of taxa dominant organism's lineage"); + file.PrintHeaderKeys(); + return file; } +// Integration test for using multiple systematics managers in a world and recording data TEST_CASE("Run world", "[evo]") { using mut_count_t = std::unordered_map; using data_t = emp::datastruct::mut_landscape_info>; @@ -694,6 +809,32 @@ TEST_CASE("Run world", "[evo]") { world.AddSystematics(gene_sys); world.AddSystematics(phen_sys); + std::function, emp::AvidaGP&)> check_update = [&gene_sys, &world](emp::Ptr tax, emp::AvidaGP & org){ + CHECK(tax->GetOriginationTime() == gene_sys->GetUpdate()); + CHECK(tax->GetOriginationTime() == world.GetUpdate()); + CHECK(tax->GetNumOff() == 0); + }; + + gene_sys->OnNew(check_update); + + std::function)> extinction_checks = [&gene_sys, &world](emp::Ptr tax){ + CHECK(tax->GetDestructionTime() == gene_sys->GetUpdate()); + CHECK(tax->GetDestructionTime() == world.GetUpdate()); + CHECK(tax->GetNumOrgs() == 0); + }; + + gene_sys->OnExtinct(extinction_checks); + + std::function)> prune_checks = [&world](emp::Ptr tax){ + CHECK(tax->GetNumOrgs() == 0); + CHECK(tax->GetNumOff() == 0); + CHECK(tax->GetOriginationTime() <= world.GetUpdate()); + CHECK(tax->GetDestructionTime() <= world.GetUpdate()); + }; + + gene_sys->OnPrune(prune_checks); + + emp::Signal on_mutate_sig; ///< Trigger signal before organism gives birth. emp::Signal record_fit_sig; ///< Trigger signal before organism gives birth. emp::Signal)> record_phen_sig; ///< Trigger signal before organism gives birth. @@ -710,9 +851,12 @@ TEST_CASE("Run world", "[evo]") { world.GetSystematics(1).Cast()->GetTaxonAt(pos)->GetData().RecordPhenotype(phen); }); - // world.OnOrgPlacement([&last_mutation, &world](size_t pos){ - // world.GetSystematics(0).Cast()->GetTaxonAt(pos)->GetData().RecordMutation(last_mutation); - // }); + emp::Ptr> sys0 = world.GetSystematics(0); + emp::Ptr sys0_cast = sys0.DynamicCast(); + std::function, emp::AvidaGP&)> capture_mut_fun = [&last_mutation](emp::Ptr tax, emp::AvidaGP & org){ + tax->GetData().RecordMutation(last_mutation); + }; + sys0_cast->OnNew(capture_mut_fun); world.SetupSystematicsFile().SetTimingRepeat(1); world.SetupFitnessFile().SetTimingRepeat(1); @@ -720,7 +864,7 @@ TEST_CASE("Run world", "[evo]") { emp::AddPhylodiversityFile(world, 0, "genotype_phylodiversity.csv").SetTimingRepeat(1); emp::AddPhylodiversityFile(world, 1, "phenotype_phylodiversity.csv").SetTimingRepeat(1); emp::AddLineageMutationFile(world).SetTimingRepeat(1); - // AddDominantFile(world).SetTimingRepeat(1); + AddDominantFile(world).SetTimingRepeat(1); // emp::AddMullerPlotFile(world).SetTimingOnce(1); @@ -759,15 +903,7 @@ TEST_CASE("Run world", "[evo]") { 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)); - // }; - // } - - // Build a random initial popoulation. + // Build a random initial population. for (size_t i = 0; i < 1; i++) { emp::AvidaGP cpu; cpu.PushRandom(random, 20); @@ -784,47 +920,34 @@ TEST_CASE("Run world", "[evo]") { // Update the status of all organisms. world.ResetHardware(); world.Process(200); - double fit0 = world.CalcFitnessID(0); - std::cout << (ud+1) << " : " << 0 << " : " << fit0 << std::endl; + TournamentSelect(world, 2, 100); - // Keep the best individual. - EliteSelect(world, 1, 1); - - // Run a tournament for the rest... - TournamentSelect(world, 2, 99); - // LexicaseSelect(world, fit_set, POP_SIZE-1); - // EcoSelect(world, fit_fun, fit_set, 100, 5, POP_SIZE-1); for (size_t i = 0; i < world.GetSize(); i++) { record_fit_sig.Trigger(i, world.CalcFitnessID(i)); record_phen_sig.Trigger(i, phen_fun(world.GetOrg(i))); } world.Update(); - + CHECK(world.GetUpdate() == gene_sys->GetUpdate()); + CHECK(world.GetUpdate() == phen_sys->GetUpdate()); + CHECK(gene_sys->GetTaxonAt(0)->GetOriginationTime() <= world.GetUpdate()); } - - // 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; } - - -TEST_CASE("Test GetCanopy", "[evo]") -{ +TEST_CASE("Test GetCanopy", "[evo]") { emp::Systematics sys([](const int & i){return i;}, true, true, true, false); - auto id1 = sys.AddOrg(1, nullptr, 0); - auto id2 = sys.AddOrg(2, id1, 2); - auto id3 = sys.AddOrg(3, id1, 3); - auto id4 = sys.AddOrg(4, id2, 3); + sys.SetUpdate(0); + auto id1 = sys.AddOrg(1, nullptr); + sys.SetUpdate(2); + auto id2 = sys.AddOrg(2, id1); + sys.SetUpdate(3); + auto id3 = sys.AddOrg(3, id1); + auto id4 = sys.AddOrg(4, id2); - sys.RemoveOrg(id1, 3); - sys.RemoveOrg(id2, 5); + sys.RemoveOrg(id1); + sys.SetUpdate(5); + sys.RemoveOrg(id2); auto can_set = sys.GetCanopyExtantRoots(4); @@ -841,7 +964,8 @@ TEST_CASE("Test GetCanopy", "[evo]") CHECK(Has(can_set, id1)); CHECK(Has(can_set, id2)); - sys.RemoveOrg(id3, 7); + sys.SetUpdate(7); + sys.RemoveOrg(id3); can_set = sys.GetCanopyExtantRoots(2); @@ -851,10 +975,14 @@ TEST_CASE("Test GetCanopy", "[evo]") CHECK(can_set.size() == 1); CHECK(Has(can_set, id2)); - auto id5 = sys.AddOrg(5, id4, 8); - sys.RemoveOrg(id4, 9); - auto id6 = sys.AddOrg(6, id5, 10); - sys.RemoveOrg(id5, 11); + sys.SetUpdate(8); + auto id5 = sys.AddOrg(5, id4); + sys.SetUpdate(9); + sys.RemoveOrg(id4); + sys.SetUpdate(10); + auto id6 = sys.AddOrg(6, id5); + sys.SetUpdate(11); + sys.RemoveOrg(id5); can_set = sys.GetCanopyExtantRoots(7); // Should only be 4 @@ -866,16 +994,20 @@ TEST_CASE("Test GetCanopy", "[evo]") CHECK(can_set.size() == 1); CHECK(Has(can_set, id5)); - - auto id7 = sys.AddOrg(7, id6, 12); - auto id8 = sys.AddOrg(8, id7, 13); - auto id9 = sys.AddOrg(9, id8, 14); - auto id10 = sys.AddOrg(10, id9, 15); - - sys.RemoveOrg(id6, 20); - sys.RemoveOrg(id7, 20); - sys.RemoveOrg(id8, 20); - sys.RemoveOrg(id9, 20); + sys.SetUpdate(12); + auto id7 = sys.AddOrg(7, id6); + sys.SetUpdate(13); + auto id8 = sys.AddOrg(8, id7); + sys.SetUpdate(14); + auto id9 = sys.AddOrg(9, id8); + sys.SetUpdate(15); + auto id10 = sys.AddOrg(10, id9); + + sys.SetUpdate(20); + sys.RemoveOrg(id6); + sys.RemoveOrg(id7); + sys.RemoveOrg(id8); + sys.RemoveOrg(id9); can_set = sys.GetCanopyExtantRoots(22); // Should only be 10 @@ -904,13 +1036,6 @@ TEST_CASE("Test GetCanopy", "[evo]") CHECK(can_set.size() == 1); CHECK(Has(can_set, id5)); - - // auto id5 = sys.AddOrg(28, id2, 32); - // std::cout << "\nAddOrg 29 (id6; parent id5)\n"; - // auto id6 = sys.AddOrg(29, id5, 39); - // std::cout << "\nAddOrg 30 (id7; parent id1)\n"; - // auto id7 = sys.AddOrg(30, id1, 6); - } // Tests from Shao 1990 tree balance paper @@ -1080,3 +1205,234 @@ TEST_CASE("Tree balance", "[evo]") { CHECK(treecl.SackinIndex() == 18); CHECK(treecl.CollessLikeIndex() == Approx(1.746074)); } + +// Test that MRCA is properly updated when the MRCA is alive and then dies, +// causing a new taxon to be MRCA +TEST_CASE("Dieing MRCA", "[evo]") { + emp::Systematics tree([](const int & i){return i;}, true, true, false, false); + CHECK(!tree.GetTrackSynchronous()); + + // std::cout << "\nAddOrg 25 (id1, no parent)\n"; + tree.SetUpdate(0); + auto id1 = tree.AddOrg(25, nullptr); + // std::cout << "\nAddOrg -10 (id2; parent id1)\n"; + tree.SetUpdate(6); + auto id2 = tree.AddOrg(-10, id1); + // std::cout << "\nAddOrg 26 (id3; parent id1)\n"; + tree.SetUpdate(10); + auto id3 = tree.AddOrg(26, id1); + // std::cout << "\nAddOrg 27 (id4; parent id2)\n"; + tree.SetUpdate(25); + auto id4 = tree.AddOrg(27, id2); + // std::cout << "\nAddOrg 28 (id5; parent id2)\n"; + tree.SetUpdate(32); + auto id5 = tree.AddOrg(28, id2); + // std::cout << "\nAddOrg 29 (id6; parent id5)\n"; + tree.SetUpdate(39); + auto id6 = tree.AddOrg(29, id5); + // std::cout << "\nAddOrg 30 (id7; parent id1)\n"; + tree.SetUpdate(6); + auto id7 = tree.AddOrg(30, id1); + + CHECK(tree.GetMRCA() == id1); + tree.RemoveOrg(id7); + tree.RemoveOrg(id3); + tree.RemoveOrg(id2); + CHECK(tree.GetMRCA() == id1); + tree.RemoveOrg(id1); + CHECK(tree.GetMRCA() == id2); + tree.RemoveOrg(id4); + CHECK(tree.GetMRCA() == id5); + tree.RemoveOrg(id5); + CHECK(tree.GetMRCA() == id6); +} + +TEST_CASE("Test RemoveBefore", "[Evolve]") { + emp::Systematics sys([](const int & i){return i;}, true, true, false, false); + + // std::cout << "\nAddOrg 25 (id1, no parent)\n"; + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, nullptr); + // std::cout << "\nAddOrg -10 (id2; parent id1)\n"; + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, id1); + // std::cout << "\nAddOrg 26 (id3; parent id1)\n"; + sys.SetUpdate(10); + auto id3 = sys.AddOrg(26, id1); + // std::cout << "\nAddOrg 27 (id4; parent id2)\n"; + sys.SetUpdate(25); + auto id4 = sys.AddOrg(27, id2); + // std::cout << "\nAddOrg 28 (id5; parent id2)\n"; + sys.SetUpdate(32); + auto id5 = sys.AddOrg(28, id2); + // std::cout << "\nAddOrg 29 (id6; parent id5)\n"; + sys.SetUpdate(39); + auto id6 = sys.AddOrg(29, id5); + // std::cout << "\nAddOrg 30 (id7; parent id1)\n"; + sys.SetUpdate(6); + auto id7 = sys.AddOrg(30, id1); + sys.SetUpdate(33); + auto id8 = sys.AddOrg(2, id3); + auto id9 = sys.AddOrg(4, id8); + sys.SetUpdate(34); + auto id10 = sys.AddOrg(5, id9); + + sys.SetUpdate(40); + sys.RemoveOrg(id1); + sys.SetUpdate(41); + sys.RemoveOrg(id2); + sys.SetUpdate(40); + sys.RemoveOrg(id9); + sys.SetUpdate(60); + sys.RemoveOrg(id8); + + CHECK(emp::Has(sys.GetAncestors(), id1)); + CHECK(emp::Has(sys.GetAncestors(), id2)); + + sys.RemoveBefore(50); + + CHECK(!emp::Has(sys.GetAncestors(), id1)); + CHECK(!emp::Has(sys.GetAncestors(), id2)); + CHECK(emp::Has(sys.GetAncestors(), id9)); + CHECK(emp::Has(sys.GetActive(), id3)); + CHECK(emp::Has(sys.GetActive(), id4)); + CHECK(emp::Has(sys.GetActive(), id5)); + CHECK(emp::Has(sys.GetActive(), id6)); + CHECK(emp::Has(sys.GetActive(), id7)); + CHECK(emp::Has(sys.GetAncestors(), id8)); + + sys.RemoveBefore(70); + CHECK(!emp::Has(sys.GetActive(), id8)); + CHECK(!emp::Has(sys.GetActive(), id9)); + +} + +TEST_CASE("Test Snapshot", "[Evolve]") { + emp::Systematics sys([](const int & i){return i;}, true, true, true, false); + + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, nullptr); + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, id1); + sys.SetUpdate(10); + auto id3 = sys.AddOrg(26, id1); + sys.SetUpdate(25); + auto id4 = sys.AddOrg(27, id2); + sys.SetUpdate(32); + auto id5 = sys.AddOrg(28, id2); + sys.SetUpdate(39); + auto id6 = sys.AddOrg(29, id5); + sys.SetUpdate(6); + auto id7 = sys.AddOrg(30, id1); + sys.SetUpdate(33); + auto id8 = sys.AddOrg(2, id3); + auto id9 = sys.AddOrg(4, id8); + sys.SetUpdate(34); + auto id10 = sys.AddOrg(5, id9); + + sys.SetUpdate(40); + sys.RemoveOrg(id1); + sys.SetUpdate(41); + sys.RemoveOrg(id2); + sys.SetUpdate(40); + sys.RemoveOrg(id9); + sys.SetUpdate(60); + sys.RemoveOrg(id8); + sys.RemoveOrg(id10); + + sys.AddSnapshotFun([](const emp::Taxon & t){return std::to_string(t.GetInfo());}, "genome", "genome"); + sys.Snapshot("systematics_snapshot.csv"); + + // TODO: Would be nice to compare this to existing snapshot file, but lines could be in any order +} + +TEST_CASE("Test Prune", "[Evolve]") { + emp::Systematics sys([](const int & i){return i;}, true, true, false, false); + + int prunes = 0; + std::function >)> prune_fun = [&prunes](emp::Ptr> tax){prunes++;}; + sys.OnPrune(prune_fun); + + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, nullptr); + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, id1); + sys.SetUpdate(10); + auto id3 = sys.AddOrg(26, id1); + sys.SetUpdate(25); + auto id4 = sys.AddOrg(27, id2); + sys.SetUpdate(32); + auto id5 = sys.AddOrg(28, id2); + sys.SetUpdate(39); + auto id6 = sys.AddOrg(29, id5); + sys.SetUpdate(6); + auto id7 = sys.AddOrg(30, id1); + sys.SetUpdate(33); + auto id8 = sys.AddOrg(2, id3); + auto id9 = sys.AddOrg(4, id8); + sys.SetUpdate(34); + auto id10 = sys.AddOrg(5, id9); + auto id11 = sys.AddOrg(5, id3); + + sys.SetUpdate(40); + sys.RemoveOrg(id1); + sys.RemoveOrg(id2); + sys.RemoveOrg(id3); + sys.RemoveOrg(id8); + sys.RemoveOrg(id9); + + CHECK(sys.GetMRCA() == id1); + + CHECK(prunes == 0); + CHECK(Has(sys.GetAncestors(), id9)); + sys.RemoveOrg(id10); + CHECK(prunes == 3); + CHECK(!Has(sys.GetAncestors(), id9)); + CHECK(Has(sys.GetAncestors(), id3)); + + sys.RemoveOrg(id11); + CHECK(prunes == 5); + CHECK(!Has(sys.GetAncestors(), id3)); + CHECK(sys.GetMRCA() == id1); + + sys.RemoveOrg(id7); + CHECK(prunes == 6); + CHECK(sys.GetMRCA() == id2); +} + +TEST_CASE("Test tracking position", "[Evolve]") { + emp::Systematics sys([](const int & i){return i;}, true, true, true, true); + + sys.SetUpdate(0); + auto id1 = sys.AddOrg(25, {0,0}, nullptr); + sys.SetUpdate(6); + auto id2 = sys.AddOrg(-10, {1,0}, id1); + CHECK(sys.Parent(id2) == id1); + sys.SetNextParent(id1); + sys.SetUpdate(10); + sys.AddOrg(26, {2,0}); + auto id3 = sys.GetMostRecent(); + CHECK(id3->GetParent() == id1); + CHECK(id3->GetInfo() == 26); + CHECK(id3->GetOriginationTime() == 10); + sys.SetNextParent({1,0}); + sys.SetUpdate(25); + sys.AddOrg(27, {3,0}); + auto id4 = sys.GetMostRecent(); + CHECK(id4->GetParent() == id2); + CHECK(id4->GetInfo() == 27); + CHECK(id4->GetOriginationTime() == 25); + + sys.SetUpdate(40); + sys.RemoveOrg({0,0}); + CHECK(id1->GetDestructionTime() == 40); + CHECK(id1->GetNumOrgs() == 0); + + sys.RemoveOrgAfterRepro(id4); + CHECK(!Has(sys.GetAncestors(), id4)); + sys.SetUpdate(34); + auto id5 = sys.AddOrg(88, {4,0}, id4); + CHECK(id4->GetNumOrgs() == 0); + CHECK(id4->GetNumOff() == 1); + CHECK(Has(sys.GetAncestors(), id4)); +} diff --git a/tests/data/DataFile.cpp b/tests/data/DataFile.cpp index bd35c60ff8..d7a00ac796 100644 --- a/tests/data/DataFile.cpp +++ b/tests/data/DataFile.cpp @@ -1,15 +1,19 @@ /** * @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 2021 + * @date 2020-2022 * * @file DataFile.cpp */ +#define CATCH_CONFIG_MAIN + #ifndef EMP_TRACK_MEM #define EMP_TRACK_MEM #endif +#include "third-party/Catch/single_include/catch2/catch.hpp" + #include #include #include @@ -17,202 +21,210 @@ #include #include -#include "third-party/Catch/single_include/catch2/catch.hpp" - #include "emp/data/DataFile.hpp" #include "emp/data/DataInterface.hpp" #include "emp/data/DataManager.hpp" #include "emp/data/DataNode.hpp" -// This function will tell us if the file generated by DataFile is identitcal + +// This function will tell us if the file generated by DataFile is identical // to the expected file. bool compareFiles(const std::string& p1, const std::string& p2) { - // From mtrw's answer to https://stackoverflow.com/questions/6163611/compare-two-files - std::ifstream f1(p1, std::ifstream::binary|std::ifstream::ate); - std::ifstream f2(p2, std::ifstream::binary|std::ifstream::ate); - - if (f1.fail() || f2.fail()) { - return false; //file problem - } - - if (f1.tellg() != f2.tellg()) { - return false; //size mismatch - } - - //seek back to beginning and use std::equal to compare contents - f1.seekg(0, std::ifstream::beg); - f2.seekg(0, std::ifstream::beg); - return std::equal(std::istreambuf_iterator(f1.rdbuf()), - std::istreambuf_iterator(), - std::istreambuf_iterator(f2.rdbuf())); + // From mtrw's answer to https://stackoverflow.com/questions/6163611/compare-two-files + std::ifstream f1(p1, std::ifstream::binary|std::ifstream::ate); + std::ifstream f2(p2, std::ifstream::binary|std::ifstream::ate); + + if (f1.fail() || f2.fail()) { + return false; //file problem + } + + if (f1.tellg() != f2.tellg()) { + return false; //size mismatch + } + + //seek back to beginning and use std::equal to compare contents + f1.seekg(0, std::ifstream::beg); + f2.seekg(0, std::ifstream::beg); + return std::equal(std::istreambuf_iterator(f1.rdbuf()), + std::istreambuf_iterator(), + std::istreambuf_iterator(f2.rdbuf())); } + int test_fun() { static int val = 10; return val += 3; } -// TEST_CASE("Test DataFile", "[data]") { -// int test_int = 5; - -// emp::DataFile dfile("new_test_file.dat"); - -// REQUIRE(dfile.GetFilename() == "new_test_file.dat"); - -// emp::DataMonitor data_fracs; -// emp::DataMonitor data_squares; -// emp::DataMonitor data_cubes; - -// dfile.AddCurrent(data_fracs); -// dfile.AddCurrent(data_squares); -// dfile.AddCurrent(data_cubes); -// dfile.AddMean(data_cubes); -// dfile.AddTotal(data_cubes); -// dfile.AddMin(data_cubes); -// dfile.AddMax(data_cubes); -// dfile.AddStandardDeviation(data_cubes); -// dfile.AddSkew(data_cubes); -// dfile.AddKurtosis(data_cubes); -// dfile.AddFun(test_fun); -// dfile.AddVar(test_int); - -// double frac = 0.0; -// for (size_t i = 0; i < 10; i++) { -// test_int += i; -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(); - -// // std::cout << i << std::endl; -// } - -// dfile.SetupLine("[[",":", "]]\n"); -// for (size_t i = 10; i < 20; i++) { -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(); - -// // std::cout << i << std::endl; -// } - -// REQUIRE(compareFiles("new_test_file.dat", "test_file.dat")); -// } +TEST_CASE("Test DataFile", "[data]") { + int test_int = 5; + + emp::DataFile dfile("new_test_file.dat"); + + REQUIRE(dfile.GetFilename() == "new_test_file.dat"); + + emp::DataMonitor data_squares; + emp::DataMonitor data_cubes; + + dfile.AddCurrent(data_squares); + dfile.AddCurrent(data_cubes); + dfile.AddTotal(data_cubes); + dfile.AddMin(data_cubes); + dfile.AddMax(data_cubes); + dfile.AddFun(test_fun); + dfile.AddVar(test_int); + + for (size_t i = 0; i < 10; i++) { + test_int += i; + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(); + + // std::cout << i << std::endl; + } + + dfile.SetupLine("[[",":", "]]\n"); + for (size_t i = 10; i < 20; i++) { + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(); + + // std::cout << i << std::endl; + } + + REQUIRE(compareFiles("new_test_file.dat", "test_file.dat")); +} TEST_CASE("Test Container DataFile", "[data]") { - emp::vector cool_data({1,2,3}); - std::function(void)> get_data = [&cool_data](){return cool_data;}; - emp::ContainerDataFile> dfile("new_test_container_file.dat"); + emp::vector cool_data({1,2,3}); + std::function(void)> get_data = [&cool_data](){return cool_data;}; + emp::ContainerDataFile> dfile("new_test_container_file.dat"); - dfile.SetUpdateContainerFun(get_data); + dfile.SetUpdateContainerFun(get_data); - std::function return_val = [](int i){return i;}; - std::function square_val = [](int i){return i*i;}; + std::function return_val = [](int i){return i;}; + std::function square_val = [](int i){return i*i;}; - dfile.AddContainerFun(return_val, "value", "value"); - dfile.AddContainerFun(square_val, "squared", "value squared"); + dfile.AddContainerFun(return_val, "value", "value"); + dfile.AddContainerFun(square_val, "squared", "value squared"); - dfile.PrintHeaderKeys(); - dfile.Update(); - cool_data.push_back(5); - dfile.Update(); + dfile.PrintHeaderKeys(); + dfile.Update(); + cool_data.push_back(5); + dfile.Update(); - // Since update is virtual, this should work on a pointer to a base datafile - emp::DataFile * data_ptr = & dfile; + // Since update is virtual, this should work on a pointer to a base datafile + emp::DataFile * data_ptr = & dfile; - cool_data.push_back(6); - data_ptr->Update(); + cool_data.push_back(6); + data_ptr->Update(); - dfile.SetTimingRepeat(2); - cool_data.clear(); - cool_data.push_back(7); - cool_data.push_back(3); - dfile.Update(2); - dfile.Update(3); - cool_data.push_back(2); - data_ptr->Update(4); - data_ptr->Update(5); + dfile.SetTimingRepeat(2); + cool_data.clear(); + cool_data.push_back(7); + cool_data.push_back(3); + dfile.Update(2); + dfile.Update(3); + cool_data.push_back(2); + data_ptr->Update(4); + data_ptr->Update(5); - REQUIRE(compareFiles("new_test_container_file.dat", "test_container_file.dat")); + REQUIRE(compareFiles("new_test_container_file.dat", "test_container_file.dat")); - auto dfile2 = emp::MakeContainerDataFile(get_data, "new_test_make_container_file.dat"); - dfile2.AddContainerFun(return_val, "value", "value"); - dfile2.AddContainerFun(square_val, "squared", "value squared"); + auto dfile2 = emp::MakeContainerDataFile(get_data, "new_test_make_container_file.dat"); + dfile2.AddContainerFun(return_val, "value", "value"); + dfile2.AddContainerFun(square_val, "squared", "value squared"); - dfile2.PrintHeaderKeys(); - dfile2.Update(); + dfile2.PrintHeaderKeys(); + dfile2.Update(); - REQUIRE(compareFiles("new_test_make_container_file.dat", "test_make_container_file.dat")); + REQUIRE(compareFiles("new_test_make_container_file.dat", "test_make_container_file.dat")); } -// TEST_CASE("Test timing", "[data]") { -// int test_int = 5; +TEST_CASE("Test timing", "[data]") { + int test_int = 5; + + emp::DataFile dfile("new_test_timing_file.dat"); + + emp::DataMonitor data_squares; + emp::DataMonitor data_cubes; -// emp::DataFile dfile("new_test_timing_file.dat"); + dfile.AddVar(test_int); + dfile.AddCurrent(data_squares); + dfile.AddCurrent(data_cubes); + dfile.AddTotal(data_cubes); + dfile.AddMin(data_cubes); + dfile.AddMax(data_cubes); + dfile.AddFun(test_fun); -// emp::DataMonitor data_fracs; -// emp::DataMonitor data_squares; -// emp::DataMonitor data_cubes; + dfile.SetTimingRepeat(2); -// dfile.AddVar(test_int); -// dfile.AddCurrent(data_fracs); -// dfile.AddCurrent(data_squares); -// dfile.AddCurrent(data_cubes); -// dfile.AddMean(data_cubes); -// dfile.AddTotal(data_cubes); -// dfile.AddMin(data_cubes); -// dfile.AddMax(data_cubes); -// dfile.AddFun(test_fun); + for (size_t i = 0; i < 10; i++) { + test_int = i; + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(i); -// double frac = 0.0; + // std::cout << i << std::endl; + } -// dfile.SetTimingRepeat(2); + dfile.SetTimingOnce(5); -// for (size_t i = 0; i < 10; i++) { -// test_int = i; -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(i); + for (size_t i = 0; i < 10; i++) { + test_int = i; + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(i); + // std::cout << i << std::endl; + } -// // std::cout << i << std::endl; -// } + dfile.SetTimingRange(2, 3, 9); -// dfile.SetTimingOnce(5); + for (size_t i = 0; i < 10; i++) { + test_int = i; + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(i); -// for (size_t i = 0; i < 10; i++) { -// test_int = i; -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(i); -// // std::cout << i << std::endl; -// } + // std::cout << i << std::endl; + } -// dfile.SetTimingRange(2, 3, 9); + dfile.SetTiming([](size_t ud){return (bool)floor(sqrt((double)ud) == ceil(sqrt((double)ud)));}); -// for (size_t i = 0; i < 10; i++) { -// test_int = i; -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(i); + for (size_t i = 0; i < 10; i++) { + test_int = i; + data_squares.Add((int)(i*i)); + data_cubes.Add(i*i*i); + dfile.Update(i); -// // std::cout << i << std::endl; -// } + // std::cout << i << std::endl; + } -// dfile.SetTiming([](size_t ud){return (bool)floor(sqrt((double)ud) == ceil(sqrt((double)ud)));}); + REQUIRE(compareFiles("new_test_timing_file.dat", "test_timing_file.dat")); +} + +TEST_CASE("Test Floating Point DataFile", "[data]") { + // We're not going to bother checking output because + // floating point precision is messy, but this + // should at least not have any run-time errors + + double test_double = .2; -// for (size_t i = 0; i < 10; i++) { -// test_int = i; -// data_fracs.Add(frac += 0.01); -// data_squares.Add((int)(i*i)); -// data_cubes.Add(i*i*i); -// dfile.Update(i); + emp::DataFile dfile("new_fp_test_file.dat"); -// // std::cout << i << std::endl; -// } + emp::DataMonitor data_frac; -// REQUIRE(compareFiles("new_test_timing_file.dat", "test_timing_file.dat")); -// } + dfile.AddVar(test_double); + dfile.AddCurrent(data_frac); + dfile.AddTotal(data_frac); + dfile.AddMin(data_frac); + dfile.AddMax(data_frac); + dfile.AddAllStats(data_frac); + + for (size_t i = 0; i < 10; i++) { + test_double += .3; + data_frac.Add(test_double); + dfile.Update(i); + } + +} diff --git a/tests/data/Datum.cpp b/tests/data/Datum.cpp index 172bad8d5f..dd2d710a29 100644 --- a/tests/data/Datum.cpp +++ b/tests/data/Datum.cpp @@ -49,7 +49,7 @@ TEST_CASE("Test Datum", "[data]") CHECK(d1 == "789"); CHECK(d2 == "123.000000"); - CHECK(d3 == "456.000000"); + CHECK(d3 == "456"); CHECK(d4 == "789.000000"); // Check string comparisons (d1 and d3) @@ -120,6 +120,6 @@ TEST_CASE("Test Datum", "[data]") y = d2.AsString(); CHECK(x == 123.0); - CHECK(y == "123.000000"); + CHECK(y == "123"); } diff --git a/tests/data/test_file.dat b/tests/data/test_file.dat index 6dc26e85df..93d09151fe 100644 --- a/tests/data/test_file.dat +++ b/tests/data/test_file.dat @@ -1,20 +1,20 @@ -0.01,0,0,0,0,0,0,0,-nan,-nan,13,5 -0.02,1,1,0.5,1,0,1,0.5,0,-2,16,6 -0.03,4,8,3,9,0,8,3.55903,0.665469,-1.5,19,8 -0.04,9,27,9,36,0,27,10.8397,0.90094,-0.906129,22,11 -0.05,16,64,20,100,0,64,24.0416,0.992224,-0.568543,25,15 -0.06,25,125,37.5,225,0,125,44.8655,1.0341,-0.382533,28,20 -0.07,36,216,63,441,0,216,75.0124,1.05548,-0.274217,31,26 -0.08,49,343,98,784,0,343,116.183,1.06714,-0.207518,34,33 -0.09,64,512,144,1296,0,512,170.078,1.07375,-0.1645,37,41 -0.1,81,729,202.5,2025,0,729,238.399,1.07755,-0.135707,40,50 -[[0.11:100:1000:275:3025:0:1000:322.847:1.07969:-0.115862:43:50]] -[[0.12:121:1331:363:4356:0:1331:425.121:1.08084:-0.101862:46:50]] -[[0.13:144:1728:468:6084:0:1728:546.924:1.08137:-0.0918054:49:50]] -[[0.14:169:2197:591.5:8281:0:2197:689.956:1.0815:-0.0844808:52:50]] -[[0.15:196:2744:735:11025:0:2744:855.917:1.08138:-0.0790924:55:50]] -[[0.16:225:3375:900:14400:0:3375:1046.51:1.0811:-0.0751027:58:50]] -[[0.17:256:4096:1088:18496:0:4096:1263.43:1.08071:-0.0721403:61:50]] -[[0.18:289:4913:1300.5:23409:0:4913:1508.39:1.08027:-0.0699431:64:50]] -[[0.19:324:5832:1539:29241:0:5832:1783.08:1.07978:-0.0683227:67:50]] -[[0.2:361:6859:1805:36100:0:6859:2089.2:1.07928:-0.0671417:70:50]] +0,0,0,0,0,13,5 +1,1,1,0,1,16,6 +4,8,9,0,8,19,8 +9,27,36,0,27,22,11 +16,64,100,0,64,25,15 +25,125,225,0,125,28,20 +36,216,441,0,216,31,26 +49,343,784,0,343,34,33 +64,512,1296,0,512,37,41 +81,729,2025,0,729,40,50 +[[100:1000:3025:0:1000:43:50]] +[[121:1331:4356:0:1331:46:50]] +[[144:1728:6084:0:1728:49:50]] +[[169:2197:8281:0:2197:52:50]] +[[196:2744:11025:0:2744:55:50]] +[[225:3375:14400:0:3375:58:50]] +[[256:4096:18496:0:4096:61:50]] +[[289:4913:23409:0:4913:64:50]] +[[324:5832:29241:0:5832:67:50]] +[[361:6859:36100:0:6859:70:50]] diff --git a/tests/data/test_timing_file.dat b/tests/data/test_timing_file.dat index e6a0127aab..87a937e4b8 100644 --- a/tests/data/test_timing_file.dat +++ b/tests/data/test_timing_file.dat @@ -1,13 +1,13 @@ -0,0.01,0,0,0,0,0,0,73 -2,0.03,4,8,3,9,0,8,76 -4,0.05,16,64,20,100,0,64,79 -6,0.07,36,216,63,441,0,216,82 -8,0.09,64,512,144,1296,0,512,85 -5,0.16,25,125,140.625,2250,0,729,88 -2,0.23,4,8,176.478,4059,0,729,91 -5,0.26,25,125,164.423,4275,0,729,94 -8,0.29,64,512,184.345,5346,0,729,97 -0,0.31,0,0,195.968,6075,0,729,100 -1,0.32,1,1,189.875,6076,0,729,103 -4,0.35,16,64,176.429,6175,0,729,106 -9,0.4,81,729,202.5,8100,0,729,109 +0,0,0,0,0,0,73 +2,4,8,9,0,8,76 +4,16,64,100,0,64,79 +6,36,216,441,0,216,82 +8,64,512,1296,0,512,85 +5,25,125,2250,0,729,88 +2,4,8,4059,0,729,91 +5,25,125,4275,0,729,94 +8,64,512,5346,0,729,97 +0,0,0,6075,0,729,100 +1,1,1,6076,0,729,103 +4,16,64,6175,0,729,106 +9,81,729,8100,0,729,109 diff --git a/tests/hardware/Makefile b/tests/hardware/Makefile index d94cc64f18..b993e3ba55 100644 --- a/tests/hardware/Makefile +++ b/tests/hardware/Makefile @@ -1,4 +1,4 @@ -TEST_NAMES = event_driven_gp avida_gp BitSorter LinearCode +TEST_NAMES = event_driven_gp avida_gp BitSorter LinearCode VirtualCPU # -O3 -Wl,--stack,8388608 -ftrack-macro-expansion=0 FLAGS = -std=c++17 -g -pthread -Wall -Wno-unused-function -Wno-unused-private-field -I../../include/ -I../../ -I../../third-party/cereal/include/ -DCATCH_CONFIG_MAIN diff --git a/tests/hardware/VirtualCPU.cpp b/tests/hardware/VirtualCPU.cpp new file mode 100644 index 0000000000..ac3ff7114e --- /dev/null +++ b/tests/hardware/VirtualCPU.cpp @@ -0,0 +1,1598 @@ +/** + * @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 2021-2022 + * + * @file VirtualCPU.cpp + * + * TODO + * [ ] *INSTRUCTIONS struct + * [ ] Constructors assign values correctly + * [ ] Comparison operators work correctly + * [ ] Set functions as expected + * [ ] Defaults? + * [ ] Args? + * [ ] Expanded heads? + * [ ] Initialize (all internal method calls _should_ already be tested, just need combined) + */ + +#define CATCH_CONFIG_MAIN + +#include "third-party/Catch/single_include/catch2/catch.hpp" + +#include +#include + +#include "emp/hardware/VirtualCPU.hpp" + + +// VirtualCPU is currently constructed to always be derived from. +// Here we create an empty derived class +class Derived : public emp::VirtualCPU { + public: + using base_t = emp::VirtualCPU; + Derived() : base_t() { ; } + Derived(const Derived::genome_t& genome) : base_t(genome) { ; } + Derived(const Derived& other) : base_t(other) { ; } + Derived(const Derived&& other) : base_t(other) { ; } +}; + +Derived CreateSeedCPU(){ + Derived cpu_init; + emp::Random random(56); + for(size_t i = 0; i < 10; ++i) + cpu_init.PushRandomInst(random); + // Make a change to the working genome + while(cpu_init.genome[0] == cpu_init.genome_working[0]) + cpu_init.genome_working[0] = cpu_init.GetRandomInst(random); + CHECK(cpu_init.genome.size() == 10); + CHECK(cpu_init.genome_working.size() == 10); + CHECK(7 == (cpu_init.inst_ptr = 7)); + CHECK(3 == (cpu_init.read_head = 3)); + CHECK(4 == (cpu_init.write_head = 4)); + cpu_init.label_idx_vec.push_back(3); + CHECK(2 == (cpu_init.regs[0] = 2)); + CHECK(38 == (cpu_init.regs[1] = 38)); + CHECK(2309 == (cpu_init.regs[2] = 2309)); + CHECK(5 == (cpu_init.inputs[0] = 5)); + CHECK(2 == (cpu_init.outputs[0] = 2)); + CHECK(1 == (cpu_init.active_stack_idx = 1)); + cpu_init.stacks[cpu_init.active_stack_idx].push_back(1); + CHECK(cpu_init.stacks[cpu_init.active_stack_idx].size() == 1); + return cpu_init; +} + +TEST_CASE("VirtualCPU_Variables", "[Hardware]") { + Derived cpu; + CHECK(cpu.stacks.size() == cpu.NUM_STACKS); // NUM_STACKS is actually used + // All stacks are initially empty + for(size_t idx = 0; idx < cpu.stacks.size(); ++idx){ + CHECK(cpu.stacks[idx].empty()); + } + CHECK(cpu.active_stack_idx == 0); // Default to first stack + CHECK(cpu.GetNumNops() == 3); // Default instruction set has 3 nops + CHECK(cpu.nop_id_map.size() == 3); // All three default nops present in set + CHECK(emp::Has(cpu.nop_id_map, 0)); // NopA in set + CHECK(emp::Has(cpu.nop_id_map, 1)); // NopB in set + CHECK(emp::Has(cpu.nop_id_map, 2)); // NopC in set + CHECK(cpu.GetNumRegs() == 3); // 3 nops in instruction set forces cpu to have 3 registers + // All registers start at their index value + for(size_t idx = 0; idx < cpu.GetNumRegs(); ++idx){ + CHECK(cpu.regs[idx] == idx); + } + CHECK(cpu.inputs.size() == 0); // Start with no inputs + CHECK(cpu.outputs.size() == 0); // Start with no outputs + CHECK(cpu.inst_ptr == 0); // All heads default to 0 + CHECK(cpu.read_head == 0); // All heads default to 0 + CHECK(cpu.write_head == 0); // All heads default to 0 + CHECK(cpu.flow_head == 0); // All heads default to 0 + CHECK(cpu.copied_inst_id_vec.size() == 0); // No instructions copied + CHECK(cpu.label_idx_vec.size() == 0); // No labels in empty genome + CHECK(cpu.nops_need_curated == true); // Nops are NOT curated at initialization + CHECK(cpu.expanded_nop_args == false);// Nop arguments are NOT expanded by default + CHECK(cpu.are_nops_counted == true); // Defaults to false but flipped during initialization + CHECK(cpu.are_regs_expanded == true); // Defaults to false but flipped during initialization + /* + [X] NUM_STACKS is actually used + [X] Stacks are initially empty + [X] We store all the nops in the nop set + [X] Correct number of registers is initialized + [X] Correct number of inputs are initialized + [X] Correct number of outputs are initialized + [X] Correct number of stacks are initialized + [X] We default to the first stack + [X] Heads are initialized to the start of the genome: + [X] IP + [X] Read + [X] Write + [X] Flow + [ ] Expanded heads + [x] Copied instructions are initially non-existent + [X] We have no labels by default + [X] Nops need curated by default + [X] Default to NON-expanded nop set + [X] are_nops_counted set true during initialization + [X] are_regs_expanded set true during initialization + */ +} +TEST_CASE("VirtualCPU_Constructors", "[Hardware]") { + { // Default constructor + Derived cpu; + CHECK(cpu.GetInstLib().Raw() == &Derived::inst_lib_t::DefaultInstLib()); + CHECK(cpu.GetGenomeSize() == 0); + CHECK(cpu.GetWorkingGenomeSize() == 0); + // VARIABLES -- should default + { + CHECK(cpu.stacks.size() == cpu.NUM_STACKS); // NUM_STACKS is actually used + // All stacks are initially empty + for(size_t idx = 0; idx < cpu.stacks.size(); ++idx){ + CHECK(cpu.stacks[idx].empty()); + } + CHECK(cpu.active_stack_idx == 0); // Default to first stack + CHECK(cpu.GetNumNops() == 3); // Default instruction set has 3 nops + CHECK(cpu.nop_id_map.size() == 3); // All three default nops present in set + CHECK(emp::Has(cpu.nop_id_map, 0)); // NopA in set + CHECK(emp::Has(cpu.nop_id_map, 1)); // NopB in set + CHECK(emp::Has(cpu.nop_id_map, 2)); // NopC in set + CHECK(cpu.GetNumRegs() == 3); // 3 nops in instruction set forces cpu to have 3 registers + // All registers start at their index value + for(size_t idx = 0; idx < cpu.GetNumRegs(); ++idx){ + CHECK(cpu.regs[idx] == idx); + } + CHECK(cpu.inputs.size() == 0); // Start with no inputs + CHECK(cpu.outputs.size() == 0); // Start with no outputs + CHECK(cpu.inst_ptr == 0); // All heads default to 0 + CHECK(cpu.read_head == 0); // All heads default to 0 + CHECK(cpu.write_head == 0); // All heads default to 0 + CHECK(cpu.flow_head == 0); // All heads default to 0 + CHECK(cpu.copied_inst_id_vec.size() == 0); // No instructions copied + CHECK(cpu.label_idx_vec.size() == 0); // No labels in empty genome + CHECK(cpu.nops_need_curated == true); // Nops are NOT curated at initialization + CHECK(cpu.expanded_nop_args == false);// Nop arguments are NOT expanded by default + CHECK(cpu.are_nops_counted == true); // Defaults to false but flipped during initialization + CHECK(cpu.are_regs_expanded == true); // Defaults to false but flipped during initialization + } + } + { // Genome constructor + // Create default CPU to steal its instruction library + Derived cpu_init; + // Create a genome with ten random instructions + Derived::genome_t genome(Derived::inst_lib_t::DefaultInstLib()); + emp::Random random(55); + for(size_t i = 0; i < 10; ++i) + genome.push_back(cpu_init.GetRandomInst(random)); + CHECK(genome.size() == 10); + // Create VirtualCPU using genome-only constructor + Derived cpu(genome); + // Ensure cpu has a two copies of genome - one to keep and one to work with + CHECK(cpu.GetGenomeSize() == 10); + CHECK(cpu.GetWorkingGenomeSize() == 10); + for(size_t i = 0; i < 10; ++i){ + CHECK(cpu.genome[i] == genome[i]); + CHECK(cpu.genome_working[i] == genome[i]); + } + // VARIABLES -- should default other than genome / working genome + { + CHECK(cpu.stacks.size() == cpu.NUM_STACKS); // NUM_STACKS is actually used + // All stacks are initially empty + for(size_t idx = 0; idx < cpu.stacks.size(); ++idx){ + CHECK(cpu.stacks[idx].empty()); + } + CHECK(cpu.active_stack_idx == 0); // Default to first stack + CHECK(cpu.GetNumNops() == 3); // Default instruction set has 3 nops + CHECK(cpu.nop_id_map.size() == 3); // All three default nops present in set + CHECK(emp::Has(cpu.nop_id_map, 0)); // NopA in set + CHECK(emp::Has(cpu.nop_id_map, 1)); // NopB in set + CHECK(emp::Has(cpu.nop_id_map, 2)); // NopC in set + CHECK(cpu.GetNumRegs() == 3); // 3 nops in instruction set forces cpu to have 3 registers + // All registers start at their index value + for(size_t idx = 0; idx < cpu.GetNumRegs(); ++idx){ + CHECK(cpu.regs[idx] == idx); + } + CHECK(cpu.inputs.size() == 0); // Start with no inputs + CHECK(cpu.outputs.size() == 0); // Start with no outputs + CHECK(cpu.inst_ptr == 0); // All heads default to 0 + CHECK(cpu.read_head == 0); // All heads default to 0 + CHECK(cpu.write_head == 0); // All heads default to 0 + CHECK(cpu.flow_head == 0); // All heads default to 0 + CHECK(cpu.copied_inst_id_vec.size() == 0); // No instructions copied + CHECK(cpu.label_idx_vec.size() == 0); // No labels in empty genome + CHECK(cpu.nops_need_curated == true); // Nops are NOT curated at initialization + CHECK(cpu.expanded_nop_args == false);// Nop arguments are NOT expanded by default + CHECK(cpu.are_nops_counted == true); // Defaults to false but flipped during initialization + CHECK(cpu.are_regs_expanded == true); // Defaults to false but flipped during initialization + } + } + { // Copy constructor + // Create a CPU and change all possible variables + Derived cpu_init = CreateSeedCPU(); + // Create VirtualCPU using copy constructor + Derived cpu(cpu_init); + // Ensure genome and working genome were copied over + CHECK(cpu.GetGenomeSize() == cpu_init.GetGenomeSize()); + for(size_t idx = 0; idx < cpu.genome.size(); ++idx) + CHECK(cpu.genome[idx] == cpu_init.genome[idx]); + CHECK(cpu.GetWorkingGenomeSize() == cpu_init.GetWorkingGenomeSize()); + for(size_t idx = 0; idx < cpu.genome_working.size(); ++idx) + CHECK(cpu.genome_working[idx] == cpu.genome_working[idx]); + // VARIABLES -- should 100% match those of the seed cpu + { + for(size_t idx = 0; idx < cpu.stacks.size(); ++idx){ + CHECK(cpu.stacks[idx] == cpu_init.stacks[idx]); + } + CHECK(cpu.active_stack_idx == cpu_init.active_stack_idx); + CHECK(cpu.GetNumNops() == cpu_init.GetNumNops()); + CHECK(cpu.nop_id_map.size() == cpu_init.nop_id_map.size()); + for(size_t idx = 0; idx < cpu.GetNumRegs(); ++idx){ + CHECK(cpu.regs[idx] == cpu_init.regs[idx]); + } + CHECK(cpu.inputs.size() == cpu_init.inputs.size()); + CHECK(cpu.inputs[0] == cpu_init.inputs[0]); + CHECK(cpu.outputs.size() == cpu_init.outputs.size()); + CHECK(cpu.outputs[0] == cpu_init.outputs[0]); + CHECK(cpu.inst_ptr == cpu_init.inst_ptr); + CHECK(cpu.read_head == cpu_init.read_head); + CHECK(cpu.write_head == cpu_init.write_head); + CHECK(cpu.flow_head == cpu_init.flow_head); + CHECK(cpu.copied_inst_id_vec.size() == cpu_init.copied_inst_id_vec.size()); + for(size_t idx = 0; idx < cpu.copied_inst_id_vec.size(); ++idx){ + CHECK(cpu.copied_inst_id_vec[idx] == cpu_init.copied_inst_id_vec[idx]); + } + CHECK(cpu.label_idx_vec.size() == cpu_init.label_idx_vec.size()); + for(size_t idx = 0; idx < cpu.label_idx_vec.size(); ++idx){ + CHECK(cpu.label_idx_vec[idx] == cpu_init.label_idx_vec[idx]); + } + } + } + { // Move constructor + // Create a CPU and change all possible variables then repeat the process to invoke + // move constructor instead of copy constructor + Derived cpu_init = CreateSeedCPU(); + Derived cpu(CreateSeedCPU()); + // Ensure genome and working genome were copied over + CHECK(cpu.GetGenomeSize() == cpu_init.GetGenomeSize()); + for(size_t idx = 0; idx < cpu.genome.size(); ++idx) + CHECK(cpu.genome[idx] == cpu_init.genome[idx]); + CHECK(cpu.GetWorkingGenomeSize() == cpu_init.GetWorkingGenomeSize()); + for(size_t idx = 0; idx < cpu.genome_working.size(); ++idx) + CHECK(cpu.genome_working[idx] == cpu.genome_working[idx]); + // VARIABLES -- should 100% match those of the seed cpu + { + for(size_t idx = 0; idx < cpu.stacks.size(); ++idx){ + CHECK(cpu.stacks[idx] == cpu_init.stacks[idx]); + } + CHECK(cpu.active_stack_idx == cpu_init.active_stack_idx); + CHECK(cpu.GetNumNops() == cpu_init.GetNumNops()); + CHECK(cpu.nop_id_map.size() == cpu_init.nop_id_map.size()); + for(size_t idx = 0; idx < cpu.GetNumRegs(); ++idx){ + CHECK(cpu.regs[idx] == cpu_init.regs[idx]); + } + CHECK(cpu.inputs.size() == cpu_init.inputs.size()); + CHECK(cpu.inputs[0] == cpu_init.inputs[0]); + CHECK(cpu.outputs.size() == cpu_init.outputs.size()); + CHECK(cpu.outputs[0] == cpu_init.outputs[0]); + CHECK(cpu.inst_ptr == cpu_init.inst_ptr); + CHECK(cpu.read_head == cpu_init.read_head); + CHECK(cpu.write_head == cpu_init.write_head); + CHECK(cpu.flow_head == cpu_init.flow_head); + CHECK(cpu.copied_inst_id_vec.size() == cpu_init.copied_inst_id_vec.size()); + for(size_t idx = 0; idx < cpu.copied_inst_id_vec.size(); ++idx){ + CHECK(cpu.copied_inst_id_vec[idx] == cpu_init.copied_inst_id_vec[idx]); + } + CHECK(cpu.label_idx_vec.size() == cpu_init.label_idx_vec.size()); + for(size_t idx = 0; idx < cpu.label_idx_vec.size(); ++idx){ + CHECK(cpu.label_idx_vec[idx] == cpu_init.label_idx_vec[idx]); + } + } + } + /* + Constructors: + [X] Default constructor gives us a default genome (empty) + [X] Genome constructor sets genome/working genome + [X] Copy constructor functions as expected + [X] Move constructor functions as expected + [X] Working genome is based on the actual genome + [X] Variables are initialized as expected for each constructor (see above) + [X] All defaults for default constructor + [X] All but genome/working genome defaults for genome constructor + [X] All variables copied over for copy constructor + [X] All variables moved for move constructor + */ +} +TEST_CASE("VirtualCPU_Getters", "[Hardware]") { + { // Default case + Derived cpu; + // Basic getters + CHECK(cpu.GetNumRegs() == 3); + CHECK(cpu.GetNumRegs() == cpu.regs.size()); + CHECK(cpu.GetNumNops() == 3); + // Genome getters + CHECK(cpu.GetGenomeSize() == 0); + CHECK(cpu.GetWorkingGenomeSize() == 0); + emp::Random random(1000); + cpu.PushRandomInst(random, 10); + CHECK(cpu.GetGenomeSize() == 10); + CHECK(cpu.GetWorkingGenomeSize() == 10); + cpu.genome_working.push_back(cpu.GetRandomInst(random)); + CHECK(cpu.GetGenomeSize() == 10); + CHECK(cpu.GetWorkingGenomeSize() == 11); + // Outputs + CHECK(cpu.GetOutputs().size() == 0); + for(size_t idx = 0; idx < 5; ++idx) cpu.outputs[idx] = idx * idx; + CHECK(cpu.GetOutputs().size() == 5); + for(size_t idx = 0; idx < 5; ++idx) CHECK(cpu.outputs[idx] == idx * idx); + // Instruction library + CHECK(cpu.GetInstLib().Raw() == &Derived::inst_lib_t::DefaultInstLib()); + } + { + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("NopE", Derived::inst_lib_t::Inst_NopC, 0, "No-operation E"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // Basic getters + CHECK(cpu.GetNumRegs() == 5); + CHECK(cpu.GetNumRegs() == cpu.regs.size()); + CHECK(cpu.GetNumNops() == 5); + } + /* + [X] GetNumNops returns + [X] 3 by default + [X] Something else if we stuff more nops in the instruction set + [X] GetNumRegs returns + [X] 3 by default + [X] Something else if we stuff more nops in the instruction set + [X] GetGenomeSize returns original genome size + [X] GetWorkingGenomeSize returns working genome size + [X] GetOutputs returns ALL outputs + [X] GetInstLib returns correct pointer to instruction library + */ +} +TEST_CASE("VirtualCPU_Setters", "[Hardware]") { + Derived cpu; + CHECK(cpu.inputs.size() == 0); + emp::vector input_vec; + input_vec.push_back(10); + input_vec.push_back(22); + input_vec.push_back(50); + cpu.SetInputs(input_vec); + CHECK(cpu.inputs.size() == 3); + CHECK(cpu.inputs[0] == 10); + CHECK(cpu.inputs[1] == 22); + CHECK(cpu.inputs[2] == 50); + /* + [X] SetInputs sets ALL inputs + */ +} +TEST_CASE("VirtualCPU_Genome_and_Instructions", "[Hardware]") { + { + Derived cpu; + auto inst_lib_ptr = cpu.GetInstLib(); + emp::Random random(100); + // GetDefaultInst + Derived::inst_t default_inst = cpu.GetDefaultInst(); + CHECK(default_inst.idx == 0); + // GetRandomInst + Derived::inst_t random_inst = cpu.GetRandomInst(random); + for(size_t i = 0; (i < 20) && (random_inst.idx == 0); ++i) cpu.GetRandomInst(random); + CHECK(random_inst.idx != 0); + CHECK(random_inst.idx < inst_lib_ptr->GetSize()); + // PushInst(idx) + CHECK(cpu.GetGenomeSize() == 0); + CHECK(cpu.GetWorkingGenomeSize() == 0); + cpu.CurateNops(); + CHECK(!cpu.nops_need_curated); + cpu.PushInst(0); // Index 0 -> NopA + CHECK(cpu.GetGenomeSize() == 1); + CHECK(cpu.GetWorkingGenomeSize() == 1); + CHECK(cpu.genome[0].idx == 0); + CHECK(cpu.genome_working[0].idx == 0); + CHECK(cpu.nops_need_curated); + // PushInst(name) + cpu.CurateNops(); + cpu.PushInst("NopB"); // NopB -> Index 1 + CHECK(cpu.GetGenomeSize() == 2); + CHECK(cpu.GetWorkingGenomeSize() == 2); + CHECK(cpu.genome[1].idx == 1); + CHECK(cpu.genome_working[1].idx == 1); + CHECK(cpu.nops_need_curated); + // PushInst(inst) + cpu.CurateNops(); + cpu.PushInst(default_inst); // NopA -> Index 0 + CHECK(cpu.GetGenomeSize() == 3); + CHECK(cpu.GetWorkingGenomeSize() == 3); + CHECK(cpu.genome[2].idx == 0); + CHECK(cpu.genome_working[2].idx == 0); + CHECK(cpu.nops_need_curated); + // PushInst(inst, count) + cpu.CurateNops(); + cpu.PushInst(default_inst, 5); // NopA -> Index 0 + CHECK(cpu.GetGenomeSize() == 8); + CHECK(cpu.GetWorkingGenomeSize() == 8); + for(size_t i = 3; i < 8; ++i){ + CHECK(cpu.genome[i].idx == 0); + CHECK(cpu.genome_working[i].idx == 0); + } + CHECK(cpu.nops_need_curated); + // PushDefaultInst() + cpu.CurateNops(); + cpu.PushDefaultInst(); // NopA -> Index 0 + CHECK(cpu.GetGenomeSize() == 9); + CHECK(cpu.GetWorkingGenomeSize() == 9); + CHECK(cpu.genome[8].idx == 0); + CHECK(cpu.nops_need_curated); + // PushDefaultInst(count) + cpu.CurateNops(); + cpu.PushDefaultInst(11); // NopA -> Index 0 + CHECK(cpu.GetGenomeSize() == 20); + CHECK(cpu.GetWorkingGenomeSize() == 20); + for(size_t i = 9; i < 19; ++i){ + CHECK(cpu.genome[i].idx == 0); + CHECK(cpu.genome_working[i].idx == 0); + } + CHECK(cpu.nops_need_curated); + // SetInst(pos, inst) + cpu.CurateNops(); + Derived::inst_t third_inst = Derived::inst_t(2); // NopC + cpu.SetInst(0, third_inst); + CHECK(cpu.genome[0].idx == 2); + CHECK(cpu.genome_working[0].idx == 2); + CHECK(cpu.GetGenomeSize() == 20); + CHECK(cpu.GetWorkingGenomeSize() == 20); + CHECK(cpu.nops_need_curated); + // RandomizeInst(pos, rand) + cpu.CurateNops(); + for(size_t i = 0; (i < 20) && (cpu.genome[0] == 2); ++i) cpu.RandomizeInst(0,random); + CHECK(cpu.genome[0].idx != 2); + CHECK(cpu.genome_working[0].idx != 2); + CHECK(cpu.GetGenomeSize() == 20); + CHECK(cpu.GetWorkingGenomeSize() == 20); + CHECK(cpu.nops_need_curated); + // PushRandomInst(rand) + cpu.CurateNops(); + cpu.PushRandomInst(random); + CHECK(cpu.GetGenomeSize() == 21); + CHECK(cpu.GetWorkingGenomeSize() == 21); + CHECK(cpu.nops_need_curated); + // PushRandomInst(rand, count) + cpu.CurateNops(); + cpu.PushRandomInst(random, 9); + CHECK(cpu.GetGenomeSize() == 30); + CHECK(cpu.GetWorkingGenomeSize() == 30); + CHECK(cpu.nops_need_curated); + } + { // Load + Derived cpu; + CHECK(cpu.GetGenomeSize() == 0); + CHECK(cpu.GetWorkingGenomeSize() == 0); + cpu.CurateNops(); + CHECK(!cpu.nops_need_curated); + std::stringstream sstr; + sstr << "NopA\nAdd\nSub\nNopB\n"; + cpu.Load(sstr); + CHECK(cpu.GetGenomeSize() == 4); + CHECK(cpu.GetWorkingGenomeSize() == 4); + CHECK(cpu.genome[0].idx == 0); + CHECK(cpu.genome_working[0].idx == 0); + CHECK(cpu.genome[3].idx == 1); + CHECK(cpu.genome_working[3].idx == 1); + cpu.Load("./ancestor_default.org"); // Load default avida ancestor from file + CHECK(cpu.GetGenomeSize() == 50); // Should reset old genome + CHECK(cpu.GetWorkingGenomeSize() == 50); + CHECK(cpu.genome[2].idx == 2); + CHECK(cpu.genome_working[2].idx == 2); + CHECK(cpu.genome[49].idx == 1); + CHECK(cpu.genome_working[49].idx == 1); + } + /* + [X] PushInst adds the instruction to the end of the genome AND working genome + [X] Update labels? + [X] By index + [X] By name + [X] By copy + [X] By copy (multiple times) + [X] PushDefaultInst pushes the first instruction in the library + [X] Update labels? + [X] GetRandomInst returns a random instruction within the instruction library + [X] SetInst overwrites an instruction in the genome/working genome + [X] Recalcuates labels/nops? + [X] RandomizeInst calls SetInst on the genome position, but with a random instruction + [X] Load + [X] Loads genome from a stream + [X] That stream can be a file + */ +} +TEST_CASE("VirtualCPU_Head_Manipulation", "[Hardware]") { + { // Instruction pointer + Derived cpu; + emp::Random random(10); + cpu.PushRandomInst(random, 10); + CHECK(cpu.inst_ptr == 0); + cpu.AdvanceIP(); + CHECK(cpu.inst_ptr == 1); + cpu.AdvanceIP(3); + CHECK(cpu.inst_ptr == 4); + cpu.AdvanceIP(11); + CHECK(cpu.inst_ptr == 5); + cpu.ResetIP(); + CHECK(cpu.inst_ptr == 0); + cpu.SetIP(7); + CHECK(cpu.inst_ptr == 7); + cpu.SetIP(18); + CHECK(cpu.inst_ptr == 8); + size_t idx = 0; + cpu.ResetModdedHead(idx); + CHECK(cpu.inst_ptr == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.inst_ptr == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.inst_ptr == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.inst_ptr == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.inst_ptr == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.inst_ptr == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.inst_ptr == 8); + idx = 16; + cpu.ResetModdedHead(idx); + CHECK(cpu.inst_ptr == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.inst_ptr == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.inst_ptr == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.inst_ptr == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.inst_ptr == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.inst_ptr == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.inst_ptr == 8); + } + { // Read head + Derived cpu; + emp::Random random(10); + cpu.PushRandomInst(random, 10); + CHECK(cpu.read_head == 0); + cpu.AdvanceRH(); + CHECK(cpu.read_head == 1); + cpu.AdvanceRH(3); + CHECK(cpu.read_head == 4); + cpu.AdvanceRH(11); + CHECK(cpu.read_head == 5); + cpu.ResetRH(); + CHECK(cpu.read_head == 0); + cpu.SetRH(7); + CHECK(cpu.read_head == 7); + cpu.SetRH(18); + CHECK(cpu.read_head == 8); + size_t idx = 1; + cpu.ResetModdedHead(idx); + CHECK(cpu.read_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.read_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.read_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.read_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.read_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.read_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.read_head == 8); + idx = 17; + cpu.ResetModdedHead(idx); + CHECK(cpu.read_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.read_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.read_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.read_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.read_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.read_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.read_head == 8); + } + { // Write head + Derived cpu; + emp::Random random(10); + cpu.PushRandomInst(random, 10); + CHECK(cpu.write_head == 0); + cpu.AdvanceWH(); + CHECK(cpu.write_head == 1); + cpu.AdvanceWH(3); + CHECK(cpu.write_head == 4); + cpu.AdvanceWH(11); + CHECK(cpu.write_head == 5); + cpu.ResetWH(); + CHECK(cpu.write_head == 0); + cpu.SetWH(7); + CHECK(cpu.write_head == 7); + cpu.SetWH(18); + CHECK(cpu.write_head == 8); + size_t idx = 2; + cpu.ResetModdedHead(idx); + CHECK(cpu.write_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.write_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.write_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.write_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.write_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.write_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.write_head == 8); + idx = 18; + cpu.ResetModdedHead(idx); + CHECK(cpu.write_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.write_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.write_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.write_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.write_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.write_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.write_head == 8); + } + { // Flow head + Derived cpu; + emp::Random random(10); + cpu.PushRandomInst(random, 10); + CHECK(cpu.flow_head == 0); + cpu.AdvanceFH(); + CHECK(cpu.flow_head == 1); + cpu.AdvanceFH(3); + CHECK(cpu.flow_head == 4); + cpu.AdvanceFH(11); + CHECK(cpu.flow_head == 5); + cpu.ResetFH(); + CHECK(cpu.flow_head == 0); + cpu.SetFH(7); + CHECK(cpu.flow_head == 7); + cpu.SetFH(18); + CHECK(cpu.flow_head == 8); + size_t idx = 3; + cpu.ResetModdedHead(idx); + CHECK(cpu.flow_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.flow_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.flow_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.flow_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.flow_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.flow_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.flow_head == 8); + idx = 19; + cpu.ResetModdedHead(idx); + CHECK(cpu.flow_head == 0); + cpu.AdvanceModdedHead(idx); + CHECK(cpu.flow_head == 1); + cpu.AdvanceModdedHead(idx, 3); + CHECK(cpu.flow_head == 4); + cpu.AdvanceModdedHead(idx, 11); + CHECK(cpu.flow_head == 5); + cpu.ResetModdedHead(idx); + CHECK(cpu.flow_head == 0); + cpu.SetModdedHead(idx, 7); + CHECK(cpu.flow_head == 7); + cpu.SetModdedHead(idx, 18); + CHECK(cpu.flow_head == 8); + } + /* + [X] ResetIP resets IP to 0 + [X] AdvanceIP moves IP forward and wraps around genome end if needed + [X] SetIP assigns IP to certain location and wraps around genome end if needed + [X] ResetRH resets RH to 0 + [X] AdvanceRH moves RH forward and wraps around genome end if needed + [X] ResetRH resets RH to 0 + [X] AdvanceWH moves WH forward and wraps around genome end if needed + [X] ResetWH resets WH to 0 + [X] AdvanceWH moves WH forward and wraps around genome end if needed + [X] ResetFH resets FH to 0 + [X] AdvanceFH moves FH forward and wraps around genome end if needed + [X] SetFH assigns FH to certain location and wraps around genome end if needed + [X] ResetModdedHead resets head to 0 + [X] AdvanceModdedHead moves head forward and wraps around genome end if needed + [X] SetModdedHead assigns head to certain location and wraps around genome end if needed + */ +} +TEST_CASE("VirtualCPU_Hardware_Manipulation", "[Hardware]") { + /* + [ ] Initialize + [X] ResetHeads + [X] ResetIO + [X] ResetMemory + [X] Reset registers + [X] Reset stacks + [X] Reset active stack index to 0 + [X] ResetBookkeeping + [X] ResetHardware + [ ] ResetWorkingGenome + [ ] ClearGenome + [ ] Clears main genome + [ ] Clears working genome + [ ] Resets hardware + [X] CurateNops + [X] Counts nops if needed + [X] Finds all labels + [X] Add nops to preceeding instructions nop_vec + [X] Wraps? + [X] Sets boolean flag to false + [X] CountNops + [X] Calculates the number of nops + [X] Maps nop ids to indices + [X] Maps nop indices to ids + [X] ExpandRegisters + [X] Sets num_regs variable + [X] Resizes register vector + */ + { // ResetHeads + Derived cpu = CreateSeedCPU(); + cpu.ResetHeads(); + CHECK(cpu.inst_ptr == 0); + CHECK(cpu.read_head == 0); + CHECK(cpu.write_head == 0); + CHECK(cpu.flow_head == 0); + } + { // ResetIO + Derived cpu = CreateSeedCPU(); + cpu.inputs[0] = 10; + cpu.inputs[1] = 11; + cpu.inputs[2] = 12; + cpu.outputs[0] = 5; + cpu.outputs[1] = 6; + cpu.outputs[2] = 7; + cpu.outputs[3] = 8; + CHECK(cpu.inputs.size() == 3); + CHECK(cpu.outputs.size() == 4); + cpu.ResetIO(); + CHECK(cpu.inputs.size() == 0); + CHECK(cpu.outputs.size() == 0); + } + { // ResetMemory + Derived cpu = CreateSeedCPU(); + CHECK(cpu.regs[0] == 2); + CHECK(cpu.regs[1] == 38); + CHECK(cpu.regs[2] == 2309); + CHECK(cpu.active_stack_idx == 1); + CHECK(cpu.stacks[1].size() == 1); + cpu.ResetMemory(); + CHECK(cpu.regs[0] == 0); + CHECK(cpu.regs[1] == 1); + CHECK(cpu.regs[2] == 2); + CHECK(cpu.active_stack_idx == 0); + CHECK(cpu.stacks[0].size() == 0); + CHECK(cpu.stacks[1].size() == 0); + } + { // ResetBookkeeping + Derived cpu = CreateSeedCPU(); + cpu.copied_inst_id_vec.push_back(3); + cpu.num_insts_executed = 4; + CHECK(cpu.copied_inst_id_vec.size() == 1); + CHECK(cpu.num_insts_executed == 4); + cpu.ResetBookkeeping(); + CHECK(cpu.copied_inst_id_vec.size() == 0); + CHECK(cpu.num_insts_executed == 0); + } + { // ResetHardware + // Create cpu with some variables set ... + Derived cpu = CreateSeedCPU(); + // IO + cpu.inputs[0] = 10; + cpu.inputs[1] = 11; + cpu.inputs[2] = 12; + cpu.outputs[0] = 5; + cpu.outputs[1] = 6; + cpu.outputs[2] = 7; + cpu.outputs[3] = 8; + CHECK(cpu.inputs.size() == 3); + CHECK(cpu.outputs.size() == 4); + // Memory + CHECK(cpu.regs[0] == 2); + CHECK(cpu.regs[1] == 38); + CHECK(cpu.regs[2] == 2309); + CHECK(cpu.active_stack_idx == 1); + CHECK(cpu.stacks[1].size() == 1); + // Bookkeeping + cpu.copied_inst_id_vec.push_back(3); + cpu.num_insts_executed = 4; + CHECK(cpu.copied_inst_id_vec.size() == 1); + CHECK(cpu.num_insts_executed == 4); + + cpu.ResetHardware(); + + // Heads + CHECK(cpu.inst_ptr == 0); + CHECK(cpu.read_head == 0); + CHECK(cpu.write_head == 0); + CHECK(cpu.flow_head == 0); + // IO + CHECK(cpu.inputs.size() == 0); + CHECK(cpu.outputs.size() == 0); + // Memory + CHECK(cpu.regs[0] == 0); + CHECK(cpu.regs[1] == 1); + CHECK(cpu.regs[2] == 2); + CHECK(cpu.active_stack_idx == 0); + CHECK(cpu.stacks[0].size() == 0); + CHECK(cpu.stacks[1].size() == 0); + // Bookkeeping + CHECK(cpu.copied_inst_id_vec.size() == 0); + CHECK(cpu.num_insts_executed == 0); + } + { // ResetWorkingGenome + Derived cpu = CreateSeedCPU(); + CHECK(cpu.genome[0] != cpu.genome_working[0]); + cpu.ResetWorkingGenome(); + // Genome + CHECK(cpu.genome[0] == cpu.genome_working[0]); + CHECK(cpu.label_idx_vec.size() == 0); + CHECK(cpu.nops_need_curated == true); + } + { // ClearGenome + // Create cpu with some variables set ... + Derived cpu = CreateSeedCPU(); + // IO + cpu.inputs[0] = 10; + cpu.inputs[1] = 11; + cpu.inputs[2] = 12; + cpu.outputs[0] = 5; + cpu.outputs[1] = 6; + cpu.outputs[2] = 7; + cpu.outputs[3] = 8; + CHECK(cpu.inputs.size() == 3); + CHECK(cpu.outputs.size() == 4); + // Memory + CHECK(cpu.regs[0] == 2); + CHECK(cpu.regs[1] == 38); + CHECK(cpu.regs[2] == 2309); + CHECK(cpu.active_stack_idx == 1); + CHECK(cpu.stacks[1].size() == 1); + // Bookkeeping + cpu.copied_inst_id_vec.push_back(3); + cpu.num_insts_executed = 4; + CHECK(cpu.copied_inst_id_vec.size() == 1); + CHECK(cpu.num_insts_executed == 4); + + cpu.ClearGenome(); + + // Heads + CHECK(cpu.inst_ptr == 0); + CHECK(cpu.read_head == 0); + CHECK(cpu.write_head == 0); + CHECK(cpu.flow_head == 0); + // IO + CHECK(cpu.inputs.size() == 0); + CHECK(cpu.outputs.size() == 0); + // Memory + CHECK(cpu.regs[0] == 0); + CHECK(cpu.regs[1] == 1); + CHECK(cpu.regs[2] == 2); + CHECK(cpu.active_stack_idx == 0); + CHECK(cpu.stacks[0].size() == 0); + CHECK(cpu.stacks[1].size() == 0); + // Bookkeeping + CHECK(cpu.copied_inst_id_vec.size() == 0); + CHECK(cpu.num_insts_executed == 0); + // Genome + CHECK(cpu.genome.size() == 0); + CHECK(cpu.genome_working.size() == 0); + CHECK(cpu.label_idx_vec.size() == 0); + CHECK(cpu.nops_need_curated == true); + } + { // CountNops and ExpandRegisters + // Everything defaults to 3 + Derived cpu_default; + CHECK(cpu_default.GetNumNops() == 3); + CHECK(cpu_default.regs.size() == 3); + CHECK(cpu_default.GetNumRegs() == 3); + + // Add NopD and NopE, which bumps us to 5 nops and registers + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("NopE", Derived::inst_lib_t::Inst_NopC, 0, "No-operation E"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu_more_nops(genome); + CHECK(cpu_more_nops.GetNumNops() == 5); + CHECK(cpu_more_nops.regs.size() == 5); + CHECK(cpu_more_nops.GetNumRegs() == 5); + + // Add NopE but not NopD. Since they are disjoint we have still 3 nops and registers + Derived::inst_lib_t inst_lib_2; + inst_lib_2.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib_2.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib_2.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib_2.AddInst("NopE", Derived::inst_lib_t::Inst_NopC, 0, "No-operation E"); + Derived::genome_t genome_2 = Derived::genome_t(inst_lib_2); + Derived cpu_bad_nops(genome_2); + CHECK(cpu_bad_nops.GetNumNops() == 3); + CHECK(cpu_bad_nops.regs.size() == 3); + CHECK(cpu_bad_nops.GetNumRegs() == 3); + } + { // CurateNops + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + cpu.PushInst("NopA"); // 0 + cpu.PushInst("NopB"); // 1 + cpu.PushInst("NopC"); // 2 + cpu.PushInst("NopD"); // 3 + cpu.PushInst("Label"); // 4 + cpu.PushInst("NopD"); // 5 + cpu.PushInst("NopB"); // 6 + cpu.PushInst("Add"); // 7 + cpu.PushInst("NopC"); // 8 + cpu.PushInst("Label"); // 9 + cpu.PushInst("NopD"); // 10 + CHECK(cpu.nops_need_curated); + cpu.CurateNops(); + CHECK(!cpu.nops_need_curated); + CHECK(cpu.label_idx_vec.size() == 2); + CHECK(cpu.label_idx_vec[0] == 4); + CHECK(cpu.label_idx_vec[1] == 9); + { // Nop vec checks + { // 0 + CHECK(cpu.genome_working[0].nop_vec.size() == 3); + CHECK(cpu.genome_working[0].nop_vec[0] == 1); + CHECK(cpu.genome_working[0].nop_vec[1] == 2); + CHECK(cpu.genome_working[0].nop_vec[2] == 3); + } + { // 1 + CHECK(cpu.genome_working[1].nop_vec.size() == 2); + CHECK(cpu.genome_working[1].nop_vec[0] == 2); + CHECK(cpu.genome_working[1].nop_vec[1] == 3); + } + { // 2 + CHECK(cpu.genome_working[2].nop_vec.size() == 1); + CHECK(cpu.genome_working[2].nop_vec[0] == 3); + } + { // 3 + CHECK(cpu.genome_working[3].nop_vec.size() == 0); + } + { // 4 + CHECK(cpu.genome_working[4].nop_vec.size() == 2); + CHECK(cpu.genome_working[4].nop_vec[0] == 3); + CHECK(cpu.genome_working[4].nop_vec[1] == 1); + } + { // 5 + CHECK(cpu.genome_working[5].nop_vec.size() == 1); + CHECK(cpu.genome_working[5].nop_vec[0] == 1); + } + { // 6 + CHECK(cpu.genome_working[6].nop_vec.size() == 0); + } + { // 7 + CHECK(cpu.genome_working[7].nop_vec.size() == 1); + CHECK(cpu.genome_working[7].nop_vec[0] == 2); + } + { // 8 + CHECK(cpu.genome_working[8].nop_vec.size() == 0); + } + { // 9 + CHECK(cpu.genome_working[9].nop_vec.size() == 5); + CHECK(cpu.genome_working[9].nop_vec[0] == 3); + CHECK(cpu.genome_working[9].nop_vec[1] == 0); + CHECK(cpu.genome_working[9].nop_vec[2] == 1); + CHECK(cpu.genome_working[9].nop_vec[3] == 2); + CHECK(cpu.genome_working[9].nop_vec[4] == 3); + } + { // 10 + CHECK(cpu.genome_working[10].nop_vec.size() == 4); + CHECK(cpu.genome_working[10].nop_vec[0] == 0); + CHECK(cpu.genome_working[10].nop_vec[1] == 1); + CHECK(cpu.genome_working[10].nop_vec[2] == 2); + CHECK(cpu.genome_working[10].nop_vec[3] == 3); + } + } + } +} +TEST_CASE("VirtualCPU_Nop_Methods", "[Hardware]") { + { // GetComplementIdx + { // Standard instruction set + Derived cpu; + CHECK(cpu.GetComplementNop(0) == 1); // A->B + CHECK(cpu.GetComplementNop(1) == 2); // B->C + CHECK(cpu.GetComplementNop(2) == 0); // C->A + } + { // Extended instruction set + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("NopE", Derived::inst_lib_t::Inst_NopC, 0, "No-operation E"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + CHECK(cpu.GetComplementNop(0) == 1); // A->B + CHECK(cpu.GetComplementNop(1) == 2); // B->C + CHECK(cpu.GetComplementNop(2) == 3); // C->D + CHECK(cpu.GetComplementNop(3) == 4); // D->E + CHECK(cpu.GetComplementNop(4) == 0); // E->A + } + } + { // GetComplementLabel + { // Standard instruction set + Derived cpu; + emp::vector v {2, 1, 0, 0, 2, 1}; + emp::vector res = cpu.GetComplementNopSequence(v); + CHECK(res[0] == 0); // C->A + CHECK(res[1] == 2); // B->C + CHECK(res[2] == 1); // A->B + CHECK(res[3] == 1); // A->B + CHECK(res[4] == 0); // C->A + CHECK(res[5] == 2); // B->C + } + { // Extended instruction set + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("NopE", Derived::inst_lib_t::Inst_NopC, 0, "No-operation E"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + emp::vector v {2, 1, 0, 0, 2, 1, 3, 4, 3, 1, 4}; + emp::vector res = cpu.GetComplementNopSequence(v); + CHECK(res[0] == 3); // C->D + CHECK(res[1] == 2); // B->C + CHECK(res[2] == 1); // A->B + CHECK(res[3] == 1); // A->B + CHECK(res[4] == 3); // C->D + CHECK(res[5] == 2); // B->C + CHECK(res[6] == 4); // D->E + CHECK(res[7] == 0); // E->A + CHECK(res[8] == 4); // D->E + CHECK(res[9] == 2); // B->C + CHECK(res[10] ==0); // E->A + } + } + { // CompareNopSequences + // Note: here we only use three nops. However, this code is dealing solely with size_ts so + // it should generalize (there are no checks on if the nop is invalid) + Derived cpu; + // Perfect match + emp::vector v_1_a {0, 1, 2, 1, 0 }; + emp::vector v_1_b {0, 1, 2, 1, 0 }; + CHECK(cpu.CompareNopSequences(v_1_a, v_1_b)); + // Second vector can be longer than first + emp::vector v_2_a {0, 1, 2, 1, 0 }; + emp::vector v_2_b {0, 1, 2, 1, 0, 1, 2 }; + CHECK(cpu.CompareNopSequences(v_2_a, v_2_b)); + // First vector CANNOT be longer than second + emp::vector v_3_a {0, 1, 2, 1, 0, 1, 2 }; + emp::vector v_3_b {0, 1, 2, 1, 0 }; + CHECK(!cpu.CompareNopSequences(v_3_a, v_3_b)); + // First vector CANNOT be empty + emp::vector v_4_a { }; + emp::vector v_4_b {0, 1, 2, 1, 0 }; + CHECK(!cpu.CompareNopSequences(v_4_a, v_4_b)); + // Second vector CANNOT be empty + emp::vector v_5_a {0, 1, 2}; + emp::vector v_5_b { }; + CHECK(!cpu.CompareNopSequences(v_5_a, v_5_b)); + // Both vectors CANNOT be empty + emp::vector v_6_a { }; + emp::vector v_6_b { }; + CHECK(!cpu.CompareNopSequences(v_6_a, v_6_b)); + // Mismatch -> return false + emp::vector v_7_a {0, 1, 2 }; + emp::vector v_7_b {0, 2, 2 }; + CHECK(!cpu.CompareNopSequences(v_7_a, v_7_b)); + // Match occurs after mismatch -> still fail + emp::vector v_8_a {0, 1, 2 }; + emp::vector v_8_b {0, 2, 2, 0, 1, 2}; + CHECK(!cpu.CompareNopSequences(v_8_a, v_8_b)); + } + { // CheckIfLastCopied + Derived cpu; + cpu.copied_inst_id_vec = {0, 1, 2}; + // True + CHECK(cpu.CheckIfLastCopied({0, 1, 2})); + CHECK(cpu.CheckIfLastCopied({1, 2})); + CHECK(cpu.CheckIfLastCopied({2})); + CHECK(cpu.CheckIfLastCopied({2})); + // False + CHECK(!cpu.CheckIfLastCopied({1})); // Mismatch + CHECK(!cpu.CheckIfLastCopied({0, 2, 2})); // Mismatch with correct size + CHECK(!cpu.CheckIfLastCopied({0, 1, 2, 0})); // Too long + CHECK(!cpu.CheckIfLastCopied({})); // Empty + } + { // FindLabel_Reverse(start_local) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x L A B x L C D x A B x C D x L A B x D A + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + std::stringstream sstr; + sstr << "Add\nLabel\nNopA\nNopB\nAdd\nLabel\nNopC\nNopD\n" + "Add\nNopA\nNopB\nAdd\nNopC\nNopD\nAdd\nLabel\nNopA\nNopB\nAdd\nNopD\nNopA"; + cpu.Load(sstr); + cpu.CurateNops(); + // Does start_local actually factor in? + cpu.inst_ptr = 8; + CHECK(cpu.FindLabel_Reverse(true) == 1); + CHECK(cpu.FindLabel_Reverse(false) == 15); + // If we have only one label with that nop sequence, it returns regardless of start_local + // Also, ensure Nops after NopC work too + cpu.inst_ptr = 11; + CHECK(cpu.FindLabel_Reverse(true) == 5); + CHECK(cpu.FindLabel_Reverse(false) == 5); + // If instruction pointer is on the only label with that sequence, return inst_ptr + cpu.inst_ptr = 5; + CHECK(cpu.FindLabel_Reverse(true) == 5); + CHECK(cpu.FindLabel_Reverse(false) == 5); + // If instruction pointer is on a label and another match exists, + // Return it if start_local = true + // Return depending on position if start_local = false + cpu.inst_ptr = 15; + CHECK(cpu.FindLabel_Reverse(true) == 1); + CHECK(cpu.FindLabel_Reverse(false) == 15); + // If no matching label found, return instruction pointer + cpu.inst_ptr = 18; + CHECK(cpu.FindLabel_Reverse(true) == 18); + CHECK(cpu.FindLabel_Reverse(false) == 18); + } + { // FindLabel(start_local, reverse = false) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x L A B x L C D x A B x C D x L A B x D A + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + std::stringstream sstr; + sstr << "Add\nLabel\nNopA\nNopB\nAdd\nLabel\nNopC\nNopD\n" + "Add\nNopA\nNopB\nAdd\nNopC\nNopD\nAdd\nLabel\nNopA\nNopB\nAdd\nNopD\nNopA"; + cpu.Load(sstr); + cpu.CurateNops(); + { // Use FindLabel_Reverse tests if reverse = true + // Does start_local actually factor in? + cpu.inst_ptr = 8; + CHECK(cpu.FindLabel(true, true) == 1); + CHECK(cpu.FindLabel(false, true) == 15); + // If we have only one label with that sequence, it returns regardless of start_local + // Also, ensure Nops after NopC work too + cpu.inst_ptr = 11; + CHECK(cpu.FindLabel(true, true) == 5); + CHECK(cpu.FindLabel(false, true) == 5); + // If instruction pointer is on the only label with that sequence, return inst_ptr + cpu.inst_ptr = 5; + CHECK(cpu.FindLabel(true, true) == 5); + CHECK(cpu.FindLabel(false, true) == 5); + // If instruction pointer is on a label and another match exists, + // Return it if start_local = true + // Return depending on position if start_local = false + cpu.inst_ptr = 15; + CHECK(cpu.FindLabel(true, true) == 1); + CHECK(cpu.FindLabel(false, true) == 15); + // If no matching label found, return instruction pointer + cpu.inst_ptr = 18; + CHECK(cpu.FindLabel(true, true) == 18); + CHECK(cpu.FindLabel(false, true) == 18); + } + { // reverse = false + // Does start_local actually factor in? + cpu.inst_ptr = 8; + CHECK(cpu.FindLabel(true) == 15); + CHECK(cpu.FindLabel(false) == 1); + // If we have only one label with that sequence, it returns regardless of start_local + // Also, ensure Nops after NopC work too + cpu.inst_ptr = 11; + CHECK(cpu.FindLabel(true) == 5); + CHECK(cpu.FindLabel(false) == 5); + // If instruction pointer is on the only label with that sequence, return inst_ptr + cpu.inst_ptr = 5; + CHECK(cpu.FindLabel(true) == 5); + CHECK(cpu.FindLabel(false) == 5); + // If instruction pointer is on a label and another match exists, + // Return it if start_local = true + // Return depending on position if start_local = false + cpu.inst_ptr = 15; + CHECK(cpu.FindLabel(true) == 1); + CHECK(cpu.FindLabel(false) == 1); + // If no matching label found, return instruction pointer + cpu.inst_ptr = 18; + CHECK(cpu.FindLabel(true) == 18); + CHECK(cpu.FindLabel(false) == 18); + } + } + { //FindNopSequence_Reverse(search_vec, start_idx) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + // Keep instruction pointer at beginning of genome and use start_idx instead + cpu.inst_ptr = 2; + // Ensure start_idx is used and search does not include the current instruction pointer + CHECK(cpu.FindNopSequence_Reverse({0, 1}, (size_t)0) == 16); + CHECK(cpu.FindNopSequence_Reverse({0, 1}, (size_t)6) == 0); + CHECK(cpu.FindNopSequence_Reverse({0, 1}, (size_t)16) == 6); + // If sequence only appears once, always return it (also check if NopD is valid) + CHECK(cpu.FindNopSequence_Reverse({3, 3}, (size_t)0) == 13); + CHECK(cpu.FindNopSequence_Reverse({3, 3}, (size_t)12) == 13); + CHECK(cpu.FindNopSequence_Reverse({3, 3}, (size_t)13) == 13); + CHECK(cpu.FindNopSequence_Reverse({3, 3}, (size_t)14) == 13); + CHECK(cpu.FindNopSequence_Reverse({3, 3}, (size_t)18) == 13); + // Found sequence can have extra nops + CHECK(cpu.FindNopSequence_Reverse({1}, (size_t)0) == 17); + CHECK(cpu.FindNopSequence_Reverse({1}, (size_t)9) == 7); + CHECK(cpu.FindNopSequence_Reverse({1}, (size_t)17) == 9); + // If pattern not found, return instruction pointer + CHECK(cpu.FindNopSequence_Reverse({1,1,1}, (size_t)0) == 2); + CHECK(cpu.FindNopSequence_Reverse({1,1,1}, (size_t)9) == 2); + CHECK(cpu.FindNopSequence_Reverse({1,1,1}, (size_t)17) == 2); + } + { //FindNopSequence_Reverse(search_vec, start_local) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + // Ensure result always matches the already-tested overload that uses start_idx + emp::vector> test_vectors; + test_vectors.push_back(emp::vector({0,1})); + test_vectors.push_back(emp::vector({3,3})); + test_vectors.push_back(emp::vector({1})); + test_vectors.push_back(emp::vector({1,1,1})); + for(emp::vector v : test_vectors){ + for(size_t idx = 0; idx < cpu.GetGenomeSize(); ++idx){ + cpu.inst_ptr = idx; + CHECK(cpu.FindNopSequence_Reverse(v, true) == + cpu.FindNopSequence_Reverse(v, (size_t)cpu.inst_ptr)); + CHECK(cpu.FindNopSequence_Reverse(v, false) == + cpu.FindNopSequence_Reverse(v, (size_t)0)); + } + } + } + { //FindNopSequence_Reverse(start_local) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + // Ensure result always matches the already-tested overload that uses start_idx + emp::vector> test_vectors; + test_vectors.push_back(emp::vector({0,1})); + test_vectors.push_back(emp::vector({3,3})); + test_vectors.push_back(emp::vector({1})); + test_vectors.push_back(emp::vector({1,1,1})); + for(emp::vector v : test_vectors){ + for(size_t idx = 0; idx < cpu.GetGenomeSize(); ++idx){ + cpu.inst_ptr = idx; + CHECK(cpu.FindNopSequence_Reverse(true) == + cpu.FindNopSequence_Reverse( + cpu.genome_working[cpu.inst_ptr].nop_vec, (size_t)cpu.inst_ptr + ) + ); + CHECK(cpu.FindNopSequence_Reverse(false) == + cpu.FindNopSequence_Reverse(cpu.genome_working[cpu.inst_ptr].nop_vec, (size_t)0)); + } + } + } + { //FindNopSequence(search_vec, start_idx, reverse = false) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + // Keep instruction pointer at beginning of genome and use start_idx instead + cpu.inst_ptr = 15; + // Ensure start_idx is used and search does not include the current instruction pointer + CHECK(cpu.FindNopSequence({0, 1}, (size_t)0) == 6); + CHECK(cpu.FindNopSequence({0, 1}, (size_t)6) == 16); + CHECK(cpu.FindNopSequence({0, 1}, (size_t)16) ==0); + // If sequence only appears once, always return it (also check if NopD is valid) + CHECK(cpu.FindNopSequence({3, 3}, (size_t)0) == 13); + CHECK(cpu.FindNopSequence({3, 3}, (size_t)12) == 13); + CHECK(cpu.FindNopSequence({3, 3}, (size_t)13) == 13); + CHECK(cpu.FindNopSequence({3, 3}, (size_t)14) == 13); + CHECK(cpu.FindNopSequence({3, 3}, (size_t)18) == 13); + // Found sequence can have extra nops + CHECK(cpu.FindNopSequence(emp::vector({1}), (size_t)0) == 1); + CHECK(cpu.FindNopSequence(emp::vector({1}), (size_t)9) == 17); + CHECK(cpu.FindNopSequence(emp::vector({1}), (size_t)17) == 1); + // If pattern not found, return instruction pointer + CHECK(cpu.FindNopSequence({1,1,1}, (size_t)0) == cpu.inst_ptr); + CHECK(cpu.FindNopSequence({1,1,1}, (size_t)9) == cpu.inst_ptr); + CHECK(cpu.FindNopSequence({1,1,1}, (size_t)17) == cpu.inst_ptr); + { // Reverse + emp::vector> test_vectors; + test_vectors.push_back(emp::vector({0,1})); + test_vectors.push_back(emp::vector({3,3})); + test_vectors.push_back(emp::vector({1})); + test_vectors.push_back(emp::vector({1,1,1})); + for(emp::vector v : test_vectors){ + for(size_t idx = 0; idx < cpu.GetGenomeSize(); ++idx){ + CHECK(cpu.FindNopSequence(v, idx, true) == cpu.FindNopSequence_Reverse(v, idx)); + } + } + } + } + { //FindNopSequence(search_vec, start_local, reverse = false) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + cpu.inst_ptr = 15; + emp::vector> test_vectors; + test_vectors.push_back(emp::vector({0,1})); + test_vectors.push_back(emp::vector({3,3})); + test_vectors.push_back(emp::vector({1})); + test_vectors.push_back(emp::vector({1,1,1})); + // Ensure output matches that of previously-tested overload + for(emp::vector v : test_vectors){ + for(size_t idx = 0; idx < cpu.GetGenomeSize(); ++idx){ + cpu.inst_ptr = idx; + CHECK(cpu.FindNopSequence(v, true, false) == cpu.FindNopSequence(v, cpu.inst_ptr)); + CHECK(cpu.FindNopSequence(v, true, true) == cpu.FindNopSequence(v,cpu.inst_ptr,true)); + CHECK(cpu.FindNopSequence(v, false, false) == + cpu.FindNopSequence(v,cpu.GetWorkingGenomeSize() - 1)); + CHECK(cpu.FindNopSequence(v, false, true) == + cpu.FindNopSequence(v,cpu.GetWorkingGenomeSize() - 1,true)); + } + } + } + { //FindNopSequence(start_local, reverse = false) + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopA\nAdd\nNopA\nNopB\nAdd\nNopB\nNopA\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + // Verify output matches previously tested overload + for(size_t idx = 0; idx < cpu.GetGenomeSize(); ++idx){ + cpu.inst_ptr = idx; + CHECK(cpu.FindNopSequence(true, false) == + cpu.FindNopSequence(cpu.genome_working[cpu.inst_ptr].nop_vec, cpu.inst_ptr)); + CHECK(cpu.FindNopSequence(true, true) == + cpu.FindNopSequence(cpu.genome_working[cpu.inst_ptr].nop_vec, cpu.inst_ptr, true)); + CHECK(cpu.FindNopSequence(false, false) == + cpu.FindNopSequence( + cpu.genome_working[cpu.inst_ptr].nop_vec, cpu.GetWorkingGenomeSize() - 1 + ) + ); + CHECK(cpu.FindNopSequence(false, true) == + cpu.FindNopSequence(cpu.genome_working[cpu.inst_ptr].nop_vec, + cpu.GetWorkingGenomeSize() - 1,true + ) + ); + } + } + /* + [X] GetComplementIdx returns the complement of a single nop + [X] Standard nops + [X] Expanded nops + [X] GetComplementLabel returns the complemented sequences of nops (rename) + [X] Standard nops + [X] Expanded nops + [X] CompareSequences determines if two nop vectors are identical + [X] Check length too! + [X] CheckIfLastCopied only returns true if the nop vector was the last thing copied + [X] FindLabel_Reverse(start_local){ + [X] FindLabel(start_local, reverse = false) + [X] FindNopSequence_Reverse(search_vec, start_idx) + [X] FindNopSequence_Reverse(search_vec, start_local) + [X] FindNopSequence_Reverse(start_local) + [X] FindNopSequence(search_vec, start_idx, reverse = false) + [X] FindNopSequence(search_vec, start_local, reverse = false) + [X] FindNopSequence(start_local, reverse = false) + */ +} +TEST_CASE("VirtualCPU_Stack_Methods", "[Hardware]") { + Derived cpu; + // Default stack + CHECK(cpu.active_stack_idx == 0); + cpu.regs[0] = 1; + cpu.regs[1] = 2; + cpu.regs[2] = 3; + cpu.StackPush(0); + CHECK(cpu.stacks[0].size() == 1); + CHECK(cpu.stacks[0][0] == 1); + cpu.StackPush(2); + CHECK(cpu.stacks[0].size() == 2); + CHECK(cpu.stacks[0][1] == 3); + cpu.StackPop(0); + CHECK(cpu.stacks[0].size() == 1); + CHECK(cpu.regs[0] == 3); + // Swap stacks + cpu.StackSwap(); + CHECK(cpu.active_stack_idx == 1); + cpu.regs[0] = 1; + cpu.regs[1] = 2; + cpu.regs[2] = 3; + cpu.StackPush(0); + CHECK(cpu.stacks[1].size() == 1); + CHECK(cpu.stacks[1][0] == 1); + cpu.StackPush(2); + CHECK(cpu.stacks[1].size() == 2); + CHECK(cpu.stacks[1][1] == 3); + cpu.StackPop(0); + CHECK(cpu.stacks[1].size() == 1); + CHECK(cpu.regs[0] == 3); + // Swap back + cpu.StackSwap(); + CHECK(cpu.active_stack_idx == 0); + /* + [X] StackPush pushes the register value onto the active stack + [X] StackPop pops the top value of the active stack and stores it in a register + [X] StackSwap actually swaps the active stack + */ +} +TEST_CASE("VirtualCPU_Processing_Methods", "[Hardware]") { + // SingleProcess(verbose = true) + Derived cpu; + cpu.PushDefaultInst(10); + CHECK(cpu.GetGenomeSize() == 10); + CHECK(cpu.GetWorkingGenomeSize() == 10); + CHECK(cpu.nops_need_curated); + CHECK(cpu.inst_ptr == 0); + cpu.SingleProcess(false); + CHECK(cpu.inst_ptr == 1); // IP incremented + CHECK(!cpu.nops_need_curated); // IP incremented + cpu.SingleProcess(false); + CHECK(cpu.inst_ptr == 2); // IP incremented + // Process(count = 1, verbose = true) + cpu.nops_need_curated = true; + cpu.Process(); + CHECK(cpu.inst_ptr == 3); // IP incremented + CHECK(!cpu.nops_need_curated); // IP incremented + cpu.Process(4, false); + CHECK(cpu.inst_ptr == 7); // IP incremented count times + cpu.Process(5, false); + CHECK(cpu.inst_ptr == 2); // IP wraps + /* + [X] SingleProcess + [X] Do bookkeeping as needed based on flags + [X] Process the next instruction + [X] Auto-advance IP? + [X] Process + [X] Calls SingleProcess N times + */ +} +TEST_CASE("VirtualCPU_String_Methods", "[Hardware]") { + Derived::inst_lib_t inst_lib; + inst_lib.AddInst("NopA", Derived::inst_lib_t::Inst_NopA, 0, "No-operation A"); + inst_lib.AddInst("NopB", Derived::inst_lib_t::Inst_NopB, 0, "No-operation B"); + inst_lib.AddInst("NopC", Derived::inst_lib_t::Inst_NopC, 0, "No-operation C"); + inst_lib.AddInst("NopD", Derived::inst_lib_t::Inst_NopC, 0, "No-operation D"); + inst_lib.AddInst("Label", Derived::inst_lib_t::Inst_NopC, 0, "Fake label"); + inst_lib.AddInst("Add", Derived::inst_lib_t::Inst_NopC, 0, "Fake add"); + Derived::genome_t genome = Derived::genome_t(inst_lib); + Derived cpu(genome); + // x A B x B D x A B x B D C x D D L A B + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + std::stringstream sstr; + sstr << "Add\nNopA\nNopB\nAdd\nNopB\nNopD\nAdd\nNopA\nNopB\nAdd\nNopB\nNopD\nNopC\n" + "Add\nNopD\nNopD\nLabel\nNopA\nNopB"; + cpu.Load(sstr); + cpu.CurateNops(); + CHECK(cpu.GetGenomeString() == "[19]fabfbdfabfbdcfddeab"); + CHECK(cpu.GetWorkingGenomeString() == "[19]fabfbdfabfbdcfddeab"); + cpu.genome_working.resize(3, 0); + CHECK(cpu.GetGenomeString() == "[19]fabfbdfabfbdcfddeab"); + CHECK(cpu.GetWorkingGenomeString() == "[3]fab"); + std::stringstream ostr; + cpu.PrintDetails(ostr); + std::cout << ostr.str(); + CHECK(ostr.str().size() > 0); + emp::vector string_vec; + std::string output_str = ostr.str(); + emp::slice(ostr.str(), string_vec, '\n'); + CHECK(string_vec.size() == 5); + CHECK(string_vec[0] == "IP: 0 RH: 0 WH: 0 FH: 0(nops: 4; regs: 4)"); + CHECK(string_vec[1] == "[0] 0"); + CHECK(string_vec[2] == "[1] 1"); + CHECK(string_vec[3] == "[2] 2"); + CHECK(string_vec[4] == "[3] 3\n"); // last line gets to keep its newline character + /* + [X] GetWorkingGenomeString returns a string representation of the working genome + [X] GetGenomeString returns a string representation of the (non-working) genome + [X] Print details dumps registers and such to the stream + */ +} diff --git a/tests/hardware/ancestor_default.org b/tests/hardware/ancestor_default.org new file mode 100644 index 0000000000..cc4be6c052 --- /dev/null +++ b/tests/hardware/ancestor_default.org @@ -0,0 +1,50 @@ +HAlloc +HSearch +NopC +NopA +MovHead +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +NopC +HSearch +HCopy +IfLabel +NopC +NopA +HDivide +MovHead +NopA +NopB diff --git a/third-party/Catch b/third-party/Catch index 0f05c034c2..62fd660583 160000 --- a/third-party/Catch +++ b/third-party/Catch @@ -1 +1 @@ -Subproject commit 0f05c034c2cf4dd039b8ea375c70dbe657f21d97 +Subproject commit 62fd660583d3ae7a7886930b413c3c570e89786c diff --git a/third-party/force-cover b/third-party/force-cover index 7bd76e823f..d0e705cb15 160000 --- a/third-party/force-cover +++ b/third-party/force-cover @@ -1 +1 @@ -Subproject commit 7bd76e823f49030c145781bc15cf85d91c1e9324 +Subproject commit d0e705cb159267ff277bb0becb464dcae5c44218