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"


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

    WHEN(" the resources are unlimited ") {
      config.LIMITED_RES_TOTAL(-1);

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

    WHEN( " the resources are limited ") {
      int original_total = 150;
      config.LIMITED_RES_TOTAL(original_total);
      SymWorld world(random, &config);

      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("Limited resources inflow", "[default]") {
  GIVEN(" a world ") {
    emp::Random random(11);
    SymConfigBase config;
    int full_share = 100;

    WHEN( " the resources are limited and inflow is zero ") {
      int original_total = 150;
      config.LIMITED_RES_TOTAL(original_total);
      config.LIMITED_RES_INFLOW(0);
      SymWorld world(random, &config);
      // Update() calls GetPermutation() which crashes if the size is zero
      world.Resize(1);

      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);
        AND_WHEN(" the world is updated ") {
          world.Update();
          THEN(" no organisms get points since the total is still zero ") {
            REQUIRE(world.PullResources(full_share) == 0);
            REQUIRE(world.PullResources(full_share) == 0);
            REQUIRE(world.PullResources(full_share) == 0);
            REQUIRE(world.PullResources(full_share) == 0);
          }
        }
      }
    }

    WHEN( " the resources are limited and inflow is turned on ") {
      int original_total = 150;
      int inflow = 25;
      config.LIMITED_RES_TOTAL(original_total);
      config.LIMITED_RES_INFLOW(inflow);
      SymWorld world(random, &config);
      // Update() calls GetPermutation() which crashes if the size is zero
      world.Resize(1);

      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);
        AND_WHEN(" the world is updated ") {
          world.Update();
          THEN(" the first organism gets some new resources, everyone else gets nothing ") {
            REQUIRE(world.PullResources(full_share) == inflow);
            REQUIRE(world.PullResources(full_share) == 0);
            REQUIRE(world.PullResources(full_share) == 0);
            REQUIRE(world.PullResources(full_share) == 0);
          }
        }
      }
    }
  }
}

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

    WHEN( "the vertical taransmission rate is 0" ) {
      config.VERTICAL_TRANSMISSION(0);

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

    WHEN( "the vertical taransmission rate is 1" ) {
      config.VERTICAL_TRANSMISSION(1);

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

    WHEN( "the vertical taransmission rate is .5" ) {
      config.VERTICAL_TRANSMISSION(.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(world.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 world(random, &config);

    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, &world, &config, 0);
        world.AddOrgAt(new_org, world.size());
      }

      THEN( "the world's size becomes the number of hosts that were added" ) {
        REQUIRE( (int) world.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 world(*random, &config);
    config.VERTICAL_TRANSMISSION(0);
    config.VERTICAL_TRANSMISSION(0);
    config.MUTATION_SIZE(0);
    config.SYM_LIMIT(500);
    config.HORIZ_TRANS(true);
    config.HOST_REPRO_RES(400);
    config.RES_DISTRIBUTE(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, &world, &config, -0.1);
        world.AddOrgAt(new_org, world.size());
      }
      int width = 10;
      int height = 1;
      int world_size = width * height;
      world.Resize(width, height);
      for (int i = 0; i < world_size; i++){
        emp::Ptr<Symbiont> new_sym = emp::NewPtr<Symbiont>(random, &world, &config, 0.1);
        world.InjectSymbiont(new_sym);
      }
      //Simulate
      for(int i = 0; i < 100; i++) {
        world.Update();
      }

      THEN( "the symbionts all die" ) {

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




  GIVEN( "a world" ) {
    emp::Random random(17);
    SymWorld world(random, &config);
    world.SetPopStruct_Mixed();
    config.GRID(0);
    config.VERTICAL_TRANSMISSION(0.7);
    config.VERTICAL_TRANSMISSION(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, &world, &config, 1);
        world.AddOrgAt(new_org, world.size());
      }

      int width = 100;
      int height = 200;
      world.Resize(width, height);

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

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

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

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

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

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

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

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

TEST_CASE( "InjectSymbiont", "[default]" ){
  GIVEN( "a world" ){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld world(random, &config);
    int world_size = 4;

    WHEN( "free living syms are not allowed" ){
      world.Resize(world_size);
      config.FREE_LIVING_SYMS(0);

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

      THEN( "syms are injected into a random host" ){
        world.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" ){
      world_size = 1000;
      world.Resize(world_size);
      config.FREE_LIVING_SYMS(1);


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

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

        size_t sym_count = 100;

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

TEST_CASE( "DoBirth", "[default]" ){
  GIVEN( "a world" ) {
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    config.FREE_LIVING_SYMS(1);
    emp::Ptr<Organism> h2 = emp::NewPtr<Host>(&random, &world, &config, int_val);
    world.AddOrgAt(h2, 3);
    emp::Ptr<Organism> host = emp::NewPtr<Host>(&random, &world, &config, int_val);

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

        REQUIRE(world.GetNumOrgs() == 2);
        bool host_isborn = false;
        for(size_t i = 0; i < 4; i++){
          if(&world.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 = emp::NewPtr<Host>(&random, &world, &config, int_val);
        world.AddOrgAt(other_host, 0);
        world.DoBirth(host, 2);

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

        bool host_isborn = false;
        bool otherhost_isdead = true;
        for(size_t i = 0; i < 4; i++){
          if(world.GetPop()[i] == host) {
            host_isborn = true;
          } else if (world.GetPop()[i] != nullptr && world.GetPop()[i] != h2){
            otherhost_isdead = false;
          }
        }
        REQUIRE(world.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 world(random, &config);
    size_t world_size = 4;
    world.Resize(world_size);

    emp::WorldPosition new_pos;

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

      WHEN( "there is a valid neighbouring host" ){
        size_t host_pos = 1;
        emp::Ptr<Host> host = emp::NewPtr<Host>(&random, &world, &config, int_val);
        world.AddOrgAt(host, host_pos);

        emp::Ptr<Organism> new_symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
        new_pos = world.SymDoBirth(new_symbiont, 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 == new_symbiont);
          REQUIRE(world.GetNumOrgs() == 1);
          REQUIRE(new_pos.IsValid() == true);

          REQUIRE(new_pos.GetIndex() == 1);
          REQUIRE(new_pos.GetPopID() == host_pos);
        }
      }

      WHEN( "there is no valid neighbouring host" ){

        new_pos = world.SymDoBirth(emp::NewPtr<Symbiont>(&random, &world, &config, int_val), 1);

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


    WHEN( "free living symbionts are allowed"){
      config.FREE_LIVING_SYMS(1);
      world_size = 2;
      world.Resize(world_size);

      THEN("it might be inserted into an empty cell"){
        emp::WorldPosition parent_pos = emp::WorldPosition(0, 1);

        new_pos = world.SymDoBirth(emp::NewPtr<Symbiont>(&random, &world, &config, int_val), parent_pos);

        REQUIRE(world.GetNumOrgs() == 1);
        REQUIRE(new_pos.IsValid() == true);
        REQUIRE(new_pos.GetIndex() == 0);
        REQUIRE(new_pos.GetPopID() == 0);
      }

      THEN("it may be inserted into an occupied cell, overwriting the previous occupant"){
        for(size_t i = 0; i < world_size; i++){
          world.AddOrgAt(emp::NewPtr<Symbiont>(&random, &world, &config, int_val), emp::WorldPosition(0, i));
        }
        REQUIRE(world.GetNumOrgs() == world_size);

        emp::WorldPosition parent_pos = emp::WorldPosition(0, 1);
        emp::Ptr<Organism> new_symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
        new_pos = world.SymDoBirth(new_symbiont, parent_pos);

        bool new_sym_born = false;
        for(size_t i = 0; i < world_size; i++){
          if(world.GetSymAt(i) == new_symbiont){
            new_sym_born = true;
          }
        }

        REQUIRE(world.GetNumOrgs() == world_size);
        REQUIRE(new_sym_born == true);
        REQUIRE(new_pos.IsValid() == true);
        REQUIRE(world.IsInboundsPos(new_pos) == true);
      }

      THEN("it might not find a valid cell and get deleted"){
        world_size = 0;
        world.Resize(0);
        emp::WorldPosition parent_pos = emp::WorldPosition(0, 1);
        emp::Ptr<Organism> new_symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
        new_pos = world.SymDoBirth(new_symbiont, parent_pos);

        REQUIRE(new_pos.IsValid() == false);
        REQUIRE(world.GetNumOrgs() == 0);
      }
    }
  }
}

TEST_CASE( "Update without free living symbionts", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    int res_per_update = 10;
    config.RES_DISTRIBUTE(res_per_update);

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

    world.AddOrgAt(host, 0);

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

        world.Update();
        REQUIRE(world.GetNumOrgs() == 0);
      }
    }
    THEN("hosts process normally"){
      int res_before_update = host->GetPoints();
      world.Update();
      int res_after_update = host->GetPoints();
      int res_change = res_after_update - res_before_update;
      REQUIRE(res_per_update == res_change);
    }
  }
}

TEST_CASE( "Update with free living symbionts", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    int int_val = 0;
    SymWorld world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    int res_per_update = 10;
    config.RES_DISTRIBUTE(res_per_update);
    int num_updates = 5;

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

    res_per_update = 80;
    config.RES_DISTRIBUTE(res_per_update);
    config.FREE_SYM_RES_DISTRIBUTE(res_per_update);
    world_size = 16;
    world.Resize(world_size);

    config.FREE_LIVING_SYMS(1);
    config.MOVE_FREE_SYMS(1);

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

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

    WHEN("there are only syms (no hosts) in the world"){
      world_size = 9;
      world.Resize(world_size);
      THEN("if only syms in the world they can get resources and reproduce"){
        emp::Ptr<Organism> sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
        world.SymDoBirth(sym, 0);
        REQUIRE(world.GetNumOrgs() == 1);

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

        int num_pop_elements = 0;
        for(int i = 0; i < world_size; i++){
          if(world.GetPop()[i]){
            num_pop_elements++;
          }
        }

        host.Delete(); //since it wasn't added to the world, have to delete it manually
      }
    }

    WHEN("there are both hosts and syms in the world"){
      world_size = 9;
      world.Resize(world_size);
      THEN("hosts and syms can mingle in the environment"){
        world.AddOrgAt(host, 0);
        world.AddOrgAt(emp::NewPtr<Symbiont>(&random, &world, &config, int_val), emp::WorldPosition(0,1));
        for(int i = 0; i < num_updates; i++){
          world.Update();
        }

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

        int free_sym_count = 0;
        int hosted_sym_count = 0;
        int host_count = 0;
        for(int i = 0; i < world_size; i++){
          if(world.GetPop()[i]){
            host_count++;
            hosted_sym_count += world.GetOrg(i).GetSymbionts().size();
          }
          if(world.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 world(random, &config);
    config.FREE_LIVING_SYMS(1);
    int int_val = 0;
    int world_size = 4;
    world.Resize(world_size);
    size_t host_pos = 0;
    emp::WorldPosition sym_pos = emp::WorldPosition(0, host_pos);

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

      WHEN("the infection fails"){
        config.SYM_INFECTION_FAILURE_RATE(1);
        emp::Ptr<Organism> sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
        world.AddOrgAt(sym, sym_pos);
        REQUIRE(world.GetNumOrgs() == 2);
        THEN("the sym is deleted"){
          world.MoveFreeSym(sym_pos);
          REQUIRE(world.GetNumOrgs() == 1);
          REQUIRE(!host->HasSym());
        }
      }
      WHEN("the infection does not fail"){
        THEN("the sym moves into the host"){
          world.MoveFreeSym(sym_pos);
          REQUIRE(world.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"){
        config.MOVE_FREE_SYMS(1);
        sym->SetInfectionChance(0);
        THEN("the sym moves to a random spot in the free world"){
          REQUIRE(world.GetSymPop()[sym_id] == sym); //there should be a sym at pos 0
          world.MoveFreeSym(sym_pos);

          size_t new_sym_id = 2;
          emp::Ptr<Organism> new_sym = world.GetSymPop()[new_sym_id];
          REQUIRE(sym == new_sym);
          REQUIRE(world.GetSymPop()[sym_id] == nullptr);
        }
      }
      WHEN("moving is turned off"){
        config.MOVE_FREE_SYMS(0);
        THEN("the sym doesn't move"){
          REQUIRE(world.GetSymPop()[sym_id] == sym);
          world.MoveFreeSym(sym_pos);
          emp::Ptr<Organism> new_sym = world.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 world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    size_t sym_index = 1;
    emp::WorldPosition sym_pos = emp::WorldPosition(0, sym_index);
    emp::Ptr<Organism> sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);

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

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

    sym.Delete();
  }
}

TEST_CASE( "MoveIntoNewFreeWorldPos", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    SymWorld world(random, &config);
    int int_val = 0;
    int world_size = 4;
    world.Resize(world_size);

    emp::Ptr<Organism> sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);

    size_t orig_pos = 3;
    emp::WorldPosition orig_sym_pos = emp::WorldPosition(0, orig_pos);

    WHEN("A symbiont is successfully moved into a new symbiont world position"){
      emp::Ptr<Organism> parent_sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
      world.AddOrgAt(parent_sym, orig_sym_pos);
      REQUIRE(world.GetNumOrgs() == 1);

      emp::WorldPosition new_pos = world.MoveIntoNewFreeWorldPos(sym, orig_sym_pos);
      THEN("It returns its new symbiont world position and can be found there"){
        REQUIRE(world.GetNumOrgs() == 2); //it didn't overwrite the parent
        REQUIRE(world.GetSymAt(new_pos.GetPopID()) == sym);
        REQUIRE(new_pos.IsValid() == true);
      }
    }

    WHEN("A symbiont unsuccessfully attempts to move into a new symbiont world position"){
      world_size = 0; // forcing an invalid new position
      world.Resize(world_size);
      emp::WorldPosition new_pos = world.MoveIntoNewFreeWorldPos(sym, orig_sym_pos);
      THEN("It returns an invalid WorldPosition and is not present in the symbiont world"){
        REQUIRE(world.GetNumOrgs() == 0); //the parent
        REQUIRE(new_pos.IsValid() == false);
      }
    }
    //TODO: CHECK MOVING OUT OF HOST
  }
}

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

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

    size_t width = 3;
    size_t height = 3;
    world.Resize(width, height);
    pop_size = world.GetPop().size();
    sym_pop_size = world.GetSymPop().size();
    REQUIRE(pop_size == sym_pop_size);
    REQUIRE(pop_size == (width * height));

    size_t world_size = 11;
    world.Resize(world_size);
    pop_size = world.GetPop().size();
    sym_pop_size = world.GetSymPop().size();
    REQUIRE(pop_size == sym_pop_size);
    REQUIRE(pop_size == world_size);
  }
}

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 world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    emp::Ptr<Organism> sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);

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

        world.AddOrgAt(sym, 0);
        REQUIRE(world.GetNumOrgs() == 1);
        REQUIRE(world.GetSymPop()[0] == sym);
      }
    }
    WHEN("a sym is added to an out of bounds pos"){
      THEN("pop and sym_pop are expanded to fit it"){
        REQUIRE(world.GetSymPop().size() == 4);
        world.AddOrgAt(sym, emp::WorldPosition(0,7));
        REQUIRE(world.GetSymPop().size() == world.GetPop().size());
        REQUIRE(world.GetSymPop().size() == 8);
      }
    }
  }
}

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


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

TEST_CASE( "DoSymDeath", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    SymWorld world(random, &config);
    int world_size = 4;
    world.Resize(world_size);
    emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, 1);
    size_t sym_position = 1;
    world.AddOrgAt(symbiont, 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(world.GetNumOrgs() == 1);
        world.DoSymDeath(sym_position);
        REQUIRE(world.GetNumOrgs() == 0);
      }
      THEN("It no longer occupies a spot in the world"){
        emp::Ptr<Organism> world_sym = world.GetSymAt(sym_position);
        REQUIRE(world_sym == symbiont);
        world.DoSymDeath(sym_position);

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

TEST_CASE( "Spatial structure", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    SymWorld world(random, &config);
    int width = 100;
    int height = 100;
    config.GRID_X(width);
    config.GRID_Y(height);
    world.Resize(width * height);
    config.FREE_LIVING_SYMS(1);
    config.MOVE_FREE_SYMS(1);
    config.SYM_HORIZ_TRANS_RES(0);
    size_t sym_limit = 10;
    config.SYM_LIMIT(sym_limit);

    WHEN("Grid is on"){
      config.GRID(1);
      world.SetPopStruct_Grid(width, height, false);

      THEN("Host babies are born next to their parents"){
        emp::Ptr<Organism> host_parent = emp::NewPtr<Host>(&random, &world, &config, 1);
        size_t host_parent_pos = 101;
        world.AddOrgAt(host_parent, host_parent_pos);

        emp::Ptr<Organism> host_baby = host_parent->Reproduce();
        world.DoBirth(host_baby, host_parent_pos);

         // We have parent position 101, so we expect baby to be in one of 8 surrounding cells
         //     | 000 | 001 | 002 | ...
         //     | 100 |*101*| 102 | ...
         //     | 200 | 201 | 202 | ...

        int possible_indices[8] = {0, 1, 2, 100, 102, 200, 201, 202};
        bool found_baby = false;
        for(int i = 0; i < 8; i++){
          if(world.GetPop()[possible_indices[i]] == host_baby){
            found_baby = true;
          }
        }

        REQUIRE(found_baby == true);
      }
      WHEN("Free living symbionts are permitted"){
        THEN("Symbiont babies are horizontally transmitted to a position near their parents"){
          emp::Ptr<Organism> sym_parent = emp::NewPtr<Symbiont>(&random, &world, &config, 1);
          emp::WorldPosition sym_parent_pos = emp::WorldPosition(0, 250);
          world.AddOrgAt(sym_parent, sym_parent_pos);

          sym_parent->HorizontalTransmission(sym_parent_pos);


          int possible_indices[8] = {149, 150, 151, 249, 251, 349, 350, 351};
          bool found_baby = false;
          for(int i = 0; i < 8; i++){
            if(world.GetSymPop()[possible_indices[i]] != nullptr && world.GetSymPop()[possible_indices[i]] != sym_parent){
              found_baby = true;
            }
          }

          REQUIRE(found_baby == true);
        }
        THEN("Symbionts randomly move to cells near their old position"){
          emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, 1);
          emp::WorldPosition original_position = emp::WorldPosition(0, 898);
          world.AddOrgAt(symbiont, original_position);
          world.MoveFreeSym(original_position);

          int possible_indices[8] = {797, 798, 799, 897, 899, 997, 998, 999};
          bool found_sym = false;
          for(int i = 0; i < 8; i++){
            if(world.GetSymPop()[possible_indices[i]] == symbiont){
              found_sym = true;
            }
          }

          REQUIRE(found_sym == true);
        }
      }
      WHEN("Free living symbionts are not permitted"){
        config.FREE_LIVING_SYMS(0);
        THEN("Symbionts are horizontally transmitted into a neighboring host"){
          emp::Ptr<Organism> host_parent = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> neighboring_host = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> distant_host = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> sym_parent = emp::NewPtr<Symbiont>(&random, &world, &config, 1);

          size_t host_parent_pos = 99;
          world.AddOrgAt(host_parent, host_parent_pos);
          world.AddOrgAt(neighboring_host, host_parent_pos + 1);
          world.AddOrgAt(distant_host, host_parent_pos + 150);
          host_parent->AddSymbiont(sym_parent);

          for(size_t sym_count = 1; sym_count <= sym_limit; sym_count++){
            sym_parent->HorizontalTransmission(emp::WorldPosition(1, host_parent_pos));
            REQUIRE(neighboring_host->GetSymbionts().size() == sym_count);
            REQUIRE(distant_host->HasSym() == false);
          }
        }
      }
    }

    WHEN("Grid is off"){
      config.GRID(0);
      world.SetPopStruct_Mixed(false);
      //given the size of the world, it's very unlikely that
      //organisms will randomly be placed in a neighbor position
      THEN("Host babies are born into a random position anywhere in the world"){
        emp::Ptr<Organism> host_parent = emp::NewPtr<Host>(&random, &world, &config, 1);
        size_t host_parent_pos = 101;
        world.AddOrgAt(host_parent, host_parent_pos);

        emp::Ptr<Organism> host_baby = host_parent->Reproduce();
        world.DoBirth(host_baby, host_parent_pos);

        int possible_indices[8] = {0, 1, 2, 100, 102, 200, 201, 202};
        bool found_baby = false;
        for(int i = 0; i < 8; i++){
          if(world.GetPop()[possible_indices[i]] == host_baby){
            found_baby = true;
          }
        }

        REQUIRE(found_baby == false);
      }
      WHEN("Free living symbionts are permitted"){
        THEN("Symbiont babies are horizontally transmitted to a random position anywhere in the world"){
          emp::Ptr<Organism> sym_parent = emp::NewPtr<Symbiont>(&random, &world, &config, 1);
          emp::WorldPosition sym_parent_pos = emp::WorldPosition(0, 250);
          world.AddOrgAt(sym_parent, sym_parent_pos);

          sym_parent->HorizontalTransmission(sym_parent_pos);


          int possible_indices[8] = {149, 150, 151, 249, 251, 349, 350, 351};
          bool found_baby = false;
          for(int i = 0; i < 8; i++){
            if(world.GetSymPop()[possible_indices[i]] != nullptr && world.GetSymPop()[possible_indices[i]] != sym_parent){
              found_baby = true;
            }
          }

          REQUIRE(found_baby == false);
        }
        THEN("Symbionts randomly move to cells anywhere in the world"){
          emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, 1);
          emp::WorldPosition original_position = emp::WorldPosition(0, 898);
          world.AddOrgAt(symbiont, original_position);
          world.MoveFreeSym(original_position);

          int possible_indices[8] = {797, 798, 799, 897, 899, 997, 998, 999};
          bool found_sym = false;
          for(int i = 0; i < 8; i++){
            if(world.GetSymPop()[possible_indices[i]] == symbiont){
              found_sym = true;
            }
          }

          REQUIRE(found_sym == false);
        }
      }
      WHEN("Free living symbionts are not permitted"){
        config.FREE_LIVING_SYMS(0);
        THEN("Symbionts are horizontally transmitted into hosts anywhere in the world"){
          emp::Ptr<Organism> host_parent = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> neighboring_host = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> distant_host = emp::NewPtr<Host>(&random, &world, &config, 1);
          emp::Ptr<Organism> sym_parent = emp::NewPtr<Symbiont>(&random, &world, &config, 1);

          size_t host_parent_pos = 99;
          world.AddOrgAt(host_parent, host_parent_pos);
          world.AddOrgAt(neighboring_host, host_parent_pos + 1);
          world.AddOrgAt(distant_host, host_parent_pos + 150);
          host_parent->AddSymbiont(sym_parent);

          for(size_t sym_count = 1; sym_count <= sym_limit; sym_count++){
            sym_parent->HorizontalTransmission(emp::WorldPosition(1, host_parent_pos));
          }

          REQUIRE(neighboring_host->HasSym());
          REQUIRE(distant_host->HasSym());
        }
      }
    }
  }
}

TEST_CASE( "Host Phylogeny", "[default]" ){
  emp::Random random(17);
  SymConfigBase config;
  config.MUTATION_SIZE(0.09);
  config.MUTATION_RATE(1);
  config.PHYLOGENY(1);
  config.NUM_PHYLO_BINS(20);
  int int_val = 0;
  SymWorld world(random, &config);
  int world_size = 4;
  world.Resize(world_size);


  emp::Ptr<Organism> host = emp::NewPtr<Host>(&random, &world, &config, int_val);
  emp::Ptr<emp::Systematics<Organism,int>> host_sys = world.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;
      int_val = -1;

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

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

      world.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(world.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(world.GetNumOrgs() == 0);
      REQUIRE(host_sys->GetNumActive() == 0);

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

      THEN("the organism is tracked by the systematic"){
        REQUIRE(world.GetNumOrgs() == 1);
        REQUIRE(host_sys->GetNumActive() == 1);
        REQUIRE(expected_taxon_info == taxon_info);
      }
    }

    WHEN("there are 20 taxonomic bins"){

      size_t count = 7;
      size_t pos = 0;

      double int_vals[7] = {-1, -0.98, -0.9, -0.8, 0.65, 0.9, 1};
      int expected_taxon_infos[7] = {0, 0, 1, 2, 16, 19, 19};

      THEN("it will be placed in the correct bin"){
        for(size_t i = 0; i < count; i++){
          world.AddOrgAt(emp::NewPtr<Host>(&random, &world, &config, int_vals[i]), pos);
          int taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
          REQUIRE(taxon_info == expected_taxon_infos[i]);
        }
      }
      host.Delete();
    }

    WHEN("there are 2 taxonomic bins"){
      config.NUM_PHYLO_BINS(2);

      size_t count = 3;
      size_t pos = 0;

      double int_vals[3] = {1, 0, -0.001};
      int expected_taxon_infos[3] = {1, 1, 0};

      THEN("it will be placed in the correct bin"){
        for(size_t i = 0; i < count; i++){
          world.AddOrgAt(emp::NewPtr<Host>(&random, &world, &config, int_vals[i]), pos);
          int taxon_info = host_sys->GetTaxonAt(pos)->GetInfo();
          REQUIRE(taxon_info == expected_taxon_infos[i]);
        }
      }
      host.Delete();
    }
  }

  //ORGANISMS AND RELATIONSHIPS TRACKED
  WHEN("Several generations pass"){
    THEN("The phylogenetic relationships are tracked and accurate"){
      world_size = 10;
      world.Resize(world_size);
      int num_descendants = 4;
      //add the first host
      world.AddOrgAt(host, 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};
      size_t parents[4] = {0, 1, 1, 3};
      for(int i = 0; i < num_descendants; i++){
        world.AddOrgAt(emp::NewPtr<Host>(&random, &world, &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);
  config.NUM_PHYLO_BINS(20);
  int int_val = 0;
  SymWorld world(random, &config);
  int world_size = 20;
  world.Resize(world_size);

  emp::Ptr<emp::Systematics<Organism,int>> sym_sys = world.GetSymSys();

  WHEN("symbionts are added to the world"){
    THEN("they get added to the correct taxonomic bins"){
      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};

      emp::Ptr<Organism> syms[count];
      emp::Ptr<Organism> sym;

      for(size_t i = 0; i < count; i++){
        sym = emp::NewPtr<Symbiont>(&random, &world, &config, int_vals[i]);
        world.InjectSymbiont(sym);
        REQUIRE(sym->GetTaxon()->GetInfo() == taxon_infos[i]);
      }
    }
  }

  WHEN("symbionts are deleted"){
    THEN("they are no longer tracked by the sym systematic"){
      world_size = 1;
      world.Resize(world_size);
      REQUIRE(sym_sys->GetNumActive() == 0);
      emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
      world.InjectSymbiont(symbiont);
      REQUIRE(sym_sys->GetNumActive() == 1);
      world.DoSymDeath(0);
      REQUIRE(sym_sys->GetNumActive() == 0);
    }

    THEN("hosted and free symbionts are deleted without a segmentation fault") {
      world_size = 4;
      world.Resize(world_size);

      // add a free living sym to the world
      emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
      world.InjectSymbiont(symbiont);

      // add a host to the world
      emp::Ptr<Organism> host = emp::NewPtr<Host>(&random, &world, &config, int_val);
      world.InjectHost(host);

      // add a hosted sym to the host
      emp::Ptr<Organism> hosted_sym = symbiont->Reproduce();
      host->AddSymbiont(hosted_sym);

      // check that free living organisms have properly been added to the world
      REQUIRE(world.GetNumOrgs() == 2);
    }
  }

  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] = emp::NewPtr<Symbiont>(&random, &world, &config, 0);
    world.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]);
      }
      syms[0].Delete();
      syms[1].Delete();
    }

    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);
      }
      world.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);

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

    syms[2].Delete();
    syms[3].Delete();
  }
}

