Program Listing for File SymWorld.test.cc

Return to documentation for file (source/test/default_mode_test/SymWorld.test.cc)

#include "../../default_mode/SymWorld.h"
#include "../../default_mode/Symbiont.h"
#include "../../lysis_mode/Phage.h"
#include "../../lysis_mode/LysisWorld.h"
#include "../../default_mode/Host.h"
#include "../../pgg_mode/Pgghost.h"
#include "../../pgg_mode/Pggsym.h"


TEST_CASE("PullResources", "[default]") {
  GIVEN(" a world ") {
    emp::Random random(19);
    SymWorld world(random);
    int full_share = 100;

    WHEN(" the resources are unlimited ") {
      world.SetLimitedRes(false);

      THEN(" organisms get as many resources as they request ") {
        REQUIRE(world.PullResources(full_share) == full_share);
      }
    }

    WHEN( " the resources are limited ") {
      world.SetLimitedRes(true);
      int original_total = 150;
      world.SetTotalRes(original_total);

      THEN(" first organism gets full share of resources, next host gets a bit, everyone else gets nothing ") {
        REQUIRE(world.PullResources(full_share) == full_share);
        REQUIRE(world.PullResources(full_share) == (original_total-full_share));
        REQUIRE(world.PullResources(full_share) == 0);
        REQUIRE(world.PullResources(full_share) == 0);
      }
    }
  }
}

TEST_CASE( "Vertical Transmission", "[default]" ) {
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymWorld w(random);

    WHEN( "the vertical taransmission rate is 0" ) {
      w.SetVertTrans(0);

      THEN( "there is never vertical transmission" ) {
        REQUIRE( w.WillTransmit() == false );
        REQUIRE( w.WillTransmit() == false );
        REQUIRE( w.WillTransmit() == false );
        REQUIRE( w.WillTransmit() == false );
        REQUIRE( w.WillTransmit() == false );
      }
    }

    WHEN( "the vertical taransmission rate is 1" ) {
      w.SetVertTrans(1);

      THEN( "there is always vertical transmission" ) {
        REQUIRE( w.WillTransmit() == true );
        REQUIRE( w.WillTransmit() == true );
        REQUIRE( w.WillTransmit() == true );
        REQUIRE( w.WillTransmit() == true );
        REQUIRE( w.WillTransmit() == true );
      }
    }

    WHEN( "the vertical taransmission rate is .5" ) {
      w.SetVertTrans(.5);

      THEN( "there is sometimes vertical transmission" ) {
        bool yes = false;
        bool no = false;
        for(int i = 0; i < 128; i++)//Odds of failure should be 1 in 170141183460469231731687303715884105728
          if(w.WillTransmit())
            yes = true;
          else
            no = true;
        REQUIRE( yes == true );
        REQUIRE( no == true );
      }
    }
  }
}

TEST_CASE( "World Capacity", "[default]" ) {
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymConfigBase config;
    SymWorld w(random);

    WHEN( "hosts are added" ) {

      int n = 7532;

      //inject organisms
      for (int i = 0; i < n; i++){
        emp::Ptr<Host> new_org;
        new_org.New(&random, &w, &config, 0);
        w.AddOrgAt(new_org, w.size());
      }

      THEN( "the world's size becomes the number of hosts that were added" ) {
        REQUIRE( (int) w.GetPop().size() == n );
      }
    }
  }
}

