diff --git a/.gitignore b/.gitignore index 390d5433..fd89f2ea 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,6 @@ log.txt *.json *_test_output/ *_output/ +Data/* diff --git a/source/catch/main.cc b/source/catch/main.cc index 1039f4ec..3a021b5f 100644 --- a/source/catch/main.cc +++ b/source/catch/main.cc @@ -35,6 +35,7 @@ #include "../test/pgg_mode_test/PGGWorld.test.cc" //SGP mode Old tests (need to update before moving) +#include "../test/sgp_mode_test/functional_tests/TaskValueEvent.test.cc" #include "../test/sgp_mode_test/unit_tests/ProgramBuilder.test.cc" #include "../test/sgp_mode_test/functional_tests/HealthMode.test.cc" #include "../test/sgp_mode_test/unit_tests/SGPWorldSetup.test.cc" @@ -64,11 +65,11 @@ #include "../test/sgp_mode_test/unit_tests/SGPCureHosts.test.cc" #include "../test/sgp_mode_test/unit_tests/SGPWorldData.test.cc" -#include "../test/sgp_mode_test/functional_tests/SenseTask_Tasks.test.cc" +// #include "../test/sgp_mode_test/functional_tests/SenseTask_Tasks.test.cc" #include "../test/sgp_mode_test/functional_tests/NutrientMode.test.cc" #include "../test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont.test.cc" #include "../test/sgp_mode_test/functional_tests/SGPSymbiont_Reproduce.test.cc" -#include "../test/sgp_mode_test/functional_tests/TempChangingEnvironments.test.cc" +// #include "../test/sgp_mode_test/functional_tests/TempChangingEnvironments.test.cc" #include "../test/sgp_mode_test/functional_tests/SGPWorld.test.cc" #include "../test/sgp_mode_test/functional_tests/PopulationStructure.test.cc" diff --git a/source/default_mode/Host.h b/source/default_mode/Host.h index 7ba219a2..f489329c 100644 --- a/source/default_mode/Host.h +++ b/source/default_mode/Host.h @@ -475,7 +475,7 @@ class Host: public Organism { * * Purpose: To set the organism's world position */ - virtual void SetLocation(emp::WorldPosition _in) { location = _in; } + virtual void SetLocation(emp::WorldPosition _in) {location = _in;} /** @@ -652,7 +652,7 @@ class Host: public Organism { */ emp::Ptr RemoveSymbiont(int index) { int num_syms = syms.size(); - if (index < 1 || index > num_syms) { + if(index < 1 || index > num_syms) { return nullptr; } else { emp::Ptr to_remove = syms[index-1]; @@ -802,7 +802,7 @@ class Host: public Organism { double mutation_rate = my_config->HOST_MUTATION_RATE(); if (mutation_rate == -1) mutation_rate = my_config->MUTATION_RATE(); - if (random->GetDouble(0.0, 1.0) <= mutation_rate) { + if(random->GetDouble(0.0, 1.0) <= mutation_rate){ interaction_val += random->GetNormal(0.0, mutation_size); if (interaction_val < -1) interaction_val = -1; else if (interaction_val > 1) interaction_val = 1; diff --git a/source/default_mode/Symbiont.h b/source/default_mode/Symbiont.h index 899a6067..e5dbddc3 100644 --- a/source/default_mode/Symbiont.h +++ b/source/default_mode/Symbiont.h @@ -451,7 +451,7 @@ class Symbiont: public Organism { * * Purpose: To set the organism's world position */ - void SetLocation(emp::WorldPosition _in) { location = _in; } + void SetLocation(emp::WorldPosition _in) {location = _in;} /** * Input: None diff --git a/source/json/json_utils.h b/source/json/json_utils.h new file mode 100644 index 00000000..cd21f0af --- /dev/null +++ b/source/json/json_utils.h @@ -0,0 +1,73 @@ +#pragma once + +#include "json.hpp" + +#include "emp/base/vector.hpp" + +#include + +namespace sym_json { + +/** + * Purpose: Convenience function for more easily getting a value from a json field + * with a default return value if the field doesn't exist. + * + * Input: json object to access, field to access in json object, and a default + * value to return if the field doesn't exist in the json object. + * + * Output: Either the accessed field value or the provided default value. + * + */ +template +RET_TYPE GetVal( + nlohmann::json& json, + const std::string& field, + RET_TYPE default_val +) { + return (json.contains(field)) ? + static_cast(json[field]) : + default_val; +} + +/** + * Purpose: Convenience function for more easily getting a value from a json field. + * This version of the GetVal function assumes that the field exists. + * + * Input: json object to access, field to access in json object + * + * Output: The accessed field value + * + */ +template +RET_TYPE GetVal( + nlohmann::json& json, + const std::string& field +) { + emp_assert(json.contains(field)); + return static_cast(json[field]); +} + +/** + * Purpose: Validate that specified strings exist as fields inside of a json object. + * This function is useful for asserting that required/expected fields + * exist. + * + * Input: json object to check, fields to check in json object + * + * Output: Boolean indicating whether all given fields are contained in the json + * object. + * + */ +bool ValidateFieldsJSON( + const nlohmann::json& json_line, + const emp::vector& fields +) { + for (const std::string& name : fields) { + if (!json_line.contains(name)) { + return false; + } + } + return true; +} + +} \ No newline at end of file diff --git a/source/sgp_mode/ProgramBuilder.h b/source/sgp_mode/ProgramBuilder.h index df5e385e..eac25c47 100644 --- a/source/sgp_mode/ProgramBuilder.h +++ b/source/sgp_mode/ProgramBuilder.h @@ -573,9 +573,9 @@ class ProgramBuilder { start_tag ); // Add not instruction - AddInst(program, io_op); + AddInst(program, io_op); AddTask_Not(program); // Add not task - AddInst(program, io_op); + AddInst(program, io_op); AddInst(program, io_op, 1); AddTask_Nand(program); // Add nand task, IO will happen at start of next time through genome // Nop filler is length minus current size + repro instructions diff --git a/source/sgp_mode/SGPConfigSetup.h b/source/sgp_mode/SGPConfigSetup.h index b09d31fe..63e2d553 100644 --- a/source/sgp_mode/SGPConfigSetup.h +++ b/source/sgp_mode/SGPConfigSetup.h @@ -68,10 +68,11 @@ EMP_EXTEND_CONFIG(SymConfigSGP, SymConfigBase, VALUE(HOST_ONLY_FIRST_TASK_CREDIT, bool, false, "Only give host credit for one task (whatever they do first)?"), VALUE(SYM_ONLY_FIRST_TASK_CREDIT, bool, false, "Only give sym credit for one task (whatever they do first)?"), - GROUP(TEMP_CHANGING_ENVIRONMENT, "Temporally changing environment settings (task rewards change over time)"), - VALUE(ENABLE_TEMP_CHANGING_ENVIRONMENT, bool, false, "Do task reward values change over time?"), - VALUE(TEMP_CHANGING_ENVIRONMENT_INTERVAL, size_t, 100, "How many updates elapse between task reward value shuffling?"), - VALUE(TEMP_CHANGING_ENVIRONMENT_ORG_TYPE, std::string, "static", "Can organisms sense task reward values? (plastic-both: both symbionts and hosts can sense whether tasks are rewarded; static: neither hosts nor symbiont can sense whether tasks are rewarded)"), + GROUP(EVENTS, "Events settings"), + VALUE(EVENTS_CFG_PATH, std::string, "events.json", "JSON file that provides event configuration"), + + GROUP(INSTRUCTIONS, "Instruction settings"), + VALUE(SENSE_TASK_INSTRUCTION, bool, false, "Should sense task instruction be included in the instruction set?"), GROUP(DATA, "Data settings"), VALUE(PRINT_INTERVAL, size_t, 1, "How often to print run status") diff --git a/source/sgp_mode/SGPWorld.h b/source/sgp_mode/SGPWorld.h index 5d26a512..ab2471db 100644 --- a/source/sgp_mode/SGPWorld.h +++ b/source/sgp_mode/SGPWorld.h @@ -14,6 +14,7 @@ #include "hardware/SGPHardwareSpec.h" #include "hardware/GenomeLibrary.h" #include "hardware/SGPHardware.h" +#include "events/EventManager.h" #include "emp/Evolve/World_structure.hpp" #include "emp/data/DataNode.hpp" @@ -49,6 +50,7 @@ class SGPWorld : public SymWorld { using task_io_t = typename task_io_bank_t::TaskIO; using mutator_t = SGPMutator; using sgp_prog_rectifier_t = sgpl::OpCodeRectifier; + using event_manager_t = EventManager; using fun_sym_do_birth_t = std::function, /* symbiont baby ptr */ @@ -381,6 +383,7 @@ class SGPWorld : public SymWorld { size_t max_world_size; // Maximum number of locations in the world ReproductionQueue repro_queue; // Stores which organisms are queued for reproduction tasks::LogicTaskEnvironment task_env; // Manages task set, task requirements, and task rewards + event_manager_t event_manager; // TODO - Consider having symbiont rectifier and host rectifier // -> Symbiont-specific instructions wouldn't be in host's instruction set sgp_prog_rectifier_t opcode_rectifier; // Used to "disable" instructions at runtime based on run configuration @@ -549,6 +552,7 @@ class SGPWorld : public SymWorld { void SetupFindHostForHorizontalTransmission(); void SetupHostSymInteractions(); void SetupTaskEnvironment(); + void SetupEvents(); void SetupMutator(); void SetupStressInteractions(); void SetupHealthInteractions(); @@ -576,7 +580,6 @@ class SGPWorld : public SymWorld { } - // Utility function to get cpu state from an org pointer sgp_cpu_peripheral_t& GetCPUState(emp::Ptr org_ptr) { return (org_ptr->IsHost()) ? @@ -660,12 +663,22 @@ class SGPWorld : public SymWorld { size_t GetTaskCount() const { return task_env.GetTaskCount(); } /* Accessor for host task profiles */ - const emp::BitVector& GetHostTaskProfile(const sgp_host_t& host){return fun_get_host_task_profile(host);} + const emp::BitVector& GetHostTaskProfile(const sgp_host_t& host) { return fun_get_host_task_profile(host); } /* Accessor for symbiont task profiles */ - const emp::BitVector& GetSymbiontTaskProfile(const sgp_sym_t& symbiont){return fun_get_sym_task_profile(symbiont);} + const emp::BitVector& GetSymbiontTaskProfile(const sgp_sym_t& symbiont) { return fun_get_sym_task_profile(symbiont); } + + /** + * Purpose: Accessor for event manager (const) + */ + const event_manager_t& GetEventManager() const { return event_manager; } - /** + /** + * Purpose: Accessor for event manager + */ + event_manager_t& GetEventManager() { return event_manager; } + + /** * Input: A host, a symbiont, the value of a task before applying nutrient interaction, and the task id. * Output: The change in the points the host will gain from the task after the nutrient interaction. * Purpose: To calculate the configured nutrient interaction for the given symbiont and task @@ -824,6 +837,10 @@ class SGPWorld : public SymWorld { */ void Update() override { emp_assert(setup); + // NOTE - When do we want events to occur? Typically, I think we want them + // as the *very* first thing that happens on an update. E.g., changing + // a task value, etc. + event_manager.ProcessEvents(*this); begin_update_sig.Trigger(); // Handle resource inflow // TODO - implement inflow configuration diff --git a/source/sgp_mode/SGPWorldSetup.cc b/source/sgp_mode/SGPWorldSetup.cc index 3fd95371..4d3f33a1 100644 --- a/source/sgp_mode/SGPWorldSetup.cc +++ b/source/sgp_mode/SGPWorldSetup.cc @@ -7,13 +7,11 @@ #include "../utils.h" #include "sgpl/utility/ThreadLocalRandom.hpp" - #include "emp/datastructs/map_utils.hpp" #include "emp/tools/string_utils.hpp" #include "emp/math/math.hpp" - // TODO - assert that sym / host has program namespace sgpmode { @@ -75,6 +73,9 @@ void SGPWorld::Setup() { // Setup any host-symbiont interactions SetupHostSymInteractions(); + // Load events + SetupEvents(); + // CureHost signal // TODO: move to default? Figure out how to remove duplication if (sgp_config.CURE()) { @@ -87,10 +88,6 @@ void SGPWorld::Setup() { ); } - if (sgp_config.ENABLE_TEMP_CHANGING_ENVIRONMENT()) { - SetupChangingEnvironment(); - } - SetupHosts(&POP_SIZE); // NOTE - any way to clean this up a little? Or, add some explanatory comments. long unsigned int total_syms = POP_SIZE * start_moi; @@ -101,95 +98,6 @@ void SGPWorld::Setup() { setup = true; } -void SGPWorld::SetupChangingEnvironment() { - // on setup, set NAND, AND-NOT, OR-NOT to be negative (at update zero) - // then during each interval apply *-1 to the changing tasks - - size_t nand_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("NAND")) { - nand_task_id = task_env.GetTaskSet().GetID("NAND"); - } - else if (task_env.GetTaskSet().HasTask("nand")) { - nand_task_id = task_env.GetTaskSet().GetID("nand"); - } - - size_t andn_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("AND_NOT")) { - andn_task_id = task_env.GetTaskSet().GetID("AND_NOT"); - } - else if (task_env.GetTaskSet().HasTask("and_not")) { - andn_task_id = task_env.GetTaskSet().GetID("and_not"); - } - - size_t orn_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("OR_NOT")) { - orn_task_id = task_env.GetTaskSet().GetID("OR_NOT"); - } - else if (task_env.GetTaskSet().HasTask("or_not")) { - orn_task_id = task_env.GetTaskSet().GetID("or_not"); - } - - // grab task ids for NOT, AND, OR - size_t not_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("NOT")) { - not_task_id = task_env.GetTaskSet().GetID("NOT"); - } - else if (task_env.GetTaskSet().HasTask("not")) { - not_task_id = task_env.GetTaskSet().GetID("not"); - } - - size_t and_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("AND")) { - and_task_id = task_env.GetTaskSet().GetID("AND"); - } - else if (task_env.GetTaskSet().HasTask("and")) { - and_task_id = task_env.GetTaskSet().GetID("and"); - } - - size_t or_task_id = task_env.GetTaskSet().GetSize(); - if (task_env.GetTaskSet().HasTask("OR")) { - or_task_id = task_env.GetTaskSet().GetID("OR"); - } - else if (task_env.GetTaskSet().HasTask("or")) { - or_task_id = task_env.GetTaskSet().GetID("or"); - } - - // update 0 will flip nand-andn-orn to rewarded and not-and-or to punished - GetTaskEnv().GetHostTaskReq(nand_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(nand_task_id).task_value; - GetTaskEnv().GetSymTaskReq(nand_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(nand_task_id).task_value; - - GetTaskEnv().GetHostTaskReq(andn_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(andn_task_id).task_value; - GetTaskEnv().GetSymTaskReq(andn_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(andn_task_id).task_value; - - GetTaskEnv().GetHostTaskReq(orn_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(orn_task_id).task_value; - GetTaskEnv().GetSymTaskReq(orn_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(orn_task_id).task_value; - - begin_update_sig.AddAction( - [this, nand_task_id, andn_task_id, orn_task_id, not_task_id, and_task_id, or_task_id]() { - if (GetUpdate() % sgp_config.TEMP_CHANGING_ENVIRONMENT_INTERVAL() == 0) { - - GetTaskEnv().GetHostTaskReq(nand_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(nand_task_id).task_value; - GetTaskEnv().GetHostTaskReq(andn_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(andn_task_id).task_value; - GetTaskEnv().GetHostTaskReq(orn_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(orn_task_id).task_value; - - GetTaskEnv().GetHostTaskReq(not_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(not_task_id).task_value; - GetTaskEnv().GetHostTaskReq(and_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(and_task_id).task_value; - GetTaskEnv().GetHostTaskReq(or_task_id).task_value = -1 * GetTaskEnv().GetHostTaskReq(or_task_id).task_value; - - GetTaskEnv().GetSymTaskReq(nand_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(nand_task_id).task_value; - GetTaskEnv().GetSymTaskReq(andn_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(andn_task_id).task_value; - GetTaskEnv().GetSymTaskReq(orn_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(orn_task_id).task_value; - - GetTaskEnv().GetSymTaskReq(not_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(not_task_id).task_value; - GetTaskEnv().GetSymTaskReq(and_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(and_task_id).task_value; - GetTaskEnv().GetSymTaskReq(or_task_id).task_value = -1 * GetTaskEnv().GetSymTaskReq(or_task_id).task_value; - - - } - } - ); -} - void SGPWorld::DisableConfigurableInstructions() { // Knock out any mode-related instructions that shouldn't be active for this run @@ -223,7 +131,7 @@ void SGPWorld::DisableConfigurableInstructions() { // if temporally changing environment are off, or if organisms aren't allowed to sense their environment, // disable the SenseTask instruction - if (!sgp_config.ENABLE_TEMP_CHANGING_ENVIRONMENT() || sgp_config.TEMP_CHANGING_ENVIRONMENT_ORG_TYPE() == "static") { + if (!sgp_config.SENSE_TASK_INSTRUCTION()) { del_inst( opcode_rectifier.mapper.begin(), opcode_rectifier.mapper.end(), @@ -549,6 +457,10 @@ void SGPWorld::SetupTaskEnvironment() { ); } +void SGPWorld::SetupEvents() { + event_manager.LoadEventsFromJSON(sgp_config.EVENTS_CFG_PATH(), *this); +} + void SGPWorld::SetupMutator() { // NOTE - can add more flexibility to mutator mutator.SetPerBitMutationRate(sgp_config.SGP_MUT_PER_BIT_RATE()); diff --git a/source/sgp_mode/events/EventManager.h b/source/sgp_mode/events/EventManager.h new file mode 100644 index 00000000..f99342b1 --- /dev/null +++ b/source/sgp_mode/events/EventManager.h @@ -0,0 +1,437 @@ +#pragma once + +#include "event_types/Event.h" +#include "EventTypeDefinition.h" +#include "EventTypeLibrary.h" + +#include "../../json/json.hpp" +#include "../../json/json_utils.h" + +#include "emp/base/vector.hpp" +#include "emp/bits/Bits.hpp" +#include "emp/tools/string_utils.hpp" +#include "emp/datastructs/set_utils.hpp" +#include "emp/math/math.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace sgpmode { + +/** + * Purpose: Manages set of events that can be loaded in from a configuration file. + */ +template +class EventManager { +public: + using json_t = nlohmann::json; // json file type + using event_t = Event; // event class alias + using world_t = WORLD_T; // world type alias + using event_type_def_t = EventTypeDefinition; + using fun_event_handler_t = typename event_type_def_t::fun_event_handler_t; + +protected: + + /** + * Purpose: Manages known event type definitions. + * Must be updated before loading event cfg file. + */ + EventTypeLibrary event_type_library; + + /** + * Purpose: Manages all one-off events in reverse sorted order by update to apply. + */ + emp::vector> one_time_events; + + /** + * Purpose: Manages all recurring events in reverse sorted order by update to apply. + */ + emp::vector> recurring_events; + + /** + * Purpose: Load a single event from JSON + * Each event type should define its own loading function (see ExampleEvent), + * so this function grabs the appropriate event type definition and then + * calls that event type's load event function. + * + * Input: json oject to load an event from, reference to the world + * + * Output: Pointer to a new event object + */ + emp::Ptr LoadEventFromJSON(json_t& event_json, world_t& world) { + // Check that event_json has event type + emp_assert(event_json.contains("event_type")); + const std::string event_type(event_json["event_type"]); + // emp::Ptr loaded_event; + // Check if event type is valid (i.e., exists in the event type library) + emp_assert(event_type_library.IsValidEventType(event_type)); + const size_t event_type_id = event_type_library.GetEventTypeID(event_type); + auto& event_type_def = event_type_library.GetEventTypeDefinition(event_type_id); + // Event json must have required fields (as determined by event type definition) + emp_assert( + sym_json::ValidateFieldsJSON(event_json, event_type_def.GetRequiredFields()) + ); + return event_type_def.LoadEventFromJSON(event_json, world); + } + + /** + * Purpose: Process all one-type events that should be applied this update. + * After processing an event, delete it. + * Note: this function assumes that one_time_events is in reverse sorted + * order by next update. + * + * Input: Reference to the world + * + * Output: None. + */ + void ProcessOneTimeEvents(world_t& world) { + if (one_time_events.empty()) { return; } + // One-time events are reverse sorted according to next update. + // Sorting should have happened on load (as well as anytime an event was added). + const size_t current_update = world.GetUpdate(); + // Process one-time events + const size_t num_events = one_time_events.size(); + // If no events to process, skip. + emp_assert(num_events > 0); + size_t events_processed = 0; + + // && event_i < num_events <-- checks for roll over + for (size_t event_i = num_events - 1; event_i >= 0 && event_i < num_events; --event_i) { + emp::Ptr event = one_time_events[event_i]; + emp_assert(!event->IsRecurring()); + const size_t event_update = event->GetNextUpdate(); + // Event's next update should never be less than current update. If so, + // we failed to process it on a previous update. :( + emp_assert(event_update >= current_update); + // If event's next update is bigger than current update, no more events + // to trigger this update. + if (event_update > current_update) { + break; + } + emp_assert(event_update == current_update); + // Process this event + event_type_library.ProcessEvent(world, event); + // One-time event processed. Delete, update # events processed. + event.Delete(); + ++events_processed; + } + // Chop off processed events + emp_assert(events_processed <= num_events); + one_time_events.resize(num_events - events_processed); + } + + /** + * Purpose: Process any recurring events that should be applied this update. + * After processing an event, delete the event if its next update is + * past its end update or if the event was marked as done by the event's + * process function. Otherwise, advance the event's timing and keep + * for future processing. + * Note: this function assumes that recurring_events is in reverse sorted order + * by each event's next update. This function will resort recurring + * events after processing. + * + * Input: Reference to the world. + * + * Output: None. + */ + void ProcessRecurringEvents(world_t& world) { + if (recurring_events.empty()) { return; } + // Recurring events are reverse sorted by the next update they should trigger + // on. + const size_t current_update = world.GetUpdate(); + const size_t num_events = recurring_events.size(); + emp_assert(num_events > 0); + size_t events_deleted = 0; + size_t events_recurred = 0; + for (size_t event_i = num_events - 1; event_i >= 0 && event_i < num_events; --event_i) { + emp::Ptr event = recurring_events[event_i]; + emp_assert(event->IsRecurring()); + const size_t event_update = event->GetNextUpdate(); + // Event's next update should never be less than current update. If so, + // we failed to process it on a previous update. :( + emp_assert(event_update >= current_update); + // If event's next update is bigger than current update, no more events + // to trigger this update. + if (event_update > current_update) { + break; + } + // Otherwise, process this event. + event_type_library.ProcessEvent(world, event); + const size_t next_update = event->AdvanceNextUpdate(); + const size_t end_update = event->GetEndUpdate(); + const bool is_done = event->IsDone(); + if (next_update > end_update || is_done) { + event.Delete(); + recurring_events[event_i] = nullptr; + ++events_deleted; + // Move deleted events to end of vector. + // - If we haven't recurred any events, either this is the first event + // or all events procssed this update have been deleted. + // - If we have recurred any events, we know that all prior deleted events + // have been swapped to the end of the vector. So, all recurred events + // are in a chunk between this (deleted) event and the rest of the deleted + // events (if any). We want to swap this nullptr with the last recurred + // event in that chunk. + if (events_recurred > 0) { + // There was an event recurred before this, potentially breaking up + // the sequence of deleted events. + // Swap current event with last event in recurred events chunk + emp_assert(recurring_events[event_i + events_recurred] != nullptr); + std::swap( + recurring_events[event_i], + recurring_events[event_i + events_recurred] + ); + } + } else { + // If this event wasn't deleted, it will occur again later. + ++events_recurred; + } + + } + // Resize away the deleted events (that have all been swapped to the end) + recurring_events.resize(num_events - events_deleted); + // Resort! Perfect application for powersort because most events should + // already be sorted... (versus likely std library's introsort) + ReorderRecurringEvents(); + } + +public: + /** + * Purpose: EventManager destructor. Responsible for cleaning up any event objects. + */ + ~EventManager() { + ClearEvents(); + } + + /** + * Purpose: + * + * Input: + * + * Output: + */ + const EventTypeLibrary& GetEventTypeLibrary() const { return event_type_library; } + + + /** + * Purpose: Delete all current events info (one-time and recurring) + * + * Input: None. + * + * Output: None. + */ + void ClearEvents() { + for (emp::Ptr event : one_time_events) { + event.Delete(); + } + one_time_events.clear(); + for (emp::Ptr event : recurring_events) { + event.Delete(); + } + recurring_events.clear(); + } + + /** + * Purpose: Accessor for recurring events. + * CAUTION: the event manager makes assumptions about the order of recurring + * events based on their timing. This accessor is used primarily for testing. + * If recurring events are modified via this function, you must resort them (ReorderRecurringEvents) + */ + emp::vector>& GetRecurringEvents() { + return recurring_events; + } + + /** + * Purpose: Accessor for one-time events. + * CAUTION: the event manager makes assumptions about the order of one-time + * events based on their timing. This accessor is used primarily for testing. + * If one-time events are modified via this function, you must resort them (ReorderOneTimeEvents) + */ + emp::vector>& GetOneTimeEvents() { + return one_time_events; + } + + /** + * Purpose: load in and process events configuratoin json file (includes + * creating events and checking if they are valid) + * + * Input: String path to event configuration file, reference to the world object + * + * Output: None. + */ + void LoadEventsFromJSON(const std::string& event_filepath, world_t& world) { + ClearEvents(); + // === Parse events file === + // Check if given events file exists. Exit if not. + const bool event_file_exists = std::filesystem::exists(event_filepath); + if (!event_file_exists) { + std::cout << "Event file does not exist: " << event_filepath << std::endl; + emp_assert(false, "Event file does not exist."); + std::exit(EXIT_FAILURE); + } + // read event.json file + std::ifstream event_ifstream(event_filepath); + nlohmann::json events_json; + event_ifstream >> events_json; + emp_assert(events_json.contains("events")); + // For each event line in events + for (auto& event_json : events_json["events"]) { + emp::Ptr new_event_ptr = LoadEventFromJSON(event_json, world); + // Categorize event as recurring or one-time + if (new_event_ptr->IsRecurring()) { + recurring_events.emplace_back(new_event_ptr); + } else { + one_time_events.emplace_back(new_event_ptr); + } + } + // Sort events according to their next update + ReorderOneTimeEvents(); + ReorderRecurringEvents(); + } + + /** + * Purpose: Process any events that need to be processed on the current world + * update. + * + * Input: Reference to the world. + * + * Output: None. + */ + void ProcessEvents(world_t& world) { + // Get current update in the world. Process all events that should occur on this update. + // Options: + // 1. Maintain unordered list of events. Loop over entire list each update, + // triggering any events where event->NextUpdate() == current update. + // - Pro: no need to maintain sorted order, simple insertion/deletion + // - Con: need to loop over all events no matter what. Could be costly if + // there are many events that occur throughout the run. + // 2. Keep events sorted by their next update to trigger on. Trigger any events + // where event->NextUpdate() == current update. + // - Pro: Very efficient to check if any events need to be triggered. Will + // end up looping over just events that need to be triggered each update. + // - Con: Recurring events need to be resorted if they need to be triggered + // again in the future. + // AML thoughts: Leaning toward option 2. In option 1, we pay the expensive part + // every update. In option 2, we only pay the resorting cost when a recurring + // event triggers (most recurring events will not happen every update). + // Process all one-time events that need to be triggered this update. + ProcessOneTimeEvents(world); + // Next, process all recurring events that need to be triggered this update. + ProcessRecurringEvents(world); + } + + /** + * Purpose: Manual add event function. Used when an event that didn't originate + * from a config file needs to be added to the event system. + * + * Input: Pointer to the event to add to the event manager. + * + * Output: None. + */ + void AddEvent(emp::Ptr event) { + // Check that this event is who they say it is + const auto& event_type_name = event->GetEventType(); + // Check that incoming event is a known event type + emp_assert(event_type_library.IsValidEventType(event_type_name)); + // Set event's type id + event->SetEventTypeID(event_type_library.GetEventTypeID(event_type_name)); + // Add event to either recurring or one-time event set, then reorder. + if (event->IsRecurring()) { + recurring_events.emplace_back(event); + ReorderRecurringEvents(); + } else { + one_time_events.emplace_back(event); + ReorderOneTimeEvents(); + } + } + + /** + * Purpose: Manually add multiple events to the event manager. Used for events + * that don't originate from the events configuration file. Faster to + * to bulk add multiple events than adding one at a time to avoid + * repeated resorting. + * + * Input: List of event pointers to be added. + * + * Output: None. + */ + void AddEvents(emp::vector> events) { + bool resort_recurring = false; + bool resort_one_time = false; + for (size_t i = 0; i < events.size(); ++i) { + // Check that this event is who they say it is + const auto& event_type_name = events[i]->GetEventType(); + // Check that incoming event is a known event type + emp_assert(event_type_library.IsValidEventType(event_type_name)); + // Set event's type id + events[i]->SetEventTypeID(event_type_library.GetEventTypeID(event_type_name)); + // Add event to either recurring or one-time event set. + if (events[i]->IsRecurring()) { + recurring_events.emplace_back(events[i]); + resort_recurring = true; + } else { + one_time_events.emplace_back(events[i]); + resort_one_time = true; + } + } + // Resort as needed + if (resort_recurring) { + ReorderRecurringEvents(); + } + if (resort_one_time) { + ReorderOneTimeEvents(); + } + } + + /** + * Purpose: Hlper function to resort the one-time events. + * One-time events should be reverse-sorted by their next update + * (i.e., soonest next update at end). One-time events must be sorted + * for processing to be correct. i.e., when adding a new event, must resort! + * + * Input: None. + * + * Output: None. + */ + void ReorderOneTimeEvents() { + std::sort( + one_time_events.begin(), + one_time_events.end(), + [](emp::Ptr a, emp::Ptr b) { + return a->GetNextUpdate() > b->GetNextUpdate(); + } + ); + } + + /** + * Purpose: Helper function to reorder recurring events. + * Should be reverse sorted by each event's next update (i.e., soonest + * next update at end of vector). Recurring events must be sorted for + * processing to be correct. I.e., when adding a new event, must sort! + * And, when re-queueing a recurring event, we must make sure it ends + * up in the appropriate spot by next update. + * + * Input: None. + * + * Output. None. + * + * + */ + void ReorderRecurringEvents() { + std::sort( + recurring_events.begin(), + recurring_events.end(), + [](emp::Ptr a, emp::Ptr b) { + return a->GetNextUpdate() > b->GetNextUpdate(); + } + ); + } + +}; + +} \ No newline at end of file diff --git a/source/sgp_mode/events/EventTiming.h b/source/sgp_mode/events/EventTiming.h new file mode 100644 index 00000000..d8d51e44 --- /dev/null +++ b/source/sgp_mode/events/EventTiming.h @@ -0,0 +1,160 @@ +#pragma once + +#include "emp/base/assert.hpp" + +namespace sgpmode { + +/** + * Purpose: Manages timing for events. + */ +class EventTiming { +protected: + /** + * Purpose: Start update for this event. + */ + size_t start_update; + + /** + * Purpose: End update for this event if recurring. + */ + size_t end_update; + + /** + * Purpose: Update interval for this event if recurring. + */ + size_t frequency; + + /** + * Purpose: Next update that this event should occur if recurring. + */ + size_t next_update; + + /** + * Purpose: Boolean indicating whether this is a recurring or one-time event. + */ + bool recurring; + +public: + /** + * Purpose: Default constructor. + */ + EventTiming() = default; + + /** + * Purpose: Constructor for recurring events. + * + * Inputs: Specify start update, end update, and frequency for recurring event. + */ + EventTiming(size_t start_u, size_t end_u, size_t freq_u) : + start_update(start_u), + end_update(end_u), + frequency(freq_u), + recurring(true) + { + next_update = start_update; + } + + /** + * Purpose: Constructor for one-time events. + * + * Inputs: Specify the start update for this one-time event. + */ + EventTiming(size_t start_u) : + start_update(start_u), + end_update((size_t)-1), + frequency((size_t)-1), + recurring(false) + { + next_update = start_update; + } + + /** + * Purpose: Reset this event as a one-time event. + * + * Inputs: Start update. + * + * Outputs: None. + */ + void Reset(size_t start_u) { + start_update = start_u; + end_update = (size_t)-1; + frequency = (size_t)-1; + recurring = false; + next_update = start_update; + } + + /** + * Purpose: Reset this event as a recurring event. + * + * Inputs: Start update, end update, and frequency. + * + * Outputs: None. + */ + void Reset(size_t start_u, size_t end_u, size_t freq_u) { + emp_assert(start_u <= end_u); + start_update = start_u; + end_update = end_u; + frequency = freq_u; + recurring = true; + next_update = start_update; + } + + /** + * Purpose: Get this event's start update. + * + * Inputs: None. + * + * Outputs: This event's start update. + */ + size_t GetStartUpdate() const { return start_update; } + + /** + * Purpose: Get this event's end update. + * + * Inputs: None. + * + * Outputs: This event's end update. + */ + size_t GetEndUpdate() const { return end_update; } + + /** + * Purpose: Get this event's frequency. + * + * Inputs: None. + * + * Outputs: This event's update interval frequency. + */ + size_t GetFrequency() const { return frequency; } + + /** + * Purpose: Check whether this event is recurring. + * + * Inputs: None. + * + * Outputs: Boolean indicating whether this event is recurring. + */ + bool IsRecurring() const { return recurring; } + + /** + * Purpose: Get this event's next update. + * + * Inputs: None. + * + * Outputs: The update that this event will trigger next. + */ + size_t GetNextUpdate() const { return next_update; } + + /** + * Purpose: Step the event timing forward if it is recurring. + * + * Inputs: None. + * + * Outputs: None. + */ + void Step() { + next_update += (recurring) ? frequency : 0; + } + +}; + +} \ No newline at end of file diff --git a/source/sgp_mode/events/EventTypeDefinition.h b/source/sgp_mode/events/EventTypeDefinition.h new file mode 100644 index 00000000..b51aedb5 --- /dev/null +++ b/source/sgp_mode/events/EventTypeDefinition.h @@ -0,0 +1,130 @@ +#pragma once + +#include "event_types/Event.h" + +#include "../../json/json.hpp" +#include "emp/base/Ptr.hpp" + +#include +#include + +namespace sgpmode { + +/** + * Purpose: The event type definition contains the information necessary to define a + * type of event. + * Event definition info: + * - event_id: unique, used to lookup handler function when processing events + * - event_name: unique, human-readable event type name, used to identify event + * in the events file + * - event_handler_fun: Function that handles processing an event of this type + * - event_json_loader_fun: Function that handles loading an event of this type + */ +template +class EventTypeDefinition { +public: + using event_t = Event; // event class alias + using world_t = WORLD_T; // world type alias + using json_t = nlohmann::json; // json file type + + // Event handler function type + using fun_event_handler_t = std::function // event being processed + )>; + + using fun_json_loader_t = std::function(json_t&, world_t&)>; + +protected: + + /** + * Purpose: Event definition ID + */ + size_t event_id; + + /** + * Purpose: Human-readable event type name + */ + std::string event_name; + + /** + * Purpose: Event handler function + */ + fun_event_handler_t event_handler_fun; + + /** + * Purpose: Event's json loader function + */ + fun_json_loader_t event_json_loader_fun; + + /** + * Purpose: Event type description + */ + std::string description; + + /** + * Purpose: List of required fields for loading event configuration + */ + emp::vector required_fields; + +public: + + /** + * Purpose: EventTypeDefinition constructor. + */ + EventTypeDefinition( + size_t a_event_id, + const std::string& a_event_name, + const fun_event_handler_t& a_event_handler, + const fun_json_loader_t& a_event_json_loader_fun, + const std::string& a_desc, + const emp::vector& a_required_fields + ) : + event_id(a_event_id), + event_name(a_event_name), + event_handler_fun(a_event_handler), + event_json_loader_fun(a_event_json_loader_fun), + description(a_desc), + required_fields(a_required_fields) + { ; } + + + /** + * Purpose: Get this event's required fields. + * + * Input: None. + * + * Output: List of strings indicating this event type's set of required fields + * for configuration. + */ + const emp::vector& GetRequiredFields() const { + return required_fields; + } + + /** + * Purpose: Runs the given event through its event handler. + * + * Input: Reference to the world and a pointer to the event to be processed. + * + * Output: None. + */ + void Process(world_t& world, emp::Ptr event) const { + event_handler_fun(world, event); + } + + /** + * Purpose: Loads event from json using event_json_loader_fun. + * + * Input: Json to load from, reference to the world. + * + * Output: Pointer to a new event object created by the event loader. + */ + emp::Ptr LoadEventFromJSON(json_t& json, world_t& world) const { + emp::Ptr event = event_json_loader_fun(json, world); + event->SetEventTypeID(event_id); + return event; + } + +}; + +} \ No newline at end of file diff --git a/source/sgp_mode/events/EventTypeLibrary.h b/source/sgp_mode/events/EventTypeLibrary.h new file mode 100644 index 00000000..f1545d8c --- /dev/null +++ b/source/sgp_mode/events/EventTypeLibrary.h @@ -0,0 +1,204 @@ +#pragma once + +#include "event_types/Event.h" +#include "event_types/TaskValueEvent.h" +#include "EventTypeDefinition.h" + +#include "emp/base/vector.hpp" +#include "emp/datastructs/map_utils.hpp" + +namespace sgpmode { + +/** + * Purpose: Manages a library of event type definitions. + */ +template +class EventTypeLibrary { +public: + using world_t = WORLD_T; + using event_type_def_t = EventTypeDefinition; + using json_t = nlohmann::json; // json file type + using fun_event_handler_t = typename event_type_def_t::fun_event_handler_t; + using fun_json_loader_t = typename event_type_def_t::fun_json_loader_t; + +protected: + + /** + * Purpose: List of event type definitions + */ + emp::vector event_definitions; + + /** + * Purpose: Mapping of event type names to their index in the event_definitions + * vector. + */ + std::unordered_map event_name_to_id; + +public: + /** + * Purpose: EventTypeLibrary constructor. + * + * Inputs: Boolean whether to add all default event types to this library on + * construction. + */ + EventTypeLibrary(bool add_default_events=true) { + if (add_default_events) { + AddDefaultEventTypes(); + } + } + + /** + * Purpose: Clear all event types from the library + * + * Input: None. + * + * Output: None. + */ + void Clear() { + event_definitions.clear(); + } + + /** + * Purpose: Get event type id using string name + * + * Input: String event type name. + * + * Output: ID of that event type in the library. + */ + size_t GetEventTypeID(const std::string& event_name) const { + emp_assert(IsValidEventType(event_name)); + return event_name_to_id.at(event_name); + } + + /** + * Purpose: Get an event type definition. + * + * Input: ID of the event type. + * + * Output: Event type definition associated with given event type id. + */ + const event_type_def_t& GetEventTypeDefinition(size_t event_type_id) const { + emp_assert(event_type_id < event_definitions.size()); + return event_definitions[event_type_id]; + } + + /** + * Purpose: Check if event type is contained in this library. + * + * Input: Event type name to check. + * + * Output: Boolean indicating whether event type name is in this library. + */ + bool IsValidEventType(const std::string& event_name) const { + return emp::Has(event_name_to_id, event_name); + } + + /** + * Purpose: Run appropriate event handler for given event. + * + * Input: Reference to the world, pointer to the event to process. + * + * Output: None. + */ + void ProcessEvent(world_t& world, emp::Ptr event_ptr) { + const size_t event_type_id = event_ptr->GetEventTypeID(); + event_definitions[event_type_id].Process(world, event_ptr); + } + + /** + * Purpose: Add all default event types to this library. Called by constructor + * by default. Should not be called a second time without clearing + * the library first to avoid adding duplicate event types. + * + * Input: None. + * + * Output: None. + */ + void AddDefaultEventTypes() { + AddEventType(); + } + + /** + * Purpose: Add a new event type to the event library. Can be used to add event + * types that don't conform to expectations (e.g., Event.h / ExampleEvent.h) + * + * Inputs: + * - event_name: String indicating event type name + * - event_handler: Function for handling events of this type + * - event_loader: Function for loading events of this type + * - event_description: Description of this event type + * - event_required_cfg_fields: Required fields for configuring events of this type + * + * Output: None. + */ + void AddEventType( + const std::string& event_name, + const fun_event_handler_t& event_handler, + const fun_json_loader_t& event_loader, + const std::string& event_description = "", + const emp::vector& event_required_cfg_fields = {} + ) { + // Should not be duplicate event type names. + emp_assert(!emp::Has(event_name_to_id, event_name)); + const size_t event_id = event_definitions.size(); + event_definitions.emplace_back( + event_id, + event_name, + event_handler, + event_loader, + event_description, + event_required_cfg_fields + ); + event_name_to_id[event_name] = event_id; + } + + /** + * Purpose: Add event type to this event type library. + * + * Input: + * - event_name: String event type name + * - event_description: String event type description + * - event_required_cfg_fields: List of required fields for configuring events + * of this type. + * + * Output: None. + */ + template + void AddEventType( + const std::string& event_name, + const std::string& event_description = "", + const emp::vector& event_required_cfg_fields = {} + ) { + AddEventType( + event_name, + [](world_t& world, emp::Ptr event_ptr) { + emp::Ptr event = static_cast(event_ptr.Raw()); + event->Process(world); + }, + [](json_t& event_json, world_t& world) -> emp::Ptr { + return EVENT_T::LoadEventFromJSON(event_json, world); + }, + event_description, + event_required_cfg_fields + ); + } + + /** + * Purpose: Add event type assuming EVENT_T has static event specs + a process function + * + * Input: None. + * + * Output: None. + */ + template + void AddEventType() { + AddEventType( + EVENT_T::event_specs.event_type, + EVENT_T::event_specs.event_description, + EVENT_T::event_specs.event_required_fields + ); + } + +}; + +} \ No newline at end of file diff --git a/source/sgp_mode/events/event_types/Event.h b/source/sgp_mode/events/event_types/Event.h new file mode 100644 index 00000000..1ebc313b --- /dev/null +++ b/source/sgp_mode/events/event_types/Event.h @@ -0,0 +1,269 @@ +#pragma once + +#include "../EventTiming.h" + +#include "../../../json/json.hpp" +#include "../../../json/json_utils.h" + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/bits/Bits.hpp" +#include "emp/datastructs/set_utils.hpp" +#include "emp/datastructs/map_utils.hpp" +#include "emp/math/math.hpp" +#include "emp/tools/string_utils.hpp" + +#include +#include +#include +#include +#include +#include + + +namespace sgpmode { + +/** + * Purpose: Helper struct used to manage minimum required specifications for new + * event types. Each event type contains a static const EventTypeDefinitionSpecs + * object, containing definitional information about the event type. + */ +struct EventTypeDefinitionSpecs { + /** + * Purpose: String that names this type of event. + */ + std::string event_type; + + /** + * Purpose: String that provides a brief description of this event type. + */ + std::string event_description; + + /** + * Purpose: Vector of strings that lists the required fields for configuring + * an event of this type. + */ + emp::vector event_required_fields; + + /** + * Purpose: Constructor. + * + * Input: + * - e_type: String specifying name of this event type. + * - e_desc: String specifying a description of this event type. + * - e_req_fields: Vector of strings specifying required fields for configuring + * an event of this type. + */ + EventTypeDefinitionSpecs( + const std::string& e_type, + const std::string& e_desc = "", + const emp::vector& e_req_fields = {} + ) : + event_type(e_type), + event_description(e_desc), + event_required_fields(e_req_fields) + { ; } +}; + +/** + * Purpose: Basic event type. Should be used as base class for event types. + * See ExampleEvent.h for a minimal example of how to implement a new + * event type. Each instance of any event will have this as its base class. + */ +class Event { +protected: + /** + * Purpose: Event type ID for this event. Filled in by the event manager when + * event is loaded from file. + */ + size_t event_type_id = 0; + + /** + * Purpose: Human-readable event type name. + */ + std::string event_type{"NULL"}; // + + /** + * Purpose: EventTiming object that specifies/manages this event's timing + * (one-time vs. recurring start/stop/step) + */ + EventTiming timing; + + /** + * Purpose: Boolean indicating whether this event is finished. Can be set by + * an event to end a recurring event before its end update. + */ + bool is_done = false; + +public: + + /** + * Purpose: Get this event's type id. + * + * Input: None. + * + * Output: This event's event type id. + * + */ + size_t GetEventTypeID() const { return event_type_id; } + + /** + * Purpose: Set this event's event type id. + * + * Input: New event type id. + * + * Output: None. + * + */ + void SetEventTypeID(size_t id) { event_type_id = id; } + + /** + * Purpose: Check whether this event has been flagged as finished. + * + * Input: None. + * + * Output: Boolean indicating whether this event is finished. + * + */ + bool IsDone() const { return is_done; } + + /** + * Purpose: Get this event's type string. + * + * Input: None. + * + * Output: String indicating this event's type. + * + */ + const std::string& GetEventType() const { return event_type; } + + /** + * Purpose: Get this event's event timing object. + * + * Input: None. + * + * Output: This event's event timing object. + * + */ + const EventTiming& GetEventTiming() const { return timing; } + + /** + * Purpose: Get whether this event is recurring. + * + * Input: None. + * + * Output: Boolean indicating whether this event is recurring. + * + */ + bool IsRecurring() const { return timing.IsRecurring(); } + + /** + * Purpose: Get this event's start update. + * + * Input: None. + * + * Output: Unsigned integer indicating this event's start update. + * + */ + size_t GetStartUpdate() const { return timing.GetStartUpdate(); } + + /** + * Purpose: Get this event's end update. + * + * Input: None. + * + * Output: Unsigned integer indicating this event's ending update. + * + */ + size_t GetEndUpdate() const { return timing.GetEndUpdate(); } + + /** + * Purpose: Get this event's update frequency + * + * Input: None. + * + * Output: Unsigned integer indicating this event's update frequency. + */ + size_t GetFrequency() const { return timing.GetFrequency(); } + + /** + * Purpose: Get the update that this event will next occur. + * + * Input: None. + * + * Output: Unsigned integer indicating the update that this event will next occur. + * + */ + size_t GetNextUpdate() const { return timing.GetNextUpdate(); } + + /** + * Purpose: Reset this event's timing as a non-recurring event with the given + * trigger update. + * + * Input: Start update to reset this event to. + * + * Output: None. + * + */ + void ResetTiming(size_t start_update) { + timing.Reset(start_update); + } + + /** + * Purpose: Reset this event's timing as a recurring event with specified start + * update, end update, and frequency. + * + * Input: Unsigned integers indicating new start update, end update, and frequency. + * + * Output: None. + * + */ + void ResetTiming(size_t start, size_t end, size_t freq) { + timing.Reset(start, end, freq); + } + + /** + * Purpose: Advance event timing (used when event is processed). + * + * Input: None. + * + * Output: Next event update after advancing. + * + */ + size_t AdvanceNextUpdate() { + timing.Step(); + return timing.GetNextUpdate(); + } + +}; + +/** + * Purpose: Basic parsing function for event timing used by many event types. + * + * Input: json object with timing information, pointer to the event to configure + * + * Output: None. + */ +void SetEventTimingFromJSON( + nlohmann::json& event_json, + emp::Ptr event_ptr +) { + emp_assert(event_json.contains("timing")); + const std::string timing_str(event_json["timing"]); + // EventTiming timing; + // Given as a single (integer) number? + if (emp::is_digits(timing_str)) { + const size_t start_update = emp::from_string(timing_str); + event_ptr->ResetTiming(start_update); + } else { + // Timing given as Start:Stop:Step + emp::vector recurring_str = emp::slice(timing_str, ':'); + emp_assert(recurring_str.size() == 3); + const size_t start_update = emp::from_string(recurring_str[0]); + const size_t stop_update = emp::from_string(recurring_str[1]); + const size_t frequency = emp::from_string(recurring_str[2]); + event_ptr->ResetTiming(start_update, stop_update, frequency); + } +} + +} \ No newline at end of file diff --git a/source/sgp_mode/events/event_types/ExampleEvent.h b/source/sgp_mode/events/event_types/ExampleEvent.h new file mode 100644 index 00000000..968a8abd --- /dev/null +++ b/source/sgp_mode/events/event_types/ExampleEvent.h @@ -0,0 +1,83 @@ +#pragma once + +#include "Event.h" + +namespace sgpmode { + +/** + * Purpose: Example event class. Used for example purposes only. + */ +class ExampleEvent : public Event { +public: + using json_t = nlohmann::json; + + /** + * Purpose: Event specification object shared by all instances of this event + * type. + */ + static const EventTypeDefinitionSpecs event_specs; + + /** + * Purpose: All event type classes should define a "LoadEventFromJSON" function + * that configures an instance of this event type. + * See TaskValueEvent for another example. + * + * Input: Json object to load event information from, reference to the world. + * + * Output: Pointer to new event instance created and configured by this load + * function. + */ + template + static emp::Ptr LoadEventFromJSON( + json_t& event_json, + WORLD_T& world + ) { + // This should be a task_value event + emp_assert(event_json["event_type"] == event_specs.event_type); + // NOTE: Caller is responsible for event deletion. + emp::Ptr event = emp::NewPtr(); + // --- TO FILL IN --- + // Configure new event object based on given event_json + // ------------------ + return event; + } + +protected: + // -- TO FILL IN -- + // Put any internal non-public variables used by your event here. + // ---------------- + +public: + /** + * Purpose: Constructor. Set the base class's event_type based on this event + * type's event specs. + */ + ExampleEvent() { + // Set event type in base class. + event_type = event_specs.event_type; + } + + /** + * Purpose: Process this event. Called by the event manager when this event should + * be triggered based on its timing. + * + * Input: Reference to the world (will likely be modified by the event) + * + * Output: None. + */ + template + void Process(WORLD_T& world) { + // --- TO FILL IN --- + // Code to process an instance of this event should go here. + // ------------------ + } +}; + +// Every event type needs to have its event specs defined. +const EventTypeDefinitionSpecs ExampleEvent::event_specs = EventTypeDefinitionSpecs{ + "example_event", + "Example of a minimally defined event type", + {"list", "required", "fields", "for", "this", "event", "here"} +}; + +} \ No newline at end of file diff --git a/source/sgp_mode/events/event_types/TaskValueEvent.h b/source/sgp_mode/events/event_types/TaskValueEvent.h new file mode 100644 index 00000000..1d416a81 --- /dev/null +++ b/source/sgp_mode/events/event_types/TaskValueEvent.h @@ -0,0 +1,253 @@ +#pragma once +#ifndef TASK_VALUE_EVENT_H +#define TASK_VALUE_EVENT_H + +#include "Event.h" +#include "../../tasks/LogicTaskEnvironment.h" + +#include "../../../json/json.hpp" +#include "../../../json/json_utils.h" + +#include "emp/base/Ptr.hpp" +#include "emp/base/vector.hpp" +#include "emp/bits/Bits.hpp" +#include "emp/datastructs/set_utils.hpp" +#include "emp/datastructs/map_utils.hpp" +#include "emp/math/math.hpp" +#include "emp/tools/string_utils.hpp" + +#include +#include +#include +#include +#include +#include + +namespace sgpmode { + +/** + * Purpose: Definition of a task_value event type. + * A task_value event modifies the value of a task. + * Event configuration options: + * - action: How should this event modify task values? + * - Options: + * - change: set the value of this task to the given value + * - add: add the given value to this task's current value + * - mult: multiply the task's current value by the given value + * - task_name: Which task(s) should be affected? Can be given as + * either a string indicating a single task or a list of + * tasks. All tasks must be valid. + * - value: Value used to update task value according to event action. + * - timing: Event timing. + * - group: Optional. Should this event affect sym, host, or shared tasks? + */ +class TaskValueEvent : public Event { +public: + + /** + * Purpose: Event specification object shared by all instances of this event + * type. + */ + static const EventTypeDefinitionSpecs event_specs; + + /** + * Purpose: Defines valid actions for a task value event. + */ + enum class ACTION_TYPE { CHANGE, ADD, MULT }; + + /** + * Purpose: Defines valid task groups that can be affected by task value event. + */ + enum class TASK_GROUP { SHARED, HOST, SYM }; + + /** + * Purpose: Mapping from string to action type. Shared by all instances of this + * class. + */ + static const std::unordered_map valid_action_types; + + /** + * Purpose: Mapping from string to task group. Shared by all instances of this + * class. + */ + static const std::unordered_map valid_task_groups; + + using json_t = nlohmann::json; + using action_t = ACTION_TYPE; + using task_group_t = TASK_GROUP; + + /** + * Purpose: Required LoadEventFromJSON function. Configures this event based on + * given json object. + * + * Input: json object to load from, reference to world. + * + * Output: Pointer to a new TaskValueEvent created by this function. + * + */ + template + static emp::Ptr LoadEventFromJSON( + json_t& event_json, + WORLD_T& world + ) { + // This should be a task_value event + emp_assert(event_json["event_type"] == event_specs.event_type); + // NOTE: Caller is responsible for event deletion. + emp::Ptr event = emp::NewPtr(); + + // Get event parameters out of json + + // --- Extract task name(s) --- + // If multiple tasks are given as an array, accept as vector. + // otherwise, wrap single given task into vector. + emp::vector& task_names = event->task_names; + if (event_json["task_name"].is_array()) { + task_names = event_json["task_name"]; + } else { + const std::string task_name = event_json["task_name"]; + task_names = {task_name}; + } + + // Convert task names to task_ids as known by the task set. + const auto& world_task_set = world.GetTaskEnv().GetTaskSet(); + emp::vector& task_ids = event->task_ids; + task_ids.resize(task_names.size()); + for (size_t task_i = 0; task_i < task_names.size(); ++task_i) { + auto& task_name = task_names[task_i]; + emp_assert(world_task_set.HasTask(task_name)); + task_ids[task_i] = world_task_set.GetID(task_name); + } + + // --- Set the action --- + const std::string action_str(event_json["action"]); + emp_assert(emp::Has(valid_action_types, action_str)); + event->action = valid_action_types.at(action_str); + + // --- Set the task value --- + event->value = sym_json::GetVal(event_json, "value"); + + // --- Set the timing --- + SetEventTimingFromJSON(event_json, event); + + // --- Task group --- + // Is there a group specification? If not, assume shared. + const std::string group_str = (event_json.contains("group")) ? event_json["group"] : "shared"; + emp_assert(emp::Has(valid_task_groups, group_str)); + event->task_group = valid_task_groups.at(group_str); + + return event; + } + +protected: + + /** + * Purpose: What task value action does this event apply? + */ + action_t action; + + /** + * Purpose: List of task ids that this event applies to + */ + emp::vector task_ids; + + /** + * Purpose: List of task names that this event applies to. Corresponds to task_ids. + * Redundant information (with task_ids) that is kept for debugging asserts. + */ + emp::vector task_names; + + /** + * Purpose: What task group does this event apply to? Sym / host / shared? + */ + task_group_t task_group; + + /** + * Purpose: Event value to be applied to task value + */ + double value; + + /** + * Purpose: Internal function that applies the correct action to the given task + * information struct. + * + * Input: Reference to the task information struct to be modified. + * + * Output: None. + */ + void ApplyAction(tasks::LogicTaskEnvironment::TaskReqInfo& task_req) { + switch (action) { + case action_t::ADD: + task_req.task_value += value; + break; + case action_t::MULT: + task_req.task_value *= value; + break; + case action_t::CHANGE: + task_req.task_value = value; + break; + } + } + +public: + + /** + * Purpose: TaskValueEvent constructor. Sets the base class's event type + * based on event specs. + */ + TaskValueEvent() { + // Set event type in base class. + event_type = event_specs.event_type; + } + + /** + * Purpose: Process this TaskValueEvent. + * + * Input: Reference to the world. + * + * Output: None. + */ + template + void Process(WORLD_T& world) { + // For each task_id, apply action + auto& world_task_env = world.GetTaskEnv(); + for (size_t task_id : task_ids) { + switch(task_group) { + case task_group_t::SHARED: + emp_assert(world_task_env.IsHostTask(task_id)); + emp_assert(world_task_env.IsSymTask(task_id)); + ApplyAction(world_task_env.GetHostTaskReq(task_id)); + ApplyAction(world_task_env.GetSymTaskReq(task_id)); + break; + case task_group_t::HOST: + ApplyAction(world_task_env.GetHostTaskReq(task_id)); + break; + case task_group_t::SYM: + ApplyAction(world_task_env.GetSymTaskReq(task_id)); + break; + } + } + } + +}; + +const EventTypeDefinitionSpecs TaskValueEvent::event_specs = EventTypeDefinitionSpecs{ + "task_value", + "Modify the value on a current task", + {"action", "task_name", "value", "timing"} +}; + +const std::unordered_map TaskValueEvent::valid_action_types = { + {"change", ACTION_TYPE::CHANGE}, + {"add", ACTION_TYPE::ADD}, + {"mult", ACTION_TYPE::MULT} +}; + +const std::unordered_map TaskValueEvent::valid_task_groups = { + {"shared", TASK_GROUP::SHARED}, + {"host", TASK_GROUP::HOST}, + {"symbiont", TASK_GROUP::SYM} +}; + +} + +#endif \ No newline at end of file diff --git a/source/sgp_mode/events/readme.md b/source/sgp_mode/events/readme.md new file mode 100644 index 00000000..f0ae8c68 --- /dev/null +++ b/source/sgp_mode/events/readme.md @@ -0,0 +1,23 @@ +# Events system notes + +## Organization + +Two layers of organization: + +1. Event types - Classes or types of events (e.g., change task value, add new task, volcano, tsunami, acid rain, etc). An event type is specified by an instance of the EventTypeDefinition class. +2. Events - Individual instances of a particular type of event. Each of these events corresponds to an event included in the events file that needs to be triggered during the run. + +### EventTypeLibrary + +The EventTypeLibrary manages EventTypeDefinitions. +Every event type is specified by an instance of EventTypeDefinition. +The EventTypeLibrary includes definitions for default event types and allows for runtime specification of additional event types. + +### EventManager + +The EventManager owns an EventTypeLibrary to keep track of valid event types. +The EventManager is responsible for loading a user-defined events file. Each event type in the events file must match a valid event type in the EventTypeLibrary. +For each event in the events file, the EventManager creates an Event object. +The event manager handles triggering events each update. + +The world object owns an event manager, and calls the process events function in the event manager each update. diff --git a/source/sgp_mode/hardware/GenomeLibrary.h b/source/sgp_mode/hardware/GenomeLibrary.h index b262760a..240a5a89 100644 --- a/source/sgp_mode/hardware/GenomeLibrary.h +++ b/source/sgp_mode/hardware/GenomeLibrary.h @@ -51,7 +51,7 @@ namespace lib_info { {"Swap", 2}, {"Add", 3}, {"Subtract", 3}, {"Nand", 3}, {"Reproduce", 0}, {"PrivateIO", 1}, {"SharedIO", 1}, {"Donate", 0}, {"Reuptake", 1}, {"Steal", 0}, {"Infect", 0}, {"DynamicInst", 3}, - {"SenseTask", 2}, + {"SenseTask", 2}, {"IO", 1} }; } diff --git a/source/sgp_mode/tasks/LogicTaskEnvironment.h b/source/sgp_mode/tasks/LogicTaskEnvironment.h index d5add786..3492de17 100644 --- a/source/sgp_mode/tasks/LogicTaskEnvironment.h +++ b/source/sgp_mode/tasks/LogicTaskEnvironment.h @@ -4,6 +4,7 @@ #include "LogicTaskIOBank.h" #include "../../json/json.hpp" +#include "../../json/json_utils.h" #include "emp/base/vector.hpp" #include "emp/bits/Bits.hpp" @@ -65,21 +66,11 @@ class LogicTaskEnvironment { // TODO - track task performance? // TODO - move this into util file in json directory - template - RET_TYPE GetVal( - json_t& json, - const std::string& field, - RET_TYPE default_val - ) { - return (json.contains(field)) ? - static_cast(json[field]) : - default_val; - } void SetTaskReqInfo(TaskReqInfo& info, json_t& task_cfg_json) { - info.task_value = GetVal(task_cfg_json, "value", 1); - info.max_repeats = GetVal(task_cfg_json, "max_repeats", std::numeric_limits::max()); - const std::string reward_mode = GetVal(task_cfg_json, "reward_mode", "add"); + info.task_value = sym_json::GetVal(task_cfg_json, "value", 1); + info.max_repeats = sym_json::GetVal(task_cfg_json, "max_repeats", std::numeric_limits::max()); + const std::string reward_mode = sym_json::GetVal(task_cfg_json, "reward_mode", "add"); emp_assert(emp::Has(this_t::predefined_reward_functions, reward_mode)); info.fun_calc_task_val = this_t::predefined_reward_functions.at(reward_mode); } diff --git a/source/test/default_mode_test/CureHosts.test.cc b/source/test/default_mode_test/CureHosts.test.cc index 1c7f9567..8b7c8d2c 100644 --- a/source/test/default_mode_test/CureHosts.test.cc +++ b/source/test/default_mode_test/CureHosts.test.cc @@ -30,7 +30,7 @@ TEST_CASE("Cure Hosts tests", "[default]") { sym_vect.emplace_back(sym); } - WHEN("Hosts are not cured") { + WHEN("Hosts are not cured"){ // Hosts and Sym world pop == pop_size REQUIRE(world.GetPop().size() == (size_t)pop_size); REQUIRE(world.GetSymPop().size() == (size_t)pop_size); @@ -97,12 +97,12 @@ TEST_CASE("Cure Hosts tests", "[default]") { config.CURE_UPDATES(num_updates); config.UPDATES(total_updates); - for (int j = 0; j < total_updates; j++) { + for(int j = 0; j < total_updates; j++) { if (config.CURE() && j == num_updates) { world.CureHosts(); } if (j > num_updates) { // syms dead, hosts don't have pointers to syms - for (int i = 0; i < pop_size; i++) { + for (int i = 0; i < pop_size; i++){ REQUIRE(host_vect[i]->HasSym() == false); } } diff --git a/source/test/pgg_mode_test/PGGSymbiont.test.cc b/source/test/pgg_mode_test/PGGSymbiont.test.cc index 700c0068..3f488112 100644 --- a/source/test/pgg_mode_test/PGGSymbiont.test.cc +++ b/source/test/pgg_mode_test/PGGSymbiont.test.cc @@ -3,305 +3,306 @@ TEST_CASE("PGGSymbiont Constructor", "[pgg]") { - emp::Ptr random = emp::NewPtr(12); - SymConfigPGG config; - PGGWorld w(*random, &config); - PGGWorld * world = &w; + emp::Ptr random = emp::NewPtr(12); + SymConfigPGG config; + PGGWorld w(*random, &config); + PGGWorld * world = &w; - double donation = 1; + double donation = 1; - double int_val = 0.5; - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val,donation); - CHECK(symbiont->GetDonation() == donation); - CHECK(symbiont->GetAge() == 0); - CHECK(symbiont->GetPoints() == 0); + double int_val = 0.5; + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val,donation); + CHECK(symbiont->GetDonation() == donation); + CHECK(symbiont->GetAge() == 0); + CHECK(symbiont->GetPoints() == 0); - donation = 2; - emp::Ptr symbiont2 = emp::NewPtr(random, world, &config, int_val,donation); - CHECK(symbiont2->GetDonation() == 2); - CHECK(symbiont2->GetAge() == 0); - CHECK(symbiont2->GetPoints() == 0); + donation = 2; + emp::Ptr symbiont2 = emp::NewPtr(random, world, &config, int_val,donation); + CHECK(symbiont2->GetDonation() == 2); + CHECK(symbiont2->GetAge() == 0); + CHECK(symbiont2->GetPoints() == 0); - int_val = 2; - REQUIRE_THROWS(emp::NewPtr(random, world, &config, int_val) ); + int_val = 2; + REQUIRE_THROWS(emp::NewPtr(random, world, &config, int_val) ); - symbiont.Delete(); - symbiont2.Delete(); - random.Delete(); + symbiont.Delete(); + symbiont2.Delete(); + random.Delete(); } TEST_CASE("PGGmutate", "[pgg]") { - emp::Ptr random = emp::NewPtr(37); - SymConfigPGG config; - PGGWorld w(*random, &config); - PGGWorld * world = &w; + emp::Ptr random = emp::NewPtr(37); + SymConfigPGG config; + PGGWorld w(*random, &config); + PGGWorld * world = &w; - WHEN("Mutation rate is not zero") { - double int_val = 0; - double donation = 0.01; - config.MUTATION_SIZE(0.002); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val,donation); + WHEN("Mutation rate is not zero") { + double int_val = 0; + double donation = 0.01; + config.MUTATION_SIZE(0.002); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val,donation); - symbiont->Mutate(); + symbiont->Mutate(); - THEN("Mutation occurs and donation value changes, but stays within bounds") { - REQUIRE(symbiont->GetDonation() != donation); - REQUIRE(symbiont->GetDonation() <= 1); - REQUIRE(symbiont->GetDonation() >= 0); - } - symbiont.Delete(); + THEN("Mutation occurs and donation value changes, but stays within bounds") { + REQUIRE(symbiont->GetDonation() != donation); + REQUIRE(symbiont->GetDonation() <= 1); + REQUIRE(symbiont->GetDonation() >= 0); } - WHEN("Mutation rate is zero") { - double int_val = 1; - double donation = 0.1; - config.SYM_HORIZ_TRANS_RES(100.0); - config.HORIZ_TRANS(true); - config.MUTATION_RATE(0); - config.MUTATION_SIZE(0); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, donation); + symbiont.Delete(); + } + WHEN("Mutation rate is zero") { + double int_val = 1; + double donation = 0.1; + config.SYM_HORIZ_TRANS_RES(100.0); + config.HORIZ_TRANS(true); + config.MUTATION_RATE(0); + config.MUTATION_SIZE(0); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, donation); - symbiont->Mutate(); + symbiont->Mutate(); - THEN("Mutation does not occur and donation value does not change") { - REQUIRE(symbiont->GetDonation() == donation); - } - symbiont.Delete(); + THEN("Mutation does not occur and donation value does not change") { + REQUIRE(symbiont->GetDonation() == donation); } - random.Delete(); + symbiont.Delete(); + } + random.Delete(); } TEST_CASE("PGGSymbiont ProcessPool", "[pgg]"){ - emp::Ptr random = emp::NewPtr(15); - SymConfigPGG config; - PGGWorld world(*random, &config); - config.SYNERGY(5); - config.PGG_SYNERGY(1.1); - double host_int_val = 1; - double sym_int_val = 0; - double donation = 0.1; - - emp::Ptr symbiont = emp::NewPtr(random, &world, &config, sym_int_val,donation); - emp::Ptr host = emp::NewPtr(random, &world, &config, host_int_val); - host->AddSymbiont(symbiont); - - host->DistribResources(40); - - CHECK(symbiont->GetPoints() == 40.4); - CHECK(host->GetPoints() == 0); - - host.Delete(); - random.Delete(); + emp::Ptr random = emp::NewPtr(15); + SymConfigPGG config; + PGGWorld world(*random, &config); + config.SYNERGY(5); + config.PGG_SYNERGY(1.1); + double host_int_val = 1; + double sym_int_val = 0; + double donation = 0.1; + + emp::Ptr symbiont = emp::NewPtr(random, &world, &config, sym_int_val,donation); + emp::Ptr host = emp::NewPtr(random, &world, &config, host_int_val); + host->AddSymbiont(symbiont); + + host->DistribResources(40); + + CHECK(symbiont->GetPoints() == 40.4); + CHECK(host->GetPoints() == 0); + + host.Delete(); + random.Delete(); } TEST_CASE("PGGProcess", "[pgg]") { - emp::Ptr random = emp::NewPtr(9); - SymConfigPGG config; - PGGWorld w(*random, &config); - PGGWorld * world = &w; + emp::Ptr random = emp::NewPtr(9); + SymConfigPGG config; + config.SPATIAL_STRUCT_MODE("well-mixed"); + PGGWorld w(*random, &config); + PGGWorld * world = &w; - //add new test for free living sym not moving when it shouldnt - WHEN("Horizontal transmission is true and points is greater than sym_h_res") { - double int_val = 1; - double points = 0.0; - config.SYM_HORIZ_TRANS_RES(140.0); - config.HORIZ_TRANS(true); - config.MUTATION_SIZE(0); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); + //add new test for free living sym not moving when it shouldnt + WHEN("Horizontal transmission is true and points is greater than sym_h_res") { + double int_val = 1; + double points = 0.0; + config.SYM_HORIZ_TRANS_RES(140.0); + config.HORIZ_TRANS(true); + config.MUTATION_SIZE(0); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); - int add_points = 200; - symbiont->AddPoints(add_points); + int add_points = 200; + symbiont->AddPoints(add_points); - int location = 10; - symbiont->Process(location); + int location = 10; + symbiont->Process(location); - THEN("Points changes and is set to 0") { - int points_post_reproduction = 0; - REQUIRE(symbiont->GetPoints() == points_post_reproduction); - } - symbiont.Delete(); + THEN("Points changes and is set to 0") { + int points_post_reproduction = 0; + REQUIRE(symbiont->GetPoints() == points_post_reproduction); } + symbiont.Delete(); + } - WHEN("Horizontal transmission is true and points is less than sym_h_res") { - double int_val = 1; - double points = 0.0; - config.SYM_HORIZ_TRANS_RES(200.0); - config.HORIZ_TRANS(true); - config.MUTATION_SIZE(0.0); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); + WHEN("Horizontal transmission is true and points is less than sym_h_res") { + double int_val = 1; + double points = 0.0; + config.SYM_HORIZ_TRANS_RES(200.0); + config.HORIZ_TRANS(true); + config.MUTATION_SIZE(0.0); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); - int add_points = 50; - symbiont->AddPoints(add_points); + int add_points = 50; + symbiont->AddPoints(add_points); - int location = 10; - symbiont->Process(location); + int location = 10; + symbiont->Process(location); - THEN("Points does not change") { - int points_post_reproduction = 50; - REQUIRE(symbiont->GetPoints() == points_post_reproduction); - } - symbiont.Delete(); + THEN("Points does not change") { + int points_post_reproduction = 50; + REQUIRE(symbiont->GetPoints() == points_post_reproduction); } + symbiont.Delete(); + } - WHEN("Horizontal transmission is false and points and points is greater then sym_h_res") { - double int_val = 1; - double points = 100.0; - config.SYM_HORIZ_TRANS_RES(80.0); - config.HORIZ_TRANS(false); - config.MUTATION_SIZE(0.0); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); + WHEN("Horizontal transmission is false and points and points is greater then sym_h_res") { + double int_val = 1; + double points = 100.0; + config.SYM_HORIZ_TRANS_RES(80.0); + config.HORIZ_TRANS(false); + config.MUTATION_SIZE(0.0); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); - int location = 10; - symbiont->Process(location); + int location = 10; + symbiont->Process(location); - THEN("Points does not change") { - int points_post_reproduction = 100; - REQUIRE(symbiont->GetPoints() == points_post_reproduction); - } - symbiont.Delete(); + THEN("Points does not change") { + int points_post_reproduction = 100; + REQUIRE(symbiont->GetPoints() == points_post_reproduction); } + symbiont.Delete(); + } - WHEN("Horizontal transmission is false and points and points is less then sym_h_res") { - double int_val = 1; - double points = 40.0; - config.SYM_HORIZ_TRANS_RES(80.0); - config.HORIZ_TRANS(false); - config.MUTATION_SIZE(0.0); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); + WHEN("Horizontal transmission is false and points and points is less then sym_h_res") { + double int_val = 1; + double points = 40.0; + config.SYM_HORIZ_TRANS_RES(80.0); + config.HORIZ_TRANS(false); + config.MUTATION_SIZE(0.0); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, int_val, 0,points); - int location = 10; - symbiont->Process(location); + int location = 10; + symbiont->Process(location); - THEN("Points does not change") { - int points_post_reproduction = 40; - REQUIRE(symbiont->GetPoints() == points_post_reproduction); - } - symbiont.Delete(); + THEN("Points does not change") { + int points_post_reproduction = 40; + REQUIRE(symbiont->GetPoints() == points_post_reproduction); } - random.Delete(); + symbiont.Delete(); + } + random.Delete(); } TEST_CASE("PGGSymbiont ProcessResources", "[pgg]"){ - emp::Ptr random = emp::NewPtr(18); - SymConfigPGG config; - PGGWorld w(*random, &config); - PGGWorld * world = &w; - config.SYNERGY(5); - + emp::Ptr random = emp::NewPtr(18); + SymConfigPGG config; + PGGWorld w(*random, &config); + PGGWorld * world = &w; + config.SYNERGY(5); - WHEN("sym_int_val < 0"){ - double sym_int_val = -0.6; + WHEN("sym_int_val < 0"){ + double sym_int_val = -0.6; - WHEN("host_int_val > 0"){ - double host_int_val = 0.2; - emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); - host->AddSymbiont(symbiont); - double expected_sym_points = 68; // hostDonation + stolen - double expected_return = 0; // hostportion * synergy + WHEN("host_int_val > 0"){ + double host_int_val = 0.2; + emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); + host->AddSymbiont(symbiont); - host->SetResInProcess(80); + double expected_sym_points = 68; // hostDonation + stolen + double expected_return = 0; // hostportion * synergy - THEN("sym receives a donation and stolen resources, host receives betrayal"){ - REQUIRE(symbiont->ProcessResources(20) == expected_return); - REQUIRE(symbiont->GetPoints() == expected_sym_points); + host->SetResInProcess(80); - } - host.Delete(); - } + THEN("sym receives a donation and stolen resources, host receives betrayal"){ + REQUIRE(symbiont->ProcessResources(20) == expected_return); + REQUIRE(symbiont->GetPoints() == expected_sym_points); - WHEN("host_int_val < 0 and resources are placed into defense"){ + } + host.Delete(); + } - WHEN("host successfully defends from symsteal"){ - double host_int_val = -0.8; - emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); - host->AddSymbiont(symbiont); + WHEN("host_int_val < 0 and resources are placed into defense"){ - double expected_sym_points = 0; // hostDonation + stolen - double expected_return = 0; // hostportion * synergy + WHEN("host successfully defends from symsteal"){ + double host_int_val = -0.8; + emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); + host->AddSymbiont(symbiont); - host->SetResInProcess(20); - THEN("symbiont is unsuccessful at stealing"){ - REQUIRE(symbiont->ProcessResources(0) == expected_return); - REQUIRE(symbiont->GetPoints() == expected_sym_points); - } - host.Delete(); - } + double expected_sym_points = 0; // hostDonation + stolen + double expected_return = 0; // hostportion * synergy - WHEN("host fails at defense"){ - double host_int_val = -0.5; - emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); - host->AddSymbiont(symbiont); + host->SetResInProcess(20); + THEN("symbiont is unsuccessful at stealing"){ + REQUIRE(symbiont->ProcessResources(0) == expected_return); + REQUIRE(symbiont->GetPoints() == expected_sym_points); + } + host.Delete(); + } - double expected_sym_points = 5; // hostDonation + stolen - double expected_return = 0; // hostportion * synergy + WHEN("host fails at defense"){ + double host_int_val = -0.5; + emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); + host->AddSymbiont(symbiont); - host->SetResInProcess(50); + double expected_sym_points = 5; // hostDonation + stolen + double expected_return = 0; // hostportion * synergy - THEN("Sym steals successfully"){ - REQUIRE(symbiont->ProcessResources(0) == expected_return); - REQUIRE(symbiont->GetPoints() == Approx(expected_sym_points)); - } - host.Delete(); - } + host->SetResInProcess(50); + THEN("Sym steals successfully"){ + REQUIRE(symbiont->ProcessResources(0) == expected_return); + REQUIRE(symbiont->GetPoints() == Approx(expected_sym_points)); } + host.Delete(); + } } - WHEN("sym_int_val > 0") { - double sym_int_val = 0.2; - double host_int_val = 0.5; - emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); - emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); - host->AddSymbiont(symbiont); + } - double expected_sym_points = 40; // hostDonation - hostPortion - double expected_return = 50; // hostPortion * synergy + WHEN("sym_int_val > 0") { + double sym_int_val = 0.2; + double host_int_val = 0.5; + emp::Ptr host = emp::NewPtr(random, world, &config, host_int_val); + emp::Ptr symbiont = emp::NewPtr(random, world, &config, sym_int_val); + host->AddSymbiont(symbiont); - host->SetResInProcess(50); + double expected_sym_points = 40; // hostDonation - hostPortion + double expected_return = 50; // hostPortion * synergy + host->SetResInProcess(50); - THEN("Sym attempts to give benefit back"){ - REQUIRE(symbiont->ProcessResources(50) == expected_return); - REQUIRE(symbiont->GetPoints() == expected_sym_points); - } - host.Delete(); + + THEN("Sym attempts to give benefit back"){ + REQUIRE(symbiont->ProcessResources(50) == expected_return); + REQUIRE(symbiont->GetPoints() == expected_sym_points); } - random.Delete(); + host.Delete(); + } + random.Delete(); } TEST_CASE("PGGSymbiont MakeNew", "[pgg]"){ - emp::Ptr random = emp::NewPtr(3); - SymConfigPGG config; - PGGWorld world(*random, &config); - - double host_int_val = 0.2; - emp::Ptr symbiont1 = emp::NewPtr(random, &world, &config, host_int_val); - emp::Ptr symbiont2 = symbiont1->MakeNew(); - THEN("The new symbiont has properties of the original symbiont and has 0 points and 0 age"){ - REQUIRE(symbiont1->GetIntVal() == symbiont2->GetIntVal()); - REQUIRE(symbiont1->GetInfectionChance() == symbiont2->GetInfectionChance()); - REQUIRE(symbiont1->GetDonation() == symbiont2->GetDonation()); - REQUIRE(symbiont2->GetPoints() == 0); - REQUIRE(symbiont2->GetAge() == 0); - //check that the offspring is the correct class - REQUIRE(symbiont2->GetName() == "PGGSymbiont"); - } - symbiont1.Delete(); - symbiont2.Delete(); - random.Delete(); + emp::Ptr random = emp::NewPtr(3); + SymConfigPGG config; + PGGWorld world(*random, &config); + + double host_int_val = 0.2; + emp::Ptr symbiont1 = emp::NewPtr(random, &world, &config, host_int_val); + emp::Ptr symbiont2 = symbiont1->MakeNew(); + THEN("The new symbiont has properties of the original symbiont and has 0 points and 0 age"){ + REQUIRE(symbiont1->GetIntVal() == symbiont2->GetIntVal()); + REQUIRE(symbiont1->GetInfectionChance() == symbiont2->GetInfectionChance()); + REQUIRE(symbiont1->GetDonation() == symbiont2->GetDonation()); + REQUIRE(symbiont2->GetPoints() == 0); + REQUIRE(symbiont2->GetAge() == 0); + //check that the offspring is the correct class + REQUIRE(symbiont2->GetName() == "PGGSymbiont"); + } + symbiont1.Delete(); + symbiont2.Delete(); + random.Delete(); } diff --git a/source/test/sgp_mode_test/functional_tests/HealthMode.test.cc b/source/test/sgp_mode_test/functional_tests/HealthMode.test.cc index 94b69e08..c6c31dab 100644 --- a/source/test/sgp_mode_test/functional_tests/HealthMode.test.cc +++ b/source/test/sgp_mode_test/functional_tests/HealthMode.test.cc @@ -45,9 +45,9 @@ void ConfigureHealthTestConfig(sgpmode::SymConfigSGP& config) { config.WORLD_WIDTH(2); config.WORLD_HEIGHT(2); config.SPATIAL_STRUCT_MODE("well-mixed"); - // general sgp settings config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); config.CYCLES_PER_UPDATE(4); @@ -647,6 +647,7 @@ TEST_CASE("Health hosts evolve", "[sgp][sgp-functional][health-mode-evolution]") config.WORLD_HEIGHT(100); config.HOST_REPRO_RES(20); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); config.CYCLES_PER_UPDATE(4); diff --git a/source/test/sgp_mode_test/functional_tests/NutrientMode.test.cc b/source/test/sgp_mode_test/functional_tests/NutrientMode.test.cc index c129b0ef..4eec61b3 100644 --- a/source/test/sgp_mode_test/functional_tests/NutrientMode.test.cc +++ b/source/test/sgp_mode_test/functional_tests/NutrientMode.test.cc @@ -26,6 +26,7 @@ TEST_CASE("SGPSymbiont Normal Nutrient without multiplier", "[sgp][sgp-functiona config.NUTRIENT_DONATE_PROP(0.5); config.NUTRIENT_STEAL_PROP(0.5); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); config.NUTRIENT_INTERACTION_MULTIPLIER(1.0); // for testing, set multiplier to 1 so we can directly compare expected transfer to actual transfer test_utils::SetWellMixed(config, 1, 0); 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..3df02c74 100644 --- a/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc +++ b/source/test/sgp_mode_test/functional_tests/PopulationStructure.test.cc @@ -38,6 +38,7 @@ TEST_CASE( "Spatial structure grid mode (sgp mode)", "[sgp][spatial-structure]" config.WORLD_HEIGHT(height); config.TASK_IO_BANK_SIZE(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FREE_LIVING_SYMS(1); config.MOVE_FREE_SYMS(1); config.SYM_HORIZ_TRANS_RES(0); @@ -277,6 +278,7 @@ TEST_CASE("Spatial structure loaded from files (sgp mode)", "[sgp][spatial-struc config.SYM_HORIZ_TRANS_RES(0); config.TASK_IO_BANK_SIZE(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); size_t sym_limit = 10; config.SYM_LIMIT(sym_limit); config.INIT_POP_SIZE(0); @@ -344,6 +346,7 @@ TEST_CASE("World uses custom spatial structure (sgp mode)", "[sgp][spatial-struc config.SYM_LIMIT(sym_limit); config.TASK_IO_BANK_SIZE(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.SPATIAL_STRUCT_MODE("load"); config.SPATIAL_STRUCT_LOAD_MODE("edges"); config.SPATIAL_STRUCT_CFG_PATH("source/test/data/chain-edges.csv"); @@ -476,6 +479,7 @@ TEST_CASE("Organism in isolated position neither reproduces nor receives offspri config.START_MOI(0); config.TASK_IO_BANK_SIZE(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); diff --git a/source/test/sgp_mode_test/functional_tests/ProgramBuilder.test.cc b/source/test/sgp_mode_test/functional_tests/ProgramBuilder.test.cc index 86a3efbb..550c8d2e 100644 --- a/source/test/sgp_mode_test/functional_tests/ProgramBuilder.test.cc +++ b/source/test/sgp_mode_test/functional_tests/ProgramBuilder.test.cc @@ -45,6 +45,7 @@ TEST_CASE("ProgramBuilder generates a programs as advertised", "[sgp]") { config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("ProgramBuilder_test_output"); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); diff --git a/source/test/sgp_mode_test/functional_tests/SGPHardware_Tasks.test.cc b/source/test/sgp_mode_test/functional_tests/SGPHardware_Tasks.test.cc index 6d89635e..19c29039 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPHardware_Tasks.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPHardware_Tasks.test.cc @@ -28,6 +28,7 @@ TEST_CASE("Ancestor hardware can attempt reproduction and do NOT", "[sgp]") { config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPHardware_test_output"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 1); diff --git a/source/test/sgp_mode_test/functional_tests/SGPHost_Reproduce.test.cc b/source/test/sgp_mode_test/functional_tests/SGPHost_Reproduce.test.cc index 305d4752..b280e15b 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPHost_Reproduce.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPHost_Reproduce.test.cc @@ -34,6 +34,7 @@ TEST_CASE("Reproduction without points or mutations", "[sgp][sgp-functional]") { config.SEED(62); config.START_MOI(0); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -84,6 +85,7 @@ TEST_CASE("Mutations occur during reproduction", "[sgp]") { config.SGP_MUT_PER_BIT_RATE(1.0); config.HOST_REPRO_RES(0); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -119,6 +121,7 @@ TEST_CASE("SGPHost Reproduce function results in correct parental task tracking" config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPHost_test_output"); test_utils::SetWellMixed(config, 1, 1); config.START_MOI(0); @@ -196,6 +199,7 @@ TEST_CASE("SGPHost lineage tracking test", "[sgp]") { config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPHost_test_output"); test_utils::SetWellMixed(config, 1, 1); config.TASK_IO_BANK_SIZE(10); diff --git a/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont.test.cc b/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont.test.cc index 7c07ed80..5ce489bb 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont.test.cc @@ -30,6 +30,7 @@ TEST_CASE("Host Process allows symbionts to process", "[sgp][sgp-functional]") { sgpmode::SymConfigSGP config; config.CYCLES_PER_UPDATE(8); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 1, 1); world_t world(random, &config); diff --git a/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont_Tasks.test.cc b/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont_Tasks.test.cc index 91a503c9..4b7a3086 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont_Tasks.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPHost_SGPSymbiont_Tasks.test.cc @@ -37,6 +37,7 @@ TEST_CASE("Only first task credit for hosts vs. symbionts","[sgp]"){ config.TASK_PROFILE_MODE("self-all"); config.VT_TASK_MATCH(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.CYCLES_PER_UPDATE(52); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 1, 1); diff --git a/source/test/sgp_mode_test/functional_tests/SGPHost_Tasks.test.cc b/source/test/sgp_mode_test/functional_tests/SGPHost_Tasks.test.cc index 0e1250dc..6ac0a726 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPHost_Tasks.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPHost_Tasks.test.cc @@ -29,6 +29,7 @@ TEST_CASE("Host Task Credit", "[sgp]") { test_utils::SetWellMixed(config, 1, 1); config.SYM_LIMIT(2); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); diff --git a/source/test/sgp_mode_test/functional_tests/SGPSymbiont_Reproduce.test.cc b/source/test/sgp_mode_test/functional_tests/SGPSymbiont_Reproduce.test.cc index 5666092c..7147651b 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPSymbiont_Reproduce.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPSymbiont_Reproduce.test.cc @@ -29,6 +29,7 @@ TEST_CASE("SGPSymbiont Reproduce", "[sgp][sgp-functional]") { size_t not_id = 1; sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_MODE("parent-all"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 1, 1); @@ -111,6 +112,7 @@ TEST_CASE("SGPSymbiont Vertical Transmission", "[sgp][sgp-functional]"){ config.DATA_INT(26); config.VERTICAL_TRANSMISSION(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 100, 0); GIVEN("A host infected with a symbiont in the world when self-all task mode and task match required for vt"){ @@ -250,6 +252,7 @@ TEST_CASE("SGPSymbiont Vertical Transmission off", "[sgp][sgp-functional]"){ emp::Random random(51); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.DATA_INT(26); config.HOST_REPRO_RES(0); config.SYM_VERT_TRANS_RES(0); @@ -296,6 +299,7 @@ TEST_CASE("SGPSymbiont Horizontal Transmission", "[sgp][sgp-functional]"){ config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("task-profile-compatible"); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.OUSTING(true); // This way, if the offspring happens to try to infect parent's host, the tests still pass because successful horizontal transmission. GIVEN("Two hosts one of which is infected with a symbiont, horiz task match on and self-all task profile mode"){ @@ -532,6 +536,7 @@ TEST_CASE("SGPSymbiont Reproduce tracks lineage task information", "[sgp][sgp-fu config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPSymbiont_test_output"); config.START_MOI(1); // Initialize host with a symbiont config.TASK_IO_UNIQUE_OUTPUT(true); diff --git a/source/test/sgp_mode_test/functional_tests/SGPWorld.test.cc b/source/test/sgp_mode_test/functional_tests/SGPWorld.test.cc index f660d149..baedf909 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPWorld.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPWorld.test.cc @@ -28,6 +28,7 @@ TEST_CASE("A world containing a single infected host and its symbiont is updated test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); sgpmode::SGPWorld world(random, &config); world.Setup(); @@ -64,6 +65,7 @@ TEST_CASE("A world containing a single uninfected host is updated correctly", "[ test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); sgpmode::SGPWorld world(random, &config); world.Setup(); diff --git a/source/test/sgp_mode_test/functional_tests/SGPWorldData_SGPWorld.test.cc b/source/test/sgp_mode_test/functional_tests/SGPWorldData_SGPWorld.test.cc index 6dee95b1..99e91d53 100644 --- a/source/test/sgp_mode_test/functional_tests/SGPWorldData_SGPWorld.test.cc +++ b/source/test/sgp_mode_test/functional_tests/SGPWorldData_SGPWorld.test.cc @@ -52,6 +52,7 @@ TEST_CASE("Correct data files are created and written to", "[sgp][sgp-functional size_t prog_length = 20; config.CYCLES_PER_UPDATE(prog_length); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPData_test_output"); emp::Random random(config.SEED()); diff --git a/source/test/sgp_mode_test/functional_tests/StressMode.test.cc b/source/test/sgp_mode_test/functional_tests/StressMode.test.cc index f8a6d10a..9e087f72 100644 --- a/source/test/sgp_mode_test/functional_tests/StressMode.test.cc +++ b/source/test/sgp_mode_test/functional_tests/StressMode.test.cc @@ -24,6 +24,7 @@ TEST_CASE("Stress event", "[sgp]") { config.SYM_VERT_TRANS_RES(1000); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPStressMode_test_output"); test_utils::SetWellMixed(config, 100, 100); config.TASK_IO_BANK_SIZE(10); @@ -130,6 +131,7 @@ TEST_CASE("Stress hosts evolve", "[sgp][sgp-functional]") { config.HOST_REPRO_RES(20); config.BASE_DEATH_CHANCE(0); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); config.CYCLES_PER_UPDATE(4); diff --git a/source/test/sgp_mode_test/functional_tests/TaskValueEvent.test.cc b/source/test/sgp_mode_test/functional_tests/TaskValueEvent.test.cc new file mode 100644 index 00000000..db6af55d --- /dev/null +++ b/source/test/sgp_mode_test/functional_tests/TaskValueEvent.test.cc @@ -0,0 +1,437 @@ +#include "emp/math/Random.hpp" + +#include "../../test_utils.h" + +#include "../../../default_mode/SymWorld.h" +#include "../../../default_mode/WorldSetup.cc" +#include "../../../default_mode/DataNodes.h" +#include "../../../sgp_mode/SGPWorld.h" +#include "../../../sgp_mode/SGPWorld.cc" +#include "../../../sgp_mode/SGPWorldSetup.cc" +#include "../../../sgp_mode/SGPWorldData.cc" +#include "../../../sgp_mode/SGPW_InteractionMechanismSetup.cc" +#include "../../../sgp_mode/SGPW_TaskProfileSetup.cc" +#include "../../../sgp_mode/ProgramBuilder.h" +#include "../../../sgp_mode/events/EventManager.h" + +#include "../../../catch/catch.hpp" + +using world_t = sgpmode::SGPWorld; +using cpu_state_t = sgpmode::CPUState; +using hw_spec_t = sgpmode::SGPHardwareSpec; +using hardware_t = sgpmode::SGPHardware; +using program_t = typename world_t::sgp_prog_t; +using sgp_host_t = sgpmode::SGPHost; +using sgp_sym_t = sgpmode::SGPSymbiont; + + +TEST_CASE("TaskValueEvent One Time Events", "[sgp][events]") { + GIVEN("onetime-events.json") { + emp::Random random(2); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/onetime-events.json"); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + config.UPDATES(100); + config.SYM_ONLY_FIRST_TASK_CREDIT(1); + config.HOST_ONLY_FIRST_TASK_CREDIT(1); + config.INIT_POP_SIZE(1); + config.CYCLES_PER_UPDATE(52); + + config.HOST_REPRO_RES(10000); + config.SYM_HORIZ_TRANS_RES(10000); + config.SYM_VERT_TRANS_RES(10000); + + WHEN("One time add, mul, and change task-value events are loaded") { + world_t world(random, &config); + world.Setup(); + auto& builder = world.GetProgramBuilder(); + auto& event_manager = world.GetEventManager(); + REQUIRE(event_manager.GetOneTimeEvents().size() == 3); + REQUIRE(event_manager.GetRecurringEvents().size() == 0); + // Events are reverse sorted by update. + auto& one_time_events = event_manager.GetOneTimeEvents(); + REQUIRE(one_time_events[0]->GetEventType() == "task_value"); + REQUIRE(one_time_events[0]->GetStartUpdate() == 3); + REQUIRE(one_time_events[1]->GetEventType() == "task_value"); + REQUIRE(one_time_events[1]->GetStartUpdate() == 2); + REQUIRE(one_time_events[2]->GetEventType() == "task_value"); + REQUIRE(one_time_events[2]->GetStartUpdate() == 1); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + // create host with NAND operation + emp::Ptr host = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // create symbiont with NAND operation + emp::Ptr symbiont = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // add host and sym + world.AddOrgAt(host, 0); + host->AddSymbiont(symbiont); + + THEN("Events are processed") { + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + world.Update(); // Should trigger any update-0 events. (none) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(event_manager.GetOneTimeEvents().size() == 3); + REQUIRE(event_manager.GetRecurringEvents().size() == 0); + + world.Update(); // Should trigger any update-1 events. (+10) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 15); + REQUIRE(event_manager.GetOneTimeEvents().size() == 2); + REQUIRE(event_manager.GetRecurringEvents().size() == 0); + + world.Update(); // Should trigger any update-2 events. (*2) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 30); + REQUIRE(event_manager.GetOneTimeEvents().size() == 1); + REQUIRE(event_manager.GetRecurringEvents().size() == 0); + + world.Update(); // Should trigger any update-3 events. (=100) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 100); + REQUIRE(event_manager.GetOneTimeEvents().size() == 0); + REQUIRE(event_manager.GetRecurringEvents().size() == 0); + + world.Update(); // Should trigger any update-4 events. (none) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 100); + } + } + } +} + +TEST_CASE("TaskValueEvent Multiple Reoccuring Events", "[sgp][events]") { + GIVEN("reoccur-events.json") { + emp::Random random(11); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/reoccur-events.json"); + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + + WHEN("Reoccuring add and mul task-value events are loaded") { + world_t world(random, &config); + world.Setup(); + + auto& event_manager = world.GetEventManager(); + REQUIRE(event_manager.GetOneTimeEvents().size() == 0); + REQUIRE(event_manager.GetRecurringEvents().size() == 2); + // Events are reverse sorted by start update + auto& recurring_events = event_manager.GetRecurringEvents(); + REQUIRE(recurring_events[0]->GetEventType() == "task_value"); + REQUIRE(recurring_events[0]->GetStartUpdate() == 2); + REQUIRE(recurring_events[0]->GetEndUpdate() == 4); + REQUIRE(recurring_events[0]->GetFrequency() == 2); + REQUIRE(recurring_events[1]->GetEventType() == "task_value"); + REQUIRE(recurring_events[1]->GetStartUpdate() == 1); + REQUIRE(recurring_events[1]->GetEndUpdate() == 5); + REQUIRE(recurring_events[1]->GetFrequency() == 2); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + THEN("Events are processed") { + + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(event_manager.GetOneTimeEvents().size() == 0); + REQUIRE(event_manager.GetRecurringEvents().size() == 2); + + // +5 event triggers on updates 1, 3, 5 + // x2 event triggers on updates 2, 4 + + world.Update(); // Trigger any update-0 events (none) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 2); + + world.Update(); // Trigger any update-1 events (+5) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 10); + REQUIRE(recurring_events.size() == 2); + + world.Update(); // Trigger any update-2 events (x2) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 20); + REQUIRE(recurring_events.size() == 2); + + world.Update(); // Trigger any update-3 events (+5) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 25); + REQUIRE(recurring_events.size() == 2); + + world.Update(); // Trigger any update-4 events (x2) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 50); + REQUIRE(recurring_events.size() == 1); + + world.Update(); // Trigger any update-5 events (+5) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 55); + REQUIRE(recurring_events.size() == 0); + + world.Update(); // Trigger any update-6 events (none) + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 55); + REQUIRE(recurring_events.size() == 0); + } + } + } +} + + +TEST_CASE("TaskValueEvent Reoccuring Multiply Event", "[sgp][events]") { + GIVEN("multiply-reoccur-events.json") { + emp::Random random(7); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + config.SYM_ONLY_FIRST_TASK_CREDIT(1); + config.HOST_ONLY_FIRST_TASK_CREDIT(1); + config.INIT_POP_SIZE(1); + config.CYCLES_PER_UPDATE(52); + config.HOST_REPRO_RES(10000); + config.SYM_HORIZ_TRANS_RES(10000); + config.SYM_VERT_TRANS_RES(10000); + + WHEN("Reoccuring mul task-value events are loaded") { + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/multiply-reoccur-events.json"); + + world_t world(random, &config); + world.Setup(); + auto& builder = world.GetProgramBuilder(); + auto& event_manager = world.GetEventManager(); + auto& recurring_events = event_manager.GetRecurringEvents(); + // create host with NAND operation + emp::Ptr host = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // create symbiont with NAND operation + emp::Ptr symbiont = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // add host and sym + world.AddOrgAt(host, 0); + host->AddSymbiont(symbiont); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + THEN("Events are processed") { + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 1); + world.Update(); // Update 0 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 1); + world.Update(); // Update 1 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == -5); + REQUIRE(recurring_events.size() == 1); + world.Update(); // Update 2 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == -5); + REQUIRE(recurring_events.size() == 1); + world.Update(); // Update 3 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 0); + world.Update(); // Update 4 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + world.Update(); // Update 5 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + } + } + } +} + +TEST_CASE("TaskValueEvent Reoccuring Add Event", "[sgp][events]") { + GIVEN("add-reoccur-events.json") { + emp::Random random(44); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/add-reoccur-events.json"); + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + + WHEN("Reoccuring add task-value events are loaded") { + world_t world(random, &config); + world.Setup(); + auto& event_manager = world.GetEventManager(); + auto& recurring_events = event_manager.GetRecurringEvents(); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + THEN("Events are processed") { + // 1:2:1: 1, 2 + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 1); + + world.Update(); // Update 0 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(recurring_events.size() == 1); + + world.Update(); // Update 1 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 10); + REQUIRE(recurring_events.size() == 1); + + world.Update(); // Update 2 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 15); + REQUIRE(recurring_events.size() == 0); + + world.Update(); // Update 3 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 15); + REQUIRE(recurring_events.size() == 0); + } + } + } +} + +TEST_CASE("TaskValueEvent Reoccuring Change Event", "[sgp][events]") { + GIVEN("change-reoccur-events.json") { + emp::Random random(60); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/change-reoccur-events.json"); + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + + WHEN("Reoccuring change task-value event is loaded") { + world_t world(random, &config); + world.Setup(); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + THEN("Events are processed") { + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 5); + world.Update(); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 5); + world.Update(); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 25); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 25); + world.Update(); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 25); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 25); + world.Update(); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 25); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 25); + } + } + } +} + +TEST_CASE("TaskValueEvent Host/Sym Only Events", "[sgp][events]") { + GIVEN("hostsym-only-events.json") { + emp::Random random(19); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/hostsym-only-events.json"); + // Fill in any generic configs here + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + config.SYM_ONLY_FIRST_TASK_CREDIT(1); + config.HOST_ONLY_FIRST_TASK_CREDIT(1); + config.INIT_POP_SIZE(1); + config.CYCLES_PER_UPDATE(52); + config.HOST_REPRO_RES(10000); + config.SYM_HORIZ_TRANS_RES(10000); + config.SYM_VERT_TRANS_RES(10000); + + WHEN("Host only and sym only events are loaded") { + world_t world(random, &config); + world.Setup(); + auto& builder = world.GetProgramBuilder(); + // create host with NAND operation + emp::Ptr host = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // create symbiont with NAND operation + emp::Ptr symbiont = emp::NewPtr(&random, &world, &config, builder.CreateNandProgram(50)); + // add host and sym + world.AddOrgAt(host, 0); + host->AddSymbiont(symbiont); + + // get NAND Task id + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + + THEN("Events are processed") { + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 5); + + world.Update(); // Update 0 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 5); + + world.Update(); // Update 1 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 50); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 100); + + world.Update(); // Update 2 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 50); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 100); + } + } + } +} + + +TEST_CASE("TaskValueEvent Mulitple Task Names Events", "[sgp][events]") { + GIVEN("multiple-tasks-events.json") { + emp::Random random(1); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/task-value-events-cfg/multiple-tasks-events.json"); + config.UPDATES(100); + test_utils::SetWellMixed(config, 10, 0); + config.TASK_IO_BANK_SIZE(10); + + WHEN("Multiple tasks are in a single event") { + world_t world(random, &config); + world.Setup(); + // get task ids + const size_t nand_task_id = world.GetTaskEnv().GetTaskSet().GetID("NAND"); + const size_t not_task_id = world.GetTaskEnv().GetTaskSet().GetID("NOT"); + const size_t or_task_id = world.GetTaskEnv().GetTaskSet().GetID("OR"); + const size_t and_task_id = world.GetTaskEnv().GetTaskSet().GetID("AND"); + const size_t xor_task_id = world.GetTaskEnv().GetTaskSet().GetID("XOR"); + const size_t equ_task_id = world.GetTaskEnv().GetTaskSet().GetID("EQU"); + const size_t nor_task_id = world.GetTaskEnv().GetTaskSet().GetID("NOR"); + const size_t andnot_task_id = world.GetTaskEnv().GetTaskSet().GetID("AND_NOT"); + const size_t ornot_task_id = world.GetTaskEnv().GetTaskSet().GetID("OR_NOT"); + + THEN("Events are processed") { + world.Update(); // Update 0 events + // Check sym task values + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(not_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(and_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(or_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(xor_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nor_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(equ_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(andnot_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(ornot_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(not_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(and_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(or_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(xor_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nor_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(equ_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(andnot_task_id).task_value == 5); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(ornot_task_id).task_value == 5); + + world.Update(); // Update 1 events + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(not_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(and_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(or_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(xor_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(nor_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(equ_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(andnot_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetHostTaskReq(ornot_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nand_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(not_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(and_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(or_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(xor_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(nor_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(equ_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(andnot_task_id).task_value == 100); + REQUIRE(world.GetTaskEnv().GetSymTaskReq(ornot_task_id).task_value == 100); + } + } + } +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/functional_tests/SenseTask_Tasks.test.cc b/source/test/sgp_mode_test/need_updating/SenseTask_Tasks.test.cc similarity index 91% rename from source/test/sgp_mode_test/functional_tests/SenseTask_Tasks.test.cc rename to source/test/sgp_mode_test/need_updating/SenseTask_Tasks.test.cc index c536fd6a..2bfe59f3 100644 --- a/source/test/sgp_mode_test/functional_tests/SenseTask_Tasks.test.cc +++ b/source/test/sgp_mode_test/need_updating/SenseTask_Tasks.test.cc @@ -26,6 +26,7 @@ TEST_CASE("Test host SenseTask instruction after a rewarded task", "[sgp]"){ config.CYCLES_PER_UPDATE(0); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -57,10 +58,10 @@ TEST_CASE("Test host SenseTask instruction after a rewarded task", "[sgp]"){ host_hw.Reset(); host_hw.SetProgram(host_program); world.AssignNewEnvIO(host_hw.GetCPUState()); - - // NOT is currently not rewarded. + + // NOT is currently not rewarded. REQUIRE(world.GetTaskEnv().GetHostTaskReq(not_task_id).task_value < 0); - + // Initial register values host_hw.SetRegisters({3, 2, 5}); @@ -78,6 +79,7 @@ TEST_CASE("Test host SenseTask instruction after a punished task", "[sgp]"){ config.CYCLES_PER_UPDATE(0); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -110,17 +112,17 @@ TEST_CASE("Test host SenseTask instruction after a punished task", "[sgp]"){ host_hw.SetProgram(host_program); world.AssignNewEnvIO(host_hw.GetCPUState()); - // NAND is not currently punished. + // NAND is not currently punished. REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value > 0); - - + + // Initial register values host_hw.SetRegisters({7, 12, 9}); // Run host program host_hw.RunCPUStep(5); - + THEN("SenseTask puts a 1 into register 1"){ REQUIRE(host_hw.GetRegister(1) == 1); } @@ -132,6 +134,7 @@ TEST_CASE("Test symbiont SenseTask instruction after a punished task", "[sgp]"){ config.CYCLES_PER_UPDATE(0); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(1); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -161,15 +164,15 @@ TEST_CASE("Test symbiont SenseTask instruction after a punished task", "[sgp]"){ sym_hw.SetProgram(sym_program); world.AssignNewEnvIO(sym_hw.GetCPUState()); - // NOT is currently punished. + // NOT is currently punished. REQUIRE(world.GetTaskEnv().GetHostTaskReq(not_task_id).task_value < 0); - + // Initial register values sym_hw.SetRegisters({3, 2, 5}); // Run symbiont program - sym_hw.RunCPUStep(4); - + sym_hw.RunCPUStep(4); + THEN("SenseTask puts a 0 into register 1"){ REQUIRE(sym_hw.GetRegister(1) == 0); } @@ -181,6 +184,7 @@ TEST_CASE("Test symbiont SenseTask instruction after a rewarded task", "[sgp]"){ config.CYCLES_PER_UPDATE(0); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(1); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -211,15 +215,15 @@ TEST_CASE("Test symbiont SenseTask instruction after a rewarded task", "[sgp]"){ sym_hw.SetProgram(sym_program); world.AssignNewEnvIO(sym_hw.GetCPUState()); - // NAND is currently rewarded. + // NAND is currently rewarded. REQUIRE(world.GetTaskEnv().GetHostTaskReq(nand_task_id).task_value > 0); - + // Initial register values sym_hw.SetRegisters({7, 12, 9}); // run symbiont program sym_hw.RunCPUStep(5); - + THEN("SenseTask puts a 1 into register 1"){ REQUIRE(sym_hw.GetRegister(1) == 1); } diff --git a/source/test/sgp_mode_test/functional_tests/TempChangingEnvironments.test.cc b/source/test/sgp_mode_test/need_updating/TempChangingEnvironments.test.cc similarity index 100% rename from source/test/sgp_mode_test/functional_tests/TempChangingEnvironments.test.cc rename to source/test/sgp_mode_test/need_updating/TempChangingEnvironments.test.cc diff --git a/source/test/sgp_mode_test/no-events.json b/source/test/sgp_mode_test/no-events.json new file mode 100644 index 00000000..6cc4cc51 --- /dev/null +++ b/source/test/sgp_mode_test/no-events.json @@ -0,0 +1,3 @@ +{ + "events": [] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/add-reoccur-events.json b/source/test/sgp_mode_test/task-value-events-cfg/add-reoccur-events.json new file mode 100644 index 00000000..8187df25 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/add-reoccur-events.json @@ -0,0 +1,12 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "add", + "value": 5, + "timing": "1:2:1", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/change-reoccur-events.json b/source/test/sgp_mode_test/task-value-events-cfg/change-reoccur-events.json new file mode 100644 index 00000000..7ff09afe --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/change-reoccur-events.json @@ -0,0 +1,12 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "change", + "value": 25, + "timing": "1:2:1", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/hostsym-only-events.json b/source/test/sgp_mode_test/task-value-events-cfg/hostsym-only-events.json new file mode 100644 index 00000000..88a25452 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/hostsym-only-events.json @@ -0,0 +1,20 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "change", + "value": 50, + "timing": "1", + "group": "host" + }, + { + "event_type": "task_value", + "task_name": "NAND", + "action": "change", + "value": 100, + "timing": "1", + "group": "symbiont" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/multiple-tasks-events.json b/source/test/sgp_mode_test/task-value-events-cfg/multiple-tasks-events.json new file mode 100644 index 00000000..0a1596be --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/multiple-tasks-events.json @@ -0,0 +1,12 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": ["NAND", "NOT", "OR", "AND", "XOR", "EQU", "NOR", "AND_NOT", "OR_NOT"], + "action": "change", + "value": 100, + "timing": "1", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/multiply-reoccur-events.json b/source/test/sgp_mode_test/task-value-events-cfg/multiply-reoccur-events.json new file mode 100644 index 00000000..a2a24532 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/multiply-reoccur-events.json @@ -0,0 +1,12 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "mult", + "value": -1, + "timing": "1:3:2", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/onetime-events.json b/source/test/sgp_mode_test/task-value-events-cfg/onetime-events.json new file mode 100644 index 00000000..6c4ba771 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/onetime-events.json @@ -0,0 +1,28 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "add", + "value": 10, + "timing": "1", + "group": "shared" + }, + { + "event_type": "task_value", + "task_name": "NAND", + "action": "mult", + "value": 2, + "timing": "2", + "group": "shared" + }, + { + "event_type": "task_value", + "task_name": "NAND", + "action": "change", + "value": 100, + "timing": "3", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/reoccur-events.json b/source/test/sgp_mode_test/task-value-events-cfg/reoccur-events.json new file mode 100644 index 00000000..886c5b52 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/reoccur-events.json @@ -0,0 +1,20 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "add", + "value": 5, + "timing": "1:5:2", + "group": "shared" + }, + { + "event_type": "task_value", + "task_name": "NAND", + "action": "mult", + "value": 2, + "timing": "2:4:2", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/task-value-events-cfg/test-events.json b/source/test/sgp_mode_test/task-value-events-cfg/test-events.json new file mode 100644 index 00000000..bdce8810 --- /dev/null +++ b/source/test/sgp_mode_test/task-value-events-cfg/test-events.json @@ -0,0 +1,20 @@ +{ + "events": [ + { + "event_type": "task_value", + "task_name": "NAND", + "action": "add", + "value": 50, + "timing": "1", + "group": "shared" + }, + { + "event_type": "task_value", + "task_name": ["NAND"], + "action": "mult", + "value": -1, + "timing": "2:5:2", + "group": "shared" + } + ] +} \ No newline at end of file diff --git a/source/test/sgp_mode_test/unit_tests/Instructions.test.cc b/source/test/sgp_mode_test/unit_tests/Instructions.test.cc index be36a39c..196b78a5 100644 --- a/source/test/sgp_mode_test/unit_tests/Instructions.test.cc +++ b/source/test/sgp_mode_test/unit_tests/Instructions.test.cc @@ -66,6 +66,7 @@ TEST_CASE("Test non-interactive instructions", "[sgp]") { config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(0); config.TASK_IO_BANK_SIZE(10); @@ -492,6 +493,7 @@ TEST_CASE("Test host-symbiont interactive instructions", "[sgp]") { config.DONATION_STEAL_INST(true); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -689,6 +691,7 @@ TEST_CASE("Test freeliving symbiont instructions", "[sgp]") { config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("Instructions_test_output"); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); diff --git a/source/test/sgp_mode_test/unit_tests/SGPCureHosts.test.cc b/source/test/sgp_mode_test/unit_tests/SGPCureHosts.test.cc index e755f505..f0d03975 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPCureHosts.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPCureHosts.test.cc @@ -24,6 +24,7 @@ TEST_CASE("SGP Cure Hosts tests", "[sgp]") { // set up configs sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPCureHosts_test_output"); config.SEED(61); test_utils::SetWellMixed(config, 2, 2); diff --git a/source/test/sgp_mode_test/unit_tests/SGPHardware.test.cc b/source/test/sgp_mode_test/unit_tests/SGPHardware.test.cc index 869abbad..254c3e02 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPHardware.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPHardware.test.cc @@ -12,8 +12,8 @@ #include #include -TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ - using world_t = sgpmode::SGPWorld; +TEST_CASE("Test Printing Simple Instructions", "[sgp]") { + using world_t = sgpmode::SGPWorld; using cpu_state_t = sgpmode::CPUState; using hw_spec_t = sgpmode::SGPHardwareSpec; using hardware_t = sgpmode::SGPHardware; @@ -26,8 +26,9 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("hardware_test_output"); - config.POP_SIZE(1); + config.INIT_POP_SIZE(1); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -42,7 +43,7 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ std::ostringstream output; - WHEN("Program contains Nop Instruction"){ + WHEN("Program contains Nop Instruction") { program_t program; prog_builder.AddStartAnchor(program); prog_builder.AddInst(program, "Nop-0", 0); @@ -51,11 +52,11 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Nop Instruction should be printed"){ + THEN("Global Anchor and Nop Instruction should be printed") { REQUIRE(output.str() == "AA:\n nop-0 \n"); } } - WHEN("Program contains Increment Instruction"){ + WHEN("Program contains Increment Instruction") { program_t program; prog_builder.AddStartAnchor(program); prog_builder.AddInst(program, "Increment", 0); @@ -64,11 +65,11 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Increment Instruction should be printed"){ + THEN("Global Anchor and Increment Instruction should be printed") { REQUIRE(output.str() == "AA:\n increment r0\n"); } } - WHEN("Program contains Decrement Instruction"){ + WHEN("Program contains Decrement Instruction") { program_t program; prog_builder.AddStartAnchor(program); prog_builder.AddInst(program, "Decrement", 0); @@ -77,12 +78,12 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Increment Instruction should be printed"){ + THEN("Global Anchor and Increment Instruction should be printed") { REQUIRE(output.str() == "AA:\n decrement r0\n"); } } - WHEN("Program contains Nand Instruction"){ + WHEN("Program contains Nand Instruction") { program_t program; prog_builder.AddStartAnchor(program); prog_builder.AddInst(program, "Nand", 0, 1, 0); @@ -91,13 +92,13 @@ TEST_CASE("Test Printing Simple Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Nand Instruction should be printed"){ + THEN("Global Anchor and Nand Instruction should be printed") { REQUIRE(output.str() == "AA:\n nand r0, r1, r0\n"); } } } -TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ +TEST_CASE("Test Printing Complex Instructions", "[sgp]") { using world_t = sgpmode::SGPWorld; using cpu_state_t = sgpmode::CPUState; using hw_spec_t = sgpmode::SGPHardwareSpec; @@ -111,8 +112,9 @@ TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ config.HOST_REPRO_RES(1); config.SEED(61); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("hardware_test_output"); - config.POP_SIZE(1); + config.INIT_POP_SIZE(1); config.START_MOI(0); config.TASK_IO_UNIQUE_OUTPUT(true); @@ -127,7 +129,7 @@ TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ std::ostringstream output; - WHEN("Program contains JumpIfNEq Instruction"){ + WHEN("Program contains JumpIfNEq Instruction") { program_t program; tag_t start_tag(prog_builder.GetStartTag()); tag_t tag1("0000000000000000000000000000000000000000000000000000000000000001"); @@ -145,12 +147,12 @@ TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Nop Instruction should be printed"){ + THEN("Global Anchor and Nop Instruction should be printed") { REQUIRE(output.str() == "AA:\n nop-0 \n nop-0 \n nop-0 \nAB:\n nop-0 \n nop-0 \n jumpifneq r0, r1, AB\n"); } } - WHEN("Program contains JumpIfEq Instruction"){ + WHEN("Program contains JumpIfEq Instruction") { program_t program; tag_t start_tag(prog_builder.GetStartTag()); tag_t tag1("0000000000000000000000000000000000000000000000000000000000000001"); @@ -168,12 +170,12 @@ TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Nop Instruction should be printed"){ + THEN("Global Anchor and Nop Instruction should be printed") { REQUIRE(output.str() == "AA:\n nop-0 \n nop-0 \n nop-0 \nAB:\n nop-0 \n nop-0 \n jumpifeq r0, r1, AB\n"); } } - WHEN("Program contains JumpIfLess Instruction"){ + WHEN("Program contains JumpIfLess Instruction") { program_t program; tag_t start_tag(prog_builder.GetStartTag()); tag_t tag1("0000000000000000000000000000000000000000000000000000000000000010"); @@ -191,9 +193,9 @@ TEST_CASE("Test Printing Complex Instructions", "[sgp]"){ hw.PrintCode(output); - THEN("Global Anchor and Nop Instruction should be printed"){ + THEN("Global Anchor and Nop Instruction should be printed") { REQUIRE(output.str() == "AA:\n nop-0 \n nop-0 \n nop-0 \nAB:\n nop-0 \n nop-0 \n jumpifless r0, r1, AB\n"); } } - + } \ No newline at end of file diff --git a/source/test/sgp_mode_test/unit_tests/SGPHost.test.cc b/source/test/sgp_mode_test/unit_tests/SGPHost.test.cc index 79401dca..4a1f82ef 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPHost.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPHost.test.cc @@ -30,7 +30,7 @@ TEST_CASE("Mutate", "[sgp]") { config.TASK_IO_BANK_SIZE(10); config.SGP_MUT_PER_BIT_RATE(1.0); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); - + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -66,7 +66,7 @@ TEST_CASE("No Mutate", "[sgp]") { config.TASK_IO_BANK_SIZE(10); config.SGP_MUT_PER_BIT_RATE(0.0); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); - + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -100,6 +100,7 @@ TEST_CASE("SGPHost destructor cleans up shared pointers and in-progress reproduc test_utils::SetWellMixed(config, 1, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -132,6 +133,7 @@ TEST_CASE("Host == operators", "[sgp][sgp-unit]") { emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); world_t world(random, &config); auto& prog_builder = world.GetProgramBuilder(); @@ -172,6 +174,7 @@ TEST_CASE("Host > & < operators", "[sgp][sgp-unit]") { emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); world_t world(random, &config); auto& prog_builder = world.GetProgramBuilder(); @@ -201,6 +204,7 @@ TEST_CASE("MakeNew returns identical host", "[sgp][sgp-unit]") { emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); emp::Ptr host = emp::NewPtr(&random, &world, &config); @@ -227,6 +231,7 @@ TEST_CASE("SetReproCount & GetReproCount","[sgp][sgp-unit]") { emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); world_t world(random, &config); @@ -257,6 +262,7 @@ TEST_CASE("ProcessOutputBuffer", "[sgp][sgp-unit]") { GIVEN("A host with valid values in its input and output buffers") { emp::Random random(31); sgpmode::SymConfigSGP config; + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/echo-task-env.json"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 1, 0); @@ -284,29 +290,35 @@ TEST_CASE("ProcessOutputBuffer", "[sgp][sgp-unit]") { } } - -TEST_CASE("Check that hosts and syms can't have negative points", "[sgp][sgp-unit]"){ +TEST_CASE("Check that hosts and syms can't have negative points", "[sgp][sgp-unit]") { using world_t = sgpmode::SGPWorld; using cpu_state_t = sgpmode::CPUState; using hw_spec_t = sgpmode::SGPHardwareSpec; using sgp_host_t = sgpmode::SGPHost; using sgp_sym_t = sgpmode::SGPSymbiont; - GIVEN("A host and sym starting with zero points"){ - emp::Random random(31); - sgpmode::SymConfigSGP config; - config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); - world_t world(random, &config); - auto& prog_builder = world.GetProgramBuilder(); - emp::Ptr host = emp::NewPtr(&random, &world, &config, prog_builder.CreateReproProgram(100)); - emp::Ptr sym = emp::NewPtr(&random, &world, &config, prog_builder.CreateNotProgram(100)); - - WHEN("points are added to make total points negative"){ - sym->AddPoints(-100); - host->AddPoints(-100); - THEN("point value should be set to zero"){ - REQUIRE(host->GetPoints() == 0); - REQUIRE(sym->GetPoints() == 0); + + GIVEN("A host and sym starting with zero points") { + emp::Random random(31); + sgpmode::SymConfigSGP config; + config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); + test_utils::SetWellMixed(config, 1, 0); + world_t world(random, &config); + world.Setup(); + + auto& prog_builder = world.GetProgramBuilder(); + emp::Ptr host = emp::NewPtr(&random, &world, &config, prog_builder.CreateReproProgram(100)); + emp::Ptr sym = emp::NewPtr(&random, &world, &config, prog_builder.CreateNotProgram(100)); + + WHEN("points are added to make total points negative") { + sym->AddPoints(-100); + host->AddPoints(-100); + THEN("point value should be set to zero") { + REQUIRE(host->GetPoints() == 0); + REQUIRE(sym->GetPoints() == 0); } } + host.Delete(); + sym.Delete(); } } \ No newline at end of file diff --git a/source/test/sgp_mode_test/unit_tests/SGPSymbiont.test.cc b/source/test/sgp_mode_test/unit_tests/SGPSymbiont.test.cc index 34ec3d3e..575b23c8 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPSymbiont.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPSymbiont.test.cc @@ -26,6 +26,7 @@ using sgp_sym_t = sgpmode::SGPSymbiont; emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); world_t world(random, &config); @@ -62,6 +63,7 @@ TEST_CASE("Symbiont > & < operator","[sgp][sgp-unit]") { emp::Random random(31); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); world_t world(random, &config); @@ -87,6 +89,7 @@ TEST_CASE("Symbiont Process", "[sgp][sgp-unit]") { emp::Random random(34); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.CYCLES_PER_UPDATE(3); test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); 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..adb3a878 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPWorld.test.cc @@ -31,6 +31,7 @@ TEST_CASE("Update only hosts test", "[sgp]") { emp::Random random(61); sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_IO_BANK_SIZE(10); test_utils::SetWellMixed(config, 4, 0); world_t world(random, &config); @@ -70,6 +71,7 @@ TEST_CASE("Ousting is permitted", "[sgp]") { config.OUSTING(1); config.SYM_LIMIT(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); world_t world(random, &config); world.Setup(); @@ -100,6 +102,7 @@ TEST_CASE("NoBetterOrEquallyMatchingSymbionts returns false for an incoming wors test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -134,6 +137,7 @@ TEST_CASE("NoBetterOrEquallyMatchingSymbionts returns false for an incoming equa test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -168,6 +172,7 @@ TEST_CASE("NoBetterOrEquallyMatchingSymbionts returns true for an incoming bette test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -202,6 +207,7 @@ TEST_CASE("NoBetterMatchingSymbionts returns false for an incoming worse match", test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -236,6 +242,7 @@ TEST_CASE("NoBetterMatchingSymbionts returns true for an incoming equal match", test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -270,6 +277,7 @@ TEST_CASE("NoBetterMatchingSymbionts returns true for an incoming better match", test_utils::SetWellMixed(config, 1, 0); config.SEED(2312); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); config.TASK_PROFILE_MODE("self-all"); @@ -306,6 +314,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor config.SEED(33); config.FIND_NEIGHBOR_HOST_ATTEMPTS(5); // increase attempts to avoid issues if randomly picks current host first config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("always"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); @@ -351,6 +360,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is required for horizon config.SEED(11); config.FIND_NEIGHBOR_HOST_ATTEMPTS(5); // increase attempts to avoid issues if randomly picks current host first config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("task-profile-compatible"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); @@ -396,6 +406,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor config.SEED(11); config.FIND_NEIGHBOR_HOST_ATTEMPTS(5); // increase attempts to avoid issues if randomly picks current host first config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("always"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); @@ -441,6 +452,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is required for horizon config.SEED(11); config.FIND_NEIGHBOR_HOST_ATTEMPTS(5); // increase attempts to avoid issues if randomly picks current host first config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("task-profile-compatible"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); @@ -485,6 +497,7 @@ TEST_CASE("FindHostForHorizontalTrans when task matching is not required for hor config.SEED(11); config.FIND_NEIGHBOR_HOST_ATTEMPTS(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("always"); config.TASK_PROFILE_COMPATIBILITY_MODE("task-any-match"); @@ -522,6 +535,7 @@ TEST_CASE("SGP Horizontal SymDoBirth", "[sgp][sgp-unit]") { config.OUSTING(1); config.FIND_NEIGHBOR_HOST_ATTEMPTS(1); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.HORIZONTAL_TRANSMISSION_COMPATIBILITY_MODE("task-profile-strictly-stronger-match"); world_t world(random, &config); diff --git a/source/test/sgp_mode_test/unit_tests/SGPWorldData.test.cc b/source/test/sgp_mode_test/unit_tests/SGPWorldData.test.cc index 20fd82a0..c93ce421 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPWorldData.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPWorldData.test.cc @@ -35,6 +35,7 @@ TEST_CASE("CreateDataFiles creates data files", "[sgp][sgp-functional]") { test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); config.FILE_PATH("SGPData_test_output"); config.FILE_PATH("_test"); emp::Random random(config.SEED()); diff --git a/source/test/sgp_mode_test/unit_tests/SGPWorldSetup.test.cc b/source/test/sgp_mode_test/unit_tests/SGPWorldSetup.test.cc index 89a9a0f8..5e785d03 100644 --- a/source/test/sgp_mode_test/unit_tests/SGPWorldSetup.test.cc +++ b/source/test/sgp_mode_test/unit_tests/SGPWorldSetup.test.cc @@ -31,6 +31,7 @@ TEST_CASE("Setup with an empty population", "[sgp]") { config.TASK_IO_BANK_SIZE(10); config.SEED(234); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); emp::Random random(config.SEED()); world_t world(random, &config); @@ -50,6 +51,7 @@ TEST_CASE("Setup with an empty population", "[sgp]") { TEST_CASE("SetupHosts adds correct number of hosts", "[sgp][sgp-unit]") { sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.SEED(44); @@ -71,6 +73,7 @@ TEST_CASE("SetupHosts adds correct number of hosts", "[sgp][sgp-unit]") { TEST_CASE("SetupHosts adds infected hosts correctly", "[sgp][sgp-unit]") { sgpmode::SymConfigSGP config; config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); test_utils::SetWellMixed(config, 4); config.TASK_IO_BANK_SIZE(10); config.SEED(44); @@ -100,6 +103,7 @@ TEST_CASE("Setup correctly sets host task profile functions", "[sgp][sgp-unit]") test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); WHEN("TASK_PROFILE_MODE tracks all parent tasks") { config.TASK_PROFILE_MODE("parent-all"); @@ -142,6 +146,7 @@ TEST_CASE("Setup correctly sets symbiont task profile functions", "[sgp][sgp-uni test_utils::SetWellMixed(config, 4, 0); config.TASK_IO_BANK_SIZE(10); config.TASK_ENV_CFG_PATH("source/test/sgp_mode_test/hardware-test-env.json"); + config.EVENTS_CFG_PATH("source/test/sgp_mode_test/no-events.json"); WHEN("TASK_PROFILE_MODE tracks all parent tasks") { config.TASK_PROFILE_MODE("parent-all");