TEST_CASE( "SetMutationZero", "[default]") {
  GIVEN("World first created with all mutation settings at 1") {
    emp::Random random(17);
    SymConfigBase config;
    config.MUTATION_SIZE(1);
    config.MUTATION_RATE(1);
    config.HOST_MUTATION_SIZE(1);
    config.HOST_MUTATION_RATE(1);
    config.MUTATE_LYSIS_CHANCE(1);
    config.MUTATE_INDUCTION_CHANCE(1);
    config.MUTATE_INC_VAL(1);
    config.EFFICIENCY_MUT_RATE(1);
    config.INT_VAL_MUT_RATE(1);
    config.HORIZ_MUTATION_SIZE(1);
    config.HORIZ_MUTATION_RATE(1);
    SymWorld world(random, &config);

    REQUIRE(config.MUTATION_SIZE() == 1);
    REQUIRE(config.MUTATION_RATE() == 1);
    REQUIRE(config.HOST_MUTATION_SIZE() == 1);
    REQUIRE(config.HOST_MUTATION_RATE() == 1);
    REQUIRE(config.MUTATE_LYSIS_CHANCE() == 1);
    REQUIRE(config.MUTATE_INDUCTION_CHANCE() == 1);
    REQUIRE(config.MUTATE_INC_VAL() == 1);
    REQUIRE(config.EFFICIENCY_MUT_RATE() == 1);
    REQUIRE(config.INT_VAL_MUT_RATE() == 1);
    REQUIRE(config.HORIZ_MUTATION_SIZE() == 1);
    REQUIRE(config.HORIZ_MUTATION_RATE() == 1);

    WHEN("SetMutationZero method called") {
      world.SetMutationZero();
      THEN("Mutation size and rate both 0") {
        REQUIRE(config.MUTATION_SIZE() == 0);
        REQUIRE(config.MUTATION_RATE() == 0);
        REQUIRE(config.HOST_MUTATION_SIZE() == 0);
        REQUIRE(config.HOST_MUTATION_RATE() == 0);
        REQUIRE(config.MUTATE_LYSIS_CHANCE() == 0);
        REQUIRE(config.MUTATE_INDUCTION_CHANCE() == 0);
        REQUIRE(config.MUTATE_INC_VAL() == 0);
        REQUIRE(config.EFFICIENCY_MUT_RATE() == 0);
        REQUIRE(config.INT_VAL_MUT_RATE() == 0);
        REQUIRE(config.HORIZ_MUTATION_SIZE() == 0);
        REQUIRE(config.HORIZ_MUTATION_RATE() == 0);
      }
    }
  }
}