TEST_CASE( "Interaction Patterns", "[default]" ) {
  SymConfigBase config;

  GIVEN( "a world without vertical transmission" ) {
    emp::Ptr<emp::Random> random = new emp::Random(17);
    SymWorld w(*random);
    config.VERTICAL_TRANSMISSION(0);
    w.SetVertTrans(0);
    config.MUTATION_SIZE(0);
    config.SYM_LIMIT(500);
    config.HORIZ_TRANS(true);
    config.HOST_REPRO_RES(400);
    config.RES_DISTRIBUTE(100);
    w.SetResPerUpdate(100);
    config.SYNERGY(5);

    WHEN( "hostile hosts meet generous symbionts" ) {

      //inject organisms
      for (size_t i = 0; i < 10; i++){
        emp::Ptr<Host> new_org = emp::NewPtr<Host>(random, &w, &config, -0.1);
        w.AddOrgAt(new_org, w.size());
      }

      w.Resize(10, 1);
      for (size_t i = 0; i< 10; i++){
        emp::Ptr<Symbiont> new_sym = emp::NewPtr<Symbiont>(random, &w, &config, 0.1);
        w.InjectSymbiont(new_sym);
      }
      //Simulate
      for(int i = 0; i < 100; i++) {
        w.Update();
      }

      THEN( "the symbionts all die" ) {

        for(size_t i = 0; i < w.GetPop().size(); i++)
          REQUIRE( !(w.GetPop()[i] && w.GetPop()[i]->HasSym()) );//We can't have a host exist with a symbiont in it.
      }
    }
  }




  GIVEN( "a world" ) {
    emp::Random random(17);
    SymWorld w(random);
    w.SetPopStruct_Mixed();
    config.GRID(0);
    config.VERTICAL_TRANSMISSION(0.7);
    w.SetVertTrans(0.7);
    config.MUTATION_SIZE(0.002);
    config.SYM_LIMIT(500);
    config.HORIZ_TRANS(true);
    config.HOST_REPRO_RES(10);
    config.RES_DISTRIBUTE(100);
    config.SYNERGY(5);

    WHEN( "very generous hosts meet many very hostile symbionts" ) {

      //inject organisms
      for (size_t i = 0; i < 200; i++){
        emp::Ptr<Host> new_org;
        new_org.New(&random, &w, &config, 1);
        w.AddOrgAt(new_org, w.size());
      }

      w.Resize(100, 200);

      for (size_t i = 0; i < 10000; i++){
        emp::Ptr<Symbiont> new_sym;
        new_sym.New(&random, &w, &config, -1);
        w.InjectSymbiont(new_sym);
      }

      //Simulate
      for(int i = 0; i < 100; i++)
        w.Update();

      THEN( "the hosts cannot reproduce" ) {
          REQUIRE( w.GetNumOrgs() == 200 );
      }
    }

    GIVEN( "a PGGworld without vertical transmission" ) {
    emp::Ptr<emp::Random> random = new emp::Random(17);
    PggWorld w(*random);
    config.VERTICAL_TRANSMISSION(0);
    w.SetVertTrans(0);
    config.MUTATION_SIZE(0);
    config.SYM_LIMIT(500);
    config.HORIZ_TRANS(true);
    config.HOST_REPRO_RES(400);
    config.RES_DISTRIBUTE(100);
    w.SetResPerUpdate(100);
    config.SYNERGY(5);
    config.PGG(1);

    WHEN( "hostile hosts meet generous symbionts" ) {

      //inject organisms
      for (size_t i = 0; i < 10; i++){
        emp::Ptr<PggHost> new_org = emp::NewPtr<PggHost>(random, &w, &config, -0.1);
        w.AddOrgAt(new_org, w.size());
      }
      for (size_t i = 0; i< 10; i++){
        emp::Ptr<PGGSymbiont> new_sym = emp::NewPtr<PGGSymbiont>(random, &w, &config, 0.1);
        w.InjectSymbiont(new_sym);
      }

      //Simulate
      for(int i = 0; i < 100; i++) {
        w.Update();
      }

      THEN( "the symbionts all die" ) {
        for(size_t i = 0; i < w.GetPop().size(); i++)
          REQUIRE( !(w.GetPop()[i] && w.GetPop()[i]->HasSym()) );//We can't have a host exist with a symbiont in it.
      }
    }
  }




  GIVEN( "a PGGworld" ) {
    emp::Random random(17);
    PggWorld w(random);
    w.SetPopStruct_Mixed();
    config.GRID(0);
    config.VERTICAL_TRANSMISSION(0.7);
    w.SetVertTrans(0.7);
    config.MUTATION_SIZE(0.002);
    config.SYM_LIMIT(500);
    config.HORIZ_TRANS(true);
    config.HOST_REPRO_RES(10);
    config.RES_DISTRIBUTE(100);
    config.SYNERGY(5);
    config.PGG(1);
    w.Resize(100, 200);


    WHEN( "very generous hosts meet many very hostile symbionts" ) {

      //inject organisms
      for (size_t i = 0; i < 200; i++){
        emp::Ptr<PggHost> new_org;
        new_org.New(&random, &w, &config, 1);
        w.AddOrgAt(new_org, w.size());
      }
      for (size_t i = 0; i < 10000; i++){
        emp::Ptr<PGGSymbiont> new_sym;
        new_sym.New(&random, &w, &config, -1);
        w.InjectSymbiont(new_sym);
      }

      //Simulate
      for(int i = 0; i < 100; i++)
        w.Update();

      THEN( "the hosts cannot reproduce" ) {
          REQUIRE( w.GetNumOrgs() == 200 );
      }
    }
  }
}
}

