Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 9 additions & 16 deletions source/default_mode/SymWorld.h
Original file line number Diff line number Diff line change
Expand Up @@ -1088,25 +1088,18 @@ class SymWorld : public emp::World<Organism> {
* 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<Organism> sym_baby, emp::WorldPosition parent_pos) {
virtual emp::WorldPosition SymDoBirth(emp::Ptr<Organism> sym_offspring, emp::Ptr<Organism> 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<Organism> 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;
Expand All @@ -1118,19 +1111,19 @@ class SymWorld : public emp::World<Organism> {
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<taxon_t::sym_taxon_t>()->GetData().DetermineHostSwitch(pop[new_host_pos]->GetTaxon(), sym_parent->GetHost()->GetTaxon());
sym_offspring->GetTaxon().Cast<taxon_t::sym_taxon_t>()->GetData().DetermineHostSwitch(pop[new_host_pos]->GetTaxon(), sym_parent->GetHost()->GetTaxon());
}
if (my_config->TRACK_PHYLOGENY_INTERACTIONS()) {
pop[new_host_pos]->GetTaxon().Cast<taxon_t::host_taxon_t>()->GetData().AddInteraction(sym_baby->GetTaxon());
pop[new_host_pos]->GetTaxon().Cast<taxon_t::host_taxon_t>()->GetData().AddInteraction(sym_offspring->GetTaxon());
}
}
if (my_config->FREE_HT_FAILURE() || my_config->TAG_MATCHING()) {
Expand All @@ -1142,11 +1135,11 @@ class SymWorld : public emp::World<Organism> {
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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion source/default_mode/Symbiont.h
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ class Symbiont: public Organism {
if (AttemptIndependentReproduction(location)) {
emp::Ptr<Organism> 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);

Expand Down
2 changes: 1 addition & 1 deletion source/efficient_mode/EfficientSymbiont.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Organism> 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<double, emp::data::Histogram>& data_node_attempts_horiztrans = my_world->GetHorizontalTransmissionAttemptCount();
Expand Down
2 changes: 1 addition & 1 deletion source/lysis_mode/Phage.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ class Phage: public Symbiont {
emp::DataMonitor<double, emp::data::Histogram>& data_node_successes_horiztrans = my_world->GetHorizontalTransmissionSuccessCount();

for (size_t r=0; r<repro_syms.size(); r++) {
emp::WorldPosition new_pos = my_world->SymDoBirth(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());
Expand Down
6 changes: 4 additions & 2 deletions source/sgp_mode/DevNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
* 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
Expand All @@ -31,6 +29,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
Expand Down
4 changes: 0 additions & 4 deletions source/sgp_mode/ReproductionQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ class ReproductionQueue {
continue;
}
fun_reproduce_org(repro_info);
// emp::Ptr<Organism> child = org->Reproduce();
// (child->IsHost()) ?
// world.HostDoBirth(child, org_ptr, repro_info.pos) :
// world.SymDoBirth(child, repro_info.pos);
}
Clear();
}
Expand Down
74 changes: 53 additions & 21 deletions source/sgp_mode/SGPW_InteractionMechanismSetup.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define SGP_W_INT_MECH_C

#include "SGPWorld.h"
#include <optional>


namespace sgpmode {
Expand Down Expand Up @@ -217,6 +218,25 @@ namespace sgpmode {


/************************** Stress ********************************* */
struct StressEscapee {
emp::Ptr<SGPWorld::sgp_sym_t> sym_offspring;
emp::Ptr<SGPWorld::sgp_sym_t> sym_parent;
emp::WorldPosition escape_location;

StressEscapee() = default;
StressEscapee(
emp::Ptr<SGPWorld::sgp_sym_t> _sym_offspring,
emp::Ptr<SGPWorld::sgp_sym_t> _sym_parent,
emp::WorldPosition _location
) :
sym_offspring(_sym_offspring),
sym_parent(_sym_parent),
escape_location(_location)
{ }
};

emp::vector<StressEscapee> symbiont_stress_escapees;

void SGPWorld::SetupStressInteractions() {
emp_assert(sgp_config.ENABLE_STRESS());
// Setup extinction variable
Expand All @@ -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<sgp_sym_t> endosym_ptr = static_cast<sgp_sym_t*>(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;
Expand All @@ -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<sgp_sym_t> endosym_ptr = static_cast<sgp_sym_t*>(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();
Expand All @@ -290,8 +304,8 @@ namespace sgpmode {
emp::Ptr<Organism> sym_offspring = endosym_ptr->Reproduce();
symbiont_stress_escapees.emplace_back(
static_cast<sgp_sym_t*>(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
Expand All @@ -311,8 +325,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);
Expand Down Expand Up @@ -340,8 +354,8 @@ namespace sgpmode {
emp::Ptr<Organism> sym_offspring = endosym_ptr->Reproduce();
symbiont_stress_escapees.emplace_back(
static_cast<sgp_sym_t*>(sym_offspring.Raw()),
endosym_task_profile,
endosym_ptr->GetHardware().GetCPUState().GetLocation().GetPopID()
endosym_ptr,
endosym_ptr->GetHardware().GetCPUState().GetLocation()
);
}
}
Expand Down Expand Up @@ -424,8 +438,8 @@ namespace sgpmode {
emp::Ptr<Organism> sym_offspring = endosym_ptr->Reproduce();
symbiont_stress_escapees.emplace_back(
static_cast<sgp_sym_t*>(sym_offspring.Raw()),
endosym_task_profile,
endosym_ptr->GetHardware().GetCPUState().GetLocation().GetPopID()
endosym_ptr,
endosym_ptr->GetHardware().GetCPUState().GetLocation()
);
}
}
Expand All @@ -452,10 +466,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<size_t> 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
}
);
}


Expand Down
60 changes: 19 additions & 41 deletions source/sgp_mode/SGPW_TaskProfileSetup.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,57 +85,34 @@ 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_trans_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_trans_compatibility_check = [this](
sgp_host_t& host,
sgp_sym_t& sym
) -> bool {
const auto& host_profile = fun_get_host_task_profile(host);
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_trans_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_trans_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;
Expand All @@ -146,24 +123,25 @@ 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<sgp_sym_t> sym_parent_ptr /* Pointer to symbiont parent (producing the sym offspring) */
emp::Ptr<sgp_sym_t> sym_offspring_ptr,
emp::Ptr<sgp_sym_t> sym_parent_ptr,
const emp::WorldPosition& parent_pos
) -> std::optional<emp::WorldPosition> {
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<Organism> neighbor_org_ptr = GetOrgPtr(candidate_pos.GetIndex());
emp_assert(neighbor_org_ptr->IsHost());
// Cast neighbor as sgp_host_t ptr.
emp::Ptr<sgp_host_t> neighbor_host_ptr = static_cast<sgp_host_t*>(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<Organism> prospective_org_ptr = GetOrgPtr(candidate_pos.GetIndex());
emp_assert(prospective_org_ptr->IsHost());
emp::Ptr<sgp_host_t> prospective_host_ptr = static_cast<sgp_host_t*>(prospective_org_ptr.Raw());

const bool compatible = fun_horizontal_trans_compatibility_check(
*prospective_host_ptr,
*sym_parent_ptr
);

if (compatible) {
return std::optional<emp::WorldPosition>{candidate_pos};
}
Expand Down
Loading
Loading