TEST_CASE( "No mutation updates", "[default] "){
  GIVEN("a world with 1 mutation update and 1 non-mutation update"){
    emp::Random random(17);
    SymConfigBase config;
    config.MUTATION_SIZE(1);
    config.MUTATION_RATE(1);
    config.UPDATES(1);
    config.NO_MUT_UPDATES(1);
    SymWorld world(random, &config);
    double int_val = 0.4;
    int world_size = 100;
    world.Resize(world_size);
    emp::Ptr<Organism> symbiont = emp::NewPtr<Symbiont>(&random, &world, &config, int_val);
    emp::Ptr<Organism> host = emp::NewPtr<Host>(&random, &world, &config, int_val);

    WHEN("A host and symbiont reproduce at first"){
      emp::Ptr<Organism> mut_sym_baby = symbiont->Reproduce();
      emp::Ptr<Organism> mut_host_baby = host->Reproduce();

      THEN("Mutation rate and size are still 1"){
        REQUIRE(config.MUTATION_RATE() == 1);
        REQUIRE(config.MUTATION_SIZE() == 1);
      }
      THEN("Host and symbiont offspring are mutated"){
        REQUIRE(mut_sym_baby->GetIntVal() > int_val - 0.00001);
        REQUIRE(mut_host_baby->GetIntVal() > int_val - 0.00001);
      }
      mut_sym_baby.Delete();
      mut_host_baby.Delete();
    }
    WHEN("The experiment runs and host and symbiont reproduce after"){
      world.RunExperiment(false);
      emp::Ptr<Organism> no_mut_sym_baby = symbiont->Reproduce();
      emp::Ptr<Organism> no_mut_host_baby = host->Reproduce();

      THEN("Mutation sizes and rates are all 0"){
        REQUIRE(config.MUTATION_RATE() == 0);
        REQUIRE(config.MUTATION_SIZE() == 0);
        REQUIRE(config.HOST_MUTATION_SIZE() == 0);
        REQUIRE(config.HOST_MUTATION_RATE() == 0);
        REQUIRE(config.MUTATE_LYSIS_CHANCE() == 0);
        REQUIRE(config.MUTATE_INDUCTION_CHANCE() == 0);
        REQUIRE(config.MUTATE_INC_VAL() == 0);
      }
      THEN("Host and symbiont offspring aren't mutated"){
        REQUIRE(no_mut_sym_baby->GetIntVal() < int_val + 0.00001);
        REQUIRE(no_mut_sym_baby->GetIntVal() > int_val - 0.00001);
        REQUIRE(no_mut_host_baby->GetIntVal() < int_val + 0.00001);
        REQUIRE(no_mut_host_baby->GetIntVal() > int_val - 0.00001);
      }
      no_mut_sym_baby.Delete();
      no_mut_host_baby.Delete();
    }
    symbiont.Delete();
    host.Delete();
  }
}