TEST_CASE( "Hosts injected correctly", "[default]" ) {
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymConfigBase config;
    SymWorld w(random);

    WHEN( "host added with interaction value 1" ) {
      //inject organism
      emp::Ptr<Host> new_org1;
      new_org1.New(&random, &w, &config, 1);
      w.AddOrgAt(new_org1, 0);

      THEN( "host has interaction value of 1" ) {
        REQUIRE( w.GetOrg(0).GetIntVal() == 1 );
      }
    }
    WHEN( "host added with interaction value -1" ) {
      //inject organism
      emp::Ptr<Host> new_org1;
      new_org1.New(&random, &w, &config, -1);
      w.AddOrgAt(new_org1, 0);

      THEN( "host has interaction value of -1" ) {
        REQUIRE( w.GetOrg(0).GetIntVal() == -1 );
      }
    }
    WHEN( "host added with interaction value 0" ) {
      //inject organism
      emp::Ptr<Host> new_org1;
      new_org1.New(&random, &w, &config, 0);
      w.AddOrgAt(new_org1, 0);

      THEN( "host has interaction value of 0" ) {
        REQUIRE( w.GetOrg(0).GetIntVal() == 0 );
      }
    }
  }
}

TEST_CASE( "InjectSymbiont", "[default]" ){
  GIVEN( "a world" ){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);

    WHEN( "free living syms are not allowed" ){
      config.FREE_LIVING_SYMS(0);
      w.SetFreeLivingSyms(false);

      emp::Ptr<Organism> host = new Host(&random, &w, &config, int_val);
      w.AddOrgAt(host, 0);
      emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);

      THEN( "syms are injected into a random host" ){
        w.InjectSymbiont(sym);
        emp::vector<emp::Ptr<Organism>> host_syms = host->GetSymbionts();

        REQUIRE(host_syms.size() == 1);
        REQUIRE(host_syms.at(0) == sym);
      }
    }
    WHEN( "free living syms are allowed" ){
      w.Resize(1000);
      config.FREE_LIVING_SYMS(1);
      w.SetFreeLivingSyms(true);


      THEN( "syms can be injected into a random empty cell" ){

        REQUIRE(w.GetNumOrgs() == 0);

        size_t sym_count = 100;

        for(size_t i = 0; i < sym_count; i++){
          w.InjectSymbiont(new Symbiont(&random, &w, &config, int_val));
        }
        //since spot of injection is random, a few symbionts
        //will get overwritten, and thus # injected != # remaining in world
        REQUIRE(w.GetNumOrgs() < (sym_count + 1));
        REQUIRE(w.GetNumOrgs() > (sym_count - 10));
      }
    }
  }
}

TEST_CASE( "DoBirth", "[default]" ){
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);
    w.SetFreeLivingSyms(true);
    emp::Ptr<Organism> h2 = new Host(&random, &w, &config, int_val);
    w.AddOrgAt(h2, 3);
    emp::Ptr<Organism> host = new Host(&random, &w, &config, int_val);

    WHEN( "born into an empty spot" ){
      THEN( "occupies that spot" ){
        w.DoBirth(host, 2);

        REQUIRE(w.GetNumOrgs() == 2);
        bool host_isborn = false;
        for(size_t i = 0; i < 4; i++){
          if(&w.GetOrg(i) == host) {
            host_isborn = true;
            break;
          }
        }
        REQUIRE(host_isborn == true);
      }
    }
    WHEN( "born into a spot occupied by another host" ){
      THEN( "kills that host and replaces it" ){
        emp::Ptr<Organism> other_host = new Host(&random, &w, &config, int_val);
        w.AddOrgAt(other_host, 0);
        w.DoBirth(host, 2);

        REQUIRE(w.GetNumOrgs() == 2);

        bool host_isborn = false;
        bool otherhost_isdead = true;
        for(size_t i = 0; i < 4; i++){
          if(w.GetPop()[i] == host) {
            host_isborn = true;
          } else if (w.GetPop()[i] != nullptr && w.GetPop()[i] != h2){
            otherhost_isdead = false;
          }
        }
        REQUIRE(w.GetNumOrgs() == 2);
        REQUIRE(host_isborn == true);
        REQUIRE(otherhost_isdead == true);
      }
    }
  }
}

