diff --git a/source/default_mode/SymWorld.h b/source/default_mode/SymWorld.h index 665eeff2..7a8c40af 100644 --- a/source/default_mode/SymWorld.h +++ b/source/default_mode/SymWorld.h @@ -1088,25 +1088,18 @@ class SymWorld : public emp::World { * in a host near its parent's location, or deleted if the parent's location has * no eligible near-by hosts. */ - virtual emp::WorldPosition SymDoBirth(emp::Ptr sym_baby, emp::WorldPosition parent_pos) { + virtual emp::WorldPosition SymDoBirth(emp::Ptr sym_offspring, emp::Ptr sym_parent, emp::WorldPosition parent_pos) { const size_t i = parent_pos.GetPopID(); if (my_config->FREE_LIVING_SYMS() == 0) { const int new_host_pos = GetNeighborHost(i); if (new_host_pos > -1) { //-1 means no living neighbors - emp::Ptr sym_parent; - if (parent_pos.GetIndex() == 0) { // free living parent - sym_parent = GetSymAt(i); - } else { // hosted parent - emp_assert(pop[i]->HasSym() && pop[i]->GetSymbionts().size() >= (parent_pos.GetIndex() - 1)); - sym_parent = pop[i]->GetSymbionts().at(parent_pos.GetIndex() - 1); - } - + // infections can fail from size limits or tag mismatch // (or, theoretically, no neighbouring hosts) const bool size_failed = pop[new_host_pos]->GetSymbionts().size() >= (long unsigned)my_config->SYM_LIMIT(); bool tag_failed = false; if (my_config->TAG_MATCHING()) { - const double tag_distance = (*tag_metric)(pop[new_host_pos]->GetTag(), sym_baby->GetTag()) * TAG_LENGTH; + const double tag_distance = (*tag_metric)(pop[new_host_pos]->GetTag(), sym_offspring->GetTag()) * TAG_LENGTH; const double permissiveness_mean = (my_config->HOST_TAG_PERMISSIVENESS_EVOLVES()) ? pop[new_host_pos]->GetTagPermissiveness() : my_config->TAG_PERMISSIVENESS(); const double cutoff = GetRandom().GetPoisson(permissiveness_mean * TAG_LENGTH); tag_failed = tag_distance > cutoff; @@ -1118,19 +1111,19 @@ class SymWorld : public emp::World { else if (!tag_failed && size_failed) { GetHorizontalTransmissionSizeFailCount().AddDatum(sym_parent->GetIntVal()); } - sym_baby.Delete(); + sym_offspring.Delete(); return emp::WorldPosition(); } - const int new_index = pop[new_host_pos]->AddSymbiont(sym_baby); + const int new_index = pop[new_host_pos]->AddSymbiont(sym_offspring); if (new_index > 0) { // sym successfully infected if (my_config->PHYLOGENY()) { if (phylo_taxon_type == PHYLO_TAXON_TYPE::INDIVIDUAL) { - sym_baby->GetTaxon().Cast()->GetData().DetermineHostSwitch(pop[new_host_pos]->GetTaxon(), sym_parent->GetHost()->GetTaxon()); + sym_offspring->GetTaxon().Cast()->GetData().DetermineHostSwitch(pop[new_host_pos]->GetTaxon(), sym_parent->GetHost()->GetTaxon()); } if (my_config->TRACK_PHYLOGENY_INTERACTIONS()) { - pop[new_host_pos]->GetTaxon().Cast()->GetData().AddInteraction(sym_baby->GetTaxon()); + pop[new_host_pos]->GetTaxon().Cast()->GetData().AddInteraction(sym_offspring->GetTaxon()); } } if (my_config->FREE_HT_FAILURE() || my_config->TAG_MATCHING()) { @@ -1142,11 +1135,11 @@ class SymWorld : public emp::World { return emp::WorldPosition(); } } else { // no living neighbors - sym_baby.Delete(); + sym_offspring.Delete(); return emp::WorldPosition(); } } else { - return MoveIntoNewFreeWorldPos(sym_baby, parent_pos); + return MoveIntoNewFreeWorldPos(sym_offspring, parent_pos); } } diff --git a/source/default_mode/Symbiont.h b/source/default_mode/Symbiont.h index 899a6067..173288a2 100644 --- a/source/default_mode/Symbiont.h +++ b/source/default_mode/Symbiont.h @@ -849,7 +849,7 @@ class Symbiont: public Organism { if (AttemptIndependentReproduction(location)) { emp::Ptr sym_baby = Reproduce(); if (my_config->TAG_MATCHING() || my_config->FREE_HT_FAILURE()) sym_baby->SetPoints(0); - emp::WorldPosition new_pos = my_world->SymDoBirth(sym_baby, location); + emp::WorldPosition new_pos = my_world->SymDoBirth(sym_baby, this, location); AfterIndependentReproduction(new_pos); diff --git a/source/efficient_mode/EfficientSymbiont.h b/source/efficient_mode/EfficientSymbiont.h index b033181f..f485d8d0 100644 --- a/source/efficient_mode/EfficientSymbiont.h +++ b/source/efficient_mode/EfficientSymbiont.h @@ -278,7 +278,7 @@ class EfficientSymbiont: public Symbiont { // new symbiont in this host with mutated value SetPoints(0); //TODO: test just subtracting points instead of setting to 0 emp::Ptr sym_baby = Reproduce("horizontal"); - emp::WorldPosition new_pos = my_world->SymDoBirth(sym_baby, location); + emp::WorldPosition new_pos = my_world->SymDoBirth(sym_baby, this, location); //horizontal transmission data nodes emp::DataMonitor& data_node_attempts_horiztrans = my_world->GetHorizontalTransmissionAttemptCount(); diff --git a/source/lysis_mode/Phage.h b/source/lysis_mode/Phage.h index 388da1d1..02d1b13a 100644 --- a/source/lysis_mode/Phage.h +++ b/source/lysis_mode/Phage.h @@ -314,7 +314,7 @@ class Phage: public Symbiont { emp::DataMonitor& data_node_successes_horiztrans = my_world->GetHorizontalTransmissionSuccessCount(); for (size_t r=0; rSymDoBirth(repro_syms[r], location); + emp::WorldPosition new_pos = my_world->SymDoBirth(repro_syms[r], this, location); //horizontal transmission data nodes data_node_attempts_horiztrans.AddDatum(GetIntVal()); diff --git a/source/sgp_mode/DevNotes.md b/source/sgp_mode/DevNotes.md index 47d0c293..78c73fb1 100644 --- a/source/sgp_mode/DevNotes.md +++ b/source/sgp_mode/DevNotes.md @@ -6,13 +6,13 @@ * Move ProcessSymOutputBuffer into SGPSymbiont to parallel host * Move SymDoMutation into SGPSymbiont to parallel host * Look at whether can move SymDonateToHost and SymStealFromHost into SGPSym -* Look at fun_host_sym_stress_trans_compatibility_check to try to reduce code duplication of task-profile setups, possibly with decorator pattern, but also definitely just in own file * Move world properties back into protected and make necessary accesssors -* Try to fold ProcessStressEscapees into existing code/reduce duplication * Rename "SetReproCount" to lineage length since it's confusing (or did I already?) * Look into what is going on with SGPHost local sgp_config not working * Streamline Host ProcessOutputBuffer and check if easier access to some variables * Break Host Reproduce into helper functions +* Fix all tests for debug mode. Currently github runs test-all and test-debug-default. Look at failing tests for test-debug-all +* Review death of organisms and solidify protocol for when to use SendToGraveyard(org). Remove extrenuous .Delete() emp::DoDeath() and Organism::SetDead() calls. Consider Stress mode where we want the symbiont to exits in graveyard, but we dont want the host to be alive and in the world. * Compare ecto relevant code (i.e. default mode) between main and this refactor to see if something changed, when was the last time the ecto integration test didn't seg fault on Mac? Prior to aux bump? @@ -31,6 +31,10 @@ [x] Make a list from this commit for further shifting todos and update this doc with those todos https://github.com/anyaevostinar/SymbulationEmp/commit/9ea1d53c8bf70c612d1454fac0510ddaf0c70e9d for AEV TODO and Refactor note for what else I already had decided would be good to do [x] Horizontal transmission to make parallel to default mode and integrated to support tags with tasks - Decisions made: reproduce is shared between horizontal transmission and free-living sym reproduction. If free-living sym repro is on, then reproduce places offspring into sym pop (like in default), and then offspring can infect with Infect instruction (not yet implemented). If FLS is off, reproduce does horizontal transmission. Also decided that if HT is off, an "attempt" is not counted. +[x] Try to fold ProcessStressEscapees into existing code/reduce duplication + - Initially we attempted to turn ReproductionQueue into a birth queue, however this hurts performance. ReproductionQueue will skip organisms who have died and therefore should not reproduce before Reproduce() is called. With a birth queue, the children would already be constructed. This uses more memory and wastes resources on construction. Instead ProcessStressEscapees was moved into a signal. +[x] Look at fun_host_sym_stress_trans_compatibility_check to try to reduce code duplication of task-profile setups, possibly with decorator pattern, but also definitely just in own file + - Completed with above refactor. # Journal 4/15/26 diff --git a/source/sgp_mode/ReproductionQueue.h b/source/sgp_mode/ReproductionQueue.h index 4c10ec2b..4ead0de7 100644 --- a/source/sgp_mode/ReproductionQueue.h +++ b/source/sgp_mode/ReproductionQueue.h @@ -87,10 +87,6 @@ class ReproductionQueue { continue; } fun_reproduce_org(repro_info); - // emp::Ptr child = org->Reproduce(); - // (child->IsHost()) ? - // world.HostDoBirth(child, org_ptr, repro_info.pos) : - // world.SymDoBirth(child, repro_info.pos); } Clear(); } diff --git a/source/sgp_mode/SGPW_InteractionMechanismSetup.cc b/source/sgp_mode/SGPW_InteractionMechanismSetup.cc index 1b2bdbf3..4b35ba7f 100644 --- a/source/sgp_mode/SGPW_InteractionMechanismSetup.cc +++ b/source/sgp_mode/SGPW_InteractionMechanismSetup.cc @@ -2,6 +2,7 @@ #define SGP_W_INT_MECH_C #include "SGPWorld.h" +#include namespace sgpmode { @@ -217,6 +218,25 @@ namespace sgpmode { /************************** Stress ********************************* */ + struct StressEscapee { + emp::Ptr sym_offspring; + emp::Ptr sym_parent; + emp::WorldPosition escape_location; + + StressEscapee() = default; + StressEscapee( + emp::Ptr _sym_offspring, + emp::Ptr _sym_parent, + emp::WorldPosition _location + ) : + sym_offspring(_sym_offspring), + sym_parent(_sym_parent), + escape_location(_location) + { } + }; + + emp::vector symbiont_stress_escapees; + void SGPWorld::SetupStressInteractions() { emp_assert(sgp_config.ENABLE_STRESS()); // Setup extinction variable @@ -234,22 +254,17 @@ namespace sgpmode { // NOTE - this can be simplified assuming no other desired differences in logic // for parasite vs. mutualist (repeated code; only death chance is different) if (GetStressSymType() == stress_sym_mode_t::MUTUALIST) { - // Use mutualist death chance before_host_cpu_exec_sig.AddAction( [this](sgp_host_t& host) { if (!stress_extinction_update) return; - // If host has a mutualist symbiont with a matching task profile, death_chance = mutualist death chance - // Otherwise, base death chance. + const emp::BitVector& host_task_profile = fun_get_host_task_profile(host); bool interact = false; auto& endosymbionts = host.GetSymbionts(); for (size_t sym_i = 0; sym_i < endosymbionts.size(); ++sym_i) { // Check if symbiont matches task profile emp::Ptr endosym_ptr = static_cast(endosymbionts[sym_i].Raw()); - // interact = utils::AnyMatchingOnes( - // host_task_profile, - // fun_get_sym_task_profile(*endosym_ptr) - // ); + interact = fun_task_profile_compatibility_check(host_task_profile, fun_get_sym_task_profile(*endosym_ptr)); if (interact) { break; @@ -271,15 +286,14 @@ namespace sgpmode { before_host_cpu_exec_sig.AddAction( [this](sgp_host_t& host) { if (!stress_extinction_update) return; - // If host has a symbiont, death_chance = parasite death chance - // Otherwise, base death chance. + + // base death chance if no symbionts double death_chance = sgp_config.BASE_DEATH_CHANCE(); auto& endosymbionts = host.GetSymbionts(); const emp::BitVector& host_task_profile = fun_get_host_task_profile(host); for (size_t sym_i = 0; sym_i < endosymbionts.size(); ++sym_i) { // Check if symbiont matches task profile emp::Ptr endosym_ptr = static_cast(endosymbionts[sym_i].Raw()); - const emp::BitVector& endosym_task_profile = fun_get_sym_task_profile(*endosym_ptr); const bool can_escape = fun_task_profile_compatibility_check(host_task_profile, fun_get_sym_task_profile(*endosym_ptr)); if (can_escape) { death_chance = sgp_config.PARASITE_DEATH_CHANCE(); @@ -290,17 +304,27 @@ namespace sgpmode { emp::Ptr sym_offspring = endosym_ptr->Reproduce(); symbiont_stress_escapees.emplace_back( static_cast(sym_offspring.Raw()), - endosym_task_profile, - endosym_ptr->GetHardware().GetCPUState().GetLocation().GetPopID() + endosym_ptr, + endosym_ptr->GetHardware().GetCPUState().GetLocation() ); } // Once we leave this signal, the host (and this symbiont) will // potentially be deleted. // So, we need to handle the reproduction here (versus putting it into the queue). } + //TODO POSSIBLE BUG. What happens if a host does not die here, but then is + //killed when a host reproduces on top of it. We loose the Symbiont!!! } // Kill host with chosen probability if (random_ptr->P(death_chance)) { + //We are clearing syms to make sure the host doent delete them in its destructor. + //we cant use vector.clear() because that calls ~Symbiont + //TODO Ideally we should be able to call SendToGraveyard(host); + for (emp::Ptr i : endosymbionts) { + //This symbiont will die but we want to keep it in the graveyard + SendToGraveyard(i); + } + host.ClearSyms(); host.SetDead(); } } @@ -311,8 +335,8 @@ namespace sgpmode { before_host_cpu_exec_sig.AddAction( [this](sgp_host_t& host) { if (!stress_extinction_update) return; - // If host has a symbiont, death_chance = parasite death chance - // Otherwise, base death chance. + + // base death chance if no symbionts double death_chance = sgp_config.BASE_DEATH_CHANCE(); auto& endosymbionts = host.GetSymbionts(); const emp::BitVector& host_task_profile = fun_get_host_task_profile(host); @@ -340,13 +364,17 @@ namespace sgpmode { emp::Ptr sym_offspring = endosym_ptr->Reproduce(); symbiont_stress_escapees.emplace_back( static_cast(sym_offspring.Raw()), - endosym_task_profile, - endosym_ptr->GetHardware().GetCPUState().GetLocation().GetPopID() + endosym_ptr, + endosym_ptr->GetHardware().GetCPUState().GetLocation() ); } + //This symbiont will die but we want to keep it in the graveyard + SendToGraveyard(endosym_ptr); } - // ------ - // Mark host as dead + //We are clearing syms to make sure the host doent delete them in its destructor. + //we cant use vector.clear() because that calls ~Symbiont + //TODO Ideally we should be able to call SendToGraveyard(host); + host.ClearSyms(); host.SetDead(); } } @@ -424,11 +452,17 @@ namespace sgpmode { emp::Ptr sym_offspring = endosym_ptr->Reproduce(); symbiont_stress_escapees.emplace_back( static_cast(sym_offspring.Raw()), - endosym_task_profile, - endosym_ptr->GetHardware().GetCPUState().GetLocation().GetPopID() + endosym_ptr, + endosym_ptr->GetHardware().GetCPUState().GetLocation() ); } + //This symbiont will die but we want to keep it in the graveyard + SendToGraveyard(endosym_ptr); } + //We are clearing syms to make sure the host doent delete them in its destructor. + //we cant use vector.clear() because that calls ~Symbiont + //TODO Ideally we should be able to call SendToGraveyard(host); + host.ClearSyms(); host.SetDead(); } } @@ -452,10 +486,28 @@ namespace sgpmode { exit(-1); } - // TODO - Add instruction-mediated stress interaction mode + after_reproduction_sig.AddAction( + [this]() { + // Process escapees in random order (to avoid strongly favoring all offspring from "late" escapee) + emp::vector escapee_ids; + escapee_ids.resize(symbiont_stress_escapees.size(), 0); + std::iota( + escapee_ids.begin(), + escapee_ids.end(), + 0 + ); + emp::Shuffle(*random_ptr, escapee_ids); - // NOTE - What about free-living symbionts (if any)? - // Or endosymbionts? + for (size_t esc_i : escapee_ids) { + auto& escapee_info = symbiont_stress_escapees[esc_i]; + emp::WorldPosition pos = SymDoBirth(escapee_info.sym_offspring, escapee_info.sym_parent, escapee_info.escape_location); + //do we want to track success + + } + symbiont_stress_escapees.clear(); + // TODO - add data collection for successful escapes + } + ); } diff --git a/source/sgp_mode/SGPW_TaskProfileSetup.cc b/source/sgp_mode/SGPW_TaskProfileSetup.cc index 465b0c7d..ea76f0d2 100644 --- a/source/sgp_mode/SGPW_TaskProfileSetup.cc +++ b/source/sgp_mode/SGPW_TaskProfileSetup.cc @@ -85,16 +85,12 @@ void SGPWorld::SetupTaskProfileCompatibilityMode() { void SGPWorld::SetupHorizontalTransmissionCompatibilityMode() { // Setup function that determines horizontal transmission compatibility based on task profiles if (sgp_config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE() == "always") { - fun_host_sym_horizontal_trans_compatibility_check = []( + fun_horizontal_transmission_compatibility_check = []( sgp_host_t& host, sgp_sym_t& sym ) -> bool { return true; }; - fun_host_sym_stress_trans_compatibility_check = []( - sgp_host_t& host, - const emp::BitVector& profile - ) -> bool { return true; }; } else if (sgp_config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE() == "task-profile-compatible") { - fun_host_sym_horizontal_trans_compatibility_check = [this]( + fun_horizontal_transmission_compatibility_check = [this]( sgp_host_t& host, sgp_sym_t& sym ) -> bool { @@ -102,40 +98,21 @@ void SGPWorld::SetupHorizontalTransmissionCompatibilityMode() { const auto& sym_profile = fun_get_sym_task_profile(sym); return fun_task_profile_compatibility_check(host_profile, sym_profile); }; - fun_host_sym_stress_trans_compatibility_check = [this]( - sgp_host_t& host, - const emp::BitVector& profile - ) -> bool { - const auto& host_profile = fun_get_host_task_profile(host); - return fun_task_profile_compatibility_check(host_profile, profile); - }; } else if (sgp_config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE() == "task-profile-strictly-stronger-match") { - fun_host_sym_horizontal_trans_compatibility_check = [this]( + fun_horizontal_transmission_compatibility_check = [this]( sgp_host_t& host, sgp_sym_t& sym ) -> bool { - const emp::BitVector& incoming_sym_task_profile = fun_get_sym_task_profile(sym); - return NoBetterOrEquallyMatchingSymbionts(host, incoming_sym_task_profile); - }; - fun_host_sym_stress_trans_compatibility_check = [this]( - sgp_host_t& host, - const emp::BitVector& profile - ) -> bool { - return NoBetterOrEquallyMatchingSymbionts(host, profile); + const auto& sym_profile = fun_get_sym_task_profile(sym); + return NoBetterOrEquallyMatchingSymbionts(host, sym_profile); }; } else if (sgp_config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE() == "task-profile-stronger-or-equal-match") { - fun_host_sym_horizontal_trans_compatibility_check = [this]( + fun_horizontal_transmission_compatibility_check = [this]( sgp_host_t& host, sgp_sym_t& sym ) -> bool { - const emp::BitVector& incoming_sym_task_profile = fun_get_sym_task_profile(sym); - return NoBetterMatchingSymbionts(host, incoming_sym_task_profile); - }; - fun_host_sym_stress_trans_compatibility_check = [this]( - sgp_host_t& host, - const emp::BitVector& profile - ) -> bool { - return NoBetterMatchingSymbionts(host, profile); + const auto& sym_profile = fun_get_sym_task_profile(sym); + return NoBetterMatchingSymbionts(host, sym_profile); }; } else { std::cout << "Unrecognized HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE: " << sgp_config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE() << std::endl; @@ -146,24 +123,24 @@ void SGPWorld::SetupHorizontalTransmissionCompatibilityMode() { void SGPWorld::SetupFindHostForHorizontalTransmission() { // Setup function that gets host neighbor (used for symbiont) - // Excludes current host, since they really shouldn't be considered a neighbor // TODO - add different configuration options for this? fun_find_host_for_horizontal_trans = [this]( - size_t host_world_id, /* Parent's host location id in world (pops[0][id])*/ - emp::Ptr sym_parent_ptr /* Pointer to symbiont parent (producing the sym offspring) */ + emp::Ptr sym_parent_ptr, + const emp::WorldPosition& parent_pos ) -> std::optional { for (size_t attempt_i = 0; attempt_i < sgp_config.FIND_NEIGHBOR_HOST_ATTEMPTS(); ++attempt_i) { + const size_t host_world_id = parent_pos.GetPopID(); emp::WorldPosition candidate_pos(GetRandomNeighborPos(host_world_id)); if (candidate_pos.IsValid() && IsOccupied(candidate_pos) && candidate_pos.GetIndex() != host_world_id) { - emp::Ptr neighbor_org_ptr = GetOrgPtr(candidate_pos.GetIndex()); - emp_assert(neighbor_org_ptr->IsHost()); - // Cast neighbor as sgp_host_t ptr. - emp::Ptr neighbor_host_ptr = static_cast(neighbor_org_ptr.Raw()); - //TODO: Should this check be done during AddSymbiont instead of here? - const bool compatible = fun_host_sym_horizontal_trans_compatibility_check( - *neighbor_host_ptr, + emp::Ptr prospective_org_ptr = GetOrgPtr(candidate_pos.GetIndex()); + emp_assert(prospective_org_ptr->IsHost()); + emp::Ptr prospective_host_ptr = static_cast(prospective_org_ptr.Raw()); + + const bool compatible = fun_horizontal_transmission_compatibility_check( + *prospective_host_ptr, *sym_parent_ptr ); + if (compatible) { return std::optional{candidate_pos}; } diff --git a/source/sgp_mode/SGPWorld.cc b/source/sgp_mode/SGPWorld.cc index 03c7c974..0b5d8fad 100644 --- a/source/sgp_mode/SGPWorld.cc +++ b/source/sgp_mode/SGPWorld.cc @@ -140,17 +140,17 @@ void SGPWorld::DoReproduction() { // Called for symbionts in the reproduction queue // fun_sym_do_birth is set to either free living or horizontal infection based on config emp::WorldPosition SGPWorld::SymDoBirth( - emp::Ptr sym_baby, + emp::Ptr sym_offspring, + emp::Ptr sym_parent, emp::WorldPosition parent_pos ) { - emp_assert(!sym_baby->IsHost()); - emp::Ptr sym_baby_ptr = static_cast(sym_baby.Raw()); - // Trigger any before birth actions - before_sym_do_birth_sig.Trigger(sym_baby_ptr, parent_pos); - emp::WorldPosition sym_baby_pos(fun_sym_do_birth(sym_baby_ptr, parent_pos)); - - - return sym_baby_pos; + emp_assert(!sym_offspring->IsHost()); + emp::Ptr sym_offspring_ptr = static_cast(sym_offspring.Raw()); + emp::Ptr sym_parent_ptr = static_cast(sym_parent.Raw()); + + before_sym_do_birth_sig.Trigger(sym_offspring_ptr, parent_pos); + emp::WorldPosition sym_offspring_pos(fun_sym_do_birth(sym_offspring_ptr, sym_parent_ptr, parent_pos)); + return sym_offspring_pos; } emp::WorldPosition SGPWorld::HostDoBirth( @@ -197,85 +197,30 @@ emp::WorldPosition SGPWorld::FreeLivingSymDoBirth( const emp::WorldPosition& parent_pos ) { // TODO - add any signals? + //Sym deletion done in SymWorld::MoveIntoNewFreeWorldPos return MoveIntoNewFreeWorldPos(sym_baby_ptr, parent_pos); } emp::WorldPosition SGPWorld::SymAttemptHorizontalInfection( - emp::Ptr sym_baby_ptr, + emp::Ptr sym_offspring_ptr, + emp::Ptr sym_parent_ptr, const emp::WorldPosition& parent_pos ) { - // TODO - add any signals? - const size_t parent_pop_idx = parent_pos.GetPopID(); - emp::Ptr parent = this->GetOrgPtr(parent_pop_idx)->GetSymbionts()[parent_pos.GetIndex() - 1]; - emp_assert(!parent->IsHost()); - emp::Ptr sym_parent = static_cast(parent.Raw()); - // hew_host_pos is an optional - const auto new_host_pos = FindHostForHorizontalTrans(parent_pop_idx, sym_parent); + emp_assert(!sym_parent_ptr->IsHost()); + const auto new_host_pos = FindHostForHorizontalTrans(sym_parent_ptr, parent_pos); + if (new_host_pos) { const size_t host_id = new_host_pos.value().GetIndex(); - int new_index = pop[host_id]->AddSymbiont(sym_baby_ptr); + int new_index = pop[host_id]->AddSymbiont(sym_offspring_ptr); if (new_index > 0) { //sym successfully infected return emp::WorldPosition(new_index, host_id); - } else { - //sym got killed trying to infect - return emp::WorldPosition(); } - } else { - sym_baby_ptr.Delete(); + //AddSymbiont deletes symbiont return emp::WorldPosition(); } -} - -// Process any symbiont offspring that "escaped" the stress event -void SGPWorld::ProcessStressEscapees() { - emp_assert(repro_queue.GetSize() == 0); - - // Process escapees in random order (to avoid strongly favoring all offspring from "late" escapee) - escapee_ids.resize(symbiont_stress_escapees.size(), 0); - std::iota( - escapee_ids.begin(), - escapee_ids.end(), - 0 - ); - emp::Shuffle(*random_ptr, escapee_ids); - // for (size_t esc_i = 0; esc_i < symbiont_stress_escapees.size(); ++esc_i) { - for (size_t esc_i : escapee_ids) { - // (1) Find place to AddSymbiont - auto& escapee_info = symbiont_stress_escapees[esc_i]; - - bool success = false; - for (size_t attempt_i = 0; attempt_i < sgp_config.FIND_NEIGHBOR_HOST_ATTEMPTS(); ++attempt_i) { - emp::WorldPosition candidate_pos(GetRandomNeighborPos(escapee_info.escape_location)); - if (candidate_pos.IsValid() && IsOccupied(candidate_pos)) { - emp::Ptr neighbor_org_ptr = GetOrgPtr(candidate_pos.GetIndex()); - emp_assert(neighbor_org_ptr->IsHost()); - // Cast neighbor as sgp_host_t ptr. - emp::Ptr neighbor_host_ptr = static_cast(neighbor_org_ptr.Raw()); - // Check whether escapee can infect? - const bool can_infect = fun_host_sym_stress_trans_compatibility_check( - *neighbor_host_ptr, - escapee_info.parent_task_profile - ); - // TODO - add stress infect success tracking - if (!can_infect) continue; - // escapee_info.sym_offspring->GetHardware().GetCPUState().ResetReproState(); - //AssignNewEnvIO(escapee_info.sym_offspring->GetHardware().GetCPUState()); // AEV No longer needed, added to AddSymbiont - // int new_index = - neighbor_host_ptr->AddSymbiont(escapee_info.sym_offspring); - // AddSymbiont might fail (but when it does, it deletes the offspring) - // so not possible to keep attempting until actual success - success = true; - break; - } - } - // If sym didn't successfully infect, delete it. - if (!success) { - escapee_info.sym_offspring.Delete(); - } - } - symbiont_stress_escapees.clear(); - // TODO - add data collection for successful escapes + SendToGraveyard(sym_offspring_ptr); + return emp::WorldPosition(); } void SGPWorld::ProcessGraveyard() { @@ -305,11 +250,11 @@ void SGPWorld::SendToGraveyard(emp::Ptr org) { } std::optional SGPWorld::FindHostForHorizontalTrans( - size_t host_world_id, /* Parent's host location id in world (pops[0][id])*/ - emp::Ptr sym_parent_ptr /* Pointer to symbiont parent (producing the sym offspring) */ + emp::Ptr sym_parent_ptr, + const emp::WorldPosition& parent_pos ) { // Outsource to configurable functor - return fun_find_host_for_horizontal_trans(host_world_id, sym_parent_ptr); + return fun_find_host_for_horizontal_trans(sym_parent_ptr, parent_pos); } void SGPWorld::ProcessSymOutputBuffer(sgp_sym_t& sym) { diff --git a/source/sgp_mode/SGPWorld.h b/source/sgp_mode/SGPWorld.h index 5d26a512..5a5416a7 100644 --- a/source/sgp_mode/SGPWorld.h +++ b/source/sgp_mode/SGPWorld.h @@ -51,7 +51,8 @@ class SGPWorld : public SymWorld { using sgp_prog_rectifier_t = sgpl::OpCodeRectifier; using fun_sym_do_birth_t = std::function, /* symbiont baby ptr */ + emp::Ptr, /* symbiont offspring ptr */ + emp::Ptr, /* symbiont parent ptr*/ const emp::WorldPosition& /* parent_position */ )>; @@ -187,24 +188,6 @@ class SGPWorld : public SymWorld { } current_update_data; - struct StressEscapee { - emp::Ptr sym_offspring; - // emp::WorldPosition escape_location; - emp::BitVector parent_task_profile; - size_t escape_location; - - StressEscapee() = default; - StressEscapee( - emp::Ptr sym, - const emp::BitVector& tasks, - size_t loc - ) : - sym_offspring(sym), - parent_task_profile(tasks), - escape_location(loc) - { } - }; - // Tag used to trigger start module in signalgp programs during run tag_t START_TAG; @@ -216,6 +199,10 @@ class SGPWorld : public SymWorld { // E.g., used for resetting any per-update data tracking. emp::Signal begin_update_sig; + // after_reproduction_sig - Triggers after the ReproductionQueue has been processed + // E.g., used for processing additional mode specific reproduction queues + emp::Signal after_reproduction_sig; + // ---- Symbiont birth signals / functors ---- // before_sym_do_birth_sig - Triggers during SymDoBirth function. // Triggers after sym offspring is created but before fun_sym_do_birth() is called. @@ -356,8 +343,8 @@ class SGPWorld : public SymWorld { // Returns a target position for symbiont to horizontally transmit into. // Returns std::nullopt if failed to find suitable target position. std::function( - size_t, /* Parent's host location id in world (pops[0][id])*/ - emp::Ptr /* Pointer to symbiont parent (producing the sym offspring) */ + emp::Ptr, //sym parent + const emp::WorldPosition& //sym parent pos )> fun_find_host_for_horizontal_trans; // External facing helpers for orgnanisms to call @@ -387,9 +374,6 @@ class SGPWorld : public SymWorld { ProgramBuilder prog_builder = ProgramBuilder(opcode_rectifier); // Utility for building signalgp programs mutator_t mutator = mutator_t(opcode_rectifier); // Handles mutating sgp programs - emp::vector symbiont_stress_escapees; - emp::vector escapee_ids; // Used to randomize order of processing escapees (to avoid biasing) - // Flag for whether setup has been run. bool setup = false; @@ -447,13 +431,7 @@ class SGPWorld : public SymWorld { // Function to check compatibility between host and symbiont // - Used to check eligibility for vertical / horizontal transmission, etc. - fun_horizontal_transmission_compatibility_check_t fun_host_sym_horizontal_trans_compatibility_check; - - // Function used to check compatibility between host and symbiont that reproduced - // via a stress event. - // - Can't use same function as when checking horizontal transmission compatibility because - // we no longer have access to the symbiont parent for a stress transmission event. - std::function fun_host_sym_stress_trans_compatibility_check; + fun_horizontal_transmission_compatibility_check_t fun_horizontal_transmission_compatibility_check; fun_task_profile_compatibility_t fun_task_profile_compatibility_check; @@ -517,7 +495,8 @@ class SGPWorld : public SymWorld { ); emp::WorldPosition SymAttemptHorizontalInfection( - emp::Ptr sym_baby_ptr, + emp::Ptr sym_offspring_ptr, + emp::Ptr sym_parent_ptr, const emp::WorldPosition& parent_pos ); @@ -528,8 +507,6 @@ class SGPWorld : public SymWorld { // Internal helper function to delete dead organisms in graveyard. void ProcessGraveyard(); - void ProcessStressEscapees(); - // --- Internal setup helper functions ---. // Called internally on world setup. // NOTE - Can we get rid of passing these values in as pointers? @@ -557,6 +534,7 @@ class SGPWorld : public SymWorld { // Clear all world signals void ClearWorldSignals() { begin_update_sig.Clear(); + after_reproduction_sig.Clear(); before_sym_do_birth_sig.Clear(); after_sym_do_birth_sig.Clear(); before_sym_vert_transmission_sig.Clear(); @@ -834,7 +812,7 @@ class SGPWorld : public SymWorld { scheduler.Run(*this); // Process reproduction queue repro_queue.Process(); - ProcessStressEscapees(); + after_reproduction_sig.Trigger(); // Process graveyard, deletes all dead organisms. ProcessGraveyard(); // NOTE - these were previously called at the beginning of the update @@ -904,7 +882,8 @@ class SGPWorld : public SymWorld { // Prototypes for reproduction handling methods // SymDoBirth is for horizontal transmission and birthing free-living symbionts. emp::WorldPosition SymDoBirth( - emp::Ptr sym_baby, + emp::Ptr sym_offspring, + emp::Ptr sym_parent, emp::WorldPosition parent_pos ) override; @@ -915,11 +894,9 @@ class SGPWorld : public SymWorld { void SymStealFromHost(Organism& to_sym, Organism& from_host); void FreeLivingSymDoInfect(Organism& sym); - // Returns neighboring host from given symbiont - // NOTE - Opinions on name change? (originally GetNeighborHost) std::optional FindHostForHorizontalTrans( - size_t host_world_id, /* Parent's host location id in world (pops[0][id])*/ - emp::Ptr sym_parent_ptr /* Pointer to symbiont parent (producing the sym offspring) */ + emp::Ptr sym_parent_ptr, + const emp::WorldPosition& parent_pos ); /** diff --git a/source/sgp_mode/SGPWorldSetup.cc b/source/sgp_mode/SGPWorldSetup.cc index 3fd95371..21d33a9d 100644 --- a/source/sgp_mode/SGPWorldSetup.cc +++ b/source/sgp_mode/SGPWorldSetup.cc @@ -250,16 +250,12 @@ void SGPWorld::SetupReproduction() { emp::Ptr child = org->Reproduce(); if (child->IsHost()) { HostDoBirth(child, org, repro_info.pos); - // Mark parent as no longer reproducing (world handles setting state, so should handle resetting) - // NOTE - could move reset repro state in Reproduce functions - // static_cast(org.Raw())->GetHardware().GetCPUState().ResetReproState(); + } else { - const emp::WorldPosition sym_baby_pos = SymDoBirth(child, repro_info.pos); + const emp::WorldPosition sym_offspring_pos = SymDoBirth(child, org, repro_info.pos); emp::Ptr sym_parent = static_cast(org.Raw()); // Trigger any post-birth actions - after_sym_do_birth_sig.Trigger(sym_baby_pos, sym_parent); - // Mark parent as no longer reproducing - // static_cast(org.Raw())->GetHardware().GetCPUState().ResetReproState(); + after_sym_do_birth_sig.Trigger(sym_offspring_pos, sym_parent); } }); @@ -293,26 +289,29 @@ void SGPWorld::SetupSymReproduction() { if (sgp_config.FREE_LIVING_SYMS()) { // Configure sym birth in free-living symbiont mode fun_sym_do_birth = [this]( - emp::Ptr sym_baby_ptr, + emp::Ptr sym_offspring_ptr, + emp::Ptr sym_parent_ptr, const emp::WorldPosition& parent_pos ) -> emp::WorldPosition { - return FreeLivingSymDoBirth(sym_baby_ptr, parent_pos); + return FreeLivingSymDoBirth(sym_offspring_ptr, parent_pos); }; } else if (sgp_config.HORIZ_TRANS()){ // Configure sym birth in non-free-living symbiont mode. fun_sym_do_birth = [this]( - emp::Ptr sym_baby_ptr, + emp::Ptr sym_offspring_ptr, + emp::Ptr sym_parent_ptr, const emp::WorldPosition& parent_pos ) -> emp::WorldPosition { - return SymAttemptHorizontalInfection(sym_baby_ptr, parent_pos); + return SymAttemptHorizontalInfection(sym_offspring_ptr, sym_parent_ptr, parent_pos); }; } else { // Neither horizontal transmission nor free-living symbionts, so fun_sym_do_birth should just return invalid position and clean up the offspring fun_sym_do_birth = [this]( - emp::Ptr sym_baby_ptr, + emp::Ptr sym_offspring_ptr, + emp::Ptr sym_parent_ptr, const emp::WorldPosition& parent_pos ) -> emp::WorldPosition { - sym_baby_ptr.Delete(); + SendToGraveyard(sym_offspring_ptr); return emp::WorldPosition(); }; } diff --git a/source/test/default_mode_test/Phylogenies.test.cc b/source/test/default_mode_test/Phylogenies.test.cc index 2b12bb08..f071ce83 100644 --- a/source/test/default_mode_test/Phylogenies.test.cc +++ b/source/test/default_mode_test/Phylogenies.test.cc @@ -586,7 +586,7 @@ TEST_CASE("Individual-level phylogenies", "[default]") { world.Update(); // update 1 emp::Ptr symbiont_2 = symbiont_1->Reproduce(); // symbionts are added to systematic on Reproduce() - emp::WorldPosition symbiont_2_pos = world.SymDoBirth(symbiont_2, symbiont_1_pos); + emp::WorldPosition symbiont_2_pos = world.SymDoBirth(symbiont_2, symbiont_1, symbiont_1_pos); REQUIRE(world.GetNumOrgs() == 2); REQUIRE(symbiont_2_pos.GetPopID() != symbiont_1_pos.GetPopID()); diff --git a/source/test/default_mode_test/PopulationStructure.test.cc b/source/test/default_mode_test/PopulationStructure.test.cc index df1f679b..faac4bbb 100644 --- a/source/test/default_mode_test/PopulationStructure.test.cc +++ b/source/test/default_mode_test/PopulationStructure.test.cc @@ -360,7 +360,7 @@ TEST_CASE("World uses custom spatial structure", "[default][spatial-structure]") size_t neighbor_sym_count = 0; for (size_t sym_count = 0; sym_count < sym_limit; sym_count++) { emp::Ptr sym_offspring = sym_parent->Reproduce(); - world.SymDoBirth(sym_offspring, sym_parent->GetLocation()); + world.SymDoBirth(sym_offspring, sym_parent ,sym_parent->GetLocation()); ++neighbor_sym_count; REQUIRE(neighboring_host->GetSymbionts().size() == neighbor_sym_count); REQUIRE(!distant_host->HasSym()); diff --git a/source/test/default_mode_test/SymWorld.test.cc b/source/test/default_mode_test/SymWorld.test.cc index 1557cefd..fb1696c3 100644 --- a/source/test/default_mode_test/SymWorld.test.cc +++ b/source/test/default_mode_test/SymWorld.test.cc @@ -464,114 +464,112 @@ TEST_CASE( "SymDoBirth", "[default]" ) { WHEN( "free living symbionts are not allowed" ) { config.FREE_LIVING_SYMS(0); - WHEN( "there is a valid neighbouring host" ) { + WHEN( "there is a host with a symbiont in the world" ) { size_t host_pos = 1; emp::Ptr host = emp::NewPtr(&random, &world, &config, int_val); world.AddOrgAt(host, host_pos); - emp::Ptr uninfected_host = emp::NewPtr(&random, &world, &config, int_val); - world.AddOrgAt(uninfected_host, host_pos + 1); + emp::WorldPosition parent_sym_pos = emp::WorldPosition(1, host_pos); + emp::Ptr symbiont_parent = emp::NewPtr(&random, &world, &config, int_val); + host->AddSymbiont(symbiont_parent); - WHEN("there is room in the neighboring host and free failure due to size constraints is off") { - config.FREE_HT_FAILURE(0); - config.SYM_LIMIT(2); + WHEN( "there is a valid neighbouring host" ) { - emp::WorldPosition parent_sym_pos = emp::WorldPosition(1, host_pos); - emp::Ptr parent_symbiont = emp::NewPtr(&random, &world, &config, int_val); - host->AddSymbiont(parent_symbiont); - emp::Ptr new_symbiont = emp::NewPtr(&random, &world, &config, int_val); - new_pos = world.SymDoBirth(new_symbiont, parent_sym_pos); + emp::Ptr uninfected_host = emp::NewPtr(&random, &world, &config, int_val); + world.AddOrgAt(uninfected_host, host_pos + 1); - emp::vector> syms = uninfected_host->GetSymbionts(); - emp::Ptr host_sym = syms[0]; + WHEN("there is room in the neighboring host and free failure due to size constraints is off") { + config.FREE_HT_FAILURE(0); + config.SYM_LIMIT(2); - THEN( "the sym is inserted into the valid neighbouring host" ) { - REQUIRE(host_sym == new_symbiont); - REQUIRE(world.GetNumOrgs() == 2); - REQUIRE(new_pos.IsValid() == true); - REQUIRE(parent_symbiont->GetPoints() == 0); + emp::Ptr new_symbiont = emp::NewPtr(&random, &world, &config, int_val); + new_pos = world.SymDoBirth(new_symbiont, symbiont_parent, parent_sym_pos); - REQUIRE(new_pos.GetIndex() == 1); - REQUIRE(new_pos.GetPopID() == host_pos + 1); - REQUIRE(new_symbiont->GetPoints() == 0); - } - } - WHEN("there is room in the host and free failure due to size constraints is on") { - config.FREE_HT_FAILURE(1); - config.SYM_LIMIT(2); - - emp::Ptr symbiont_parent = emp::NewPtr(&random, &world, &config, int_val); - host->AddSymbiont(symbiont_parent); - - // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) - size_t starting_resources = 20; - size_t horiz_trans_res_required = 10; - config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); - - symbiont_parent->SetPoints(starting_resources); - symbiont_parent->IndependentReproduction(emp::WorldPosition(1, host_pos)); - - THEN("the sym child is inserted in the neighboring host and the parent spends points") { - REQUIRE(symbiont_parent->GetPoints() == 0); - REQUIRE(host->GetSymbionts().size() == 1); - REQUIRE(uninfected_host->GetSymbionts().size() == 1); - REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); - REQUIRE(uninfected_host->GetSymbionts().at(0)->GetPoints() == 0); + emp::vector> syms = uninfected_host->GetSymbionts(); + emp::Ptr host_sym = syms[0]; + + THEN( "the sym is inserted into the valid neighbouring host" ) { + REQUIRE(host_sym == new_symbiont); + REQUIRE(world.GetNumOrgs() == 2); + REQUIRE(new_pos.IsValid() == true); + REQUIRE(symbiont_parent->GetPoints() == 0); + + REQUIRE(new_pos.GetIndex() == 1); + REQUIRE(new_pos.GetPopID() == host_pos + 1); + REQUIRE(new_symbiont->GetPoints() == 0); + } } - } - WHEN("there is no room in the neighbor host and free failure due to size constraints is on") { - uninfected_host->AddSymbiont(emp::NewPtr(&random, &world, &config, int_val)); - config.FREE_HT_FAILURE(1); - config.SYM_LIMIT(1); - - emp::Ptr symbiont_parent = emp::NewPtr(&random, &world, &config, int_val); - host->AddSymbiont(symbiont_parent); - - // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) - size_t starting_resources = 20; - size_t horiz_trans_res_required = 10; - config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); - - symbiont_parent->SetPoints(starting_resources); - symbiont_parent->IndependentReproduction(emp::WorldPosition(1, host_pos)); - - THEN("the sym child is inserted nowhere and the parent spends no points") { - REQUIRE(symbiont_parent->GetPoints() == starting_resources); - REQUIRE(host->GetSymbionts().size() == 1); - REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); + WHEN("there is room in the host and free failure due to size constraints is on") { + config.FREE_HT_FAILURE(1); + config.SYM_LIMIT(2); + + // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) + size_t starting_resources = 20; + size_t horiz_trans_res_required = 10; + config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); + + symbiont_parent->SetPoints(starting_resources); + symbiont_parent->IndependentReproduction(parent_sym_pos); + + THEN("the sym child is inserted in the neighboring host and the parent spends points") { + REQUIRE(symbiont_parent->GetPoints() == 0); + REQUIRE(host->GetSymbionts().size() == 1); + REQUIRE(uninfected_host->GetSymbionts().size() == 1); + REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); + REQUIRE(uninfected_host->GetSymbionts().at(0)->GetPoints() == 0); + } } - } - WHEN("there is no room in the host and free failure due to size constraints is off") { - config.FREE_HT_FAILURE(0); - config.SYM_LIMIT(1); + WHEN("there is no room in the neighbor host and free failure due to size constraints is on") { + uninfected_host->AddSymbiont(emp::NewPtr(&random, &world, &config, int_val)); + config.FREE_HT_FAILURE(1); + config.SYM_LIMIT(1); - emp::Ptr symbiont_parent = emp::NewPtr(&random, &world, &config, int_val); - host->AddSymbiont(symbiont_parent); - // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) - size_t starting_resources = 20; - size_t horiz_trans_res_required = 10; - config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); + // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) + size_t starting_resources = 20; + size_t horiz_trans_res_required = 10; + config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); - symbiont_parent->SetPoints(starting_resources); - symbiont_parent->IndependentReproduction(emp::WorldPosition(1, host_pos)); + symbiont_parent->SetPoints(starting_resources); + symbiont_parent->IndependentReproduction(parent_sym_pos); - THEN("the sym child is inserted nowhere and the parent's points get set to 0") { - REQUIRE(symbiont_parent->GetPoints() == 0); - REQUIRE(host->GetSymbionts().size() == 1); - REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); + THEN("the sym child is inserted nowhere and the parent spends no points") { + REQUIRE(symbiont_parent->GetPoints() == starting_resources); + REQUIRE(host->GetSymbionts().size() == 1); + REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); + } + } + WHEN("there is no room in the host and free failure due to size constraints is off") { + config.FREE_HT_FAILURE(0); + config.SYM_LIMIT(1); + + // there MUST be a symbiont parent in the free failure condition (seg fault otherwise) + size_t starting_resources = 20; + size_t horiz_trans_res_required = 10; + config.SYM_HORIZ_TRANS_RES(horiz_trans_res_required); + + symbiont_parent->SetPoints(starting_resources); + symbiont_parent->IndependentReproduction(parent_sym_pos); + + THEN("the sym child is inserted nowhere and the parent's points get set to 0") { + REQUIRE(symbiont_parent->GetPoints() == 0); + REQUIRE(host->GetSymbionts().size() == 1); + REQUIRE(host->GetSymbionts().at(0) == symbiont_parent); + } } } - } - WHEN( "there is no valid neighbouring host" ) { + WHEN( "there is no valid neighbouring host" ) { + emp::Ptr symbiont_offspring = emp::NewPtr(&random, &world, &config, int_val); + new_pos = world.SymDoBirth(symbiont_offspring, symbiont_parent, parent_sym_pos); - new_pos = world.SymDoBirth(emp::NewPtr(&random, &world, &config, int_val), 2); + - THEN( "the sym is killed" ) { - //the world should be empty - REQUIRE(world.GetNumOrgs() == 0); - REQUIRE(new_pos.IsValid() == false); + THEN( "the sym is killed" ) { + //the world should be empty + REQUIRE(world.GetNumOrgs() == 1); + REQUIRE(new_pos.IsValid() == false); + } } } } @@ -582,50 +580,56 @@ TEST_CASE( "SymDoBirth", "[default]" ) { world_size = 2; world.Resize(world_size); - THEN("it might be inserted into an empty cell") { - emp::WorldPosition parent_pos = emp::WorldPosition(0, 1); + WHEN( "there is a parent symbiont in the world and an offspring" ) { + emp::WorldPosition parent_pos = emp::WorldPosition(0, 0); + emp::Ptr parent = emp::NewPtr(&random, &world, &config, int_val); + world.AddOrgAt(parent, parent_pos); - new_pos = world.SymDoBirth(emp::NewPtr(&random, &world, &config, int_val), parent_pos); + THEN("it might be inserted into an empty cell") { + new_pos = world.SymDoBirth(emp::NewPtr(&random, &world, &config, int_val), parent, 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(&random, &world, &config, int_val), emp::WorldPosition(0, i)); + REQUIRE(world.GetNumOrgs() == 2); + REQUIRE(new_pos.IsValid() == true); + REQUIRE(new_pos.GetIndex() == 0); + REQUIRE(new_pos.GetPopID() == 1); } - REQUIRE(world.GetNumOrgs() == world_size); - emp::WorldPosition parent_pos = emp::WorldPosition(0, 1); - emp::Ptr new_symbiont = emp::NewPtr(&random, &world, &config, int_val); - new_pos = world.SymDoBirth(new_symbiont, parent_pos); + THEN("it may be inserted into an occupied cell, overwriting the previous occupant") { + for (size_t i = 0; i < world_size; i++) { + if (i == parent_pos.GetPopID()) { + continue; + } else { + world.AddOrgAt(emp::NewPtr(&random, &world, &config, int_val), emp::WorldPosition(0,i)); + } + } + REQUIRE(world.GetNumOrgs() == world_size); - bool new_sym_born = false; - for (size_t i = 0; i < world_size; i++) { - if (world.GetSymAt(i) == new_symbiont) { - new_sym_born = true; + emp::Ptr new_symbiont = emp::NewPtr(&random, &world, &config, int_val); + new_pos = world.SymDoBirth(new_symbiont, parent, 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; + } } + world.CleanupGraveyard(); + REQUIRE(world.GetNumOrgs() == world_size); + REQUIRE(new_sym_born == true); + REQUIRE(new_pos.IsValid() == true); + REQUIRE(world.IsInboundsPos(new_pos) == true); + world.CleanupGraveyard(); } - world.CleanupGraveyard(); - REQUIRE(world.GetNumOrgs() == world_size); - REQUIRE(new_sym_born == true); - REQUIRE(new_pos.IsValid() == true); - REQUIRE(world.IsInboundsPos(new_pos) == true); - world.CleanupGraveyard(); - } - 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 new_symbiont = emp::NewPtr(&random, &world, &config, int_val); - new_pos = world.SymDoBirth(new_symbiont, parent_pos); + THEN("it might not find a valid cell and get deleted") { + world_size = 1; + world.Resize(world_size); + emp::Ptr new_symbiont = emp::NewPtr(&random, &world, &config, int_val); + new_pos = world.SymDoBirth(new_symbiont, parent, parent_pos); - REQUIRE(new_pos.IsValid() == false); - REQUIRE(world.GetNumOrgs() == 0); + REQUIRE(new_pos.IsValid() == false); + REQUIRE(world.GetNumOrgs() == 1); + } } } } @@ -718,9 +722,12 @@ TEST_CASE( "Update with free living symbionts", "[default]" ) { world_size = 9; world.Resize(world_size); THEN("if only syms in the world they can get resources and reproduce") { + emp::WorldPosition parent_pos = emp::WorldPosition(0, 1); + emp::Ptr sym_parent = emp::NewPtr(&random, &world, &config, int_val); + world.AddOrgAt(sym_parent, parent_pos); emp::Ptr sym = emp::NewPtr(&random, &world, &config, int_val); - world.SymDoBirth(sym, 0); - REQUIRE(world.GetNumOrgs() == 1); + world.SymDoBirth(sym, sym_parent, parent_pos); + REQUIRE(world.GetNumOrgs() == 2); for (int i = 0; i < num_updates; i++) { world.Update(); diff --git a/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc b/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc index 8ae8e59e..71c61250 100644 --- a/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc +++ b/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc @@ -424,7 +424,7 @@ TEST_CASE("World uses custom spatial structure (sgp mode)", "[sgp][spatial-struc size_t neighbor_sym_count = 0; for (size_t sym_count = 0; sym_count < sym_limit; sym_count++) { emp::Ptr sym_offspring = sym_parent->Reproduce(); - world.SymDoBirth(sym_offspring, sym_parent->GetLocation()); + world.SymDoBirth(sym_offspring, sym_parent, sym_parent->GetLocation()); ++neighbor_sym_count; REQUIRE(neighboring_host->GetSymbionts().size() == neighbor_sym_count); REQUIRE(!distant_host->HasSym()); diff --git a/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc b/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc index a5918809..004d0ac4 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc @@ -318,9 +318,8 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor host->AddSymbiont(symbiont); world.AddOrgAt(host, 0); - size_t source_id = symbiont->GetLocation().GetPopID(); - WHEN("There exists three nearby hosts all with matching tasks with the incoming symbiont") { + WHEN("There exists three nearby hosts all with matching tasks with the parent of the incoming symbiont") { for (size_t i = 1; i < 4; i++) { emp::WorldPosition neighbor_position = emp::WorldPosition(i,0); emp::Ptr neighbor_host = emp::NewPtr(&random, &world, &config); @@ -330,7 +329,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor } symbiont->GetHardware().GetCPUState().MarkTaskPerformed(8); WHEN("Task matching is not required for horizontal transmission") { - auto pos_found = world.FindHostForHorizontalTrans(source_id, symbiont); + auto pos_found = world.FindHostForHorizontalTrans(symbiont, symbiont->GetLocation()); THEN("The position of the nearby matching host is not 0 (which is the current host), and PopID is same as current host") { REQUIRE(pos_found); REQUIRE(world.IsOccupied(*pos_found)); @@ -363,19 +362,18 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is required for horizon host->AddSymbiont(symbiont); world.AddOrgAt(host, 0); - size_t source_id = symbiont->GetLocation().GetPopID(); WHEN("There exists a nearby host") { emp::WorldPosition neighbor_position = emp::WorldPosition(1,0); emp::Ptr neighbor_host = emp::NewPtr(&random, &world, &config); world.AddOrgAt(neighbor_host, neighbor_position); - WHEN("The nearby host has matching tasks with the incoming symbiont") { + WHEN("The nearby host has matching tasks with the parent of the incoming symbiont") { neighbor_host->GetHardware().GetCPUState().MarkTaskPerformed(8); symbiont->GetHardware().GetCPUState().MarkTaskPerformed(8); WHEN("Task matching is required for horizontal transmission") { - auto pos_found = world.FindHostForHorizontalTrans(source_id, symbiont); + auto pos_found = world.FindHostForHorizontalTrans(symbiont, symbiont->GetLocation()); THEN("The position of the nearby, matching host is returned") { REQUIRE(pos_found.has_value() == true); REQUIRE(pos_found->GetIndex() == neighbor_position.GetIndex()); @@ -408,19 +406,19 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor host->AddSymbiont(symbiont); world.AddOrgAt(host, 0); - size_t source_id = symbiont->GetLocation().GetPopID(); WHEN("There exists a nearby host") { emp::WorldPosition neighbor_position = emp::WorldPosition(1,0); emp::Ptr neighbor_host = emp::NewPtr(&random, &world, &config); world.AddOrgAt(neighbor_host, neighbor_position); - WHEN("The nearby host does not have matching tasks with the incoming symbiont") { - neighbor_host->GetHardware().GetCPUState().MarkTaskPerformed(8); - symbiont->GetHardware().GetCPUState().MarkTaskPerformed(6); + WHEN("The nearby host does not have matching tasks with the parent of the incoming symbiont") { + neighbor_host->GetHardware().GetCPUState().MarkTaskPerformed(6); + symbiont->GetHardware().GetCPUState().MarkTaskPerformed(8); + WHEN("Task matching is not required for horizontal transmission") { - auto pos_found = world.FindHostForHorizontalTrans(source_id, symbiont); + auto pos_found = world.FindHostForHorizontalTrans(symbiont, symbiont->GetLocation()); THEN("The position of the nearby, non-matching host is returned") { REQUIRE(pos_found.has_value() == true); REQUIRE(pos_found->GetIndex() == neighbor_position.GetIndex()); @@ -453,20 +451,18 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is required for horizon host->AddSymbiont(symbiont); world.AddOrgAt(host, 0); - size_t source_id = symbiont->GetLocation().GetPopID(); WHEN("There exists a nearby host") { emp::WorldPosition neighbor_position = emp::WorldPosition(1,0); emp::Ptr neighbor_host = emp::NewPtr(&random, &world, &config); world.AddOrgAt(neighbor_host, neighbor_position); - WHEN("The nearby host does not have matching tasks with the incoming symbiont") { - neighbor_host->GetHardware().GetCPUState().MarkTaskPerformed(8); - symbiont->GetHardware().GetCPUState().MarkTaskPerformed(6); - - + WHEN("The nearby host does not have matching tasks with the parent of the incoming symbiont") { + neighbor_host->GetHardware().GetCPUState().MarkTaskPerformed(6); + symbiont->GetHardware().GetCPUState().MarkTaskPerformed(8); + WHEN("Task matching is required for horizontal transmission") { - auto pos_found = world.FindHostForHorizontalTrans(source_id, symbiont); + auto pos_found = world.FindHostForHorizontalTrans(symbiont, symbiont->GetLocation()); THEN("Nothing is returned (no acceptable neighboring host)") { REQUIRE(pos_found.has_value() == false); } @@ -497,10 +493,9 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor host->AddSymbiont(symbiont); world.AddOrgAt(host, 0); - size_t source_id = symbiont->GetLocation().GetPopID(); WHEN("There does not exist a nearby host") { - auto pos_found = world.FindHostForHorizontalTrans(source_id, symbiont); + auto pos_found = world.FindHostForHorizontalTrans(symbiont, symbiont->GetLocation()); THEN("Nothing is returned (no acceptable neighboring host)") { REQUIRE(pos_found.has_value() == false); } @@ -552,7 +547,7 @@ TEST_CASE("SGP Horizontal SymDoBirth", "[sgp][sgp-unit]") { WHEN("Preferential ousting is on and the target host has a symbiont") { WHEN("The incoming symbiont has a better match") { symbiont_parent->GetHardware().GetCPUState().SetParentTaskPerformed(1); - world.SymDoBirth(symbiont_offspring, parent_pos); + world.SymDoBirth(symbiont_offspring, symbiont_parent, parent_pos); THEN("The incoming symbiont successfully ousts") { REQUIRE(target_host->HasSym()); REQUIRE(target_host->GetSymbionts().at(0) == symbiont_offspring); @@ -564,7 +559,7 @@ TEST_CASE("SGP Horizontal SymDoBirth", "[sgp][sgp-unit]") { WHEN("The incoming symbiont has a worse match") { target_symbiont->GetHardware().GetCPUState().SetParentTaskPerformed(1); - world.SymDoBirth(symbiont_offspring, parent_pos); + world.SymDoBirth(symbiont_offspring, symbiont_parent, parent_pos); THEN("The incoming symbiont does not oust") { REQUIRE(target_host->GetSymbionts().at(0).DynamicCast() == target_symbiont); }