Skip to content

Commit

Permalink
Implement per individual file and demo plotting script
Browse files Browse the repository at this point in the history
This is useful for double checking household and workplace sizes
  • Loading branch information
ptheywood committed Nov 19, 2024
1 parent 49fd4e7 commit 0ae4710
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 11 deletions.
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ SET(LIBRARY_SRC
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/OutputFile.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/PerformanceFile.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/PerIndividualFile.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/TimeSeriesFile.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/person.h
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/population.h
Expand All @@ -38,6 +39,8 @@ SET(LIBRARY_SRC
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output.cu
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/PerformanceFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/TimeSeriesFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/PerIndividualFile.cu
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/output/TimeSeriesFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/person.cu
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/population.cu
${CMAKE_CURRENT_SOURCE_DIR}/exateppabm/util.cu
Expand Down
2 changes: 1 addition & 1 deletion src/exateppabm/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ void setup(CLI::App& app, std::shared_ptr<exateppabm::cli::params> params) {
app.add_flag("-v, --verbose", params->verbosity, "Verbosity of simulation output, forwarded to FLAME GPU 2");
app.add_option("-i, --input-file", params->inputParamFile, "Path to input parameters file");
app.add_option("-n, --param-number", params->inputParamLine, "The line from the parameters file to use. 1 indexed (assuming there is a header)");

app.add_option("-o, --output-dir", params->outputDir, "Path to output directory");
app.add_flag("--individual-file", params->individualFile, "Enable the creation of the per individual file");
}

void print(const exateppabm::cli::params params) {
Expand Down
4 changes: 4 additions & 0 deletions src/exateppabm/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ struct params {
* Path to directory for file output
*/
std::string outputDir = std::filesystem::current_path();
/**
* bool indicating if the individual file should be written
*/
bool individualFile = false;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/exateppabm/exatepp_abm.cu
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ int entrypoint(int argc, char* argv[]) {
exateppabm::disease::SEIR::define(model, *config);

// Add init, step and exit functions related to data collection and output. This may want refactoring when multiple output files are supported or collected data becomes more complex.
exateppabm::output::define(model, cli_params->outputDir);
exateppabm::output::define(model, cli_params->outputDir, cli_params->individualFile);

// Build the model control flow. This will want abstracting more in the future @todo
// @note - not using the DAG control flow due to bugs encountered in another project when splitting between compilation units.
Expand Down
46 changes: 38 additions & 8 deletions src/exateppabm/output.cu
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "exateppabm/output/OutputFile.h"
#include "exateppabm/output/TimeSeriesFile.h"
#include "exateppabm/output/PerIndividualFile.h"
#include "exateppabm/person.h"
#include "exateppabm/population.h"
#include "exateppabm/demographics.h"
Expand All @@ -26,6 +27,9 @@ std::filesystem::path _outputDirectory;
// Object representing the time series output file
std::unique_ptr<TimeSeriesFile> _timeSeriesFile = nullptr;

// Object representing the per individual file
std::unique_ptr<PerIndividualFile> _perIndividualFile = nullptr;

} // namespace

/**
Expand Down Expand Up @@ -86,13 +90,6 @@ FLAMEGPU_STEP_FUNCTION(output_step) {
observations.total_infected += totalInfectedPerDemographic[i];
}

/*
// @todo - not currently used direct agent data iteration
for (const auto& person : population) {
auto infected = person.getVariable<exateppabm::disease::SEIR::InfectionStateUnderlyingType>(exateppabm::person::v::INFECTION_STATE);
}
*/

// Append this steps' data to the namespace-scoped data structure
_timeSeriesFile->appendObservations(observations);
}
Expand All @@ -107,8 +104,35 @@ FLAMEGPU_EXIT_FUNCTION(output_exit) {
_timeSeriesFile->close();
}


FLAMEGPU_EXIT_FUNCTION(output_exit_per_individual) {
// Collect per agent data
// Get a handle to the person agent host api object
auto personAgent = FLAMEGPU->agent(exateppabm::person::NAME, exateppabm::person::states::DEFAULT);
// Get a handle to the person agent population on the host
flamegpu::DeviceAgentVector population = personAgent.getPopulationData();
for (const auto& person : population) {
exateppabm::output::PerIndividualFile::Person personData = {};
personData.id = static_cast<std::uint32_t>(person.getID());
personData.age_group = person.getVariable<demographics::AgeUnderlyingType>(person::v::AGE_DEMOGRAPHIC);
personData.occupation_network = person.getVariable<std::uint32_t>(person::v::WORKPLACE_IDX);
personData.house_no = person.getVariable<std::uint32_t>(person::v::HOUSEHOLD_IDX);
personData.infection_count = person.getVariable<std::uint32_t>(person::v::INFECTION_COUNT);
_perIndividualFile->appendPerson(personData);
}


// Write the per individual data to disk
// Open the file handle
_perIndividualFile->open();
// Write data to the opened file
_perIndividualFile->write();
// Close the file handle
_perIndividualFile->close();
}

// @todo - may need to split this due to order of execution within init/step/exit funcs, if any others exist.
void define(flamegpu::ModelDescription& model, const std::filesystem::path outputDirectory) {
void define(flamegpu::ModelDescription& model, const std::filesystem::path outputDirectory, const bool individualFile) {
// Store the output directory for access in the FLAME GPU exit function (and init?)
// This will want refactoring for ensembles
_outputDirectory = outputDirectory;
Expand All @@ -121,6 +145,12 @@ void define(flamegpu::ModelDescription& model, const std::filesystem::path outpu
model.addStepFunction(output_step);
// Add the exit function to the model
model.addExitFunction(output_exit);

// optionally prepare for per individual file output
if (individualFile) {
_perIndividualFile = std::make_unique<PerIndividualFile>(_outputDirectory);
model.addExitFunction(output_exit_per_individual);
}
}

} // namespace output
Expand Down
4 changes: 3 additions & 1 deletion src/exateppabm/output.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "output/OutputFile.h"
#include "output/TimeSeriesFile.h"
#include "output/PerIndividualFile.h"

namespace exateppabm {
namespace output {
Expand All @@ -22,8 +23,9 @@ namespace output {
*
* @param model flamegpu2 model description object to mutate
* @param outputDirectory path to directory for file output
* @param individualFile if the per individual file should be written or not
*/
void define(flamegpu::ModelDescription& model, std::filesystem::path outputDirectory);
void define(flamegpu::ModelDescription& model, std::filesystem::path outputDirectory, bool individualFile);

} // namespace output
} // namespace exateppabm
48 changes: 48 additions & 0 deletions src/exateppabm/output/PerIndividualFile.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "PerIndividualFile.h"

#include <fmt/core.h>

#include <vector>

namespace exateppabm {
namespace output {

PerIndividualFile::PerIndividualFile(std::filesystem::path directory) : OutputFile(directory / PerIndividualFile::DEFAULT_FILENAME) { }

PerIndividualFile::~PerIndividualFile() { }

void PerIndividualFile::reset(const std::uint32_t n_total) {
// Reset the observations vector
this->_perPerson = std::vector<Person>();
this->_perPerson.reserve(n_total);
}

void PerIndividualFile::appendPerson(const Person person) {
// @todo = ensure _perPerson has been initialised?
this->_perPerson.push_back(person);
}

bool PerIndividualFile::write() {
if (!this->_handle) {
this->open();
}

// Print to the file handle
fmt::print(_handle, "ID,age_group,occupation_network,house_no,infection_count\n");
for (const auto& person : _perPerson) {
fmt::print(
_handle,
"{},{},{},{},{}\n",
person.id,
person.age_group,
person.occupation_network,
person.house_no,
person.infection_count);
}

fmt::print("Per individual data written to {}\n", std::filesystem::absolute(this->_filepath).c_str());
return true;
}

} // namespace output
} // namespace exateppabm
92 changes: 92 additions & 0 deletions src/exateppabm/output/PerIndividualFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#include "OutputFile.h"

#include <filesystem>
#include <vector>

#include "exateppabm/demographics.h"
#include "exateppabm/person.h"

namespace exateppabm {
namespace output {

/**
* Class for representing the per individual file
*/
class PerIndividualFile : public OutputFile {
public:
// Forward decl nested struct
struct Person;

/**
* Constructor setting the path for file output.
* @param directory parent directory for the file to be stored in (using the default filename)
*/
explicit PerIndividualFile(std::filesystem::path directory);

/**
* Dtor
*/
~PerIndividualFile();

/**
* Reset the objects internal data structures, and pre-allocate memory for storing data for the current simulation
* @param n_total number of people in total
*/
void reset(std::uint32_t n_total);

/**
* Append a set of observations for a single time point to the internal data structure
*
* @param observations observations from a single time point
*/
void appendPerson(Person observations);

/**
* Write the contents of the time series file to disk at the configured path
*
* @return bool indicating success
*/
bool write();

/**
* Structure for data observed at a single point in time within the time series
*/
struct Person {
/**
* ID for the person
*/
std::uint32_t id = 0;
/**
* Age group
*/
exateppabm::demographics::AgeUnderlyingType age_group = 0;
/**
* Work network index
*/
std::uint32_t occupation_network = 0;
/**
* Household index
*/
std::uint32_t house_no = 0;
/**
* Cumulative number of times this individual was infected
*/
std::uint32_t infection_count = 0;
};

private:
/**
* Default filename for output
* @todo - factor in the run index for ensembles?
*/
constexpr static char DEFAULT_FILENAME[] = "individual_file.csv";
/**
* Private member containing the observation data
*/
std::vector<Person> _perPerson;
};

} // namespace output
} // namespace exateppabm
1 change: 1 addition & 0 deletions src/exateppabm/output/TimeSeriesFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class TimeSeriesFile : public OutputFile {
private:
/**
* Default filename for output
* @todo - factor in the run index for ensembles?
*/
constexpr static char DEFAULT_FILENAME[] = "timeseries.csv";
/**
Expand Down
7 changes: 7 additions & 0 deletions src/exateppabm/person.cu
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ FLAMEGPU_AGENT_FUNCTION(interactHousehold, flamegpu::MessageBruteForce, flamegpu
float sd = FLAMEGPU->environment.getProperty<float>("sd_time_to_infected");
stateDuration = (FLAMEGPU->random.normal<float>() * 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<std::uint32_t>(v::INFECTION_COUNT, FLAMEGPU->getVariable<std::uint32_t>(v::INFECTION_COUNT) + 1);
break;
}
}
Expand Down Expand Up @@ -211,6 +213,8 @@ FLAMEGPU_AGENT_FUNCTION(interactWorkplace, flamegpu::MessageBruteForce, flamegpu
float sd = FLAMEGPU->environment.getProperty<float>("sd_time_to_infected");
stateDuration = (FLAMEGPU->random.normal<float>() * 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<std::uint32_t>(v::INFECTION_COUNT, FLAMEGPU->getVariable<std::uint32_t>(v::INFECTION_COUNT) + 1);
break;
}
}
Expand Down Expand Up @@ -259,6 +263,9 @@ void define(flamegpu::ModelDescription& model, const exateppabm::input::config&
// Time until next state change? Defaults to the simulation duration + 1.
agent.newVariable<float>(person::v::INFECTION_STATE_DURATION, params.duration + 1);

// Integer count for the number of times infected, defaults to 0
agent.newVariable<std::uint32_t>(person::v::INFECTION_COUNT, 0u);

// age demographic
// @todo make this an enum, and update uses of it, but flame's templating disagrees?
agent.newVariable<demographics::AgeUnderlyingType>(person::v::AGE_DEMOGRAPHIC, exateppabm::demographics::Age::AGE_0_9);
Expand Down
1 change: 1 addition & 0 deletions src/exateppabm/person.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ __host__ __device__ constexpr char z[] = "z";
__host__ __device__ constexpr char INFECTION_STATE[] = "infection_state";
__host__ __device__ constexpr char INFECTION_STATE_CHANGE_DAY[] = "infection_state_change_day";
__host__ __device__ constexpr char INFECTION_STATE_DURATION[] = "infection_state_duration";
__host__ __device__ constexpr char INFECTION_COUNT[] = "infection_count";
__host__ __device__ constexpr char AGE_DEMOGRAPHIC[] = "age_demographic";
__host__ __device__ constexpr char HOUSEHOLD_IDX[] = "household_idx";
__host__ __device__ constexpr char HOUSEHOLD_SIZE[] = "household_size";
Expand Down
5 changes: 5 additions & 0 deletions src/exateppabm/population.cu
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ std::unique_ptr<flamegpu::AgentVector> generate(flamegpu::ModelDescription& mode
float infectionStateDuration = infectionStatus == disease::SEIR::InfectionState::Infected ? config.mean_time_to_recovered: 0;
person.setVariable<float>(exateppabm::person::v::INFECTION_STATE_DURATION, infectionStateDuration);

// initialise the agents infection count
if (infectionStatus == disease::SEIR::Infected) {
person.setVariable<std::uint32_t>(exateppabm::person::v::INFECTION_COUNT, 1);
}

// Demographic
// @todo - this is a bit grim, enum class aren't as nice as hoped.
float demo_random = demo_dist(rng);
Expand Down
Loading

0 comments on commit 0ae4710

Please sign in to comment.