TEST_CASE( "SymDoBirth", "[default]" ) {
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);

    WHEN( "free living symbionts are not allowed" ) {
      config.FREE_LIVING_SYMS(0);
      w.SetFreeLivingSyms(false);

      WHEN( "there is a valid neighbouring host" ){
        emp::Ptr<Host> host = new Host(&random, &w, &config, int_val);
        w.AddOrgAt(host, 0);

        emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
        w.SymDoBirth(sym, 1);

        emp::vector<emp::Ptr<Organism>> syms = host->GetSymbionts();
        emp::Ptr<Organism> host_sym = syms[0];

        THEN( "the sym is inserted into the valid neighbouring host" ){
          REQUIRE(host_sym == sym);
          REQUIRE(w.GetNumOrgs() == 1);
        }
      }

      WHEN( "there is no valid neighbouring host" ){
        emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
        w.SymDoBirth(sym, 1);

        THEN( "the sym is killed" ){
          //the world should be empty
          REQUIRE(w.GetNumOrgs() == 0);
        }
      }
    }


    WHEN( "free living symbionts are allowed"){
      config.FREE_LIVING_SYMS(1);
      w.SetFreeLivingSyms(true);
      config.SYM_LIMIT(3);

      emp::Ptr<Organism> host1 = new Host(&random, &w, &config, int_val);

      emp::Ptr<Organism> sym1 = new Symbiont(&random, &w, &config, int_val);
      emp::Ptr<Organism> sym2 = new Symbiont(&random, &w, &config, int_val);

      WHEN("sym is inserted into an empty world"){
        THEN("it occupies some empty cell"){
          emp::WorldPosition parent_pos = emp::WorldPosition(3, 3);
          w.AddOrgAt(new Symbiont(&random, &w, &config, int_val), parent_pos);
          w.SymDoBirth(sym1, parent_pos);
          REQUIRE(w.GetNumOrgs() == 2);
        }
      }
      WHEN("sym is inserted into a not-empty world"){
        THEN("it might be inserted into an empty cell"){
          w.AddOrgAt(host1, 0);
          w.SymDoBirth(sym1, 2);

          REQUIRE(w.GetNumOrgs() == 2);

          bool sym_injected = false;
          for(size_t i = 0; i < 4; i++){
            if(w.GetSymPop()[i] == sym1) {
              sym_injected = true;
              break;
            }
          }
          REQUIRE(sym_injected == true);
        }
        THEN("it might be insterted into a cell with a sym, killing and replacing it"){
          w.Resize(2,1);
          w.AddOrgAt(sym1, 0);
          w.AddOrgAt(sym2, emp::WorldPosition(0, 1));

          emp::Ptr<Organism> new_sym = new Symbiont(&random, &w, &config, int_val);

          w.SymDoBirth(new_sym, 0);

          REQUIRE(w.GetNumOrgs() == 2);

          bool new_sym_born = false;
          bool sym1_deleted = true;
          bool sym2_deleted = true;
          for(size_t i = 0; i < 2; i++){
            emp::Ptr<Organism> element = w.GetSymPop()[i];
            if(element == new_sym) new_sym_born = true;
            else if(element == sym1) sym1_deleted = false;
            else if(element == sym2) sym2_deleted = false;
          }

          REQUIRE(new_sym_born == true);
          REQUIRE(sym1_deleted != sym2_deleted); //Only one sym should be deleted
        }
      }
    }
  }
}

TEST_CASE( "Update", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);
    int resPerUpdate = 10;
    config.RES_DISTRIBUTE(resPerUpdate);

    emp::Ptr<Host> host = new Host(&random, &w, &config, int_val);

    WHEN("free living syms are not allowed"){
      w.AddOrgAt(host, 0);

      WHEN("a host is dead"){
        THEN("it is removed from the world"){
          host->SetDead();
          REQUIRE(w.GetNumOrgs() == 1);

          w.Update();
          REQUIRE(w.GetNumOrgs() == 0);
        }
      }
      THEN("hosts process normally"){
        int resBeforeUpdate = host->GetPoints();
        w.Update();
        int resAfterUpdate = host->GetPoints();
        int resChange = resAfterUpdate - resBeforeUpdate;
        REQUIRE(resPerUpdate == resChange);
      }
    }


    WHEN("free living syms are allowed"){
      int resPerUpdate = 80;
      config.RES_DISTRIBUTE(resPerUpdate);
      config.FREE_SYM_RES_DISTRIBUTE(resPerUpdate);
      w.Resize(4,4);
      w.SetFreeLivingSyms(1);
      config.FREE_LIVING_SYMS(1);
      config.MOVE_FREE_SYMS(1);

      WHEN("there are no syms in the world"){
        THEN("hosts process normally"){
          host = new Host(&random, &w, &config, int_val);
          w.AddOrgAt(host, 0);
          int orig_points = host->GetPoints();
          w.Update();

          REQUIRE(host->GetPoints() - orig_points == resPerUpdate);
        }
      }

      WHEN("lysis is permitted, and thus phage are used"){
        LysisWorld lw(random);
        lw.Resize(4,4);
        lw.SetFreeLivingSyms(1);

        config.LYSIS(1);
        config.LYSIS_CHANCE(1);
        int burst_time = 2;
        config.BURST_TIME(burst_time);
        emp::Ptr<Organism> p = new Phage(&random, &lw, &config, int_val);

        WHEN("there are no hosts"){
          THEN("phage don't reproduce or get points on update"){
            lw.AddOrgAt(p, 0);

            int orig_num_orgs = lw.GetNumOrgs();
            int orig_points = p->GetPoints();

            for(int i = 0; i < 4; i ++){
              lw.Update();
            }

            int new_num_orgs = lw.GetNumOrgs();
            int new_points = p->GetPoints();

            REQUIRE(new_num_orgs == orig_num_orgs);
            REQUIRE(new_points == orig_points);
          }
        }

        WHEN("there are hosts"){
          THEN("phage and hosts mingle in the world"){
            lw.AddOrgAt(host, 0);
            lw.AddOrgAt(p, emp::WorldPosition(0,1));

            for(int i = 0; i < 5; i++){
              lw.Update();
            }

            REQUIRE(lw.GetNumOrgs() == 2);
          }
        }
      }

      WHEN("lysis is not permitted, and symbionts are used"){
        config.LYSIS(0);
        config.HORIZ_TRANS(1);
        w.Resize(3,3);

        THEN("if only syms in the world they can get resources and reproduce"){
          emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
          w.SymDoBirth(sym, 0);
          REQUIRE(w.GetNumOrgs() == 1);

          for(int i = 0; i <= 4; i++){
            w.Update();
          }
          //the sym has reproduced at least once
          REQUIRE(w.GetNumOrgs() > 1);

          int num_pop_elements = 0;
          for(int i = 0; i < 9; i++) if(w.GetPop()[i]) num_pop_elements++;
          REQUIRE(num_pop_elements == 0);
        }
        THEN("hosts and syms can mingle in the environment"){
          w.AddOrgAt(host, 0);
          w.AddOrgAt(new Symbiont(&random, &w, &config, int_val), emp::WorldPosition(0,1));
          for(int i = 0; i <= 4; i++){ w.Update(); }

          //the organisms have done something
          REQUIRE(w.GetNumOrgs() > 2);

          int free_sym_count = 0;
          int hosted_sym_count = 0;
          int host_count = 0;
          for(int i = 0; i < 9; i++){
            if(w.GetPop()[i]){
              host_count++;
              hosted_sym_count += w.GetOrg(i).GetSymbionts().size();
            }
            if(w.GetSymPop()[i]) free_sym_count++;
          }
          //there should be at least one free sym, hosted sym, and host
          REQUIRE(free_sym_count > 0);
          REQUIRE(hosted_sym_count > 0);
          REQUIRE(host_count > 0);
        }
      }
    }
  }
}

