diff --git a/src/exateppabm/disease/SEIR.h b/src/exateppabm/disease/SEIR.h index acaf910..0693475 100644 --- a/src/exateppabm/disease/SEIR.h +++ b/src/exateppabm/disease/SEIR.h @@ -3,6 +3,7 @@ #include #include "flamegpu/flamegpu.h" #include "exateppabm/input.h" +#include "exateppabm/person.h" namespace exateppabm { namespace disease { @@ -53,6 +54,46 @@ void define(flamegpu::ModelDescription& model, const exateppabm::input::config& void appendLayers(flamegpu::ModelDescription& model); +/** + * Device utility function to get an individuals current infection status from global agent memory + * + * Templated for due to the templated DeviceAPI object + * * + * @param FLAMEGPU flame gpu device API object + * @return individuals current infection state + */ +template +FLAMEGPU_DEVICE_FUNCTION disease::SEIR::InfectionStateUnderlyingType getCurrentInfectionStatus(flamegpu::DeviceAPI* FLAMEGPU) { + return FLAMEGPU->template getVariable(person::v::INFECTION_STATE); +} + + +/** + * Device utility function for when an individual is exposed, moving from susceptible to exposed + * + * Templated for due to the templated DeviceAPI object + * + * @param FLAMEGPU flamegpu device api object + * @param current infection status for the individual to be mutated in-place + */ +template +FLAMEGPU_DEVICE_FUNCTION void susceptibleToExposed(flamegpu::DeviceAPI* FLAMEGPU, disease::SEIR::InfectionStateUnderlyingType& infectionStatus) { + // Generate how long the individual will be in the exposed for. + float mean = FLAMEGPU->environment.template getProperty("mean_time_to_infected"); + float sd = FLAMEGPU->environment.template getProperty("sd_time_to_infected"); + float stateDuration = (FLAMEGPU->random.template normal() * sd) + mean; + + // Update the referenced value containing the individuals current infections status, used to reduce branching within a device for loop. + infectionStatus = disease::SEIR::InfectionState::Exposed; + // Update individuals infection state in global agent memory + FLAMEGPU->template setVariable(person::v::INFECTION_STATE, infectionStatus); + // Update the individual infection state duration in global agent memory + FLAMEGPU->template setVariable(person::v::INFECTION_STATE_DURATION, stateDuration); + + // Increment the infection counter for this individual + person::incrementInfectionCounter(FLAMEGPU); +} + } // namespace SEIR } // namespace disease diff --git a/src/exateppabm/household.cu b/src/exateppabm/household.cu index 5cf07af..2e61cf0 100644 --- a/src/exateppabm/household.cu +++ b/src/exateppabm/household.cu @@ -73,13 +73,12 @@ FLAMEGPU_AGENT_FUNCTION(interactHousehold, flamegpu::MessageBucket, flamegpu::Me p_s2e *= relativeSusceptibility; // Check if the current individual is susceptible to being infected - auto infectionState = FLAMEGPU->getVariable(person::v::INFECTION_STATE); + auto infectionState = disease::SEIR::getCurrentInfectionStatus(FLAMEGPU); - // @todo - this will need to change for contact tracing, the message interaction will need to occur regardless. + // Only check interactions from this individual if they are susceptible. @todo - this will need to change for contact tracing. if (infectionState == disease::SEIR::Susceptible) { - // Variable to store the duration of the exposed phase (if exposed) - float stateDuration = 0.f; - + // Bool to track if individual newly exposed - used to move expensive operations outside the message iteration loop. + bool newlyExposed = false; // Iterate messages from anyone within the household for (const auto &message : FLAMEGPU->message_in(householdIdx)) { // Ignore self messages (can't infect oneself) @@ -91,25 +90,19 @@ FLAMEGPU_AGENT_FUNCTION(interactHousehold, flamegpu::MessageBucket, flamegpu::Me // Roll a dice float r = FLAMEGPU->random.uniform(); if (r < p_s2e) { - // I have been exposed - infectionState = disease::SEIR::InfectionState::Exposed; - // Generate how long until I am infected - float mean = FLAMEGPU->environment.getProperty("mean_time_to_infected"); - float sd = FLAMEGPU->environment.getProperty("sd_time_to_infected"); - stateDuration = (FLAMEGPU->random.normal() * sd) + mean; - // @todo - for now only any exposure matters. This may want to change when quantity of exposure is important? - // Increment the infection counter for this individual - FLAMEGPU->setVariable(person::v::INFECTION_COUNT, FLAMEGPU->getVariable(person::v::INFECTION_COUNT) + 1); + // set a flag indicating that the individual has been exposed in this message iteration loop + newlyExposed = true; + // break out of the message iteration loop, currently no need to check for multiple exposures on the same day. break; } } } } } - // If newly exposed, store the value in global device memory. - if (infectionState == disease::SEIR::InfectionState::Exposed) { - FLAMEGPU->setVariable(person::v::INFECTION_STATE, infectionState); - FLAMEGPU->setVariable(person::v::INFECTION_STATE_DURATION, stateDuration); + // If newly exposed, update agent data and generate new seir state information. This is done outside the message iteration loop to be more GPU-shaped. + if (newlyExposed) { + // Transition from susceptible to exposed in SEIR + disease::SEIR::susceptibleToExposed(FLAMEGPU, infectionState); } } diff --git a/src/exateppabm/person.h b/src/exateppabm/person.h index 61a9e06..65343ac 100644 --- a/src/exateppabm/person.h +++ b/src/exateppabm/person.h @@ -82,6 +82,18 @@ void define(flamegpu::ModelDescription& model, const exateppabm::input::config& */ void appendLayers(flamegpu::ModelDescription& model); +/** + * Device utility function for Increasing an individuals infection counter, for more legible agent code + * + * Templated for due to the templated DeviceAPI object + * + * @todo - consider moving to the disease namespace? + */ +template +FLAMEGPU_DEVICE_FUNCTION void incrementInfectionCounter(flamegpu::DeviceAPI* FLAMEGPU) { + FLAMEGPU->template setVariable(v::INFECTION_COUNT, FLAMEGPU->template getVariable(v::INFECTION_COUNT) + 1); +} + // Undefine the host device macro to avoid potential macro collisions #undef DEVICE_CONSTEXPR_STRING diff --git a/src/exateppabm/random_interactions.cu b/src/exateppabm/random_interactions.cu index bb0b9a4..7e469f1 100644 --- a/src/exateppabm/random_interactions.cu +++ b/src/exateppabm/random_interactions.cu @@ -179,13 +179,12 @@ FLAMEGPU_AGENT_FUNCTION(interactRandomDailyNetwork, flamegpu::MessageArray, flam p_s2e *= relativeSusceptibility; // Check if the current individual is susceptible to being infected - auto infectionState = FLAMEGPU->getVariable(person::v::INFECTION_STATE); + auto infectionState = disease::SEIR::getCurrentInfectionStatus(FLAMEGPU); - // @todo - this will need to change for contact tracing, the message interaction will need to occur regardless. + // Only check interactions from this individual if they are susceptible. @todo - this will need to change for contact tracing. if (infectionState == disease::SEIR::Susceptible) { - // Variable to store the duration of the exposed phase (if exposed) - float stateDuration = 0.f; - + // Bool to track if individual newly exposed - used to move expensive operations outside the message iteration loop. + bool newlyExposed = false; // For each interaction this agent is set to perform const std::uint32_t randomInteractionCount = FLAMEGPU->getVariable(person::v::RANDOM_INTERACTION_COUNT); for (std::uint32_t randomInteractionIdx = 0; randomInteractionIdx < randomInteractionCount; ++randomInteractionIdx) { @@ -200,24 +199,18 @@ FLAMEGPU_AGENT_FUNCTION(interactRandomDailyNetwork, flamegpu::MessageArray, flam // Roll a dice float r = FLAMEGPU->random.uniform(); if (r < p_s2e) { - // I have been exposed - infectionState = disease::SEIR::InfectionState::Exposed; - // Generate how long until I am infected - float mean = FLAMEGPU->environment.getProperty("mean_time_to_infected"); - float sd = FLAMEGPU->environment.getProperty("sd_time_to_infected"); - stateDuration = (FLAMEGPU->random.normal() * sd) + mean; - // @todo - for now only any exposure matters. This may want to change when quantity of exposure is important? - // Increment the infection counter for this individual - FLAMEGPU->setVariable(person::v::INFECTION_COUNT, FLAMEGPU->getVariable(person::v::INFECTION_COUNT) + 1); + // set a flag indicating that the individual has been exposed in this message iteration loop + newlyExposed = true; + // break out of the loop over today's random interactions - can only be exposed once break; } } } } - // If newly exposed, store the value in global device memory. - if (infectionState == disease::SEIR::InfectionState::Exposed) { - FLAMEGPU->setVariable(person::v::INFECTION_STATE, infectionState); - FLAMEGPU->setVariable(person::v::INFECTION_STATE_DURATION, stateDuration); + // If newly exposed, update agent data and generate new seir state information. This is done outside the message iteration loop to be more GPU-shaped. + if (newlyExposed) { + // Transition from susceptible to exposed in SEIR + disease::SEIR::susceptibleToExposed(FLAMEGPU, infectionState); } } diff --git a/src/exateppabm/workplace.cu b/src/exateppabm/workplace.cu index d7d8ceb..e8d675f 100644 --- a/src/exateppabm/workplace.cu +++ b/src/exateppabm/workplace.cu @@ -68,12 +68,12 @@ FLAMEGPU_AGENT_FUNCTION(interactWorkplace, flamegpu::MessageArray, flamegpu::Mes p_s2e *= relativeSusceptibility; // Check if the current individual is susceptible to being infected - auto infectionState = FLAMEGPU->getVariable(person::v::INFECTION_STATE); + auto infectionState = disease::SEIR::getCurrentInfectionStatus(FLAMEGPU); - // @todo - this will need to change for contact tracing, the message interaction will need to occur regardless. + // Only check interactions from this individual if they are susceptible. @todo - this will need to change for contact tracing. if (infectionState == disease::SEIR::Susceptible) { - // Variable to store the duration of the exposed phase (if exposed) - float stateDuration = 0.f; + // Bool to track if individual newly exposed - used to move expensive operations outside the message iteration loop. + bool newlyExposed = false; // Iterate my downstream neighbours (the graph is undirected, so no need to iterate in and out auto workplaceGraph = FLAMEGPU->environment.getDirectedGraph("WORKPLACE_DIGRAPH"); std::uint32_t myVertexIndex = workplaceGraph.getVertexIndex(id); @@ -93,25 +93,19 @@ FLAMEGPU_AGENT_FUNCTION(interactWorkplace, flamegpu::MessageArray, flamegpu::Mes // Roll a dice to determine if exposure occurred float r = FLAMEGPU->random.uniform(); if (r < p_s2e) { - // I have been exposed - infectionState = disease::SEIR::InfectionState::Exposed; - // Generate how long until I am infected - float mean = FLAMEGPU->environment.getProperty("mean_time_to_infected"); - float sd = FLAMEGPU->environment.getProperty("sd_time_to_infected"); - stateDuration = (FLAMEGPU->random.normal() * sd) + mean; - // @todo - for now only any exposure matters. This may want to change when quantity of exposure is important? - // Increment the infection counter for this individual - FLAMEGPU->setVariable(person::v::INFECTION_COUNT, FLAMEGPU->getVariable(person::v::INFECTION_COUNT) + 1); + // set a flag indicating that the individual has been exposed in this message iteration loop + newlyExposed = true; + // break out of the message iteration loop, currently no need to check for multiple exposures on the same day. break; } } } } } - // If newly exposed, store the value in global device memory. - if (infectionState == disease::SEIR::InfectionState::Exposed) { - FLAMEGPU->setVariable(person::v::INFECTION_STATE, infectionState); - FLAMEGPU->setVariable(person::v::INFECTION_STATE_DURATION, stateDuration); + // If newly exposed, update agent data and generate new seir state information. This is done outside the message iteration loop to be more GPU-shaped. + if (newlyExposed) { + // Transition from susceptible to exposed in SEIR + disease::SEIR::susceptibleToExposed(FLAMEGPU, infectionState); } }