TEST_CASE( "IsInboundsPos", "[default]" ){
  GIVEN("a world"){
    emp::Random random(17);
    SymConfigBase config;
    SymWorld world(random, &config);
    int world_size = 4;
    world.Resize(world_size);

    size_t valid_pos = 3;
    REQUIRE(world.IsInboundsPos(valid_pos) == true);

    emp::WorldPosition valid_pos_wp = emp::WorldPosition(3,0);
    REQUIRE(world.IsInboundsPos(valid_pos_wp) == true);

    valid_pos_wp = emp::WorldPosition(0,3);
    REQUIRE(world.IsInboundsPos(valid_pos_wp) == true);

    emp::WorldPosition invalid_pos = emp::WorldPosition(4,3);
    REQUIRE(world.IsInboundsPos(invalid_pos) == false);

    invalid_pos = emp::WorldPosition(0,4);
    REQUIRE(world.IsInboundsPos(invalid_pos) == false);
  }
}

TEST_CASE("InjectHost", "[default]") {
  GIVEN("a world") {
    emp::Random random(17);
    SymConfigBase config;
    SymWorld world(random, &config);
    int int_val = 0;

    WHEN("Spatial structure is turned off") {
      config.GRID(0);
      THEN("Hosts are placed side-by-side in the world") {
        REQUIRE(world.GetNumOrgs() == 0);

        emp::Ptr<Organism> host0 = emp::NewPtr<Host>(&random, &world, &config, int_val);
        emp::Ptr<Organism> host1 = emp::NewPtr<Host>(&random, &world, &config, int_val);
        emp::Ptr<Organism> host2 = emp::NewPtr<Host>(&random, &world, &config, int_val);

        world.InjectHost(host0);
        world.InjectHost(host1);
        world.InjectHost(host2);

        REQUIRE(world.GetNumOrgs() == 3);
        REQUIRE(world.GetPop()[0] == host0);
        REQUIRE(world.GetPop()[1] == host1);
        REQUIRE(world.GetPop()[2] == host2);
      }
    }
    WHEN("Spatial structure is turned on") {
      config.GRID(1);
      world.Resize(9); // world should be resized before injecting hosts if spatial structure is on
      THEN("Hosts are placed randomly in the world") {
        REQUIRE(world.GetNumOrgs() == 0);

        emp::Ptr<Organism> host0 = emp::NewPtr<Host>(&random, &world, &config, int_val);
        emp::Ptr<Organism> host1 = emp::NewPtr<Host>(&random, &world, &config, int_val);
        emp::Ptr<Organism> host2 = emp::NewPtr<Host>(&random, &world, &config, int_val);

        world.InjectHost(host0);
        world.InjectHost(host1);
        world.InjectHost(host2);

        REQUIRE(world.GetNumOrgs() == 3);
        bool hosts_sequentially_placed = world.GetPop()[0] == host0 && world.GetPop()[1] == host1 && world.GetPop()[2] == host2;
        REQUIRE(hosts_sequentially_placed == false);
      }
    }
  }
}

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

    size_t width = 10;
    size_t height = 20;
    config.GRID_X(width);
    config.GRID_Y(height);

    WHEN("Grid is on") {
      config.GRID(1);
      world.Setup();
      THEN("World size, width, and height are set correctly") {
        REQUIRE(world.GetWidth() == width);
        REQUIRE(world.GetHeight() == height);
        REQUIRE(world.GetSize() == width * height);
      }
    }

    WHEN("Config option POP_SIZE is -1") {
      config.POP_SIZE(-1);
      world.Setup();
      THEN("The world has full starting population") {
        REQUIRE(world.GetNumOrgs() == width * height);
      }
    }

    WHEN("Config option POP_SIZE is greater than -1") {
      size_t pop_size = (width * height) / 2;
      config.POP_SIZE(pop_size);
      world.Setup();
      THEN("The world has a partial starting population") {
        REQUIRE(world.GetNumOrgs() == pop_size);
      }
    }

    WHEN("A world is populated with hosts") {
      double smoi = 0.02;
      config.START_MOI(smoi);
      config.SYM_LIMIT(10);
      config.POP_SIZE(-1);
      emp::DataMonitor<int>& hosted_sym_count_node = world.GetCountHostedSymsDataNode();
      world.Setup();
      world.Update();

      size_t num_syms = hosted_sym_count_node.GetTotal();

      THEN("The world is populated with a proportional number of symbionts") {
        REQUIRE(world.GetNumOrgs() == width * height);
        REQUIRE(num_syms == smoi * width * height);
      }
    }
  }
}

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

    size_t world_size = 6;
    world.Resize(world_size);
    config.FREE_LIVING_SYMS(1);

    WHEN("SetupSymbionts is called") {
      size_t num_to_add = 2;
      world.SetupSymbionts(&num_to_add);

      THEN("The specified number of symbionts are added to the world") {
        size_t num_added = world.GetNumOrgs();
        REQUIRE(num_added == num_to_add);

        emp::Ptr<Organism> symbiont;
        for (size_t i = 0; i < world_size; i++) {
          symbiont = world.GetSymAt(i);
          if (symbiont) break;
        }
        REQUIRE(symbiont->GetName() == "Symbiont");
      }
    }
  }
}

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

    WHEN("SetupHosts is called") {
      size_t num_to_add = 5;
      world.SetupHosts(&num_to_add);

      THEN("The specified number of hosts are added to the world") {
        size_t num_added = world.GetNumOrgs();
        REQUIRE(num_added == num_to_add);

        emp::Ptr<Organism> host = world.GetPop()[0];
        REQUIRE(host != nullptr);
        REQUIRE(host->GetName() == "Host");
      }
    }
  }
}