TEST_CASE( "MoveFreeSym", "[default]" ){
  GIVEN("free living syms are allowed"){
    emp::Random random(14);
    SymConfigBase config;
    SymWorld w(random);
    w.SetFreeLivingSyms(true);
    int int_val = 0;
    w.Resize(2,2);
    size_t host_pos = 0;
    emp::WorldPosition sym_pos = emp::WorldPosition(0, host_pos);

    emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
    w.AddOrgAt(sym, sym_pos);
    WHEN("there is a parallel host and the sym wants to infect"){
      emp::Ptr<Organism> host = new Host(&random, &w, &config, int_val);
      w.AddOrgAt(host, host_pos);
      REQUIRE(w.GetNumOrgs() == 2);
      REQUIRE(host->HasSym() == false);

      WHEN("the infection fails"){
        config.SYM_INFECTION_FAILURE_RATE(1);
        emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
        w.AddOrgAt(sym, sym_pos);
        REQUIRE(w.GetNumOrgs() == 2);
        THEN("the sym is deleted"){
          w.MoveFreeSym(sym_pos);
          REQUIRE(w.GetNumOrgs() == 1);
          REQUIRE(!host->HasSym());
        }
      }
      WHEN("the infection does not fail"){
        THEN("the sym moves into the host"){
          w.MoveFreeSym(sym_pos);
          REQUIRE(w.GetNumOrgs() == 1);
          REQUIRE(host->HasSym());
          REQUIRE(host->GetSymbionts()[0] == sym);
        }
      }
    }
    WHEN("the sym does not want to/can't infect a parallel host"){
      size_t sym_id = 0;
      WHEN("moving is turned on"){
        w.SetMoveFreeSyms(1);
        sym->SetInfectionChance(0);
        THEN("the sym moves to a random spot in the free world"){
          REQUIRE(w.GetSymPop()[sym_id] == sym); //there should be a sym at pos 0
          w.MoveFreeSym(sym_pos);

          size_t new_sym_id = 2;
          emp::Ptr<Organism> new_sym = w.GetSymPop()[new_sym_id];
          REQUIRE(sym == new_sym);
          REQUIRE(w.GetSymPop()[sym_id] == nullptr);
        }
      }
      WHEN("moving is turned off"){
        w.SetMoveFreeSyms(0);
        THEN("the sym doesn't move"){
          REQUIRE(w.GetSymPop()[sym_id] == sym);
          w.MoveFreeSym(sym_pos);
          emp::Ptr<Organism> new_sym = w.GetSymPop()[sym_id];
          REQUIRE(sym == new_sym);
        }
      }
    }
  }
}

TEST_CASE( "ExtractSym", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);
    size_t sym_index = 1;
    emp::WorldPosition sym_pos = emp::WorldPosition(0, sym_index);
    emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);

    w.AddOrgAt(sym, sym_pos);
    REQUIRE(w.GetSymPop()[sym_index] == sym);
    REQUIRE(w.GetNumOrgs() == 1);

    emp::Ptr<Organism> new_org = w.ExtractSym(sym_index);
    REQUIRE(sym == new_org);
    REQUIRE(w.GetNumOrgs() == 0);
    REQUIRE(w.GetSymPop()[sym_index] == nullptr);
  }
}

TEST_CASE( "MoveIntoNewFreeWorldPos", "[default]" ){
  GIVEN("free living syms are allowed"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);

    emp::Ptr<Organism> parent_sym = new Symbiont(&random, &w, &config, int_val);
    emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);

    size_t orig_pos = 3;
    emp::WorldPosition orig_sym_pos = emp::WorldPosition(0, orig_pos);
    w.AddOrgAt(parent_sym, orig_sym_pos);

    REQUIRE(w.GetNumOrgs() == 1);

    w.MoveIntoNewFreeWorldPos(sym, orig_sym_pos);
    REQUIRE(w.GetNumOrgs() == 2);
    REQUIRE(w.GetSymPop()[orig_pos] == parent_sym);

    bool sym_exists = false;
    for(int i = 0; i < 2; i++) {
      if (w.GetSymPop()[i] == sym) {
        sym_exists = true;
        break;
      }
    }
    REQUIRE(sym_exists == true);
    //TODO: CHECK MOVING OUT OF HOST
  }
}

TEST_CASE( "Resize", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymWorld w(random);

    size_t pop_size = w.GetPop().size();
    size_t sym_pop_size = w.GetSymPop().size();
    REQUIRE(pop_size == sym_pop_size);
    REQUIRE(pop_size == 0);

    w.Resize(3,3);
    pop_size = w.GetPop().size();
    sym_pop_size = w.GetSymPop().size();
    REQUIRE(pop_size == sym_pop_size);
    REQUIRE(pop_size == 9);

    w.Resize(11);
    pop_size = w.GetPop().size();
    sym_pop_size = w.GetSymPop().size();
    REQUIRE(pop_size == sym_pop_size);
    REQUIRE(pop_size == 11);
  }
}

TEST_CASE( "AddOrgAt", "[default]" ){
  //adding hosts to the world should be covered by Empirical tests,
  //so here we'll test adding a sym
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);
    emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);

    WHEN("a sym is added into an empty spot"){
      THEN("it occupies that spot"){
        REQUIRE(w.GetNumOrgs() == 0);
        REQUIRE(w.GetSymPop()[0] == nullptr);
        w.AddOrgAt(sym, 0);
        REQUIRE(w.GetNumOrgs() == 1);
        REQUIRE(w.GetSymPop()[0] == sym);
      }
    }
    WHEN("a sym is added into an occupied spot"){
      THEN("it replaces the occupying sym"){
        emp::Ptr<Organism> old_sym = new Symbiont(&random, &w, &config, int_val);
        w.AddOrgAt(old_sym, 0);
        REQUIRE(w.GetNumOrgs() == 1);
        REQUIRE(w.GetSymPop()[0] == old_sym);

        w.AddOrgAt(sym, 0);
        REQUIRE(w.GetNumOrgs() == 1);
        REQUIRE(w.GetSymPop()[0] == sym);
      }
    }
    WHEN("a sym is added to an out of bounds pos"){
      THEN("pop and sym_pop are expanded to fit it"){
        emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_val);
        REQUIRE(w.GetSymPop().size() == 4);
        w.AddOrgAt(sym, emp::WorldPosition(0,7));
        REQUIRE(w.GetSymPop().size() == w.GetPop().size());
        REQUIRE(w.GetSymPop().size() == 8);
      }
    }
  }
}

TEST_CASE( "GetSymAt", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld w(random);
    w.Resize(2,2);


    WHEN("a request is made for an in-bounds sym"){
      emp::Ptr<Organism> sym1 = new Symbiont(&random, &w, &config, int_val);
      emp::Ptr<Organism> sym2 = new Symbiont(&random, &w, &config, int_val);
      w.AddOrgAt(sym1, 0);
      w.AddOrgAt(sym2, emp::WorldPosition(0,1));
      THEN("the sym at that position is returned"){
        REQUIRE(w.GetSymAt(0) == sym1);
        REQUIRE(w.GetSymAt(1) == sym2);
        REQUIRE(w.GetSymAt(2) == nullptr);
      }
    }
    WHEN("a request is made for an out-of-bounds sym"){
      THEN("an exception is thrown"){
        REQUIRE_THROWS(w.GetSymAt(4));
      }
    }
  }
}

TEST_CASE( "DoSymDeath", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    SymWorld w(random);
    w.Resize(2,2);
    emp::Ptr<Organism> s = new Symbiont(&random, &w, &config, 1);
    size_t sym_position = 1;
    w.AddOrgAt(s, emp::WorldPosition(0, sym_position));

    WHEN("A sym is deleted from a position"){
      THEN("It is no longer included in the count of organisms in the world"){
        REQUIRE(w.GetNumOrgs() == 1);
        w.DoSymDeath(sym_position);
        REQUIRE(w.GetNumOrgs() == 0);
      }
      THEN("It no longer occupies a spot in the world"){
        emp::Ptr<Organism> world_sym = w.GetSymAt(sym_position);
        REQUIRE(world_sym == s);
        w.DoSymDeath(sym_position);

        emp::Ptr<Organism> world_sym_deleted = w.GetSymAt(sym_position);
        REQUIRE(world_sym_deleted == nullptr);
      }
    }
  }
}

TEST_CASE( "Host Phylogeny", "[default]" ){
  emp::Random random(17);
  SymConfigBase config;
  config.MUTATION_SIZE(0.09);
  config.MUTATION_RATE(1);
  config.PHYLOGENY(1);
  int int_val = 0;
  SymWorld w(random);
  w.Resize(2,2);
  w.SetTrackPhylogeny(1);
  w.SetNumPhyloBins(20);


  emp::Ptr<Organism> host = new Host(&random, &w, &config, int_val);
  emp::Ptr<emp::Systematics<Organism,int>> host_sys = w.GetHostSys();

  //ORGANISMS ADDED TO SYSTEMATICS
  WHEN("an organism is added to the world"){
    WHEN("the cell it's added to is occupied"){
      size_t pos = 0;

      emp::Ptr<Organism> occupying_host = new Host(&random, &w, &config, -1);
      w.AddOrgAt(occupying_host, pos);
      size_t expected_occupying_taxon_info = 0;
      size_t taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();

      REQUIRE(w.GetNumOrgs() == 1);
      REQUIRE(host_sys->GetNumActive() == 1);
      REQUIRE(expected_occupying_taxon_info == taxon_info);

      w.AddOrgAt(host, pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      size_t expected_taxon_info = 10;

      THEN("the occupying organism is removed from the systematic"){
        REQUIRE(w.GetNumOrgs() == 1);
        REQUIRE(host_sys->GetNumActive() == 1);
        REQUIRE(expected_taxon_info == taxon_info);
      }
    }
    WHEN("the cell it's added to is empty"){
      size_t pos = 0;

      REQUIRE(w.GetNumOrgs() == 0);
      REQUIRE(host_sys->GetNumActive() == 0);

      w.AddOrgAt(host, pos);
      size_t taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      size_t expected_taxon_info = 10;

      THEN("the occupying organism is removed from the systematic"){
        REQUIRE(w.GetNumOrgs() == 1);
        REQUIRE(host_sys->GetNumActive() == 1);
        REQUIRE(expected_taxon_info == taxon_info);
      }
    }

    //ORGANISMS ADDED TO TAXA
    THEN("the organsim is added to the correct taxon"){
      size_t pos = 0;

      //taxon info 0
      double int_val = -1;
      int expected_taxon_info = 0;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      int taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 0
      int_val = -0.98;
      expected_taxon_info = 0;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 1
      int_val = -0.9;
      expected_taxon_info = 1;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 2
      int_val = -0.8;
      expected_taxon_info = 2;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 16
      int_val = 0.65 ;
      expected_taxon_info = 16;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 19
      int_val = 0.9;
      expected_taxon_info = 19;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 19
      int_val = 1;
      expected_taxon_info = 19;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //true even if the number of bins changes!
      w.SetNumPhyloBins(2);
      //taxon info 1
      int_val = 1;
      expected_taxon_info = 1;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 1
      int_val = 0;
      expected_taxon_info = 1;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);

      //taxon info 1
      int_val = -0.001;
      expected_taxon_info = 0;
      w.AddOrgAt(new Host(&random, &w, &config, int_val), pos);
      taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
      REQUIRE(expected_taxon_info == taxon_info);
    }
  }

  //ORGANISMS AND RELATIONSHIPS TRACKED
  WHEN("Several generations pass"){
    THEN("The phylogenetic relationships are tracked and accurate"){
      w.Resize(10);
      int num_descendants = 4;
      //add the first host
      w.AddOrgAt(new Host(&random, &w, &config, 0), 0);

      //populate the world with descendents with various interaction values
      //Can't use num_descendants for the following array sizes because some
      //compilers don't allow it
      double int_vals[4] = {0.1, -0.05, -0.2, 0.14};
      //bins: parent org in 10, then in int_vals order: 11, 9, 8, 11
      size_t parents[4] = {0, 1, 1, 3};
      for(int i = 0; i < num_descendants; i++){
        w.AddOrgAt(new Host(&random, &w, &config, int_vals[i]), (i+1), parents[i]);
      }

      char lineages[][30] = {"Lineage:\n10\n",
                             "Lineage:\n11\n10\n",
                             "Lineage:\n9\n11\n10\n",
                             "Lineage:\n8\n11\n10\n",
                             "Lineage:\n11\n8\n11\n10\n",
                           };


      for(int i = 0; i < (num_descendants+1); i++){
        std::stringstream result;
        host_sys->PrintLineage(host_sys->GetTaxonAt(i), result);
        REQUIRE(result.str() == lineages[i]);
      }
    }
  }
}

TEST_CASE( "Symbiont Phylogeny", "[default]" ){
  emp::Random random(17);
  SymConfigBase config;
  config.MUTATION_SIZE(0.09);
  config.MUTATION_RATE(1);
  config.FREE_LIVING_SYMS(1);
  config.PHYLOGENY(1);
  int int_val = 0;
  SymWorld w(random);
  w.Resize(20);
  w.SetTrackPhylogeny(1);
  w.SetFreeLivingSyms(1);
  w.SetNumPhyloBins(20);

  emp::Ptr<emp::Systematics<Organism,int>> sym_sys = w.GetSymSys();
  WHEN("symbionts are added to the world"){
    REQUIRE(sym_sys->GetNumActive() == 0);
    size_t count = 8;
    //Can't use count for the following array sizes because some
    //compilers don't allow it
    double int_vals[8] = {-1, -0.9, -0.82, 0, 0.5, 0.65, 0.9, 1};
    int taxon_infos[8] = {0, 1, 1, 10, 15, 16, 19, 19};
    //int taxon_infos[8] = {0, 0, 0, 2, 3, 4, 4, 4};
    emp::Ptr<Organism> syms[count];
    for(size_t i = 0; i < count; i++){
      emp::Ptr<Organism> sym = new Symbiont(&random, &w, &config, int_vals[i]);
      w.InjectSymbiont(sym);
      REQUIRE(sym->GetTaxon()->GetInfo() == taxon_infos[i]);
    }
  }
  WHEN("symbionts are deleted"){
    THEN("they are no longer tracked by the sym systematic"){
      REQUIRE(sym_sys->GetNumActive() == 0);
      emp::Ptr<Organism> s1 = new Symbiont(&random, &w, &config, int_val);
      w.InjectSymbiont(s1);
      REQUIRE(sym_sys->GetNumActive() == 1);
      s1.Delete();
      REQUIRE(sym_sys->GetNumActive() == 0);
    }
  }
  WHEN("generations pass"){
    config.MUTATION_SIZE(1);
    config.MUTATION_RATE(1);
    config.PHYLOGENY(1);
    size_t num_syms = 4;

    emp::Ptr<Organism> syms[num_syms];
    syms[0] = new Symbiont(&random, &w, &config, 0);
    w.AddSymToSystematic(syms[0]);

    for(size_t i = 1; i < num_syms; i++){
      syms[i] = syms[i-1]->reproduce();
    }

    THEN("Their lineages are tracked"){
      char lineages[][30] = {"Lineage:\n10\n",
                             "Lineage:\n16\n10\n",
                             "Lineage:\n19\n16\n10\n",
                             "Lineage:\n16\n19\n16\n10\n",
                           };

      for(size_t i = 0; i < num_syms; i++){
        std::stringstream result;
        sym_sys->PrintLineage(syms[i]->GetTaxon(), result);
        REQUIRE(result.str() == lineages[i]);
      }
    }
    THEN("Their birth and destruction dates are tracked"){
      //all curr syms should have orig times of 0
      for(size_t i = 0; i < num_syms; i++) REQUIRE(syms[i]->GetTaxon()->GetOriginationTime() == 0);


      w.Update();

      //after update, times should now be 1
      emp::Ptr<emp::Taxon<int>> dest_tax = syms[0]->GetTaxon();
      syms[0].Delete();
      REQUIRE(dest_tax->GetDestructionTime() == 1);

      syms[0] = syms[1]->reproduce();
      REQUIRE(syms[0]->GetTaxon()->GetOriginationTime() == 1);

      //another update, times 2
      w.Update();
      dest_tax = syms[0]->GetTaxon();
      syms[0].Delete();
      REQUIRE(dest_tax->GetDestructionTime() == 2);
    }
  }
}