diff --git a/Makefile b/Makefile index d35695ba..8303d860 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,11 @@ install: build README.md: README.Rmd Rscript --vanilla -e 'rmarkdown::render("README.Rmd")' -update: - wget https://raw.githubusercontent.com/UofUEpiBio/epiworld/master/epiworld.hpp && \ - mv epiworld.hpp inst/include/epiworld.hpp +# update: +# wget https://raw.githubusercontent.com/UofUEpiBio/epiworld/master/epiworld.hpp && \ +# mv epiworld.hpp inst/include/epiworld.hpp local-update: - rsync -avz ../epiworld/epiworld.hpp inst/include/epiworld.hpp + rsync -avz ../epiworld/include/epiworld inst/include/. check: build R CMD check epiworldR_*.tar.gz diff --git a/README.md b/README.md index ff8c5a50..f0e6612e 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,8 @@ summary(sir) #> Number of entities : 0 #> Days (duration) : 50 (of 50) #> Number of viruses : 1 -#> Last run elapsed t : 193.00ms -#> Last run speed : 25.77 million agents x day / second +#> Last run elapsed t : 190.00ms +#> Last run speed : 26.22 million agents x day / second #> Rewiring : off #> #> Global actions: diff --git a/inst/include/epiworld.hpp b/inst/include/epiworld.hpp deleted file mode 100644 index fc7afdde..00000000 --- a/inst/include/epiworld.hpp +++ /dev/null @@ -1,18518 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef EPIWORLD_HPP -#define EPIWORLD_HPP - -namespace epiworld { - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/config.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_CONFIG_HPP -#define EPIWORLD_CONFIG_HPP - -#ifndef printf_epiworld - #define printf_epiworld fflush(stdout);printf -#endif - -#ifndef EPIWORLD_MAXNEIGHBORS - #define EPIWORLD_MAXNEIGHBORS 1048576 -#endif - -#ifdef _OPENMP - #include -// #else -// #define omp_get_thread_num() 0 -// #define omp_set_num_threads() 1 -#endif - -#ifndef epiworld_double - #define epiworld_double float -#endif - -#ifndef epiworld_fast_int - #define epiworld_fast_int long long int -#endif - -#ifndef epiworld_fast_uint - #define epiworld_fast_uint unsigned long long int -#endif - -#define EPI_DEFAULT_TSEQ int - -template -class Model; - -template -class Agent; - -template -class PersonTools; - -template -class Virus; - -template -class Viruses; - -template -class Viruses_const; - -template -class Tool; - -template -class Tools; - -template -class Tools_const; - -template -class Entity; - -template -using VirusPtr = std::shared_ptr< Virus< TSeq > >; - -template -using ToolPtr = std::shared_ptr< Tool< TSeq > >; - -template -using ToolFun = std::function&,Agent*,VirusPtr,Model*)>; - -template -using MixerFun = std::function*,VirusPtr,Model*)>; - -template -using MutFun = std::function*,Virus&,Model*)>; - -template -using PostRecoveryFun = std::function*,Virus&,Model*)>; - -template -using VirusFun = std::function*,Virus&,Model*)>; - -template -using UpdateFun = std::function*,Model*)>; - -template -using GlobalFun = std::function*)>; - -template -struct Action; - -template -using ActionFun = std::function&,Model*)>; - -/** - * @brief Decides how to distribute viruses at initialization - */ -template -using VirusToAgentFun = std::function&,Model*)>; - -/** - * @brief Decides how to distribute tools at initialization - */ -template -using ToolToAgentFun = std::function&,Model*)>; - -/** - * @brief Decides how to distribute entities at initialization - */ -template -using EntityToAgentFun = std::function&,Model*)>; - -/** - * @brief Action data for update an agent - * - * @tparam TSeq - */ -template -struct Action { - Agent * agent; - VirusPtr virus; - ToolPtr tool; - Entity * entity; - epiworld_fast_int new_state; - epiworld_fast_int queue; - ActionFun call; - int idx_agent; - int idx_object; -public: -/** - * @brief Construct a new Action object - * - * All the parameters are rather optional. - * - * @param agent_ Agent over who the action will happen - * @param virus_ Virus to add - * @param tool_ Tool to add - * @param virus_idx Index of virus to be removed (if needed) - * @param tool_idx Index of tool to be removed (if needed) - * @param new_state_ Next state - * @param queue_ Efect on the queue - * @param call_ The action call (if needed) - * @param idx_agent_ Location of agent in object. - * @param idx_object_ Location of object in agent. - */ - Action( - Agent * agent_, - VirusPtr virus_, - ToolPtr tool_, - Entity * entity_, - epiworld_fast_int new_state_, - epiworld_fast_int queue_, - ActionFun call_, - int idx_agent_, - int idx_object_ - ) : agent(agent_), virus(virus_), tool(tool_), entity(entity_), - new_state(new_state_), - queue(queue_), call(call_), idx_agent(idx_agent_), idx_object(idx_object_) { - return; - }; -}; - -/** - * @name Constants in epiworld - * - * @details The following are the default values some probabilities and - * rates take when no value has been specified in the model. - */ -///@{ -#ifndef DEFAULT_TOOL_CONTAGION_REDUCTION - #define DEFAULT_TOOL_CONTAGION_REDUCTION 0.0 -#endif - -#ifndef DEFAULT_TOOL_TRANSMISSION_REDUCTION - #define DEFAULT_TOOL_TRANSMISSION_REDUCTION 0.0 -#endif - -#ifndef DEFAULT_TOOL_RECOVERY_ENHANCER - #define DEFAULT_TOOL_RECOVERY_ENHANCER 0.0 -#endif - -#ifndef DEFAULT_TOOL_DEATH_REDUCTION - #define DEFAULT_TOOL_DEATH_REDUCTION 0.0 -#endif - -#ifndef EPI_DEFAULT_VIRUS_PROB_INFECTION - #define EPI_DEFAULT_VIRUS_PROB_INFECTION 1.0 -#endif - -#ifndef EPI_DEFAULT_VIRUS_PROB_RECOVERY - #define EPI_DEFAULT_VIRUS_PROB_RECOVERY 0.1428 -#endif - -#ifndef EPI_DEFAULT_VIRUS_PROB_DEATH - #define EPI_DEFAULT_VIRUS_PROB_DEATH 0.0 -#endif - -#ifndef EPI_DEFAULT_INCUBATION_DAYS - #define EPI_DEFAULT_INCUBATION_DAYS 7.0 -#endif -///@} - -#ifdef EPI_DEBUG - #define EPI_DEBUG_PRINTF printf_epiworld - - #define EPI_DEBUG_ERROR(etype, msg) \ - (etype)("[[epi-debug]] (error) " + std::string(msg)); - - #define EPI_DEBUG_NOTIFY_ACTIVE() \ - EPI_DEBUG_PRINTF("DEBUGGING ON (compiled with EPI_DEBUG defined)%s\n", ""); - - #define EPI_DEBUG_ALL_NON_NEGATIVE(vect) \ - for (auto & v : vect) \ - if (static_cast(v) < 0.0) \ - throw EPI_DEBUG_ERROR(std::logic_error, "A negative value not allowed."); - - #define EPI_DEBUG_SUM_DBL(vect, num) \ - double _epi_debug_sum = 0.0; \ - for (auto & v : vect) \ - { \ - _epi_debug_sum += static_cast(v);\ - if (_epi_debug_sum > static_cast(num)) \ - throw EPI_DEBUG_ERROR(std::logic_error, "The sum of elements not reached."); \ - } - - #define EPI_DEBUG_SUM_INT(vect, num) \ - int _epi_debug_sum = 0; \ - for (auto & v : vect) \ - { \ - _epi_debug_sum += static_cast(v);\ - if (_epi_debug_sum > static_cast(num)) \ - throw EPI_DEBUG_ERROR(std::logic_error, "The sum of elements not reached."); \ - } - - #define EPI_DEBUG_VECTOR_MATCH_INT(a, b, c) \ - if (a.size() != b.size()) {\ - EPI_DEBUG_PRINTF("In '%s'", std::string(c).c_str()); \ - EPI_DEBUG_PRINTF("Size of vector a: %lu\n", (a).size());\ - EPI_DEBUG_PRINTF("Size of vector b: %lu\n", (b).size());\ - throw EPI_DEBUG_ERROR(std::length_error, "The vectors do not match size."); \ - }\ - for (int _i = 0; _i < static_cast(a.size()); ++_i) \ - if (a[_i] != b[_i]) {\ - EPI_DEBUG_PRINTF("In '%s'", std::string(c).c_str()); \ - EPI_DEBUG_PRINTF("Iterating the last 5 values%s:\n", ""); \ - for (int _j = std::max(0, static_cast(_i) - 4); _j <= _i; ++_j) \ - { \ - EPI_DEBUG_PRINTF( \ - "a[%i]: %i; b[%i]: %i\n", \ - _j, \ - static_cast(a[_j]), \ - _j, static_cast(b[_j])); \ - } \ - throw EPI_DEBUG_ERROR(std::logic_error, "The vectors do not match."); \ - } - - #define EPI_DEBUG_FAIL_AT_TRUE(a,b) \ - if (a) \ - {\ - throw EPI_DEBUG_ERROR(std::logic_error, b); \ - } -#else - #define EPI_DEBUG_PRINTF(fmt, ...) - #define EPI_DEBUG_ERROR(fmt, ...) - #define EPI_DEBUG_NOTIFY_ACTIVE() - #define EPI_DEBUG_ALL_NON_NEGATIVE(vect) - #define EPI_DEBUG_SUM_DBL(vect, num) - #define EPI_DEBUG_SUM_INT(vect, num) - #define EPI_DEBUG_VECTOR_MATCH_INT(a, b, c) - #define EPI_DEBUG_FAIL_AT_TRUE(a, b) \ - if (a) \ - return false; -#endif - -#ifdef EPI_DEBUG_NO_THREAD_ID - #define EPI_GET_THREAD_ID() 0 -#else - #define EPI_GET_THREAD_ID() omp_get_thread_num() -#endif - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/config.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/epiworld-macros.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MACROS_HPP -#define EPIWORLD_MACROS_HPP - - - -/** - * @brief Helper macro to define a new tool - * - */ -#define EPI_NEW_TOOL(fname,tseq) inline epiworld_double \ -(fname)(\ - epiworld::Tool< tseq > & t, \ - epiworld::Agent< tseq > * p, \ - std::shared_ptr> v, \ - epiworld::Model< tseq > * m\ - ) - -/** - * @brief Create a Tool within a function - * - */ -#define EPI_NEW_TOOL_LAMBDA(funname,tseq) \ - epiworld::ToolFun funname = \ - [](epiworld::Tool & t, \ - epiworld::Agent * p, \ - std::shared_ptr> v, \ - epiworld::Model * m) -> epiworld_double - -/** - * @brief Helper macro for accessing model parameters - * - */ -#define EPI_PARAMS(i) m->operator()(i) - -/** - * @brief Helper macro for defining Mutation Functions - * - */ -#define EPI_NEW_MUTFUN(funname,tseq) inline bool \ - (funname)(\ - epiworld::Agent * p, \ - epiworld::Virus & v, \ - epiworld::Model * m ) - -#define EPI_NEW_MUTFUN_LAMBDA(funname,tseq) \ - epiworld::MutFun funname = \ - [](epiworld::Agent * p, \ - epiworld::Virus & v, \ - epiworld::Model * m) -> void - -#define EPI_NEW_POSTRECOVERYFUN(funname,tseq) inline void \ - (funname)( \ - epiworld::Agent * p, \ - epiworld::Virus & v, \ - epiworld::Model * m\ - ) - -#define EPI_NEW_POSTRECOVERYFUN_LAMBDA(funname,tseq) \ - epiworld::PostRecoveryFun funname = \ - [](epiworld::Agent * p, \ - epiworld::Virus & v , \ - epiworld::Model * m) -> void - -#define EPI_NEW_VIRUSFUN(funname,tseq) inline epiworld_double \ - (funname)( \ - epiworld::Agent * p, \ - epiworld::Virus & v, \ - epiworld::Model * m\ - ) - -#define EPI_NEW_VIRUSFUN_LAMBDA(funname,TSeq) \ - epiworld::VirusFun funname = \ - [](epiworld::Agent * p, \ - epiworld::Virus & v, \ - epiworld::Model * m) -> epiworld_double - -#define EPI_RUNIF() m->runif() - -#define EPIWORLD_RUN(a) \ - if (a.get_verbose()) \ - { \ - printf_epiworld("Running the model...\n");\ - } \ - for (epiworld_fast_uint niter = 0; niter < a.get_ndays(); ++niter) - -#define EPI_TOKENPASTE(a,b) a ## b -#define MPAR(num) *(m->EPI_TOKENPASTE(p,num)) - -#define EPI_NEW_UPDATEFUN(funname,tseq) inline void \ - (funname)(epiworld::Agent * p, epiworld::Model * m) - -#define EPI_NEW_UPDATEFUN_LAMBDA(funname,tseq) \ - epiworld::UpdateFun funname = \ - [](epiworld::Agent * p, epiworld::Model * m) -> void - -#define EPI_NEW_GLOBALFUN(funname,tseq) inline void \ - (funname)(epiworld::Model* m) - -#define EPI_NEW_GLOBALFUN_LAMBDA(funname,tseq) \ - epiworld::GlobalFun funname = \ - [](epiworld::Model* m) -> void - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/epiworld-macros.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/misc.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MISC_HPP -#define EPIWORLD_MISC_HPP - -template -class Model; - -template -class Agent; - -// Relevant for anything using vecHasher function ------------------------------ -/** - * @brief Vector hasher - * @tparam T - */ -template -struct vecHasher { - std::size_t operator()(std::vector< T > const& dat) const noexcept { - - std::hash< T > hasher; - std::size_t hash = hasher(dat[0u]); - - // ^ makes bitwise XOR - // 0x9e3779b9 is a 32 bit constant (comes from the golden ratio) - // << is a shift operator, something like lhs * 2^(rhs) - if (dat.size() > 1u) - for (epiworld_fast_uint i = 1u; i < dat.size(); ++i) - hash ^= hasher(dat[i]) + 0x9e3779b9 + (hash<<6) + (hash>>2); - - return hash; - - } -}; - -template -using MapVec_type = std::unordered_map< std::vector< Ta >, Tb, vecHasher>; - -/** - * @name Default sequence initializers - * - * @details - * If the user does not provide a default sequence, this function is used when - * a sequence needs to be initialized. Some examples: `Agent`, `Virus`, and - * `Tool` need a default sequence. - * - * @tparam TSeq - * @return TSeq - */ -///@{ -template -inline TSeq default_sequence(int seq_count); - -// Making it 'static' so that we don't have problems when including the -// header. This is important during the linkage, e.g., in R. -// See https://en.cppreference.com/w/cpp/language/storage_duration#Linkage -// static int _n_sequences_created = 0; - -template<> -inline bool default_sequence(int seq_count) { - - if (seq_count == 2) - throw std::logic_error("Maximum number of sequence created."); - - return seq_count++ ? false : true; -} - -template<> -inline int default_sequence(int seq_count) { - return seq_count++; -} - -template<> -inline epiworld_double default_sequence(int seq_count) { - return static_cast(seq_count++); -} - -template<> -inline std::vector default_sequence(int seq_count) { - - if (seq_count == 2) - throw std::logic_error("Maximum number of sequence created."); - - return {seq_count++ ? false : true}; -} - -template<> -inline std::vector default_sequence(int seq_count) { - return {seq_count++}; -} - -template<> -inline std::vector default_sequence(int seq_count) { - return {static_cast(seq_count++)}; -} -///@} - -/** - * @brief Check whether `a` is included in `b` - * - * @tparam Ta Type of `a`. Could be int, epiworld_double, etc. - * @param a Scalar of class `Ta`. - * @param b Vector `std::vector` of class `Ta`. - * @return `true` if `a in b`, and `false` otherwise. - */ -template -inline bool IN(const Ta & a, const std::vector< Ta > & b) noexcept -{ - for (const auto & i : b) - if (a == i) - return true; - - return false; -} - -/** - * @brief Conditional Weighted Sampling - * - * @details - * The sampling function will draw one of `{-1, 0,...,probs.size() - 1}` in a - * weighted fashion. The probabilities are drawn given that either one or none - * of the cases is drawn; in the latter returns -1. - * - * @param probs Vector of probabilities. - * @param m A `Model`. This is used to draw random uniform numbers. - * @return int If -1 then it means that none got sampled, otherwise the index - * of the entry that got drawn. - */ -template -inline int roulette( - const std::vector< TDbl > & probs, - Model * m - ) -{ - - // Step 1: Computing the prob on none - TDbl p_none = 1.0; - std::vector< int > certain_infection; - certain_infection.reserve(probs.size()); - - for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) - { - p_none *= (1.0 - probs[p]); - - if (probs[p] > (1 - 1e-100)) - certain_infection.push_back(p); - - } - - TDbl r = static_cast(m->runif()); - // If there are one or more probs that go close to 1, sample - // uniformly - if (certain_infection.size() > 0) - return certain_infection[std::floor(r * certain_infection.size())]; - - // Step 2: Calculating the prob of none or single - std::vector< TDbl > probs_only_p(probs.size()); - TDbl p_none_or_single = p_none; - for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) - { - probs_only_p[p] = probs[p] * (p_none / (1.0 - probs[p])); - p_none_or_single += probs_only_p[p]; - } - - // Step 3: Roulette - TDbl cumsum = p_none/p_none_or_single; - if (r < cumsum) - { - return -1; - } - - for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) - { - // If it yield here, then bingo, the individual will acquire the disease - cumsum += probs_only_p[p]/(p_none_or_single); - if (r < cumsum) - return static_cast(p); - - } - - - #ifdef EPI_DEBUG - printf_epiworld("[epi-debug] roulette::cumsum = %.4f\n", cumsum); - #endif - - return static_cast(probs.size() - 1u); - -} - -template -inline int roulette(std::vector< double > & probs, Model * m) -{ - return roulette(probs, m); -} - -template -inline int roulette(std::vector< float > & probs, Model * m) -{ - return roulette(probs, m); -} - - -template -inline int roulette( - epiworld_fast_uint nelements, - Model * m - ) -{ - - if ((nelements * 2) > m->array_double_tmp.size()) - { - throw std::logic_error( - "Trying to sample from more data than there is in roulette!" + - std::to_string(nelements) + " vs " + - std::to_string(m->array_double_tmp.size()) - ); - } - - // Step 1: Computing the prob on none - epiworld_double p_none = 1.0; - epiworld_fast_uint ncertain = 0u; - // std::vector< int > certain_infection; - for (epiworld_fast_uint p = 0u; p < nelements; ++p) - { - p_none *= (1.0 - m->array_double_tmp[p]); - - if (m->array_double_tmp[p] > (1 - 1e-100)) - m->array_double_tmp[nelements + ncertain++] = p; - // certain_infection.push_back(p); - - } - - epiworld_double r = m->runif(); - // If there are one or more probs that go close to 1, sample - // uniformly - if (ncertain > 0u) - return m->array_double_tmp[nelements + std::floor(ncertain * r)]; // certain_infection[std::floor(r * certain_infection.size())]; - - // Step 2: Calculating the prob of none or single - // std::vector< epiworld_double > probs_only_p; - epiworld_double p_none_or_single = p_none; - for (epiworld_fast_uint p = 0u; p < nelements; ++p) - { - m->array_double_tmp[nelements + p] = - m->array_double_tmp[p] * (p_none / (1.0 - m->array_double_tmp[p])); - p_none_or_single += m->array_double_tmp[nelements + p]; - } - - // Step 3: Roulette - epiworld_double cumsum = p_none/p_none_or_single; - if (r < cumsum) - return -1; - - for (epiworld_fast_uint p = 0u; p < nelements; ++p) - { - // If it yield here, then bingo, the individual will acquire the disease - cumsum += m->array_double_tmp[nelements + p]/(p_none_or_single); - if (r < cumsum) - return static_cast(p); - - } - - return static_cast(nelements - 1u); - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/misc.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/progress.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_PROGRESS_HPP -#define EPIWORLD_PROGRESS_HPP - -#ifndef EPIWORLD_PROGRESS_BAR_WIDTH -#define EPIWORLD_PROGRESS_BAR_WIDTH 80 -#endif - -/** - * @brief A simple progress bar - */ -class Progress { -private: - int width; ///< Total width size (number of bars) - int n; ///< Total number of iterations - epiworld_double step_size; ///< Size of the step - int last_loc; ///< Last location of the bar - int cur_loc; ///< Last location of the bar - int i; ///< Current iteration step - -public: - Progress() {}; - Progress(int n_, int width_); - ~Progress() {}; - - void start(); - void next(); - void end(); - -}; - -inline Progress::Progress(int n_, int width_) { - - - width = std::max(7, width_ - 7); - n = n_; - step_size = static_cast(width)/static_cast(n); - last_loc = 0; - i = 0; - -} - -inline void Progress::start() -{ - - #ifndef EPI_DEBUG - for (int j = 0; j < (width); ++j) - { - printf_epiworld("_"); - } - printf_epiworld("\n"); - #endif -} - -inline void Progress::next() { - - if (i == 0) - start(); - - cur_loc = std::floor((++i) * step_size); - - - #ifndef EPI_DEBUG - for (int j = 0; j < (cur_loc - last_loc); ++j) - { - printf_epiworld("|"); - } - #endif - - if (i >= n) - end(); - - last_loc = cur_loc; - -} - -inline void Progress::end() { - - #ifndef EPI_DEBUG - printf_epiworld(" done.\n"); - #endif - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/progress.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - - // #include "math/summary-stats.hpp" - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/math/lfmcmc.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_LFMCMC_HPP -#define EPIWORLD_LFMCMC_HPP - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//math/lfmcmc/lfmcmc-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_LFMCMC_BONES_HPP -#define EPIWORLD_LFMCMC_BONES_HPP - -#ifndef epiworld_double - #define epiworld_double float -#endif - - -template -class LFMCMC; - -template -using LFMCMCSimFun = std::function&,LFMCMC*)>; - -template -using LFMCMCSummaryFun = std::function&,const TData&,LFMCMC*)>; - -template -using LFMCMCProposalFun = std::function&,const std::vector< epiworld_double >&,LFMCMC*)>; - -template -using LFMCMCKernelFun = std::function&,const std::vector< epiworld_double >&,epiworld_double,LFMCMC*)>; - -/** - * @brief Proposal function - * @param params_now Vector where to save the new parameters. - * @param params_prev Vector of reference parameters. - * @param m LFMCMC model. - * @tparam TData - */ -template -inline void proposal_fun_normal( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -); - -/** - * @brief Factory for a reflective normal kernel - * - * @details Reflective kernel corrects proposals by forcing them to be - * within prespecified boundaries. - * - * @tparam TData - * @param scale Scale of the normal kernel - * @param lb Lower bound (applies the same to all parameters) - * @param ub Upper bound (applies the same to all parameters) - * @return LFMCMCProposalFun - */ -template -inline LFMCMCProposalFun make_proposal_norm_reflective( - epiworld_double scale, - epiworld_double lb = std::numeric_limits::min(), - epiworld_double ub = std::numeric_limits::max() -); - -/** - * @brief Uniform proposal kernel - * - * Proposals are made within a radious 1 of the current - * state of the parameters. - * - * @param params_now Where to write the new parameters - * @param params_prev Reference parameters - * @tparam TData - * @param m LFMCMC model. - */ -template -inline void proposal_fun_unif( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -); - -/** - * @brief Uses the uniform kernel with euclidean distance - * - * @param stats_now Vector of current statistics based on - * simulated data. - * @param stats_obs Vector of observed statistics - * @param epsilon Epsilon parameter - * @param m LFMCMC model. - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_uniform( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -); - -/** - * @brief Gaussian kernel - * - * @tparam TData - * @param epsilon - * @param m - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_gaussian( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -); - -/** - * @brief Likelihood-Free Markov Chain Monte Carlo - * - * @tparam TData Type of data that is generated - */ -template -class LFMCMC { -private: - - // Random number sampling - std::mt19937 * engine = nullptr; - - std::shared_ptr< std::uniform_real_distribution<> > runifd = - std::make_shared< std::uniform_real_distribution<> >(0.0, 1.0); - - std::shared_ptr< std::normal_distribution<> > rnormd = - std::make_shared< std::normal_distribution<> >(0.0); - - std::shared_ptr< std::gamma_distribution<> > rgammad = - std::make_shared< std::gamma_distribution<> >(); - - // Process data - TData * observed_data; - - // Information about the size of the problem - size_t n_samples; - size_t n_statistics; - size_t n_parameters; - - epiworld_double epsilon; - - std::vector< epiworld_double > params_now; - std::vector< epiworld_double > params_prev; - std::vector< epiworld_double > params_init; - - std::vector< epiworld_double > observed_stats; ///< Observed statistics - - std::vector< epiworld_double > sampled_params; ///< Sampled Parameters - std::vector< epiworld_double > sampled_stats; ///< Sampled statistics - std::vector< epiworld_double > sampled_stats_prob; ///< Sampled statistics - std::vector< bool > sampled_accepted; ///< Indicator of accepted statistics - - std::vector< epiworld_double > accepted_params; ///< Posterior distribution (accepted samples) - std::vector< epiworld_double > accepted_stats; ///< Posterior distribution (accepted samples) - std::vector< epiworld_double > accepted_params_prob; ///< Posterior probability - - std::vector< epiworld_double > drawn_prob; ///< Drawn probabilities (runif()) - std::vector< TData > * sampled_data = nullptr; - - // Functions - LFMCMCSimFun simulation_fun; - LFMCMCSummaryFun summary_fun; - LFMCMCProposalFun proposal_fun = proposal_fun_normal; - LFMCMCKernelFun kernel_fun = kernel_fun_uniform; - - // Misc - std::vector< std::string > names_parameters; - std::vector< std::string > names_statistics; - - std::chrono::time_point time_start; - std::chrono::time_point time_end; - - // std::chrono::milliseconds - std::chrono::duration time_elapsed = - std::chrono::duration::zero(); - - inline void get_elapsed( - std::string unit, - epiworld_double * last_elapsed, - std::string * unit_abbr, - bool print - ); - - void chrono_start(); - void chrono_end(); - -public: - - void run( - std::vector< epiworld_double > param_init, - size_t n_samples_, - epiworld_double epsilon_ - ); - - LFMCMC() {}; - LFMCMC(TData & observed_data_) : observed_data(&observed_data_) {}; - ~LFMCMC() {}; - - void set_observed_data(TData & observed_data_) {observed_data = &observed_data_;}; - void set_proposal_fun(LFMCMCProposalFun fun); - void set_simulation_fun(LFMCMCSimFun fun); - void set_summary_fun(LFMCMCSummaryFun fun); - void set_kernel_fun(LFMCMCKernelFun fun); - - /** - * @name Random number generation - * - * @param eng - */ - ///@{ - void set_rand_engine(std::mt19937 & eng); - std::mt19937 & get_rand_endgine(); - void seed(epiworld_fast_uint s); - void set_rand_gamma(epiworld_double alpha, epiworld_double beta); - epiworld_double runif(); - epiworld_double rnorm(); - epiworld_double rgamma(); - epiworld_double runif(epiworld_double lb, epiworld_double ub); - epiworld_double rnorm(epiworld_double mean, epiworld_double sd); - epiworld_double rgamma(epiworld_double alpha, epiworld_double beta); - ///@} - - // Accessing parameters of the function - size_t get_n_samples() const {return n_samples;}; - size_t get_n_statistics() const {return n_statistics;}; - size_t get_n_parameters() const {return n_parameters;}; - epiworld_double get_epsilon() const {return epsilon;}; - - const std::vector< epiworld_double > & get_params_now() {return params_now;}; - const std::vector< epiworld_double > & get_params_prev() {return params_prev;}; - const std::vector< epiworld_double > & get_params_init() {return params_init;}; - const std::vector< epiworld_double > & get_statistics_obs() {return observed_stats;}; - const std::vector< epiworld_double > & get_statistics_hist() {return sampled_stats;}; - const std::vector< bool > & get_statistics_accepted() {return sampled_accepted;}; - const std::vector< epiworld_double > & get_posterior_lf_prob() {return accepted_params_prob;}; - const std::vector< epiworld_double > & get_drawn_prob() {return drawn_prob;}; - std::vector< TData > * get_sampled_data() {return sampled_data;}; - - void set_par_names(std::vector< std::string > names); - void set_stats_names(std::vector< std::string > names); - - std::vector< epiworld_double > get_params_mean(); - std::vector< epiworld_double > get_stats_mean(); - - void print() ; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//math/lfmcmc/lfmcmc-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//math/lfmcmc/lfmcmc-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_LFMCMC_MEAT_HPP -#define EPIWORLD_LFMCMC_MEAT_HPP - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//math//lfmcmc/lfmcmc-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_LFMCMC_BONES_HPP -#define EPIWORLD_LFMCMC_BONES_HPP - -#ifndef epiworld_double - #define epiworld_double float -#endif - - -template -class LFMCMC; - -template -using LFMCMCSimFun = std::function&,LFMCMC*)>; - -template -using LFMCMCSummaryFun = std::function&,const TData&,LFMCMC*)>; - -template -using LFMCMCProposalFun = std::function&,const std::vector< epiworld_double >&,LFMCMC*)>; - -template -using LFMCMCKernelFun = std::function&,const std::vector< epiworld_double >&,epiworld_double,LFMCMC*)>; - -/** - * @brief Proposal function - * @param params_now Vector where to save the new parameters. - * @param params_prev Vector of reference parameters. - * @param m LFMCMC model. - * @tparam TData - */ -template -inline void proposal_fun_normal( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -); - -/** - * @brief Factory for a reflective normal kernel - * - * @details Reflective kernel corrects proposals by forcing them to be - * within prespecified boundaries. - * - * @tparam TData - * @param scale Scale of the normal kernel - * @param lb Lower bound (applies the same to all parameters) - * @param ub Upper bound (applies the same to all parameters) - * @return LFMCMCProposalFun - */ -template -inline LFMCMCProposalFun make_proposal_norm_reflective( - epiworld_double scale, - epiworld_double lb = std::numeric_limits::min(), - epiworld_double ub = std::numeric_limits::max() -); - -/** - * @brief Uniform proposal kernel - * - * Proposals are made within a radious 1 of the current - * state of the parameters. - * - * @param params_now Where to write the new parameters - * @param params_prev Reference parameters - * @tparam TData - * @param m LFMCMC model. - */ -template -inline void proposal_fun_unif( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -); - -/** - * @brief Uses the uniform kernel with euclidean distance - * - * @param stats_now Vector of current statistics based on - * simulated data. - * @param stats_obs Vector of observed statistics - * @param epsilon Epsilon parameter - * @param m LFMCMC model. - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_uniform( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -); - -/** - * @brief Gaussian kernel - * - * @tparam TData - * @param epsilon - * @param m - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_gaussian( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -); - -/** - * @brief Likelihood-Free Markov Chain Monte Carlo - * - * @tparam TData Type of data that is generated - */ -template -class LFMCMC { -private: - - // Random number sampling - std::mt19937 * engine = nullptr; - - std::shared_ptr< std::uniform_real_distribution<> > runifd = - std::make_shared< std::uniform_real_distribution<> >(0.0, 1.0); - - std::shared_ptr< std::normal_distribution<> > rnormd = - std::make_shared< std::normal_distribution<> >(0.0); - - std::shared_ptr< std::gamma_distribution<> > rgammad = - std::make_shared< std::gamma_distribution<> >(); - - // Process data - TData * observed_data; - - // Information about the size of the problem - size_t n_samples; - size_t n_statistics; - size_t n_parameters; - - epiworld_double epsilon; - - std::vector< epiworld_double > params_now; - std::vector< epiworld_double > params_prev; - std::vector< epiworld_double > params_init; - - std::vector< epiworld_double > observed_stats; ///< Observed statistics - - std::vector< epiworld_double > sampled_params; ///< Sampled Parameters - std::vector< epiworld_double > sampled_stats; ///< Sampled statistics - std::vector< epiworld_double > sampled_stats_prob; ///< Sampled statistics - std::vector< bool > sampled_accepted; ///< Indicator of accepted statistics - - std::vector< epiworld_double > accepted_params; ///< Posterior distribution (accepted samples) - std::vector< epiworld_double > accepted_stats; ///< Posterior distribution (accepted samples) - std::vector< epiworld_double > accepted_params_prob; ///< Posterior probability - - std::vector< epiworld_double > drawn_prob; ///< Drawn probabilities (runif()) - std::vector< TData > * sampled_data = nullptr; - - // Functions - LFMCMCSimFun simulation_fun; - LFMCMCSummaryFun summary_fun; - LFMCMCProposalFun proposal_fun = proposal_fun_normal; - LFMCMCKernelFun kernel_fun = kernel_fun_uniform; - - // Misc - std::vector< std::string > names_parameters; - std::vector< std::string > names_statistics; - - std::chrono::time_point time_start; - std::chrono::time_point time_end; - - // std::chrono::milliseconds - std::chrono::duration time_elapsed = - std::chrono::duration::zero(); - - inline void get_elapsed( - std::string unit, - epiworld_double * last_elapsed, - std::string * unit_abbr, - bool print - ); - - void chrono_start(); - void chrono_end(); - -public: - - void run( - std::vector< epiworld_double > param_init, - size_t n_samples_, - epiworld_double epsilon_ - ); - - LFMCMC() {}; - LFMCMC(TData & observed_data_) : observed_data(&observed_data_) {}; - ~LFMCMC() {}; - - void set_observed_data(TData & observed_data_) {observed_data = &observed_data_;}; - void set_proposal_fun(LFMCMCProposalFun fun); - void set_simulation_fun(LFMCMCSimFun fun); - void set_summary_fun(LFMCMCSummaryFun fun); - void set_kernel_fun(LFMCMCKernelFun fun); - - /** - * @name Random number generation - * - * @param eng - */ - ///@{ - void set_rand_engine(std::mt19937 & eng); - std::mt19937 & get_rand_endgine(); - void seed(epiworld_fast_uint s); - void set_rand_gamma(epiworld_double alpha, epiworld_double beta); - epiworld_double runif(); - epiworld_double rnorm(); - epiworld_double rgamma(); - epiworld_double runif(epiworld_double lb, epiworld_double ub); - epiworld_double rnorm(epiworld_double mean, epiworld_double sd); - epiworld_double rgamma(epiworld_double alpha, epiworld_double beta); - ///@} - - // Accessing parameters of the function - size_t get_n_samples() const {return n_samples;}; - size_t get_n_statistics() const {return n_statistics;}; - size_t get_n_parameters() const {return n_parameters;}; - epiworld_double get_epsilon() const {return epsilon;}; - - const std::vector< epiworld_double > & get_params_now() {return params_now;}; - const std::vector< epiworld_double > & get_params_prev() {return params_prev;}; - const std::vector< epiworld_double > & get_params_init() {return params_init;}; - const std::vector< epiworld_double > & get_statistics_obs() {return observed_stats;}; - const std::vector< epiworld_double > & get_statistics_hist() {return sampled_stats;}; - const std::vector< bool > & get_statistics_accepted() {return sampled_accepted;}; - const std::vector< epiworld_double > & get_posterior_lf_prob() {return accepted_params_prob;}; - const std::vector< epiworld_double > & get_drawn_prob() {return drawn_prob;}; - std::vector< TData > * get_sampled_data() {return sampled_data;}; - - void set_par_names(std::vector< std::string > names); - void set_stats_names(std::vector< std::string > names); - - std::vector< epiworld_double > get_params_mean(); - std::vector< epiworld_double > get_stats_mean(); - - void print() ; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//math//lfmcmc/lfmcmc-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/** - * @brief Proposal function - * @param params_now Vector where to save the new parameters. - * @param params_prev Vector of reference parameters. - * @param m LFMCMC model. - * @tparam TData - */ -template -inline void proposal_fun_normal( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -) { - - for (size_t p = 0u; p < m->get_n_parameters(); ++p) - params_now[p] = params_prev[p] + m->rnorm(); - - return; -} - -/** - * @brief Factory for a reflective normal kernel - * - * @details Reflective kernel corrects proposals by forcing them to be - * within prespecified boundaries. - * - * @tparam TData - * @param scale Scale of the normal kernel - * @param lb Lower bound (applies the same to all parameters) - * @param ub Upper bound (applies the same to all parameters) - * @return LFMCMCProposalFun - */ -template -inline LFMCMCProposalFun make_proposal_norm_reflective( - epiworld_double scale, - epiworld_double lb, - epiworld_double ub -) { - - LFMCMCProposalFun fun = - [scale,lb,ub]( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m - ) { - - // Making the proposal - for (size_t p = 0u; p < m->get_n_parameters(); ++p) - params_now[p] = params_prev[p] + m->rnorm() * scale; - - // Checking boundaries - epiworld_double d = ub - lb; - int odd; - epiworld_double d_above, d_below; - for (auto & p : params_now) - { - - // Correcting if parameter goes above the upper bound - if (p > ub) - { - d_above = p - ub; - odd = static_cast(std::floor(d_above / d)) % 2; - d_above = d_above - std::floor(d_above / d) * d; - - p = (lb + d_above) * odd + - (ub - d_above) * (1 - odd); - - // Correcting if parameter goes below upper bound - } else if (p < lb) - { - d_below = lb - p; - int odd = static_cast(std::floor(d_below / d)) % 2; - d_below = d_below - std::floor(d_below / d) * d; - - p = (ub - d_below) * odd + - (lb + d_below) * (1 - odd); - } - - } - - return; - - }; - - return fun; -} - -/** - * @brief Uniform proposal kernel - * - * Proposals are made within a radious 1 of the current - * state of the parameters. - * - * @param params_now Where to write the new parameters - * @param params_prev Reference parameters - * @tparam TData - * @param m LFMCMC model. - */ -template -inline void proposal_fun_unif( - std::vector< epiworld_double >& params_now, - const std::vector< epiworld_double >& params_prev, - LFMCMC* m -) { - - for (size_t p = 0u; p < m->get_n_parameters(); ++p) - params_now[p] = (params_prev[p] + m->runif(-1.0, 1.0)); - - return; -} - -/** - * @brief Uses the uniform kernel with euclidean distance - * - * @param stats_now Vector of current statistics based on - * simulated data. - * @param stats_obs Vector of observed statistics - * @param epsilon Epsilon parameter - * @param m LFMCMC model. - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_uniform( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -) { - - epiworld_double ans = 0.0; - for (size_t p = 0u; p < m->get_n_parameters(); ++p) - ans += std::pow(stats_obs[p] - stats_now[p], 2.0); - - return std::sqrt(ans) < epsilon ? 1.0 : 0.0; - -} - -#define sqrt2pi() 2.5066282746310002416 - -/** - * @brief Gaussian kernel - * - * @tparam TData - * @param epsilon - * @param m - * @return epiworld_double - */ -template -inline epiworld_double kernel_fun_gaussian( - const std::vector< epiworld_double >& stats_now, - const std::vector< epiworld_double >& stats_obs, - epiworld_double epsilon, - LFMCMC* m -) { - - epiworld_double ans = 0.0; - for (size_t p = 0u; p < m->get_n_parameters(); ++p) - ans += std::pow(stats_obs[p] - stats_now[p], 2.0); - - return std::exp( - -.5 * (ans/std::pow(1 + std::pow(epsilon, 2.0)/3.0, 2.0)) - ) / sqrt2pi() ; - -} - - -template -inline void LFMCMC::set_proposal_fun(LFMCMCProposalFun fun) -{ - proposal_fun = fun; -} - -template -inline void LFMCMC::set_simulation_fun(LFMCMCSimFun fun) -{ - simulation_fun = fun; -} - -template -inline void LFMCMC::set_summary_fun(LFMCMCSummaryFun fun) -{ - summary_fun = fun; -} - -template -inline void LFMCMC::set_kernel_fun(LFMCMCKernelFun fun) -{ - kernel_fun = fun; -} - - -template -inline void LFMCMC::run( - std::vector< epiworld_double > params_init_, - size_t n_samples_, - epiworld_double epsilon_ - ) -{ - - // Starting timing - chrono_start(); - - // Setting the baseline parameters of the model - n_samples = n_samples_; - epsilon = epsilon_; - params_init = params_init_; - n_parameters = params_init_.size(); - - params_now.resize(n_parameters); - params_prev.resize(n_parameters); - - if (sampled_data != nullptr) - sampled_data->resize(n_samples); - - params_prev = params_init; - params_now = params_init; - - // Computing the baseline sufficient statistics - summary_fun(observed_stats, *observed_data, this); - n_statistics = observed_stats.size(); - - // Reserving size - drawn_prob.resize(n_samples); - sampled_accepted.resize(n_samples, false); - sampled_stats.resize(n_samples * n_statistics); - sampled_stats_prob.resize(n_samples); - - accepted_params.resize(n_samples * n_parameters); - accepted_stats.resize(n_samples * n_statistics); - accepted_params_prob.resize(n_samples); - - TData data_i = simulation_fun(params_init, this); - - std::vector< epiworld_double > proposed_stats_i; - summary_fun(proposed_stats_i, data_i, this); - accepted_params_prob[0u] = kernel_fun(proposed_stats_i, observed_stats, epsilon, this); - - // Recording statistics - for (size_t i = 0u; i < n_statistics; ++i) - sampled_stats[i] = proposed_stats_i[i]; - - for (size_t k = 0u; k < n_statistics; ++k) - accepted_params[k] = proposed_stats_i[k]; - - for (size_t i = 1u; i < n_samples; ++i) - { - // Step 1: Generate a proposal and store it in params_now - proposal_fun(params_now, params_prev, this); - - // Step 2: Using params_now, simulate data - TData data_i = simulation_fun(params_now, this); - - // Are we storing the data? - if (sampled_data != nullptr) - sampled_data->operator[](i) = data_i; - - // Step 3: Generate the summary statistics of the data - summary_fun(proposed_stats_i, data_i, this); - - // Step 4: Compute the hastings ratio using the kernel function - epiworld_double hr = kernel_fun(proposed_stats_i, observed_stats, epsilon, this); - sampled_stats_prob[i] = hr; - - // Storing data - for (size_t k = 0u; k < n_statistics; ++k) - sampled_stats[i * n_statistics + k] = proposed_stats_i[k]; - - // Running Hastings ratio - epiworld_double r = runif(); - drawn_prob[i] = r; - - // Step 5: Update if likely - if (r < std::min(static_cast(1.0), hr / accepted_params_prob[i - 1u])) - { - accepted_params_prob[i] = hr; - sampled_accepted[i] = true; - - for (size_t k = 0u; k < n_statistics; ++k) - accepted_stats[i * n_statistics + k] = - proposed_stats_i[k]; - - params_prev = params_now; - - } else - { - - for (size_t k = 0u; k < n_statistics; ++k) - accepted_stats[i * n_statistics + k] = - accepted_stats[(i - 1) * n_statistics + k]; - - accepted_params_prob[i] = accepted_params_prob[i - 1u]; - } - - - for (size_t k = 0u; k < n_parameters; ++k) - accepted_params[i * n_parameters + k] = params_prev[k]; - - } - - // End timing - chrono_end(); - -} - - -template -inline epiworld_double LFMCMC::runif() -{ - return runifd->operator()(*engine); -} - -template -inline epiworld_double LFMCMC::runif( - epiworld_double lb, - epiworld_double ub -) -{ - return runifd->operator()(*engine) * (ub - lb) + lb; -} - -template -inline epiworld_double LFMCMC::rnorm() -{ - return rnormd->operator()(*engine); -} - -template -inline epiworld_double LFMCMC::rnorm( - epiworld_double mean, - epiworld_double sd - ) -{ - return (rnormd->operator()(*engine)) * sd + mean; -} - -template -inline epiworld_double LFMCMC::rgamma() -{ - return rgammad->operator()(*engine); -} - -template -inline epiworld_double LFMCMC::rgamma( - epiworld_double alpha, - epiworld_double beta - ) -{ - - auto old_param = rgammad->param(); - - rgammad->param(std::gamma_distribution<>::param_type(alpha, beta)); - - epiworld_double ans = rgammad->operator()(*engine); - - rgammad->param(old_param); - - return ans; - -} - -template -inline void LFMCMC::seed(epiworld_fast_uint s) { - - this->engine->seed(s); - -} - -template -inline void LFMCMC::set_rand_engine(std::mt19937 & eng) -{ - engine = ŋ -} - -template -inline void LFMCMC::set_rand_gamma(epiworld_double alpha, epiworld_double beta) -{ - rgammad = std::make_shared>(alpha,beta); -} - -template -inline std::mt19937 & LFMCMC::get_rand_endgine() -{ - return *engine; -} - -// Step 1: Simulate data - -// Step 2: Compute the sufficient statistics - -// Step 3: Compute the hastings-ratio - -// Step 4: Accept/reject, and go back to step 1 - -#define DURCAST(tunit,txtunit) {\ - elapsed = std::chrono::duration_cast(\ - time_end - time_start).count(); \ - abbr_unit = txtunit;} - -template -inline void LFMCMC::get_elapsed( - std::string unit, - epiworld_double * last_elapsed, - std::string * unit_abbr, - bool print -) { - - // Preparing the result - epiworld_double elapsed; - std::string abbr_unit; - - // Figuring out the length - if (unit == "auto") - { - - size_t tlength = std::to_string( - static_cast(floor(time_elapsed.count())) - ).length(); - - if (tlength <= 1) - unit = "nanoseconds"; - else if (tlength <= 3) - unit = "microseconds"; - else if (tlength <= 6) - unit = "milliseconds"; - else if (tlength <= 8) - unit = "seconds"; - else if (tlength <= 9) - unit = "minutes"; - else - unit = "hours"; - - } - - if (unit == "nanoseconds") DURCAST(nanoseconds,"ns") - else if (unit == "microseconds") DURCAST(microseconds,"\xC2\xB5s") - else if (unit == "milliseconds") DURCAST(milliseconds,"ms") - else if (unit == "seconds") DURCAST(seconds,"s") - else if (unit == "minutes") DURCAST(minutes,"m") - else if (unit == "hours") DURCAST(hours,"h") - else - throw std::range_error("The time unit " + unit + " is not supported."); - - - if (last_elapsed != nullptr) - *last_elapsed = elapsed; - if (unit_abbr != nullptr) - *unit_abbr = abbr_unit; - - if (!print) - return; - - printf_epiworld("Elapsed time : %.2f%s.\n", elapsed, abbr_unit.c_str()); -} - -#undef DURCAST - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//math//lfmcmc/lfmcmc-meat-print.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef LFMCMC_MEAT_PRINT_HPP -#define LFMCMC_MEAT_PRINT_HPP - -template -inline void LFMCMC::print() -{ - std::vector< epiworld_double > summ_params(n_parameters * 3, 0.0); - std::vector< epiworld_double > summ_stats(n_statistics * 3, 0.0); - - for (size_t k = 0u; k < n_parameters; ++k) - { - - // Retrieving the relevant parameter - std::vector< epiworld_double > par_i(n_samples); - for (size_t i = 0u; i < n_samples; ++i) - { - par_i[i] = accepted_params[i * n_parameters + k]; - summ_params[k * 3] += par_i[i]/n_samples; - } - - // Computing the 95% Credible interval - std::sort(par_i.begin(), par_i.end()); - - summ_params[k * 3 + 1u] = - par_i[std::floor(.025 * static_cast(n_samples))]; - summ_params[k * 3 + 2u] = - par_i[std::floor(.975 * static_cast(n_samples))]; - - } - - for (size_t k = 0u; k < n_statistics; ++k) - { - - // Retrieving the relevant parameter - std::vector< epiworld_double > stat_k(n_samples); - for (size_t i = 0u; i < n_samples; ++i) - { - stat_k[i] = accepted_stats[i * n_statistics + k]; - summ_stats[k * 3] += stat_k[i]/n_samples; - } - - // Computing the 95% Credible interval - std::sort(stat_k.begin(), stat_k.end()); - - summ_stats[k * 3 + 1u] = - stat_k[std::floor(.025 * static_cast(n_samples))]; - summ_stats[k * 3 + 2u] = - stat_k[std::floor(.975 * static_cast(n_samples))]; - - } - - printf_epiworld("___________________________________________\n\n"); - printf_epiworld("LIKELIHOOD-FREE MARKOV CHAIN MONTE CARLO\n\n"); - - printf_epiworld("N Samples : %ld\n", n_samples); - - std::string abbr; - epiworld_double elapsed; - get_elapsed("auto", &elapsed, &abbr, false); - printf_epiworld("Elapsed t : %.2f%s\n\n", elapsed, abbr.c_str()); - - //////////////////////////////////////////////////////////////////////////// - // PARAMETERS - //////////////////////////////////////////////////////////////////////////// - printf_epiworld("Parameters:\n"); - - // Figuring out format - std::string fmt_params; - - int nchar_par_num = 0; - for (auto & n : summ_params) - { - - int tmp_nchar = std::floor(std::log10(std::abs(n))); - if (nchar_par_num < tmp_nchar) - nchar_par_num = tmp_nchar; - } - nchar_par_num += 5; // 1 for neg padd, 2 for decimals, 1 the decimal point, and one b/c log(<10) < 1. - std::string charlen = std::to_string(nchar_par_num); - - if (names_parameters.size() != 0u) - { - int nchar_par = 0; - for (auto & n : names_parameters) - { - int tmp_nchar = n.length(); - if (nchar_par < tmp_nchar) - nchar_par = tmp_nchar; - } - - fmt_params = std::string(" -%-") + - std::to_string(nchar_par) + - std::string("s : % ") + charlen + - std::string(".2f [% ") + charlen + - std::string(".2f, % ") + charlen + - std::string(".2f] (initial : % ") + - charlen + std::string(".2f)\n"); - - for (size_t k = 0u; k < n_parameters; ++k) - { - printf_epiworld( - fmt_params.c_str(), - names_parameters[k].c_str(), - summ_params[k * 3], - summ_params[k * 3 + 1u], - summ_params[k * 3 + 2u], - params_init[k] - ); - } - - - } else { - - fmt_params = std::string(" [%-2ld]: % ") + charlen + - std::string(".2f [% ") + charlen + - std::string(".2f, % ") + charlen + - std::string(".2f] (initial : % ") + charlen + - std::string(".2f)\n"); - - for (size_t k = 0u; k < n_parameters; ++k) - { - - printf_epiworld( - fmt_params.c_str(), - k, - summ_params[k * 3], - summ_params[k * 3 + 1u], - summ_params[k * 3 + 2u], - params_init[k] - ); - } - - } - - //////////////////////////////////////////////////////////////////////////// - // Statistics - //////////////////////////////////////////////////////////////////////////// - printf_epiworld("\nStatistics:\n"); - int nchar = 0; - for (auto & s : summ_stats) - { - int tmp_nchar = std::floor(std::log10(std::abs(s))); - if (nchar < tmp_nchar) - nchar = tmp_nchar; - } - - nchar += 5; // See above - - std::string nchar_char = std::to_string(nchar); - - // Figuring out format - std::string fmt_stats; - if (names_statistics.size() != 0u) - { - int nchar_stats = 0; - for (auto & n : names_statistics) - { - int tmp_nchar = n.length(); - if (nchar_stats < tmp_nchar) - nchar_stats = tmp_nchar; - } - - fmt_stats = std::string(" -%-") + - std::to_string(nchar_stats) + - std::string("s : % ") + nchar_char + - std::string(".2f [% ") + nchar_char + - std::string(".2f, % ") + nchar_char + - std::string(".2f] (Observed: % ") + nchar_char + - std::string(".2f)\n"); - - for (size_t k = 0u; k < n_statistics; ++k) - { - printf_epiworld( - fmt_stats.c_str(), - names_statistics[k].c_str(), - summ_stats[k * 3], - summ_stats[k * 3 + 1u], - summ_stats[k * 3 + 2u], - observed_stats[k] - ); - } - - - } else { - - fmt_stats = std::string(" [%-2ld] : % ") + - nchar_char + - std::string(".2f [% ") + nchar_char + - std::string(".2f, % ") + nchar_char + - std::string(".2f] (Observed: % ") + nchar_char + - std::string(".2f)\n"); - - for (size_t k = 0u; k < n_statistics; ++k) - { - printf_epiworld( - fmt_stats.c_str(), - k, - summ_stats[k * 3], - summ_stats[k * 3 + 1u], - summ_stats[k * 3 + 2u], - observed_stats[k] - ); - } - - } - - printf_epiworld("___________________________________________\n\n"); -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//math//lfmcmc/lfmcmc-meat-print.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -template -inline void LFMCMC::chrono_start() { - time_start = std::chrono::steady_clock::now(); -} - -template -inline void LFMCMC::chrono_end() { - time_end = std::chrono::steady_clock::now(); - time_elapsed += (time_end - time_start); -} - -template -inline void LFMCMC::set_par_names(std::vector< std::string > names) -{ - - if (names.size() != n_parameters) - throw std::length_error("The number of names to add differs from the number of parameters in the model."); - - names_parameters = names; - -} -template -inline void LFMCMC::set_stats_names(std::vector< std::string > names) -{ - - if (names.size() != n_statistics) - throw std::length_error("The number of names to add differs from the number of statistics in the model."); - - names_statistics = names; - -} - -template -inline std::vector< epiworld_double > LFMCMC::get_params_mean() -{ - std::vector< epiworld_double > res(this->n_parameters, 0.0); - - for (size_t k = 0u; k < n_parameters; ++k) - { - for (size_t i = 0u; i < n_samples; ++i) - res[k] += (this->accepted_params[k + n_parameters * i])/ - static_cast< epiworld_double >(n_samples); - } - - return res; - -} - -template -inline std::vector< epiworld_double > LFMCMC::get_stats_mean() -{ - std::vector< epiworld_double > res(this->n_statistics, 0.0); - - for (size_t k = 0u; k < n_statistics; ++k) - { - for (size_t i = 0u; i < n_samples; ++i) - res[k] += (this->accepted_stats[k + n_statistics * i])/ - static_cast< epiworld_double >(n_samples); - } - - return res; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//math/lfmcmc/lfmcmc-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/math/lfmcmc.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/userdata-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_USERDATA_BONES_HPP -#define EPIWORLD_USERDATA_BONES_HPP - -template -class Model; - -template -class DataBase; - -/** - * @brief Personalized data by the user - * - * @tparam TSeq - */ -template -class UserData -{ - friend class Model; - friend class DataBase; - -private: - Model * model; - - std::vector< std::string > data_names; - std::vector< int > data_dates; - std::vector< epiworld_double > data_data; - - epiworld_fast_uint k = 0u; - epiworld_fast_uint n = 0u; - - int last_day = -1; - -public: - - UserData() = delete; - UserData(Model & m) : model(&m) {}; - UserData(Model * m) : model(m) {}; - - /** - * @brief Construct a new User Data object - * - * @param names A vector of names. The length of the vector sets - * the number of columns to record. - */ - UserData(std::vector< std::string > names); - - /** - * @name Append data - * - * @param x A vector of length `ncol()` (if vector), otherwise a `epiworld_double`. - * @param j Index of the data point, from 0 to `ncol() - 1`. - */ - ///@{ - void add(std::vector x); - void add( - epiworld_fast_uint j, - epiworld_double x - ); - ///@} - - /** - * @name Access data - * - * @param i Row (0 through ndays - 1.) - * @param j Column (0 through `ncols()`). - * @return epiworld_double& - */ - ///@{ - epiworld_double & operator()( - epiworld_fast_uint i, - epiworld_fast_uint j - ); - - epiworld_double & operator()( - epiworld_fast_uint i, - std::string name - ); - ///@} - - std::vector< std::string > & get_names(); - - std::vector< int > & get_dates(); - - std::vector< epiworld_double > & get_data(); - - void get_all( - std::vector< std::string > * names = nullptr, - std::vector< int > * date = nullptr, - std::vector< epiworld_double > * data = nullptr - ); - - epiworld_fast_uint nrow() const; - epiworld_fast_uint ncol() const; - - void write(std::string fn); - void print() const; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/userdata-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/userdata-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_USERDATA_MEAT_HPP -#define EPIWORLD_USERDATA_MEAT_HPP - -template -class UserData; - -template -inline UserData::UserData(std::vector< std::string > names) -{ - - k = names.size(); - data_names = names; - -} - -template -inline void UserData::add(std::vector x) -{ - - if (x.size() != k) - throw std::out_of_range( - "The size of -x-, " + std::to_string(x.size()) + ", does not match " + - "the number of elements registered (" + std::to_string(k)); - - for (auto & i : x) - data_data.push_back(i); - - data_dates.push_back(model->today()); - - n++; - last_day = model->today(); - -} - -template -inline void UserData::add(epiworld_fast_uint j, epiworld_double x) -{ - - // Starting with a new day? - if (static_cast(model->today()) != last_day) - { - - std::vector< epiworld_double > tmp(k, 0.0); - - tmp[j] = x; - - add(tmp); - - } - else - { - - this->operator()(n - 1, j) = x; - - } - -} - -template -inline std::vector< std::string > & UserData::get_names() -{ - return data_names; -} - -template -inline std::vector< int > & UserData::get_dates() -{ - return data_dates; -} - -template -inline std::vector< epiworld_double > & UserData::get_data() -{ - return data_data; -} - -template -inline void UserData::get_all( - std::vector< std::string > * names, - std::vector< int > * date, - std::vector< epiworld_double > * data -) -{ - - if (names != nullptr) - names = &this->data_names; - - if (date != nullptr) - date = &this->data_dates; - - if (data != nullptr) - data = &this->data_data; - -} - -template -inline epiworld_double & UserData::operator()( - epiworld_fast_uint i, - epiworld_fast_uint j -) -{ - - if (j >= k) - throw std::out_of_range("j cannot be greater than k - 1."); - - if (i >= n) - throw std::out_of_range("j cannot be greater than n - 1."); - - return data_data[k * i + j]; - -} - -template -inline epiworld_double & UserData::operator()( - epiworld_fast_uint i, - std::string name -) -{ - int loc = -1; - for (epiworld_fast_uint l = 0u; l < k; ++l) - { - - if (name == data_names[l]) - { - - loc = l; - break; - - } - - } - - if (loc < 0) - throw std::range_error( - "The variable \"" + name + "\" is not present " + - "in the user UserData database." - ); - - return operator()(i, static_cast(loc)); - -} - -template -inline epiworld_fast_uint UserData::nrow() const -{ - return n; -} - -template -inline epiworld_fast_uint UserData::ncol() const -{ - return k; -} - -template -inline void UserData::write(std::string fn) -{ - std::ofstream file_ud(fn, std::ios_base::out); - - // File header - file_ud << "\"date\""; - for (auto & cn : data_names) - file_ud << " \"" + cn + "\""; - file_ud << "\n"; - - epiworld_fast_uint ndata = 0u; - for (epiworld_fast_uint i = 0u; i < n; ++i) - { - file_ud << data_dates[i]; - - for (epiworld_fast_uint j = 0u; j < k; ++j) - file_ud << " " << data_data[ndata++]; - - file_ud << "\n"; - } - - return; -} - -template -inline void UserData::print() const -{ - // File header - printf_epiworld("Total records: %llu\n", n); - printf_epiworld("date"); - - for (auto & cn : data_names) - { - - printf_epiworld(" %s", cn.c_str()); - - } - - printf_epiworld("\n"); - - epiworld_fast_uint ndata = 0u; - - for (epiworld_fast_uint i = 0u; i < n; ++i) - { - - printf_epiworld("%i", data_dates[i]); - - for (epiworld_fast_uint j = 0u; j < k; ++j) - { - - printf_epiworld(" %.2f", data_data[ndata++]); - - } - - printf_epiworld("\n"); - - } - - return; -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/userdata-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/seq_processing.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_SEQ_PROCESSING_HPP -#define EPIWORLD_SEQ_PROCESSING_HPP - -/** - * @brief Hasher function to turn the sequence into an integer vector - * - * @tparam TSeq - * @param x - * @return std::vector - */ -template -inline std::vector default_seq_hasher(const TSeq & x); - -template<> -inline std::vector default_seq_hasher>(const std::vector & x) { - return x; -} - -template<> -inline std::vector default_seq_hasher>(const std::vector & x) { - std::vector ans(x.size()); - size_t j = 0; - for (const auto & i : x) - ans[j++] = i? 1 : 0; - return ans; -} - -template<> -inline std::vector default_seq_hasher(const int & x) { - return {x}; -} - -template<> -inline std::vector default_seq_hasher(const bool & x) { - return {x ? 1 : 0}; -} - -/** - * @brief Default way to write sequences - * - * @tparam TSeq - * @param seq - * @return std::string - */ -template -inline std::string default_seq_writer(const TSeq & seq); - -template<> -inline std::string default_seq_writer>( - const std::vector & seq -) { - - std::string out = ""; - for (const auto & s : seq) - out = out + std::to_string(s); - - return out; - -} - -template<> -inline std::string default_seq_writer>( - const std::vector & seq -) { - - std::string out = ""; - for (const auto & s : seq) - out = out + (s ? "1" : "0"); - - return out; - -} - -template<> -inline std::string default_seq_writer( - const bool & seq -) { - - return seq ? "1" : "0"; - -} - -template<> -inline std::string default_seq_writer( - const int & seq -) { - - return std::to_string(seq); - -} - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/seq_processing.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/database-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_DATABASE_BONES_HPP -#define EPIWORLD_DATABASE_BONES_HPP - -template -class Model; - -template -class Virus; - -template -class UserData; - -template -inline void default_add_virus(Action & a, Model * m); - -template -inline void default_add_tool(Action & a, Model * m); - -template -inline void default_rm_virus(Action & a, Model * m); - -template -inline void default_rm_tool(Action & a, Model * m); - -/** - * @brief Statistical data about the process - * - * @tparam TSeq - */ -template -class DataBase { - friend class Model; - friend void default_add_virus(Action & a, Model * m); - friend void default_add_tool(Action & a, Model * m); - friend void default_rm_virus(Action & a, Model * m); - friend void default_rm_tool(Action & a, Model * m); -private: - Model * model; - - // Variants information - MapVec_type virus_id; ///< The squence is the key - std::vector< std::string > virus_name; - std::vector< TSeq> virus_sequence; - std::vector< int > virus_origin_date; - std::vector< int > virus_parent_id; - - MapVec_type tool_id; ///< The squence is the key - std::vector< std::string > tool_name; - std::vector< TSeq> tool_sequence; - std::vector< int > tool_origin_date; - - std::function(const TSeq&)> seq_hasher = default_seq_hasher; - std::function seq_writer = default_seq_writer; - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - std::vector< std::vector > today_virus; - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - std::vector< std::vector > today_tool; - - // {Susceptible, Infected, etc.} - std::vector< int > today_total; - - // Totals - int today_total_nviruses_active = 0; - - int sampling_freq = 1; - - // Variants history - std::vector< int > hist_virus_date; - std::vector< int > hist_virus_id; - std::vector< epiworld_fast_uint > hist_virus_state; - std::vector< int > hist_virus_counts; - - // Tools history - std::vector< int > hist_tool_date; - std::vector< int > hist_tool_id; - std::vector< epiworld_fast_uint > hist_tool_state; - std::vector< int > hist_tool_counts; - - // Overall hist - std::vector< int > hist_total_date; - std::vector< int > hist_total_nviruses_active; - std::vector< epiworld_fast_uint > hist_total_state; - std::vector< int > hist_total_counts; - std::vector< int > hist_transition_matrix; - - // Transmission network - std::vector< int > transmission_date; ///< Date of the transmission event - std::vector< int > transmission_source; ///< Id of the source - std::vector< int > transmission_target; ///< Id of the target - std::vector< int > transmission_virus; ///< Id of the variant - std::vector< int > transmission_source_exposure_date; ///< Date when the source acquired the variant - - std::vector< int > transition_matrix; - - UserData user_data; - - void update_state( - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state, - bool undo = false - ); - - void update_virus( - epiworld_fast_uint virus_id, - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state - ); - - void update_tool( - epiworld_fast_uint tool_id, - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state - ); - - void record_transition(epiworld_fast_uint from, epiworld_fast_uint to, bool undo); - - -public: - - #ifdef EPI_DEBUG - int n_transmissions_potential = 0; - int n_transmissions_today = 0; - #endif - - DataBase() = delete; - DataBase(Model & m) : model(&m), user_data(m) {}; - DataBase(const DataBase & db); - // DataBase & operator=(const DataBase & m); - - /** - * @brief Registering a new variant - * - * @param v Pointer to the new virus. - * Since viruses are originated in the agent, the numbers simply move around. - * From the parent virus to the new virus. And the total number of infected - * does not change. - */ - void record_virus(Virus & v); - void record_tool(Tool & t); - void set_seq_hasher(std::function(TSeq)> fun); - void reset(); - Model * get_model(); - void record(); - - const std::vector< TSeq > & get_sequence() const; - const std::vector< int > & get_nexposed() const; - size_t size() const; - - /** - * @name Get recorded information from the model - * - * @param what std::string, The state, e.g., 0, 1, 2, ... - * @return In `get_today_total`, the current counts of `what`. - * @return In `get_today_virus`, the current counts of `what` for - * each virus. - * @return In `get_hist_total`, the time series of `what` - * @return In `get_hist_virus`, the time series of what for each virus. - * @return In `get_hist_total_date` and `get_hist_virus_date` the - * corresponding date - */ - ///@{ - int get_today_total(std::string what) const; - int get_today_total(epiworld_fast_uint what) const; - void get_today_total( - std::vector< std::string > * state = nullptr, - std::vector< int > * counts = nullptr - ) const; - - void get_today_virus( - std::vector< std::string > & state, - std::vector< int > & id, - std::vector< int > & counts - ) const; - - void get_hist_total( - std::vector< int > * date, - std::vector< std::string > * state, - std::vector< int > * counts - ) const; - - void get_hist_virus( - std::vector< int > & date, - std::vector< int > & id, - std::vector< std::string > & state, - std::vector< int > & counts - ) const; - - void get_hist_tool( - std::vector< int > & date, - std::vector< int > & id, - std::vector< std::string > & state, - std::vector< int > & counts - ) const; - - void get_hist_transition_matrix( - std::vector< std::string > & state_from, - std::vector< std::string > & state_to, - std::vector< int > & date, - std::vector< int > & counts, - bool skip_zeros - ) const; - ///@} - - /** - * @brief Get the transmissions object - * - * @param date - * @param source - * @param target - * @param virus - * @param source_exposure_date - */ - ///@{ - void get_transmissions( - std::vector & date, - std::vector & source, - std::vector & target, - std::vector & virus, - std::vector & source_exposure_date - ) const; - - void get_transmissions( - int * date, - int * source, - int * target, - int * virus, - int * source_exposure_date - ) const; - ///@} - - void write_data( - std::string fn_virus_info, - std::string fn_virus_hist, - std::string fn_tool_info, - std::string fn_tool_hist, - std::string fn_total_hist, - std::string fn_transmission, - std::string fn_transition, - std::string fn_reproductive_number, - std::string fn_generation_time - ) const; - - void record_transmission(int i, int j, int virus, int i_expo_date); - - size_t get_n_viruses() const; - size_t get_n_tools() const; - - void set_user_data(std::vector< std::string > names); - void add_user_data(std::vector< epiworld_double > x); - void add_user_data(epiworld_fast_uint j, epiworld_double x); - UserData & get_user_data(); - - - /** - * @brief Computes the reproductive number of each case - * - * @details By definition, whereas it computes R0 (basic reproductive number) - * or Rt/R (the effective reproductive number) will depend on whether the - * virus is allowed to circulate naïvely or not, respectively. - * - * @param fn File where to write out the reproductive number. - */ - ///@{ - MapVec_type reproductive_number() const; - - void reproductive_number( - std::string fn - ) const; - ///@} - - /** - * @brief Calculates the transition probabilities - * - * @return std::vector< epiworld_double > - */ - std::vector< epiworld_double > transition_probability( - bool print = true - ) const; - - bool operator==(const DataBase & other) const; - bool operator!=(const DataBase & other) const {return !operator==(other);}; - - /** - * Calculates the generating time - * @param agent_id,virus_id,time,gentime vectors where to save the values agent_id - */ - ///@{ - void generation_time( - std::vector< int > & agent_id, - std::vector< int > & virus_id, - std::vector< int > & time, - std::vector< int > & gentime - ) const; - - void generation_time( - std::string fn - ) const; - ///@} - -}; - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/database-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/database-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_DATABASE_MEAT_HPP -#define EPIWORLD_DATABASE_MEAT_HPP - -template -inline void DataBase::reset() -{ - - // Initializing the counts - today_total.resize(model->nstates); - std::fill(today_total.begin(), today_total.end(), 0); - for (auto & p : model->get_agents()) - ++today_total[p.get_state()]; - - #ifdef EPI_DEBUG - // Only the first should be different from zero - { - int n = static_cast(model->size()); - if (today_total[0] != n) - throw std::runtime_error("The number of susceptible agents is not equal to the total number of agents."); - - if (std::accumulate(today_total.begin(), today_total.end(), 0) != n) - throw std::runtime_error("The total number of agents is not equal to the sum of the number of agents in each state."); - - } - #endif - - - transition_matrix.resize(model->nstates * model->nstates); - std::fill(transition_matrix.begin(), transition_matrix.end(), 0); - for (size_t s = 0u; s < model->nstates; ++s) - transition_matrix[s + s * model->nstates] = today_total[s]; - - hist_virus_date.clear(); - hist_virus_id.clear(); - hist_virus_state.clear(); - hist_virus_counts.clear(); - - hist_tool_date.clear(); - hist_tool_id.clear(); - hist_tool_state.clear(); - hist_tool_counts.clear(); - - today_virus.resize(get_n_viruses()); - std::fill(today_virus.begin(), today_virus.begin(), std::vector(model->nstates, 0)); - - today_tool.resize(get_n_tools()); - std::fill(today_tool.begin(), today_tool.begin(), std::vector(model->nstates, 0)); - - hist_total_date.clear(); - hist_total_state.clear(); - hist_total_nviruses_active.clear(); - hist_total_counts.clear(); - hist_transition_matrix.clear(); - - transmission_date.clear(); - transmission_virus.clear(); - transmission_source.clear(); - transmission_target.clear(); - transmission_source_exposure_date.clear(); - - return; - -} - -template -inline DataBase::DataBase(const DataBase & db) : - virus_id(db.virus_id), - virus_name(db.virus_name), - virus_sequence(db.virus_sequence), - virus_origin_date(db.virus_origin_date), - virus_parent_id(db.virus_parent_id), - tool_id(db.tool_id), - tool_name(db.tool_name), - tool_sequence(db.tool_sequence), - tool_origin_date(db.tool_origin_date), - seq_hasher(db.seq_hasher), - seq_writer(db.seq_writer), - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - today_virus(db.today_virus), - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - today_tool(db.today_tool), - // {Susceptible, Infected, etc.} - today_total(db.today_total), - // Totals - today_total_nviruses_active(db.today_total_nviruses_active), - sampling_freq(db.sampling_freq), - // Variants history - hist_virus_date(db.hist_virus_date), - hist_virus_id(db.hist_virus_id), - hist_virus_state(db.hist_virus_state), - hist_virus_counts(db.hist_virus_counts), - // Tools history - hist_tool_date(db.hist_tool_date), - hist_tool_id(db.hist_tool_id), - hist_tool_state(db.hist_tool_state), - hist_tool_counts(db.hist_tool_counts), - // Overall hist - hist_total_date(db.hist_total_date), - hist_total_nviruses_active(db.hist_total_nviruses_active), - hist_total_state(db.hist_total_state), - hist_total_counts(db.hist_total_counts), - hist_transition_matrix(db.hist_transition_matrix), - // Transmission network - transmission_date(db.transmission_date), - transmission_source(db.transmission_source), - transmission_target(db.transmission_target), - transmission_virus(db.transmission_virus), - transmission_source_exposure_date(db.transmission_source_exposure_date), - transition_matrix(db.transition_matrix), - user_data(nullptr) -{} - -// DataBase & DataBase::operator=(const DataBase & m) -// { - -// } - -template -inline Model * DataBase::get_model() { - return model; -} - -template -inline const std::vector< TSeq > & DataBase::get_sequence() const { - return virus_sequence; -} - -template -inline void DataBase::record() -{ - - //////////////////////////////////////////////////////////////////////////// - // DEBUGGING BLOCK - //////////////////////////////////////////////////////////////////////////// - EPI_DEBUG_SUM_INT(today_total, model->size()) - EPI_DEBUG_ALL_NON_NEGATIVE(today_total) - - #ifdef EPI_DEBUG - // Checking whether the sums correspond - std::vector< int > _today_total_cp(today_total.size(), 0); - for (auto & p : model->population) - _today_total_cp[p.get_state()]++; - - EPI_DEBUG_VECTOR_MATCH_INT( - _today_total_cp, today_total, - "Sums of __today_total_cp in database-meat.hpp" - ) - - if (model->today() == 0) - { - if (hist_total_date.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_date should be of length 0.") - if (hist_total_nviruses_active.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_nviruses_active should be of length 0.") - if (hist_total_state.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_state should be of length 0.") - if (hist_total_counts.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_counts should be of length 0.") - if (hist_virus_date.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_date should be of length 0.") - if (hist_virus_id.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_id should be of length 0.") - if (hist_virus_state.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_state should be of length 0.") - if (hist_virus_counts.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_counts should be of length 0.") - if (hist_tool_date.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_date should be of length 0.") - if (hist_tool_id.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_id should be of length 0.") - if (hist_tool_state.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_state should be of length 0.") - if (hist_tool_counts.size() != 0) - EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_counts should be of length 0.") - } - #endif - //////////////////////////////////////////////////////////////////////////// - - // Only store every now and then - if ((model->today() % sampling_freq) == 0) - { - - // Recording virus's history - for (auto & p : virus_id) - { - - for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) - { - - hist_virus_date.push_back(model->today()); - hist_virus_id.push_back(p.second); - hist_virus_state.push_back(s); - hist_virus_counts.push_back(today_virus[p.second][s]); - - } - - } - - // Recording tool's history - for (auto & p : tool_id) - { - - for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) - { - - hist_tool_date.push_back(model->today()); - hist_tool_id.push_back(p.second); - hist_tool_state.push_back(s); - hist_tool_counts.push_back(today_tool[p.second][s]); - - } - - } - - // Recording the overall history - for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) - { - hist_total_date.push_back(model->today()); - hist_total_nviruses_active.push_back(today_total_nviruses_active); - hist_total_state.push_back(s); - hist_total_counts.push_back(today_total[s]); - } - - for (auto cell : transition_matrix) - hist_transition_matrix.push_back(cell); - - // Now the diagonal must reflect the state - for (size_t s_i = 0u; s_i < model->nstates; ++s_i) - { - - for (size_t s_j = 0u; s_j < model->nstates; ++s_j) - { - - if ((s_i != s_j) && (transition_matrix[s_i + s_j * model->nstates] > 0)) - { - transition_matrix[s_j + s_j * model->nstates] += - transition_matrix[s_i + s_j * model->nstates]; - - transition_matrix[s_i + s_j * model->nstates] = 0; - } - - } - - } - - #ifdef EPI_DEBUG - for (size_t s_i = 0u; s_i < model->nstates; ++s_i) - { - if (transition_matrix[s_i + s_i * model->nstates] != - today_total[s_i]) - throw std::logic_error( - "The diagonal of the updated transition Matrix should match the daily totals" - ); - } - #endif - - } - -} - -template -inline void DataBase::record_virus(Virus & v) -{ - - // If no sequence, then need to add one. This is regardless of the case - if (v.get_sequence() == nullptr) - v.set_sequence(default_sequence( - static_cast(virus_name.size()) - )); - - // Negative id -> virus hasn't been recorded - if (v.get_id() < 0) - { - - - // Generating the hash - std::vector< int > hash = seq_hasher(*v.get_sequence()); - - epiworld_fast_uint new_id = virus_id.size(); - virus_id[hash] = new_id; - virus_name.push_back(v.get_name()); - virus_sequence.push_back(*v.get_sequence()); - virus_origin_date.push_back(model->today()); - - virus_parent_id.push_back(v.get_id()); // Must be -99 - - today_virus.push_back({}); - today_virus[new_id].resize(model->nstates, 0); - - // Updating the variant - v.set_id(new_id); - v.set_date(model->today()); - - today_total_nviruses_active++; - - } else { // In this case, the virus is already on record, need to make sure - // The new sequence is new. - - // Updating registry - std::vector< int > hash = seq_hasher(*v.get_sequence()); - epiworld_fast_uint old_id = v.get_id(); - epiworld_fast_uint new_id; - - // If the sequence is new, then it means that the - if (virus_id.find(hash) == virus_id.end()) - { - - new_id = virus_id.size(); - virus_id[hash] = new_id; - virus_name.push_back(v.get_name()); - virus_sequence.push_back(*v.get_sequence()); - virus_origin_date.push_back(model->today()); - - virus_parent_id.push_back(old_id); - - today_virus.push_back({}); - today_virus[new_id].resize(model->nstates, 0); - - // Updating the variant - v.set_id(new_id); - v.set_date(model->today()); - - today_total_nviruses_active++; - - } else { - - // Finding the id - new_id = virus_id[hash]; - - // Reflecting the change - v.set_id(new_id); - v.set_date(virus_origin_date[new_id]); - - } - - // Moving statistics (only if we are affecting an individual) - if (v.get_agent() != nullptr) - { - // Correcting math - epiworld_fast_uint tmp_state = v.get_agent()->get_state(); - today_virus[old_id][tmp_state]--; - today_virus[new_id][tmp_state]++; - - } - - } - - return; - -} - -template -inline void DataBase::record_tool(Tool & t) -{ - - if (t.get_sequence() == nullptr) - t.set_sequence(default_sequence( - static_cast(tool_name.size()) - )); - - if (t.get_id() < 0) - { - - std::vector< int > hash = seq_hasher(*t.get_sequence()); - epiworld_fast_uint new_id = tool_id.size(); - tool_id[hash] = new_id; - tool_name.push_back(t.get_name()); - tool_sequence.push_back(*t.get_sequence()); - tool_origin_date.push_back(model->today()); - - today_tool.push_back({}); - today_tool[new_id].resize(model->nstates, 0); - - // Updating the tool - t.set_id(new_id); - t.set_date(model->today()); - - } else { - - // Updating registry - std::vector< int > hash = seq_hasher(*t.get_sequence()); - epiworld_fast_uint old_id = t.get_id(); - epiworld_fast_uint new_id; - - if (tool_id.find(hash) == tool_id.end()) - { - - new_id = tool_id.size(); - tool_id[hash] = new_id; - tool_name.push_back(t.get_name()); - tool_sequence.push_back(*t.get_sequence()); - tool_origin_date.push_back(model->today()); - - today_tool.push_back({}); - today_tool[new_id].resize(model->nstates, 0); - - // Updating the tool - t.set_id(new_id); - t.set_date(model->today()); - - } else { - - // Finding the id - new_id = tool_id[hash]; - - // Reflecting the change - t.set_id(new_id); - t.set_date(tool_origin_date[new_id]); - - } - - // Moving statistics (only if we are affecting an individual) - if (t.get_agent() != nullptr) - { - // Correcting math - epiworld_fast_uint tmp_state = t.get_agent()->get_state(); - today_tool[old_id][tmp_state]--; - today_tool[new_id][tmp_state]++; - - } - - } - - - - return; -} - -template -inline size_t DataBase::size() const -{ - return virus_id.size(); -} - -template -inline void DataBase::update_state( - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state, - bool undo -) { - - if (undo) - { - - today_total[prev_state]++; - today_total[new_state]--; - - } else { - - today_total[prev_state]--; - today_total[new_state]++; - - } - - record_transition(prev_state, new_state, undo); - - return; -} - -template -inline void DataBase::update_virus( - epiworld_fast_uint virus_id, - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state -) { - - today_virus[virus_id][prev_state]--; - today_virus[virus_id][new_state]++; - - return; - -} - -template -inline void DataBase::update_tool( - epiworld_fast_uint tool_id, - epiworld_fast_uint prev_state, - epiworld_fast_uint new_state -) { - - - today_tool[tool_id][prev_state]--; - today_tool[tool_id][new_state]++; - - return; - -} - -template -inline void DataBase::record_transition( - epiworld_fast_uint from, - epiworld_fast_uint to, - bool undo -) { - - if (undo) - { - - transition_matrix[to * model->nstates + from]--; - transition_matrix[from * model->nstates + from]++; - - } else { - - transition_matrix[to * model->nstates + from]++; - transition_matrix[from * model->nstates + from]--; - - } - - #ifdef EPI_DEBUG - if (transition_matrix[from * model->nstates + from] < 0) - throw std::logic_error("An entry in transition matrix is negative."); - #endif - -} - -template -inline int DataBase::get_today_total( - std::string what -) const -{ - - for (auto i = 0u; i < model->states_labels.size(); ++i) - { - if (model->states_labels[i] == what) - return today_total[i]; - } - - throw std::range_error("The value '" + what + "' is not in the model."); - -} - -template -inline void DataBase::get_today_total( - std::vector< std::string > * state, - std::vector< int > * counts -) const -{ - if (state != nullptr) - (*state) = model->states_labels; - - if (counts != nullptr) - *counts = today_total; - -} - -template -inline void DataBase::get_today_virus( - std::vector< std::string > & state, - std::vector< int > & id, - std::vector< int > & counts - ) const -{ - - state.resize(today_virus.size(), ""); - id.resize(today_virus.size(), 0); - counts.resize(today_virus.size(),0); - - int n = 0u; - for (epiworld_fast_uint v = 0u; v < today_virus.size(); ++v) - for (epiworld_fast_uint s = 0u; s < model->states_labels.size(); ++s) - { - state[n] = model->states_labels[s]; - id[n] = static_cast(v); - counts[n++] = today_virus[v][s]; - - } - -} - -template -inline void DataBase::get_hist_total( - std::vector< int > * date, - std::vector< std::string > * state, - std::vector< int > * counts -) const -{ - - if (date != nullptr) - *date = hist_total_date; - - if (state != nullptr) - { - state->resize(hist_total_state.size(), ""); - for (epiworld_fast_uint i = 0u; i < hist_total_state.size(); ++i) - state->operator[](i) = model->states_labels[hist_total_state[i]]; - } - - if (counts != nullptr) - *counts = hist_total_counts; - - return; - -} - -template -inline void DataBase::get_hist_virus( - std::vector< int > & date, - std::vector< int > & id, - std::vector< std::string > & state, - std::vector< int > & counts -) const { - - date = hist_virus_date; - std::vector< std::string > labels; - labels = model->states_labels; - - id = hist_virus_id; - state.resize(hist_virus_state.size(), ""); - for (epiworld_fast_uint i = 0u; i < hist_virus_state.size(); ++i) - state[i] = labels[hist_virus_state[i]]; - - counts = hist_virus_counts; - - return; - -} - - -template -inline void DataBase::get_hist_tool( - std::vector< int > & date, - std::vector< int > & id, - std::vector< std::string > & state, - std::vector< int > & counts -) const { - - date = hist_tool_date; - std::vector< std::string > labels; - labels = model->states_labels; - - id = hist_tool_id; - state.resize(hist_tool_state.size(), ""); - for (size_t i = 0u; i < hist_tool_state.size(); ++i) - state[i] = labels[hist_tool_state[i]]; - - counts = hist_tool_counts; - - return; - -} - -template -inline void DataBase::get_hist_transition_matrix( - std::vector< std::string > & state_from, - std::vector< std::string > & state_to, - std::vector< int > & date, - std::vector< int > & counts, - bool skip_zeros -) const -{ - - size_t n = this->hist_transition_matrix.size(); - - // Clearing the previous vectors - state_from.clear(); - state_to.clear(); - date.clear(); - counts.clear(); - - // Reserving space - state_from.reserve(n); - state_to.reserve(n); - date.reserve(n); - counts.reserve(n); - - size_t n_states = model->nstates; - size_t n_steps = model->get_ndays(); - - // If n is zero, then we are done - if (n == 0u) - return; - - for (size_t step = 0u; step <= n_steps; ++step) // The final step counts - { - for (size_t j = 0u; j < n_states; ++j) // Column major storage - { - for (size_t i = 0u; i < n_states; ++i) - { - // Retrieving the value of the day - int v = hist_transition_matrix[ - step * n_states * n_states + // Day of the data - j * n_states + // Column (to) - i // Row (from) - ]; - - // If we are skipping the zeros and it is zero, then don't save - if (skip_zeros && v == 0) - continue; - - state_from.push_back(model->states_labels[i]); - state_to.push_back(model->states_labels[j]); - date.push_back(hist_total_date[step * n_states]); - counts.push_back(v); - - } - - } - } - - return; - - -} - -template -inline void DataBase::get_transmissions( - std::vector & date, - std::vector & source, - std::vector & target, - std::vector & virus, - std::vector & source_exposure_date -) const -{ - - size_t nevents = transmission_date.size(); - - date.resize(nevents); - source.resize(nevents); - target.resize(nevents); - virus.resize(nevents); - source_exposure_date.resize(nevents); - - get_transmissions( - &date[0u], - &source[0u], - &target[0u], - &virus[0u], - &source_exposure_date[0u] - ); - -} - -template -inline void DataBase::get_transmissions( - int * date, - int * source, - int * target, - int * virus, - int * source_exposure_date -) const -{ - - size_t nevents = transmission_date.size(); - - for (size_t i = 0u; i < nevents; ++i) - { - - *(date + i) = transmission_date.at(i); - *(source + i) = transmission_source.at(i); - *(target + i) = transmission_target.at(i); - *(virus + i) = transmission_virus.at(i); - *(source_exposure_date + i) = transmission_source_exposure_date.at(i); - - } - -} - -template -inline void DataBase::write_data( - std::string fn_virus_info, - std::string fn_virus_hist, - std::string fn_tool_info, - std::string fn_tool_hist, - std::string fn_total_hist, - std::string fn_transmission, - std::string fn_transition, - std::string fn_reproductive_number, - std::string fn_generation_time -) const -{ - - if (fn_virus_info != "") - { - std::ofstream file_virus_info(fn_virus_info, std::ios_base::out); - - // Check if the file exists and throw an error if it doesn't - if (!file_virus_info) - { - throw std::runtime_error( - "Could not open file \"" + fn_virus_info + - "\" for writing.") - ; - } - - - file_virus_info << - #ifdef EPI_DEBUG - "thread" << "virus_id " << "virus " << "virus_sequence " << "date_recorded " << "parent\n"; - #else - "virus_id " << "virus " << "virus_sequence " << "date_recorded " << "parent\n"; - #endif - - for (const auto & v : virus_id) - { - int id = v.second; - file_virus_info << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - id << " \"" << - virus_name[id] << "\" " << - seq_writer(virus_sequence[id]) << " " << - virus_origin_date[id] << " " << - virus_parent_id[id] << "\n"; - } - - } - - if (fn_virus_hist != "") - { - std::ofstream file_virus(fn_virus_hist, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_virus) - { - throw std::runtime_error( - "Could not open file \"" + fn_virus_hist + - "\" for writing.") - ; - } - - file_virus << - #ifdef EPI_DEBUG - "thread "<< "date " << "id " << "state " << "n\n"; - #else - "date " << "virus_id virus" << "state " << "n\n"; - #endif - - for (epiworld_fast_uint i = 0; i < hist_virus_id.size(); ++i) - file_virus << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - hist_virus_date[i] << " " << - hist_virus_id[i] << " \"" << - virus_name[hist_virus_id[i]] << "\" " << - model->states_labels[hist_virus_state[i]] << " " << - hist_virus_counts[i] << "\n"; - } - - if (fn_tool_info != "") - { - std::ofstream file_tool_info(fn_tool_info, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_tool_info) - { - throw std::runtime_error( - "Could not open file \"" + fn_tool_info + - "\" for writing.") - ; - } - - file_tool_info << - #ifdef EPI_DEBUG - "thread " << - #endif - "id " << "tool_name " << "tool_sequence " << "date_recorded\n"; - - for (const auto & t : tool_id) - { - int id = t.second; - file_tool_info << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - id << " \"" << - tool_name[id] << "\" " << - seq_writer(tool_sequence[id]) << " " << - tool_origin_date[id] << "\n"; - } - - } - - if (fn_tool_hist != "") - { - std::ofstream file_tool_hist(fn_tool_hist, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_tool_hist) - { - throw std::runtime_error( - "Could not open file \"" + fn_tool_hist + - "\" for writing.") - ; - } - - file_tool_hist << - #ifdef EPI_DEBUG - "thread " << - #endif - "date " << "id " << "state " << "n\n"; - - for (epiworld_fast_uint i = 0; i < hist_tool_id.size(); ++i) - file_tool_hist << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - hist_tool_date[i] << " " << - hist_tool_id[i] << " " << - model->states_labels[hist_tool_state[i]] << " " << - hist_tool_counts[i] << "\n"; - } - - if (fn_total_hist != "") - { - std::ofstream file_total(fn_total_hist, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_total) - { - throw std::runtime_error( - "Could not open file \"" + fn_total_hist + - "\" for writing.") - ; - } - - file_total << - #ifdef EPI_DEBUG - "thread " << - #endif - "date " << "nviruses " << "state " << "counts\n"; - - for (epiworld_fast_uint i = 0; i < hist_total_date.size(); ++i) - file_total << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - hist_total_date[i] << " " << - hist_total_nviruses_active[i] << " \"" << - model->states_labels[hist_total_state[i]] << "\" " << - hist_total_counts[i] << "\n"; - } - - if (fn_transmission != "") - { - std::ofstream file_transmission(fn_transmission, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_transmission) - { - throw std::runtime_error( - "Could not open file \"" + fn_transmission + - "\" for writing.") - ; - } - - file_transmission << - #ifdef EPI_DEBUG - "thread " << - #endif - "date " << "virus_id virus " << "source_exposure_date " << "source " << "target\n"; - - for (epiworld_fast_uint i = 0; i < transmission_target.size(); ++i) - file_transmission << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - transmission_date[i] << " " << - transmission_virus[i] << " \"" << - virus_name[transmission_virus[i]] << "\" " << - transmission_source_exposure_date[i] << " " << - transmission_source[i] << " " << - transmission_target[i] << "\n"; - - } - - if (fn_transition != "") - { - std::ofstream file_transition(fn_transition, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!file_transition) - { - throw std::runtime_error( - "Could not open file \"" + fn_transition + - "\" for writing.") - ; - } - - file_transition << - #ifdef EPI_DEBUG - "thread " << - #endif - "date " << "from " << "to " << "counts\n"; - - int ns = model->nstates; - - for (int i = 0; i <= model->today(); ++i) - { - - for (int from = 0u; from < ns; ++from) - for (int to = 0u; to < ns; ++to) - file_transition << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - i << " " << - model->states_labels[from] << " " << - model->states_labels[to] << " " << - hist_transition_matrix[i * (ns * ns) + to * ns + from] << "\n"; - - } - - } - - if (fn_reproductive_number != "") - reproductive_number(fn_reproductive_number); - - if (fn_generation_time != "") - generation_time(fn_generation_time); - -} - -template -inline void DataBase::record_transmission( - int i, - int j, - int virus, - int i_expo_date -) { - - transmission_date.push_back(model->today()); - transmission_source.push_back(i); - transmission_target.push_back(j); - transmission_virus.push_back(virus); - transmission_source_exposure_date.push_back(i_expo_date); - -} - -template -inline size_t DataBase::get_n_viruses() const -{ - return virus_id.size(); -} - -template -inline size_t DataBase::get_n_tools() const -{ - return tool_id.size(); -} - - -template -inline void DataBase::set_user_data( - std::vector< std::string > names -) -{ - user_data = UserData(names); - user_data.model = model; -} - -template -inline void DataBase::add_user_data( - std::vector< epiworld_double > x -) -{ - - user_data.add(x); - -} - -template -inline void DataBase::add_user_data( - epiworld_fast_uint k, - epiworld_double x -) -{ - - user_data.add(k, x); - -} - -template -inline UserData & DataBase::get_user_data() -{ - return user_data; -} - -template -inline MapVec_type DataBase::reproductive_number() -const { - - // Checking size - MapVec_type map; - - // Number of digits of maxid - for (size_t i = 0u; i < transmission_date.size(); ++i) - { - // Fabricating id - std::vector< int > h = { - transmission_virus[i], - transmission_source[i], - transmission_source_exposure_date[i] - }; - - // Adding to counter - if (map.find(h) == map.end()) - map[h] = 1; - else - map[h]++; - - // The target is added - std::vector< int > h_target = { - transmission_virus[i], - transmission_target[i], - transmission_date[i] - }; - - map[h_target] = 0; - - } - - return map; - -} - -template -inline void DataBase::reproductive_number( - std::string fn -) const { - - - auto map = reproductive_number(); - - std::ofstream fn_file(fn, std::ios_base::out); - - // Repeat the same error if the file doesn't exists - if (!fn_file) - { - throw std::runtime_error( - "Could not open file \"" + fn + - "\" for writing.") - ; - } - - fn_file << - #ifdef EPI_DEBUG - "thread " << - #endif - "virus_id virus source source_exposure_date rt\n"; - - - for (auto & m : map) - fn_file << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - m.first[0u] << " \"" << - virus_name[m.first[0u]] << "\" " << - m.first[1u] << " " << - m.first[2u] << " " << - m.second << "\n"; - - return; - -} - -template -inline std::vector< epiworld_double > DataBase::transition_probability( - bool print -) const { - - auto states_labels = model->get_states(); - size_t n_state = states_labels.size(); - size_t n_days = model->get_ndays(); - std::vector< epiworld_double > res(n_state * n_state, 0.0); - std::vector< epiworld_double > days_to_include(n_state, 0.0); - - for (size_t t = 1; t < n_days; ++t) - { - - for (size_t s_i = 0; s_i < n_state; ++s_i) - { - epiworld_double daily_total = hist_total_counts[(t - 1) * n_state + s_i]; - - if (daily_total == 0) - continue; - - days_to_include[s_i] += 1.0; - - for (size_t s_j = 0u; s_j < n_state; ++s_j) - { - #ifdef EPI_DEBUG - epiworld_double entry = hist_transition_matrix[ - s_i + s_j * n_state + - t * (n_state * n_state) - ]; - - if (entry > daily_total) - throw std::logic_error( - "The entry in hist_transition_matrix cannot have more elememnts than the total" - ); - - res[s_i + s_j * n_state] += (entry / daily_total); - #else - res[s_i + s_j * n_state] += ( - hist_transition_matrix[ - s_i + s_j * n_state + - t * (n_state * n_state) - ] / daily_total - ); - #endif - } - - } - - } - - for (size_t s_i = 0; s_i < n_state; ++s_i) - { - for (size_t s_j = 0; s_j < n_state; ++s_j) - res[s_i + s_j * n_state] /= days_to_include[s_i]; - } - - if (print) - { - - size_t nchar = 0u; - for (auto & l : states_labels) - if (l.length() > nchar) - nchar = l.length(); - - std::string fmt = " - %-" + std::to_string(nchar) + "s"; - - printf_epiworld("\nTransition Probabilities:\n"); - for (size_t s_i = 0u; s_i < n_state; ++s_i) - { - printf_epiworld(fmt.c_str(), states_labels[s_i].c_str()); - for (size_t s_j = 0u; s_j < n_state; ++s_j) - { - if (std::isnan(res[s_i + s_j * n_state])) - { - printf_epiworld(" -"); - } else { - printf_epiworld(" % 4.2f", res[s_i + s_j * n_state]); - } - } - printf_epiworld("\n"); - } - - printf_epiworld("\n"); - - } - - return res; - - -} - -#define VECT_MATCH(a, b, c) \ - EPI_DEBUG_FAIL_AT_TRUE(a.size() != b.size(), c) \ - for (size_t __i = 0u; __i < a.size(); ++__i) \ - {\ - EPI_DEBUG_FAIL_AT_TRUE(a[__i] != b[__i], c) \ - } - -template<> -inline bool DataBase>::operator==(const DataBase> & other) const -{ - VECT_MATCH( - virus_name, other.virus_name, - "DataBase:: virus_name don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - virus_sequence.size() != other.virus_sequence.size(), - "DataBase:: virus_sequence don't match." - ) - - for (size_t i = 0u; i < virus_sequence.size(); ++i) - { - VECT_MATCH( - virus_sequence[i], other.virus_sequence[i], - "DataBase:: virus_sequence[i] don't match" - ) - } - - VECT_MATCH( - virus_origin_date, - other.virus_origin_date, - "DataBase:: virus_origin_date[i] don't match" - ) - - VECT_MATCH( - virus_parent_id, - other.virus_parent_id, - "DataBase:: virus_parent_id[i] don't match" - ) - - VECT_MATCH( - tool_name, - other.tool_name, - "DataBase:: tool_name[i] don't match" - ) - - VECT_MATCH( - tool_sequence, - other.tool_sequence, - "DataBase:: tool_sequence[i] don't match" - ) - - VECT_MATCH( - tool_origin_date, - other.tool_origin_date, - "DataBase:: tool_origin_date[i] don't match" - ) - - - EPI_DEBUG_FAIL_AT_TRUE( - sampling_freq != other.sampling_freq, - "DataBase:: sampling_freq don't match." - ) - - // Variants history - VECT_MATCH( - hist_virus_date, - other.hist_virus_date, - "DataBase:: hist_virus_date[i] don't match" - ) - - VECT_MATCH( - hist_virus_id, - other.hist_virus_id, - "DataBase:: hist_virus_id[i] don't match" - ) - - VECT_MATCH( - hist_virus_state, - other.hist_virus_state, - "DataBase:: hist_virus_state[i] don't match" - ) - - VECT_MATCH( - hist_virus_counts, - other.hist_virus_counts, - "DataBase:: hist_virus_counts[i] don't match" - ) - - // Tools history - VECT_MATCH( - hist_tool_date, - other.hist_tool_date, - "DataBase:: hist_tool_date[i] don't match" - ) - - VECT_MATCH( - hist_tool_id, - other.hist_tool_id, - "DataBase:: hist_tool_id[i] don't match" - ) - - VECT_MATCH( - hist_tool_state, - other.hist_tool_state, - "DataBase:: hist_tool_state[i] don't match" - ) - - VECT_MATCH( - hist_tool_counts, - other.hist_tool_counts, - "DataBase:: hist_tool_counts[i] don't match" - ) - - // Overall hist - VECT_MATCH( - hist_total_date, - other.hist_total_date, - "DataBase:: hist_total_date[i] don't match" - ) - - VECT_MATCH( - hist_total_nviruses_active, - other.hist_total_nviruses_active, - "DataBase:: hist_total_nviruses_active[i] don't match" - ) - - VECT_MATCH( - hist_total_state, - other.hist_total_state, - "DataBase:: hist_total_state[i] don't match" - ) - - VECT_MATCH( - hist_total_counts, - other.hist_total_counts, - "DataBase:: hist_total_counts[i] don't match" - ) - - VECT_MATCH( - hist_transition_matrix, - other.hist_transition_matrix, - "DataBase:: hist_transition_matrix[i] don't match" - ) - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - EPI_DEBUG_FAIL_AT_TRUE( - today_virus.size() != other.today_virus.size(), - "DataBase:: today_virus don't match." - ) - - for (size_t i = 0u; i < today_virus.size(); ++i) - { - VECT_MATCH( - today_virus[i], other.today_virus[i], - "DataBase:: today_virus[i] don't match" - ) - } - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - if (today_tool.size() != other.today_tool.size()) - return false; - - for (size_t i = 0u; i < today_tool.size(); ++i) - { - VECT_MATCH( - today_tool[i], other.today_tool[i], - "DataBase:: today_tool[i] don't match" - ) - } - - // {Susceptible, Infected, etc.} - VECT_MATCH( - today_total, other.today_total, - "DataBase:: today_total don't match" - ) - - // Totals - EPI_DEBUG_FAIL_AT_TRUE( - today_total_nviruses_active != other.today_total_nviruses_active, - "DataBase:: today_total_nviruses_active don't match." - ) - - // Transmission network - VECT_MATCH( - transmission_date, - other.transmission_date, ///< Date of the transmission eve, - "DataBase:: transmission_date[i] don't match" - ) - - VECT_MATCH( - transmission_source, - other.transmission_source, ///< Id of the sour, - "DataBase:: transmission_source[i] don't match" - ) - - VECT_MATCH( - transmission_target, - other.transmission_target, ///< Id of the targ, - "DataBase:: transmission_target[i] don't match" - ) - - VECT_MATCH( - transmission_virus, - other.transmission_virus, ///< Id of the varia, - "DataBase:: transmission_virus[i] don't match" - ) - - VECT_MATCH( - transmission_source_exposure_date, - other.transmission_source_exposure_date, ///< Date when the source acquired the varia, - "DataBase:: transmission_source_exposure_date[i] don't match" - ) - - - VECT_MATCH( - transition_matrix, - other.transition_matrix, - "DataBase:: transition_matrix[i] don't match" - ) - - - return true; - -} - -template -inline bool DataBase::operator==(const DataBase & other) const -{ - VECT_MATCH( - virus_name, - other.virus_name, - "DataBase:: virus_name[i] don't match" - ) - - VECT_MATCH( - virus_sequence, - other.virus_sequence, - "DataBase:: virus_sequence[i] don't match" - ) - - VECT_MATCH( - virus_origin_date, - other.virus_origin_date, - "DataBase:: virus_origin_date[i] don't match" - ) - - VECT_MATCH( - virus_parent_id, - other.virus_parent_id, - "DataBase:: virus_parent_id[i] don't match" - ) - - VECT_MATCH( - tool_name, - other.tool_name, - "DataBase:: tool_name[i] don't match" - ) - - VECT_MATCH( - tool_sequence, - other.tool_sequence, - "DataBase:: tool_sequence[i] don't match" - ) - - VECT_MATCH( - tool_origin_date, - other.tool_origin_date, - "DataBase:: tool_origin_date[i] don't match" - ) - - - EPI_DEBUG_FAIL_AT_TRUE( - sampling_freq != other.sampling_freq, - "DataBase:: sampling_freq don't match." - ) - - // Variants history - VECT_MATCH( - hist_virus_date, - other.hist_virus_date, - "DataBase:: hist_virus_date[i] don't match" - ) - - VECT_MATCH( - hist_virus_id, - other.hist_virus_id, - "DataBase:: hist_virus_id[i] don't match" - ) - - VECT_MATCH( - hist_virus_state, - other.hist_virus_state, - "DataBase:: hist_virus_state[i] don't match" - ) - - VECT_MATCH( - hist_virus_counts, - other.hist_virus_counts, - "DataBase:: hist_virus_counts[i] don't match" - ) - - // Tools history - VECT_MATCH( - hist_tool_date, - other.hist_tool_date, - "DataBase:: hist_tool_date[i] don't match" - ) - - VECT_MATCH( - hist_tool_id, - other.hist_tool_id, - "DataBase:: hist_tool_id[i] don't match" - ) - - VECT_MATCH( - hist_tool_state, - other.hist_tool_state, - "DataBase:: hist_tool_state[i] don't match" - ) - - VECT_MATCH( - hist_tool_counts, - other.hist_tool_counts, - "DataBase:: hist_tool_counts[i] don't match" - ) - - // Overall hist - VECT_MATCH( - hist_total_date, - other.hist_total_date, - "DataBase:: hist_total_date[i] don't match" - ) - - VECT_MATCH( - hist_total_nviruses_active, - other.hist_total_nviruses_active, - "DataBase:: hist_total_nviruses_active[i] don't match" - ) - - VECT_MATCH( - hist_total_state, - other.hist_total_state, - "DataBase:: hist_total_state[i] don't match" - ) - - VECT_MATCH( - hist_total_counts, - other.hist_total_counts, - "DataBase:: hist_total_counts[i] don't match" - ) - - VECT_MATCH( - hist_transition_matrix, - other.hist_transition_matrix, - "DataBase:: hist_transition_matrix[i] don't match" - ) - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - EPI_DEBUG_FAIL_AT_TRUE( - today_virus.size() != other.today_virus.size(), - "DataBase:: today_virus.size() don't match." - ) - - for (size_t i = 0u; i < today_virus.size(); ++i) - { - VECT_MATCH( - today_virus[i], other.today_virus[i], - "DataBase:: today_virus[i] don't match" - ) - } - - // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} - EPI_DEBUG_FAIL_AT_TRUE( - today_tool.size() != other.today_tool.size(), - "DataBase:: today_tool.size() don't match." - ) - - for (size_t i = 0u; i < today_tool.size(); ++i) - { - VECT_MATCH( - today_tool[i], other.today_tool[i], - "DataBase:: today_tool[i] don't match" - ) - } - - // {Susceptible, Infected, etc.} - VECT_MATCH( - today_total, other.today_total, - "DataBase:: today_total[i] don't match" - ) - - // Totals - EPI_DEBUG_FAIL_AT_TRUE( - today_total_nviruses_active != other.today_total_nviruses_active, - "DataBase:: today_total_nviruses_active don't match." - ) - - // Transmission network - VECT_MATCH( ///< Date of the transmission eve - transmission_date, - other.transmission_date, - "DataBase:: transmission_date[i] don't match" - ) - - VECT_MATCH( ///< Id of the sour - transmission_source, - other.transmission_source, - "DataBase:: transmission_source[i] don't match" - ) - - VECT_MATCH( ///< Id of the targ - transmission_target, - other.transmission_target, - "DataBase:: transmission_target[i] don't match" - ) - - VECT_MATCH( ///< Id of the varia - transmission_virus, - other.transmission_virus, - "DataBase:: transmission_virus[i] don't match" - ) - - VECT_MATCH( ///< Date when the source acquired the varia - transmission_source_exposure_date, - other.transmission_source_exposure_date, - "DataBase:: transmission_source_exposure_date[i] don't match" - ) - - VECT_MATCH( - transition_matrix, - other.transition_matrix, - "DataBase:: transition_matrix[i] don't match" - ) - - return true; - -} - -template -inline void DataBase::generation_time( - std::vector< int > & agent_id, - std::vector< int > & virus_id, - std::vector< int > & time, - std::vector< int > & gentime -) const { - - size_t nevents = transmission_date.size(); - - agent_id.reserve(nevents); - virus_id.reserve(nevents); - time.reserve(nevents); - gentime.reserve(nevents); - - // Iterating through the individuals - for (size_t i = 0u; i < nevents; ++i) - { - int agent_id_i = transmission_target[i]; - agent_id.push_back(agent_id_i); - virus_id.push_back(transmission_virus[i]); - time.push_back(transmission_date[i]); - - bool found = false; - for (size_t j = i; j < nevents; ++j) - { - - if (transmission_source[j] == agent_id_i) - { - gentime.push_back(transmission_date[j] - time[i]); - found = true; - break; - } - - } - - // If there's no transmission, we set the generation time to - // minus 1; - if (!found) - gentime.push_back(-1); - - } - - agent_id.shrink_to_fit(); - virus_id.shrink_to_fit(); - time.shrink_to_fit(); - gentime.shrink_to_fit(); - - return; - -} - -template -inline void DataBase::generation_time( - std::string fn -) const -{ - - std::vector< int > agent_id; - std::vector< int > virus_id; - std::vector< int > time; - std::vector< int > gentime; - - generation_time(agent_id, virus_id, time, gentime); - - std::ofstream fn_file(fn, std::ios_base::out); - - // Throw an error if the file doesn't exists using throw - if (!fn_file) - { - throw std::runtime_error( - "DataBase::generation_time: " - "Cannot open file " + fn + "." - ); - } - - - - fn_file << - #ifdef EPI_DEBUG - "thread " << - #endif - "virus source source_exposure_date gentime\n"; - - size_t n = agent_id.size(); - for (size_t i = 0u; i < n; ++i) - fn_file << - #ifdef EPI_DEBUG - EPI_GET_THREAD_ID() << " " << - #endif - virus_id[i] << " " << - agent_id[i] << " " << - time[i] << " " << - gentime[i] << "\n"; - - return; - -} - -#undef VECT_MATCH - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/database-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/adjlist-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_ADJLIST_BONES_HPP -#define EPIWORLD_ADJLIST_BONES_HPP - -class AdjList { -private: - - std::vector> dat; - bool directed; - epiworld_fast_uint N = 0; - epiworld_fast_uint E = 0; - -public: - - AdjList() {}; - - /** - * @brief Construct a new Adj List object - * - * @details - * Ids in the network are assume to range from `0` to `size - 1`. - * - * @param source Unsigned int vector with the source - * @param target Unsigned int vector with the target - * @param size Number of vertices in the network. - * @param directed Bool true if the network is directed - */ - AdjList( - const std::vector< int > & source, - const std::vector< int > & target, - int size, - bool directed - ); - - AdjList(AdjList && a); // Move constructor - AdjList(const AdjList & a); // Copy constructor - AdjList& operator=(const AdjList& a); - - - /** - * @brief Read an edgelist - * - * Ids in the network are assume to range from `0` to `size - 1`. - * - * @param fn Path to the file - * @param skip Number of lines to skip (e.g., 1 if there's a header) - * @param directed `true` if the network is directed - * @param size Number of vertices in the network. - */ - void read_edgelist( - std::string fn, - int size, - int skip = 0, - bool directed = true - ); - - std::map operator()( - epiworld_fast_uint i - ) const; - - void print(epiworld_fast_uint limit = 20u) const; - size_t vcount() const; ///< Number of vertices/nodes in the network. - size_t ecount() const; ///< Number of edges/arcs/ties in the network. - - std::vector> & get_dat() { - return dat; - }; - - bool is_directed() const; ///< `true` if the network is directed. - -}; - - -#endif - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/adjlist-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/adjlist-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_ADJLIST_MEAT_HPP -#define EPIWORLD_ADJLIST_MEAT_HPP - -inline AdjList::AdjList( - const std::vector< int > & source, - const std::vector< int > & target, - int size, - bool directed -) : directed(directed) { - - - dat.resize(size, std::map({})); - int max_id = size - 1; - - int i,j; - for (int m = 0; m < static_cast(source.size()); ++m) - { - - i = source[m]; - j = target[m]; - - if (i > max_id) - throw std::range_error( - "The source["+std::to_string(m)+"] = " + std::to_string(i) + - " is above the max_id " + std::to_string(max_id) - ); - - if (j > max_id) - throw std::range_error( - "The target["+std::to_string(m)+"] = " + std::to_string(j) + - " is above the max_id " + std::to_string(max_id) - ); - - // Adding nodes - if (dat[i].find(j) == dat[i].end()) - dat[i].insert(std::pair(j, 1u)); - else - dat[i][j]++; - - if (!directed) - { - - if (dat[j].find(i) == dat[j].end()) - dat[j].insert(std::pair(i, 1u)); - else - dat[j][i]++; - - } - - E++; - - } - - N = size; - - return; - -} - - -inline AdjList::AdjList(AdjList && a) : - dat(std::move(a.dat)), - directed(a.directed), - N(a.N), - E(a.E) -{ - -} - -inline AdjList::AdjList(const AdjList & a) : - dat(a.dat), - directed(a.directed), - N(a.N), - E(a.E) -{ - -} - -inline AdjList& AdjList::operator=(const AdjList& a) -{ - if (this == &a) - return *this; - - this->dat = a.dat; - this->directed = a.directed; - this->N = a.N; - this->E = a.E; - - return *this; -} - -inline void AdjList::read_edgelist( - std::string fn, - int size, - int skip, - bool directed -) { - - int i,j; - std::ifstream filei(fn); - - if (!filei) - throw std::logic_error("The file " + fn + " was not found."); - - int linenum = 0; - std::vector< int > source_; - std::vector< int > target_; - - source_.reserve(1e5); - target_.reserve(1e5); - - int max_id = size - 1; - - while (!filei.eof()) - { - - if (linenum++ < skip) - continue; - - filei >> i >> j; - - // Looking for exceptions - if (filei.bad()) - throw std::logic_error( - "I/O error while reading the file " + - fn - ); - - if (filei.fail()) - break; - - if (i > max_id) - throw std::range_error( - "The source["+std::to_string(linenum)+"] = " + std::to_string(i) + - " is above the max_id " + std::to_string(max_id) - ); - - if (j > max_id) - throw std::range_error( - "The target["+std::to_string(linenum)+"] = " + std::to_string(j) + - " is above the max_id " + std::to_string(max_id) - ); - - source_.push_back(i); - target_.push_back(j); - - } - - // Now using the right constructor - *this = AdjList(source_, target_, size, directed); - - return; - -} - -inline std::map AdjList::operator()( - epiworld_fast_uint i - ) const { - - if (i >= N) - throw std::range_error( - "The vertex id " + std::to_string(i) + " is not in the network." - ); - - return dat[i]; - -} - -inline void AdjList::print(epiworld_fast_uint limit) const { - - - epiworld_fast_uint counter = 0; - printf_epiworld("Nodeset:\n"); - int i = -1; - for (auto & n : dat) - { - - if (counter++ > limit) - break; - - printf_epiworld(" % 3i: {", ++i); - int niter = 0; - for (auto n_n : n) - if (++niter < static_cast(n.size())) - { - printf_epiworld("%i, ", static_cast(n_n.first)); - } - else { - printf_epiworld("%i}\n", static_cast(n_n.first)); - } - } - - if (limit < dat.size()) - { - printf_epiworld( - " (... skipping %i records ...)\n", - static_cast(dat.size() - limit) - ); - } - -} - -inline size_t AdjList::vcount() const -{ - return N; -} - -inline size_t AdjList::ecount() const -{ - return E; -} - -inline bool AdjList::is_directed() const { - - if (dat.size() == 0u) - throw std::logic_error("The edgelist is empty."); - - return directed; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/adjlist-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/randgraph.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_RANDGRA -#define EPIWORLD_RANDGRA - -template -class Model; - -template -class Agent; - -class AdjList; - - -template -inline void rewire_degseq( - TDat * agents, - Model * model, - epiworld_double proportion - ); - -template -inline void rewire_degseq( - std::vector< Agent > * agents, - Model * model, - epiworld_double proportion - ) -{ - - #ifdef EPI_DEBUG - std::vector< int > _degree0(agents->size(), 0); - for (size_t i = 0u; i < _degree0.size(); ++i) - _degree0[i] = model->get_agents()[i].get_neighbors().size(); - #endif - - // Identifying individuals with degree > 0 - std::vector< epiworld_fast_uint > non_isolates; - std::vector< epiworld_double > weights; - epiworld_double nedges = 0.0; - - for (epiworld_fast_uint i = 0u; i < agents->size(); ++i) - { - if (agents->operator[](i).get_neighbors().size() > 0u) - { - non_isolates.push_back(i); - epiworld_double wtemp = static_cast( - agents->operator[](i).get_neighbors().size() - ); - weights.push_back(wtemp); - nedges += wtemp; - } - } - - if (non_isolates.size() == 0u) - throw std::logic_error("The graph is completely disconnected."); - - // Cumulative probs - weights[0u] /= nedges; - for (epiworld_fast_uint i = 1u; i < non_isolates.size(); ++i) - { - weights[i] /= nedges; - weights[i] += weights[i - 1u]; - } - - // Only swap if needed - epiworld_fast_uint N = non_isolates.size(); - epiworld_double prob; - int nrewires = floor(proportion * nedges); - while (nrewires-- > 0) - { - - // Picking egos - prob = model->runif(); - int id0 = N - 1; - for (epiworld_fast_uint i = 0u; i < N; ++i) - if (prob <= weights[i]) - { - id0 = i; - break; - } - - prob = model->runif(); - int id1 = N - 1; - for (epiworld_fast_uint i = 0u; i < N; ++i) - if (prob <= weights[i]) - { - id1 = i; - break; - } - - // Correcting for under or overflow. - if (id1 == id0) - id1++; - - if (id1 >= static_cast(N)) - id1 = 0; - - Agent & p0 = agents->operator[](non_isolates[id0]); - Agent & p1 = agents->operator[](non_isolates[id1]); - - // Picking alters (relative location in their lists) - // In this case, these are uniformly distributed within the list - int id01 = std::floor(p0.get_n_neighbors() * model->runif()); - int id11 = std::floor(p1.get_n_neighbors() * model->runif()); - - // When rewiring, we need to flip the individuals from the other - // end as well, since we are dealing withi an undirected graph - - // Finding what neighbour is id0 - model->get_agents()[id0].swap_neighbors( - model->get_agents()[id1], - id01, - id11 - ); - - - } - - #ifdef EPI_DEBUG - for (size_t _i = 0u; _i < _degree0.size(); ++_i) - { - if (_degree0[_i] != static_cast(model->get_agents()[_i].get_n_neighbors())) - throw std::logic_error("[epi-debug] Degree does not match afted rewire_degseq."); - } - #endif - - return; - -} - -template -inline void rewire_degseq( - AdjList * agents, - Model * model, - epiworld_double proportion - ) -{ - - // Identifying individuals with degree > 0 - std::vector< epiworld_fast_int > nties(agents->vcount(), 0); - - #ifdef EPI_DEBUG - std::vector< int > _degree0(agents->vcount(), 0); - for (size_t i = 0u; i < _degree0.size(); ++i) - _degree0[i] = agents->get_dat()[i].size(); - #endif - - std::vector< epiworld_fast_uint > non_isolates; - non_isolates.reserve(nties.size()); - - std::vector< epiworld_double > weights; - weights.reserve(nties.size()); - - epiworld_double nedges = 0.0; - auto & dat = agents->get_dat(); - - for (size_t i = 0u; i < dat.size(); ++i) - nties[i] += dat[i].size(); - - bool directed = agents->is_directed(); - for (size_t i = 0u; i < dat.size(); ++i) - { - if (nties[i] > 0) - { - non_isolates.push_back(i); - if (directed) - { - weights.push_back( - static_cast(nties[i]) - ); - nedges += static_cast(nties[i]); - } - else { - weights.push_back( - static_cast(nties[i])/2.0 - ); - nedges += static_cast(nties[i]) / 2.0; - } - } - } - - if (non_isolates.size() == 0u) - throw std::logic_error("The graph is completely disconnected."); - - // Cumulative probs - weights[0u] /= nedges; - for (epiworld_fast_uint i = 1u; i < non_isolates.size(); ++i) - { - weights[i] /= nedges; - weights[i] += weights[i - 1u]; - } - - // Only swap if needed - epiworld_fast_uint N = non_isolates.size(); - epiworld_double prob; - int nrewires = floor(proportion * nedges / ( - agents->is_directed() ? 1.0 : 2.0 - )); - - while (nrewires-- > 0) - { - - // Picking egos - prob = model->runif(); - int id0 = N - 1; - for (epiworld_fast_uint i = 0u; i < N; ++i) - if (prob <= weights[i]) - { - id0 = i; - break; - } - - prob = model->runif(); - int id1 = N - 1; - for (epiworld_fast_uint i = 0u; i < N; ++i) - if (prob <= weights[i]) - { - id1 = i; - break; - } - - // Correcting for under or overflow. - if (id1 == id0) - id1++; - - if (id1 >= static_cast(N)) - id1 = 0; - - std::map & p0 = agents->get_dat()[non_isolates[id0]]; - std::map & p1 = agents->get_dat()[non_isolates[id1]]; - - // Picking alters (relative location in their lists) - // In this case, these are uniformly distributed within the list - int id01 = std::floor(p0.size() * model->runif()); - int id11 = std::floor(p1.size() * model->runif()); - - // Since it is a map, we need to find the actual ids (positions) - // are not good enough. - int count = 0; - for (auto & n : p0) - if (count++ == id01) - id01 = n.first; - - count = 0; - for (auto & n : p1) - if (count++ == id11) - id11 = n.first; - - // When rewiring, we need to flip the individuals from the other - // end as well, since we are dealing withi an undirected graph - - // Finding what neighbour is id0 - if (!agents->is_directed()) - { - - std::map & p01 = agents->get_dat()[id01]; - std::map & p11 = agents->get_dat()[id11]; - - std::swap(p01[id0], p11[id1]); - - } - - // Moving alter first - std::swap(p0[id01], p1[id11]); - - } - - #ifdef EPI_DEBUG - for (size_t _i = 0u; _i < _degree0.size(); ++_i) - { - if (_degree0[_i] != static_cast(agents->get_dat()[_i].size())) - throw std::logic_error( - "[epi-debug] Degree does not match afted rewire_degseq. " + - std::string("Expected: ") + - std::to_string(_degree0[_i]) + - std::string(", observed: ") + - std::to_string(agents->get_dat()[_i].size()) - ); - } - #endif - - - return; - -} - -template -inline AdjList rgraph_bernoulli( - epiworld_fast_uint n, - epiworld_double p, - bool directed, - Model & model -) { - - std::vector< int > source; - std::vector< int > target; - - // Checking the density (how many) - std::binomial_distribution<> d( - n * (n - 1.0) / (directed ? 1.0 : 2.0), - p - ); - - epiworld_fast_uint m = d(model.get_rand_endgine()); - - source.resize(m); - target.resize(m); - - epiworld_fast_uint a,b; - for (epiworld_fast_uint i = 0u; i < m; ++i) - { - a = floor(model.runif() * n); - - if (!directed) - b = floor(model.runif() * a); - else - { - b = floor(model.runif() * n); - if (b == a) - b++; - - if (b >= n) - b = 0u; - } - - source[i] = static_cast(a); - target[i] = static_cast(b); - - } - - AdjList al(source, target, static_cast(n), directed); - - return al; - -} - -template -inline AdjList rgraph_bernoulli2( - epiworld_fast_uint n, - epiworld_double p, - bool directed, - Model & model -) { - - std::vector< int > source; - std::vector< int > target; - - // Checking the density (how many) - std::binomial_distribution<> d( - n * (n - 1.0) / (directed ? 1.0 : 2.0), - p - ); - - // Need to compensate for the possible number of diagonal - // elements sampled. If n * n, then each diag element has - // 1/(n^2) chance of sampling - - epiworld_fast_uint m = d(model.get_rand_endgine()); - - source.resize(m); - target.resize(m); - - double n2 = static_cast(n * n); - - int loc,row,col; - for (epiworld_fast_uint i = 0u; i < m; ++i) - { - loc = floor(model.runif() * n2); - col = floor(static_cast(loc)/static_cast(n)); - row = loc - row * n; - - // Undirected needs to swap - if (!directed && (col > row)) - std::swap(col, row); - - source[i] = row; - target[i] = col; - - } - - AdjList al(source, target, static_cast(n), directed); - - return al; - -} - -inline AdjList rgraph_ring_lattice( - epiworld_fast_uint n, - epiworld_fast_uint k, - bool directed = false -) { - - if ((n - 1u) < k) - throw std::logic_error("k can be at most n - 1."); - - std::vector< int > source; - std::vector< int > target; - - if (!directed) - if (k > 1u) k = static_cast< size_t >(floor(k / 2.0)); - - for (size_t i = 0; i < n; ++i) - { - - for (size_t j = 1u; j <= k; ++j) - { - - // Next neighbor - size_t l = i + j; - if (l >= n) l = l - n; - - source.push_back(i); - target.push_back(l); - - } - - } - - return AdjList(source, target, n, directed); - -} - -/** - * @brief Smallworld network (Watts-Strogatz) - * - * @tparam TSeq - * @param n - * @param k - * @param p - * @param directed - * @param model - * @return AdjList - */ -template -inline AdjList rgraph_smallworld( - epiworld_fast_uint n, - epiworld_fast_uint k, - epiworld_double p, - bool directed, - Model & model -) { - - // Creating the ring lattice - AdjList ring = rgraph_ring_lattice(n,k,directed); - - // Rewiring and returning - if (k > 0u) - rewire_degseq(&ring, &model, p); - - return ring; - -} - -/** - * @brief Generates a blocked network - * - * Since block sizes and number of connections between blocks are fixed, - * this routine is fully deterministic. - * - * @tparam TSeq - * @param n Size of the network - * @param blocksize Size of the block. - * @param ncons Number of connections between blocks - * @param model A model - * @return AdjList - */ -template -inline AdjList rgraph_blocked( - epiworld_fast_uint n, - epiworld_fast_uint blocksize, - epiworld_fast_uint ncons, - Model& -) { - - std::vector< int > source_; - std::vector< int > target_; - - size_t i = 0u; - size_t cum_node_count = 0u; - while (i < n) - { - - for (size_t j = 0; j < blocksize; ++j) - { - - for (size_t k = 0; k < j; ++k) - { - // No loops - if (k == j) - continue; - - // Exists the loop in case there are no more - // nodes available - if ((i + k) >= n) - break; - - source_.push_back(static_cast(j + i)); - target_.push_back(static_cast(k + i)); - } - - // No more nodes left to build connections - if (++cum_node_count >= n) - break; - - } - - // Connections between this and the previou sone - if (i != 0) - { - - size_t max_cons = std::min(ncons, n - cum_node_count); - - // Generating the connections - for (size_t j = 0u; j < max_cons; ++j) - { - - source_.push_back(static_cast(i + j - blocksize)); - target_.push_back(static_cast(i + j)); - - } - } - - i += blocksize; - - } - - return AdjList(source_, target_, n, false); - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/randgraph.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/queue-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_QUEUE_BONES_HPP -#define EPIWORLD_QUEUE_BONES_HPP - -/** - * @brief Controls which agents are verified at each step - * - * @details The idea is that only agents who are either in - * an infected state or have an infected neighbor should be - * checked. Otherwise it makes no sense (no chance to recover - * or capture the disease). - * - * @tparam TSeq - */ -template -class Queue -{ - friend class Model; - -private: - - /** - * @brief Count of ego's neighbors in queue (including ego) - */ - std::vector< epiworld_fast_int > active; - Model * model = nullptr; - int n_in_queue = 0; - - // Auxiliary variable that checks how many steps - // left are there - // int n_steps_left; - // bool queuing_started = false; - -public: - - void operator+=(Agent * p); - void operator-=(Agent * p); - epiworld_fast_int & operator[](epiworld_fast_uint i); - - // void initialize(Model * m, Agent * p); - void reset(); - - bool operator==(const Queue & other) const; - bool operator!=(const Queue & other) const {return !operator==(other);}; - - static const int NoOne = 0; - static const int OnlySelf = 1; - static const int Everyone = 2; - -}; - -template -inline void Queue::operator+=(Agent * p) -{ - - if (++active[p->id] == 1) - n_in_queue++; - - for (auto n : p->neighbors) - { - - if (++active[n] == 1) - n_in_queue++; - - } - -} - -template -inline void Queue::operator-=(Agent * p) -{ - - if (--active[p->id] == 0) - n_in_queue--; - - for (auto n : p->neighbors) - { - if (--active[n] == 0) - n_in_queue--; - } - -} - -template -inline epiworld_fast_int & Queue::operator[](epiworld_fast_uint i) -{ - return active[i]; -} - -template -inline void Queue::reset() -{ - - if (n_in_queue) - { - - for (auto & q : this->active) - q = 0; - - n_in_queue = 0; - - } - - active.resize(model->size(), 0); - -} - -template -inline bool Queue::operator==(const Queue & other) const -{ - if (active.size() != other.active.size()) - return false; - - for (size_t i = 0u; i < active.size(); ++i) - { - if (active[i] != other.active[i]) - return false; - } - - return true; -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/queue-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/globalactions-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_GLOBALACTIONS_BONES_HPP -#define EPIWORLD_GLOBALACTIONS_BONES_HPP - -// template -// using GlobalFun = std::function*)>; - -/** - * @brief Template for a Global Action - * @details Global actions are functions that Model executes - * at the end of a day. - * - */ -template -class GlobalAction -{ -private: - GlobalFun fun = nullptr; - std::string name = "A global action"; - int day = -99; -public: - - GlobalAction() {}; - - /** - * @brief Construct a new Global Action object - * - * @param fun A function that takes a Model * as argument and returns void. - * @param name A descriptive name for the action. - * @param day The day when the action will be executed. If negative, it will be executed every day. - */ - GlobalAction(GlobalFun fun, std::string name, int day = -99); - - ~GlobalAction() {}; - - void operator()(Model * m, int day); - - void set_name(std::string name); - std::string get_name() const; - - void set_day(int day); - int get_day() const; - - void print() const; - - // Comparison operators - bool operator==(const GlobalAction & other) const; - bool operator!=(const GlobalAction & other) const; - -}; - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/globalactions-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/globalactions-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_GLOBALACTIONS_MEAT_HPP -#define EPIWORLD_GLOBALACTIONS_MEAT_HPP - -template -inline GlobalAction::GlobalAction( - GlobalFun fun, - std::string name, - int day - ) -{ - this->fun = fun; - this->name = name; - this->day = day; -} - -template -inline void GlobalAction::operator()(Model * m, int day) -{ - - if (this->fun == nullptr) - return; - - // Actions apply if day is negative or if day is equal to the day of the action - if (this->day < 0 || this->day == day) - this->fun(m); - - return; - -} - -template -inline void GlobalAction::set_name(std::string name) -{ - this->name = name; -} - -template -inline std::string GlobalAction::get_name() const -{ - return this->name; -} - -template -inline void GlobalAction::set_day(int day) -{ - this->day = day; -} - -template -inline int GlobalAction::get_day() const -{ - return this->day; -} - -template -inline void GlobalAction::print() const -{ - printf_epiworld( - "Global action: %s\n" - " - Day: %i\n", - this->name.c_str(), - this->day - ); -} - -template -inline bool GlobalAction::operator==(const GlobalAction & other) const -{ - return (this->name == other.name) && (this->day == other.day); -} - -template -inline bool GlobalAction::operator!=(const GlobalAction & other) const -{ - return !(*this == other); -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/globalactions-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/model-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODEL_HPP -#define EPIWORLD_MODEL_HPP - -template -class Agent; - -template -class AgentsSample; - -template -class Virus; - -template -class Viruses; - -template -class Viruses_const; - -template -class Tool; - -class AdjList; - -template -class DataBase; - -template -class Queue; - -template -struct Action; - -template -class GlobalAction; - -template -inline epiworld_double susceptibility_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model* m - ); -template -inline epiworld_double transmission_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model* m - ); -template -inline epiworld_double recovery_enhancer_mixer_default( - Agent* p, - VirusPtr v, - Model* m - ); -template -inline epiworld_double death_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model* m - ); - -template -inline std::function*)> make_save_run( - std::string fmt = "%03lu-episimulation.csv", - bool total_hist = true, - bool virus_info = false, - bool virus_hist = false, - bool tool_info = false, - bool tool_hist = false, - bool transmission = false, - bool transition = false, - bool reproductive = false, - bool generation = false - ); - -// template -// class VirusPtr; - -// template -// class ToolPtr; - -/** - * @brief Core class of epiworld. - * - * The model class provides the wrapper that puts together `Agent`, `Virus`, and - * `Tools`. - * - * @tparam TSeq Type of sequence. In principle, users can build models in which - * virus and human sequence is represented as numeric vectors (if needed.) - */ -template -class Model { - friend class Agent; - friend class AgentsSample; - friend class DataBase; - friend class Queue; -protected: - - std::string name = ""; ///< Name of the model - - DataBase db = DataBase(*this); - - std::vector< Agent > population = {}; - - bool using_backup = true; - std::vector< Agent > population_backup = {}; - - - /** - * @name Auxiliary variables for AgentsSample iterators - * - * @details These variables+objects are used by the AgentsSample - * class for building efficient iterators over agents. The idea is to - * reduce the memory allocation, so only during the first call of - * AgentsSample::AgentsSample(Model) these vectors are allocated. - */ - ///@{ - std::vector< Agent * > sampled_population; - size_t sampled_population_n = 0u; - std::vector< size_t > population_left; - size_t population_left_n = 0u; - ///@} - - /** - * @name Agents features - * - * @details Optionally, a model can include an external data source - * pointing to agents information. The data can then be access through - * the `Agent::operator()` method. - * - */ - ///@{ - double * agents_data = nullptr; - size_t agents_data_ncols = 0u; - ///@} - - bool directed = false; - - std::vector< VirusPtr > viruses = {}; - std::vector< epiworld_double > prevalence_virus = {}; ///< Initial prevalence_virus of each virus - std::vector< bool > prevalence_virus_as_proportion = {}; - std::vector< VirusToAgentFun > viruses_dist_funs = {}; - - std::vector< ToolPtr > tools = {}; - std::vector< epiworld_double > prevalence_tool = {}; - std::vector< bool > prevalence_tool_as_proportion = {}; - std::vector< ToolToAgentFun > tools_dist_funs = {}; - - std::vector< Entity > entities = {}; - std::vector< Entity > entities_backup = {}; - - std::mt19937 engine; - - std::uniform_real_distribution<> runifd = - std::uniform_real_distribution<> (0.0, 1.0); - std::normal_distribution<> rnormd = - std::normal_distribution<>(0.0); - std::gamma_distribution<> rgammad = - std::gamma_distribution<>(); - std::lognormal_distribution<> rlognormald = - std::lognormal_distribution<>(); - std::exponential_distribution<> rexpd = - std::exponential_distribution<>(); - std::binomial_distribution<> rbinomd = - std::binomial_distribution<>(); - - std::function>*,Model*,epiworld_double)> rewire_fun; - epiworld_double rewire_prop = 0.0; - - std::map parameters; - epiworld_fast_uint ndays = 0; - Progress pb; - - std::vector< UpdateFun > state_fun = {}; - std::vector< std::string > states_labels = {}; - epiworld_fast_uint nstates = 0u; - - bool verbose = true; - int current_date = 0; - - void dist_tools(); - void dist_virus(); - // void dist_entities(); - - std::chrono::time_point time_start; - std::chrono::time_point time_end; - - // std::chrono::milliseconds - std::chrono::duration time_elapsed = - std::chrono::duration::zero(); - epiworld_fast_uint n_replicates = 0u; - void chrono_start(); - void chrono_end(); - - std::vector> global_actions; - - Queue queue; - bool use_queuing = true; - - /** - * @brief Variables used to keep track of the actions - * to be made regarding viruses. - */ - std::vector< Action > actions = {}; - epiworld_fast_uint nactions = 0u; - - /** - * @brief Construct a new Action object - * - * @param agent_ Agent over which the action will be called - * @param virus_ Virus pointer included in the action - * @param tool_ Tool pointer included in the action - * @param entity_ Entity pointer included in the action - * @param new_state_ New state of the agent - * @param call_ Function the action will call - * @param queue_ Change in the queue - * @param idx_agent_ Location of agent in object. - * @param idx_object_ Location of object in agent. - */ - void actions_add( - Agent * agent_, - VirusPtr virus_, - ToolPtr tool_, - Entity * entity_, - epiworld_fast_uint new_state_, - epiworld_fast_int queue_, - ActionFun call_, - int idx_agent_, - int idx_object_ - ); - - /** - * @brief Executes the stored action - * - * @param model_ Model over which it will be executed. - */ - void actions_run(); - - /** - * @name Tool Mixers - * - * These functions combine the effects tools have to deliver - * a single effect. For example, wearing a mask, been vaccinated, - * and the immune system combine together to jointly reduce - * the susceptibility for a given virus. - * - */ - MixerFun susceptibility_reduction_mixer = susceptibility_reduction_mixer_default; - MixerFun transmission_reduction_mixer = transmission_reduction_mixer_default; - MixerFun recovery_enhancer_mixer = recovery_enhancer_mixer_default; - MixerFun death_reduction_mixer = death_reduction_mixer_default; - - /** - * @brief Advanced usage: Makes a copy of data and returns it as undeleted pointer - * - * @param copy - */ - virtual Model * clone_ptr(); - -public: - - std::vector array_double_tmp; - std::vector * > array_virus_tmp; - - Model(); - Model(const Model & m); - Model(Model & m) = delete; - Model(Model && m); - Model & operator=(const Model & m); - - virtual ~Model() {}; - - void clone_population( - std::vector< Agent > & other_population, - std::vector< Entity > & other_entities, - Model * other_model, - bool & other_directed - ) const ; - - void clone_population( - const Model & other_model - ); - - /** - * @name Set the backup object - * @details `backup` can be used to restore the entire object - * after a run. This can be useful if the user wishes to have - * individuals start with the same network from the beginning. - * - */ - ///@{ - void set_backup(); - // void restore_backup(); - ///@} - - DataBase & get_db(); - epiworld_double & operator()(std::string pname); - - size_t size() const; - - /** - * @name Random number generation - * - * @param eng Random number generator - * @param s Seed - */ - ///@{ - void set_rand_engine(std::mt19937 & eng); - std::mt19937 & get_rand_endgine(); - void seed(size_t s); - void set_rand_norm(epiworld_double mean, epiworld_double sd); - void set_rand_unif(epiworld_double a, epiworld_double b); - void set_rand_exp(epiworld_double lambda); - void set_rand_gamma(epiworld_double alpha, epiworld_double beta); - void set_rand_lognormal(epiworld_double mean, epiworld_double shape); - void set_rand_binom(int n, epiworld_double p); - epiworld_double runif(); - epiworld_double runif(epiworld_double a, epiworld_double b); - epiworld_double rnorm(); - epiworld_double rnorm(epiworld_double mean, epiworld_double sd); - epiworld_double rgamma(); - epiworld_double rgamma(epiworld_double alpha, epiworld_double beta); - epiworld_double rexp(); - epiworld_double rexp(epiworld_double lambda); - epiworld_double rlognormal(); - epiworld_double rlognormal(epiworld_double mean, epiworld_double shape); - int rbinom(); - int rbinom(int n, epiworld_double p); - ///@} - - /** - * @name Add Virus/Tool to the model - * - * This is done before the model has been initialized. - * - * @param v Virus to be added - * @param t Tool to be added - * @param preval Initial prevalence (initial state.) It can be - * specified as a proportion (between zero and one,) or an integer - * indicating number of individuals. - */ - ///@{ - void add_virus(Virus & v, epiworld_double preval); - void add_virus_n(Virus & v, epiworld_fast_uint preval); - void add_virus_fun(Virus & v, VirusToAgentFun fun); - void add_tool(Tool & t, epiworld_double preval); - void add_tool_n(Tool & t, epiworld_fast_uint preval); - void add_tool_fun(Tool & t, ToolToAgentFun fun); - void add_entity(Entity e); - void rm_virus(size_t virus_pos); - void rm_tool(size_t tool_pos); - void rm_entity(size_t entity_pos); - ///@} - - /** - * @brief Associate agents-entities from a file - * - * The structure of the file should be two columns separated by - * space. The first column indexing between 0 and nagents-1, and the - * second column between 0 and nentities - 1. - * - * @param fn Path to the file. - * @param skip How many rows to skip. - */ - void load_agents_entities_ties(std::string fn, int skip); - - /** - * @name Accessing population of the model - * - * @param fn std::string Filename of the edgelist file. - * @param skip int Number of lines to skip in `fn`. - * @param directed bool Whether the graph is directed or not. - * @param size Size of the network. - * @param al AdjList to read into the model. - */ - ///@{ - void agents_from_adjlist( - std::string fn, - int size, - int skip = 0, - bool directed = false - ); - - void agents_from_edgelist( - const std::vector< int > & source, - const std::vector< int > & target, - int size, - bool directed - ); - - void agents_from_adjlist(AdjList al); - - bool is_directed() const; - - std::vector< Agent > & get_agents(); ///< Returns a reference to the vector of agents. - - std::vector< epiworld_fast_uint > get_agents_states() const; ///< Returns a vector with the states of the agents. - - std::vector< Viruses_const > get_agents_viruses() const; ///< Returns a const vector with the viruses of the agents. - - std::vector< Viruses > get_agents_viruses(); ///< Returns a vector with the viruses of the agents. - - std::vector< Entity > & get_entities(); - - void agents_smallworld( - epiworld_fast_uint n = 1000, - epiworld_fast_uint k = 5, - bool d = false, - epiworld_double p = .01 - ); - void agents_empty_graph(epiworld_fast_uint n = 1000); - ///@} - - /** - * @name Functions to run the model - * - * @param seed Seed to be used for Pseudo-RNG. - * @param ndays Number of days (steps) of the simulation. - * @param fun In the case of `run_multiple`, a function that is called - * after each experiment. - * - */ - ///@{ - void update_state(); - void mutate_virus(); - void next(); - virtual void run( - epiworld_fast_uint ndays, - int seed = -1 - ); ///< Runs the simulation (after initialization) - void run_multiple( ///< Multiple runs of the simulation - epiworld_fast_uint ndays, - epiworld_fast_uint nexperiments, - int seed_ = -1, - std::function*)> fun = make_save_run(), - bool reset = true, - bool verbose = true, - int nthreads = 1 - ); - ///@} - - size_t get_n_viruses() const; - size_t get_n_tools() const; - epiworld_fast_uint get_ndays() const; - epiworld_fast_uint get_n_replicates() const; - void set_ndays(epiworld_fast_uint ndays); - bool get_verbose() const; - void verbose_off(); - void verbose_on(); - int today() const; ///< The current time of the model - - /** - * @name Rewire the network preserving the degree sequence. - * - * @details This implementation assumes an undirected network, - * thus if {(i,j), (k,l)} -> {(i,l), (k,j)}, the reciprocal - * is also true, i.e., {(j,i), (l,k)} -> {(j,k), (l,i)}. - * - * @param proportion Proportion of ties to be rewired. - * - * @result A rewired version of the network. - */ - ///@{ - void set_rewire_fun(std::function>*,Model*,epiworld_double)> fun); - void set_rewire_prop(epiworld_double prop); - epiworld_double get_rewire_prop() const; - void rewire(); - ///@} - - /** - * @brief Wrapper of `DataBase::write_data` - * - * @param fn_virus_info Filename. Information about the virus. - * @param fn_virus_hist Filename. History of the virus. - * @param fn_tool_info Filename. Information about the tool. - * @param fn_tool_hist Filename. History of the tool. - * @param fn_total_hist Filename. Aggregated history (state) - * @param fn_transmission Filename. Transmission history. - * @param fn_transition Filename. Markov transition history. - * @param fn_reproductive_number Filename. Case by case reproductive number - */ - void write_data( - std::string fn_virus_info, - std::string fn_virus_hist, - std::string fn_tool_info, - std::string fn_tool_hist, - std::string fn_total_hist, - std::string fn_transmission, - std::string fn_transition, - std::string fn_reproductive_number, - std::string fn_generation_time - ) const; - - /** - * @name Export the network data in edgelist form - * - * @param fn std::string. File name. - * @param source Integer vector - * @param target Integer vector - * - * @details When passing the source and target, the function will - * write the edgelist on those. - */ - ///@{ - void write_edgelist( - std::string fn - ) const; - - void write_edgelist( - std::vector< int > & source, - std::vector< int > & target - ) const; - ///@} - - std::map & params(); - - /** - * @brief Reset the model - * - * @details Resetting the model will: - * - clear the database - * - restore the population (if `set_backup()` was called before) - * - re-distribute tools - * - re-distribute viruses - * - set the date to 0 - * - */ - virtual void reset(); - void print(bool lite = false) const; - - Model && clone() const; - - /** - * @name Manage state (states) in the model - * - * @details - * - * The functions `get_state` return the current values for the - * states included in the model. - * - * @param lab `std::string` Name of the state. - * - * @return `add_state*` returns nothing. - * @return `get_state_*` returns a vector of pairs with the - * states and their labels. - */ - ///@{ - void add_state(std::string lab, UpdateFun fun = nullptr); - const std::vector< std::string > & get_states() const; - const std::vector< UpdateFun > & get_state_fun() const; - void print_state_codes() const; - ///@} - - /** - * @name Setting and accessing parameters from the model - * - * @details Tools can incorporate parameters included in the model. - * Internally, parameters in the tool are stored as pointers to - * an std::map<> of parameters in the model. Using the `epiworld_fast_uint` - * method directly fetches the parameters in the order these were - * added to the tool. Accessing parameters via the `std::string` method - * involves searching the parameter directly in the std::map<> member - * of the model (so it is not recommended.) - * - * The `par()` function members are aliases for `get_param()`. - * - * In the case of the function `read_params`, users can pass a file - * listing parameters to be included in the model. Each line in the - * file should have the following structure: - * - * ``` - * [name of parameter 1]: [value in double] - * [name of parameter 2]: [value in double] - * ... - * ``` - * - * The only condition for parameter names is that these do not include - * a colon. - * - * - * @param initial_val - * @param pname Name of the parameter to add or to fetch - * @param fn Path to the file containing parameters - * @return The current value of the parameter - * in the model. - * - */ - ///@{ - epiworld_double add_param(epiworld_double initial_val, std::string pname); - void read_params(std::string fn); - epiworld_double get_param(epiworld_fast_uint k); - epiworld_double get_param(std::string pname); - // void set_param(size_t k, epiworld_double val); - void set_param(std::string pname, epiworld_double val); - // epiworld_double par(epiworld_fast_uint k); - epiworld_double par(std::string pname); - ///@} - - void get_elapsed( - std::string unit = "auto", - epiworld_double * last_elapsed = nullptr, - epiworld_double * total_elapsed = nullptr, - std::string * unit_abbr = nullptr, - bool print = true - ) const; - - /** - * @name Set the user data object - * - * @param names string vector with the names of the variables. - */ - ///[@ - void set_user_data(std::vector< std::string > names); - void add_user_data(epiworld_fast_uint j, epiworld_double x); - void add_user_data(std::vector< epiworld_double > x); - UserData & get_user_data(); - ///@} - - /** - * @brief Set a global action - * - * @param fun A function to be called on the prescribed date - * @param name Name of the action. - * @param date Integer indicating when the function is called (see details) - * - * @details When date is less than zero, then the function is called - * at the end of every day. Otherwise, the function will be called only - * at the end of the indicated date. - */ - void add_global_action( - std::function*)> fun, - std::string name = "A global action", - int date = -99 - ); - - void add_global_action( - GlobalAction action - ); - - GlobalAction & get_global_action(std::string name); ///< Retrieve a global action by name - GlobalAction & get_global_action(size_t i); ///< Retrieve a global action by index - - void rm_global_action(std::string name); ///< Remove a global action by name - void rm_global_action(size_t i); ///< Remove a global action by index - - void run_global_actions(); - - void clear_state_set(); - - /** - * @name Queuing system - * @details When queueing is on, the model will keep track of which agents - * are either in risk of exposure or exposed. This then is used at each - * step to act only on the aforementioned agents. - * - */ - ////@{ - void queuing_on(); ///< Activates the queuing system (default.) - void queuing_off(); ///< Deactivates the queuing system. - bool is_queuing_on() const; ///< Query if the queuing system is on. - Queue & get_queue(); ///< Retrieve the `Queue` object. - ///@} - - /** - * @name Get the susceptibility reduction object - * - * @param v - * @return epiworld_double - */ - ///@{ - void set_susceptibility_reduction_mixer(MixerFun fun); - void set_transmission_reduction_mixer(MixerFun fun); - void set_recovery_enhancer_mixer(MixerFun fun); - void set_death_reduction_mixer(MixerFun fun); - ///@} - - const std::vector< VirusPtr > & get_viruses() const; - const std::vector< ToolPtr > & get_tools() const; - Virus & get_virus(size_t id); - Tool & get_tool(size_t id); - - /** - * @brief Set the agents data object - * - * @details The data should be an array with the data stored in a - * column major order, i.e., by column. - * - * @param data_ Pointer to the first element of an array of size - * `size() * ncols_`. - * @param ncols_ Number of features included in the data. - * - */ - void set_agents_data(double * data_, size_t ncols_); - double * get_agents_data(); - size_t get_agents_data_ncols() const; - - /** - * @brief Set the name object - * - * @param name - */ - void set_name(std::string name); - std::string get_name() const; - - bool operator==(const Model & other) const; - bool operator!=(const Model & other) const {return !operator==(other);}; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/model-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/model-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODEL_MEAT_HPP -#define EPIWORLD_MODEL_MEAT_HPP - - - -/** - * @brief Function factory for saving model runs - * - * @details This function is the default behavior of the `run_multiple` - * member of `Model`. By default only the total history ( - * case counts by unit of time.) - * - * @tparam TSeq - * @param fmt - * @param total_hist - * @param virus_info - * @param virus_hist - * @param tool_info - * @param tool_hist - * @param transmission - * @param transition - * @return std::function*)> - */ -template -inline std::function*)> make_save_run( - std::string fmt, - bool total_hist, - bool virus_info, - bool virus_hist, - bool tool_info, - bool tool_hist, - bool transmission, - bool transition, - bool reproductive, - bool generation - ) -{ - - // Counting number of % - int n_fmt = 0; - for (auto & f : fmt) - if (f == '%') - n_fmt++; - - if (n_fmt != 1) - throw std::logic_error("The -fmt- argument must have only one \"%\" symbol."); - - // Listting things to save - std::vector< bool > what_to_save = { - virus_info, - virus_hist, - tool_info, - tool_hist, - total_hist, - transmission, - transition, - reproductive, - generation - }; - - std::function*)> saver = [fmt,what_to_save]( - size_t niter, Model * m - ) -> void { - - std::string virus_info = ""; - std::string virus_hist = ""; - std::string tool_info = ""; - std::string tool_hist = ""; - std::string total_hist = ""; - std::string transmission = ""; - std::string transition = ""; - std::string reproductive = ""; - std::string generation = ""; - - char buff[fmt.size()*2 + 1u]; - if (what_to_save[0u]) - { - virus_info = fmt + std::string("_virus_info.csv"); - snprintf(buff, sizeof(buff), virus_info.c_str(), niter); - virus_info = buff; - } - if (what_to_save[1u]) - { - virus_hist = fmt + std::string("_virus_hist.csv"); - snprintf(buff, sizeof(buff), virus_hist.c_str(), niter); - virus_hist = buff; - } - if (what_to_save[2u]) - { - tool_info = fmt + std::string("_tool_info.csv"); - snprintf(buff, sizeof(buff), tool_info.c_str(), niter); - tool_info = buff; - } - if (what_to_save[3u]) - { - tool_hist = fmt + std::string("_tool_hist.csv"); - snprintf(buff, sizeof(buff), tool_hist.c_str(), niter); - tool_hist = buff; - } - if (what_to_save[4u]) - { - total_hist = fmt + std::string("_total_hist.csv"); - snprintf(buff, sizeof(buff), total_hist.c_str(), niter); - total_hist = buff; - } - if (what_to_save[5u]) - { - transmission = fmt + std::string("_transmission.csv"); - snprintf(buff, sizeof(buff), transmission.c_str(), niter); - transmission = buff; - } - if (what_to_save[6u]) - { - transition = fmt + std::string("_transition.csv"); - snprintf(buff, sizeof(buff), transition.c_str(), niter); - transition = buff; - } - if (what_to_save[7u]) - { - - reproductive = fmt + std::string("_reproductive.csv"); - snprintf(buff, sizeof(buff), reproductive.c_str(), niter); - reproductive = buff; - - } - if (what_to_save[8u]) - { - - generation = fmt + std::string("_generation.csv"); - snprintf(buff, sizeof(buff), generation.c_str(), niter); - generation = buff; - - } - - - m->write_data( - virus_info, - virus_hist, - tool_info, - tool_hist, - total_hist, - transmission, - transition, - reproductive, - generation - ); - - }; - - return saver; -} - -template -inline void Model::actions_add( - Agent * agent_, - VirusPtr virus_, - ToolPtr tool_, - Entity * entity_, - epiworld_fast_uint new_state_, - epiworld_fast_int queue_, - ActionFun call_, - int idx_agent_, - int idx_object_ -) { - - ++nactions; - - #ifdef EPI_DEBUG - if (nactions == 0) - throw std::logic_error("Actions cannot be zero!!"); - - if ((virus_ != nullptr) && idx_agent_ >= 0) - { - if (idx_agent_ >= static_cast(virus_->get_agent()->get_n_viruses())) - throw std::logic_error( - "The virus to add is out of range in the host agent." - ); - } - #endif - - if (nactions > actions.size()) - { - - actions.push_back( - Action( - agent_, virus_, tool_, entity_, new_state_, queue_, call_, - idx_agent_, idx_object_ - )); - - } - else - { - - Action & A = actions.at(nactions - 1u); - - A.agent = agent_; - A.virus = virus_; - A.tool = tool_; - A.entity = entity_; - A.new_state = new_state_; - A.queue = queue_; - A.call = call_; - A.idx_agent = idx_agent_; - A.idx_object = idx_object_; - - } - - return; - -} - -template -inline void Model::actions_run() -{ - // Making the call - while (nactions != 0u) - { - - Action a = actions[--nactions]; - Agent * p = a.agent; - - // Applying function - if (a.call) - { - a.call(a, this); - } - - // Updating state - if (static_cast(p->state) != a.new_state) - { - - if (a.new_state >= static_cast(nstates)) - throw std::range_error( - "The proposed state " + std::to_string(a.new_state) + " is out of range. " + - "The model currently has " + std::to_string(nstates - 1) + " states."); - - // Figuring out if we need to undo a change - // If the agent has made a change in the state recently, then we - // need to undo the accounting, e.g., if A->B was made, we need to - // undo it and set B->A so that the daily accounting is right. - if (p->state_last_changed == today()) - { - - // Updating accounting - db.update_state(p->state_prev, p->state, true); // Undoing - db.update_state(p->state_prev, a.new_state); - - for (size_t v = 0u; v < p->n_viruses; ++v) - { - db.update_virus(p->viruses[v]->id, p->state, p->state_prev); // Undoing - db.update_virus(p->viruses[v]->id, p->state_prev, a.new_state); - } - - for (size_t t = 0u; t < p->n_tools; ++t) - { - db.update_tool(p->tools[t]->id, p->state, p->state_prev); // Undoing - db.update_tool(p->tools[t]->id, p->state_prev, a.new_state); - } - - // Changing to the new state, we won't update the - // previous state in case we need to undo the change - p->state = a.new_state; - - } else { - - // Updating accounting - db.update_state(p->state, a.new_state); - - for (size_t v = 0u; v < p->n_viruses; ++v) - db.update_virus(p->viruses[v]->id, p->state, a.new_state); - - for (size_t t = 0u; t < p->n_tools; ++t) - db.update_tool(p->tools[t]->id, p->state, a.new_state); - - // Saving the last state and setting the new one - p->state_prev = p->state; - p->state = a.new_state; - - // It used to be a day before, but we still - p->state_last_changed = today(); - - } - - } - - #ifdef EPI_DEBUG - if (static_cast(p->state) >= static_cast(nstates)) - throw std::range_error( - "The new state " + std::to_string(p->state) + " is out of range. " + - "The model currently has " + std::to_string(nstates - 1) + " states."); - #endif - - // Updating queue - if (use_queuing) - { - - if (a.queue == Queue::Everyone) - queue += p; - else if (a.queue == -Queue::Everyone) - queue -= p; - else if (a.queue == Queue::OnlySelf) - queue[p->get_id()]++; - else if (a.queue == -Queue::OnlySelf) - queue[p->get_id()]--; - else if (a.queue != Queue::NoOne) - throw std::logic_error( - "The proposed queue change is not valid. Queue values can be {-2, -1, 0, 1, 2}." - ); - - } - - } - - return; - -} - -/** - * @name Default function for combining susceptibility_reduction levels - * - * @tparam TSeq - * @param pt - * @return epiworld_double - */ -///@{ -template -inline epiworld_double susceptibility_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model * m -) -{ - epiworld_double total = 1.0; - for (auto & tool : p->get_tools()) - total *= (1.0 - tool->get_susceptibility_reduction(v, m)); - - return 1.0 - total; - -} - -template -inline epiworld_double transmission_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model* m -) -{ - epiworld_double total = 1.0; - for (auto & tool : p->get_tools()) - total *= (1.0 - tool->get_transmission_reduction(v, m)); - - return (1.0 - total); - -} - -template -inline epiworld_double recovery_enhancer_mixer_default( - Agent* p, - VirusPtr v, - Model* m -) -{ - epiworld_double total = 1.0; - for (auto & tool : p->get_tools()) - total *= (1.0 - tool->get_recovery_enhancer(v, m)); - - return 1.0 - total; - -} - -template -inline epiworld_double death_reduction_mixer_default( - Agent* p, - VirusPtr v, - Model* m -) { - - epiworld_double total = 1.0; - for (auto & tool : p->get_tools()) - { - total *= (1.0 - tool->get_death_reduction(v, m)); - } - - return 1.0 - total; - -} -///@} - -template -inline Model * Model::clone_ptr() -{ - Model * ptr = new Model(*dynamic_cast*>(this)); - - #ifdef EPI_DEBUG - if (*this != *ptr) - throw std::logic_error("Model::clone_ptr The copies of the model don't match."); - #endif - - return ptr; -} - -template -inline Model::Model() -{ - db.model = this; - db.user_data = this; - if (use_queuing) - queue.model = this; -} - -template -inline Model::Model(const Model & model) : - name(model.name), - db(model.db), - population(model.population), - population_backup(model.population_backup), - directed(model.directed), - viruses(model.viruses), - prevalence_virus(model.prevalence_virus), - prevalence_virus_as_proportion(model.prevalence_virus_as_proportion), - viruses_dist_funs(model.viruses_dist_funs), - tools(model.tools), - prevalence_tool(model.prevalence_tool), - prevalence_tool_as_proportion(model.prevalence_tool_as_proportion), - tools_dist_funs(model.tools_dist_funs), - entities(model.entities), - entities_backup(model.entities_backup), - // prevalence_entity(model.prevalence_entity), - // prevalence_entity_as_proportion(model.prevalence_entity_as_proportion), - // entities_dist_funs(model.entities_dist_funs), - rewire_fun(model.rewire_fun), - rewire_prop(model.rewire_prop), - parameters(model.parameters), - ndays(model.ndays), - pb(model.pb), - state_fun(model.state_fun), - states_labels(model.states_labels), - nstates(model.nstates), - verbose(model.verbose), - current_date(model.current_date), - global_actions(model.global_actions), - queue(model.queue), - use_queuing(model.use_queuing), - array_double_tmp(model.array_double_tmp.size()), - array_virus_tmp(model.array_virus_tmp.size()) -{ - - - // Removing old neighbors - for (auto & p : population) - p.model = this; - - if (population_backup.size() != 0u) - for (auto & p : population_backup) - p.model = this; - - for (auto & e : entities) - e.model = this; - - if (entities_backup.size() != 0u) - for (auto & e : entities_backup) - e.model = this; - - // Pointing to the right place. This needs - // to be done afterwards since the state zero is set as a function - // of the population. - db.model = this; - db.user_data.model = this; - - if (use_queuing) - queue.model = this; - - agents_data = model.agents_data; - agents_data_ncols = model.agents_data_ncols; - -} - -template -inline Model::Model(Model && model) : - name(std::move(model.name)), - db(std::move(model.db)), - population(std::move(model.population)), - agents_data(std::move(model.agents_data)), - agents_data_ncols(std::move(model.agents_data_ncols)), - directed(std::move(model.directed)), - // Virus - viruses(std::move(model.viruses)), - prevalence_virus(std::move(model.prevalence_virus)), - prevalence_virus_as_proportion(std::move(model.prevalence_virus_as_proportion)), - viruses_dist_funs(std::move(model.viruses_dist_funs)), - // Tools - tools(std::move(model.tools)), - prevalence_tool(std::move(model.prevalence_tool)), - prevalence_tool_as_proportion(std::move(model.prevalence_tool_as_proportion)), - tools_dist_funs(std::move(model.tools_dist_funs)), - // Entities - entities(std::move(model.entities)), - entities_backup(std::move(model.entities_backup)), - // prevalence_entity(std::move(model.prevalence_entity)), - // prevalence_entity_as_proportion(std::move(model.prevalence_entity_as_proportion)), - // entities_dist_funs(std::move(model.entities_dist_funs)), - // Pseudo-RNG - engine(std::move(model.engine)), - runifd(std::move(model.runifd)), - rnormd(std::move(model.rnormd)), - rgammad(std::move(model.rgammad)), - rlognormald(std::move(model.rlognormald)), - rexpd(std::move(model.rexpd)), - // Rewiring - rewire_fun(std::move(model.rewire_fun)), - rewire_prop(std::move(model.rewire_prop)), - parameters(std::move(model.parameters)), - // Others - ndays(model.ndays), - pb(std::move(model.pb)), - state_fun(std::move(model.state_fun)), - states_labels(std::move(model.states_labels)), - nstates(model.nstates), - verbose(model.verbose), - current_date(std::move(model.current_date)), - global_actions(std::move(model.global_actions)), - queue(std::move(model.queue)), - use_queuing(model.use_queuing), - array_double_tmp(model.array_double_tmp.size()), - array_virus_tmp(model.array_virus_tmp.size()) -{ - - db.model = this; - db.user_data.model = this; - - if (use_queuing) - queue.model = this; - -} - -template -inline Model & Model::operator=(const Model & m) -{ - name = m.name; - - population = m.population; - population_backup = m.population_backup; - - for (auto & p : population) - p.model = this; - - if (population_backup.size() != 0) - for (auto & p : population_backup) - p.model = this; - - for (auto & e : entities) - e.model = this; - - if (entities_backup.size() != 0) - for (auto & e : entities_backup) - e.model = this; - - db = m.db; - db.model = this; - db.user_data.model = this; - - directed = m.directed; - - viruses = m.viruses; - prevalence_virus = m.prevalence_virus; - prevalence_virus_as_proportion = m.prevalence_virus_as_proportion; - viruses_dist_funs = m.viruses_dist_funs; - - tools = m.tools; - prevalence_tool = m.prevalence_tool; - prevalence_tool_as_proportion = m.prevalence_tool_as_proportion; - tools_dist_funs = m.tools_dist_funs; - - entities = m.entities; - entities_backup = m.entities_backup; - // prevalence_entity = m.prevalence_entity; - // prevalence_entity_as_proportion = m.prevalence_entity_as_proportion; - // entities_dist_funs = m.entities_dist_funs; - - rewire_fun = m.rewire_fun; - rewire_prop = m.rewire_prop; - - parameters = m.parameters; - ndays = m.ndays; - pb = m.pb; - - state_fun = m.state_fun; - states_labels = m.states_labels; - nstates = m.nstates; - - verbose = m.verbose; - - current_date = m.current_date; - - global_actions = m.global_actions; - - queue = m.queue; - use_queuing = m.use_queuing; - - // Making sure population is passed correctly - // Pointing to the right place - db.model = this; - db.user_data.model = this; - - agents_data = m.agents_data; - agents_data_ncols = m.agents_data_ncols; - - // Figure out the queuing - if (use_queuing) - queue.model = this; - - array_double_tmp.resize(std::max( - size(), - static_cast(1024 * 1024) - )); - - array_virus_tmp.resize(1024u); - - return *this; - -} - -template -inline DataBase & Model::get_db() -{ - return db; -} - -template -inline std::vector> & Model::get_agents() -{ - return population; -} - -template -inline std::vector< epiworld_fast_uint > Model::get_agents_states() const -{ - std::vector< epiworld_fast_uint > states(population.size()); - for (size_t i = 0u; i < population.size(); ++i) - states[i] = population[i].get_state(); - - return states; -} - -template -inline std::vector< Viruses_const > Model::get_agents_viruses() const -{ - - std::vector< Viruses_const > viruses(population.size()); - for (size_t i = 0u; i < population.size(); ++i) - viruses[i] = population[i].get_viruses(); - - return viruses; - -} - -// Same as before, but the non const version -template -inline std::vector< Viruses > Model::get_agents_viruses() -{ - - std::vector< Viruses > viruses(population.size()); - for (size_t i = 0u; i < population.size(); ++i) - viruses[i] = population[i].get_viruses(); - - return viruses; - -} - -template -inline std::vector> & Model::get_entities() -{ - return entities; -} - -template -inline void Model::agents_smallworld( - epiworld_fast_uint n, - epiworld_fast_uint k, - bool d, - epiworld_double p -) -{ - agents_from_adjlist( - rgraph_smallworld(n, k, p, d, *this) - ); -} - -template -inline void Model::agents_empty_graph( - epiworld_fast_uint n -) -{ - - // Resizing the people - population.clear(); - population.resize(n, Agent()); - - // Filling the model and ids - size_t i = 0u; - for (auto & p : population) - { - p.id = i++; - p.model = this; - } - - -} - -// template -// inline void Model::set_rand_engine(std::mt19937 & eng) -// { -// engine = std::make_shared< std::mt19937 >(eng); -// } - -template -inline void Model::set_rand_gamma(epiworld_double alpha, epiworld_double beta) -{ - rgammad = std::gamma_distribution<>(alpha,beta); -} - -template -inline void Model::set_rand_norm(epiworld_double mean, epiworld_double sd) -{ - rnormd = std::normal_distribution<>(mean, sd); -} - -template -inline void Model::set_rand_unif(epiworld_double a, epiworld_double b) -{ - runifd = std::uniform_real_distribution<>(a, b); -} - -template -inline void Model::set_rand_lognormal(epiworld_double mean, epiworld_double shape) -{ - rlognormald = std::lognormal_distribution<>(mean, shape); -} - -template -inline void Model::set_rand_exp(epiworld_double lambda) -{ - rexpd = std::exponential_distribution<>(lambda); -} - -template -inline void Model::set_rand_binom(int n, epiworld_double p) -{ - rbinomd = std::binomial_distribution<>(n, p); -} - -template -inline epiworld_double & Model::operator()(std::string pname) { - - if (parameters.find(pname) == parameters.end()) - throw std::range_error("The parameter "+ pname + "is not in the model."); - - return parameters[pname]; - -} - -template -inline size_t Model::size() const { - return population.size(); -} - -template -inline void Model::dist_virus() -{ - - // Starting first infection - int n = size(); - std::vector< size_t > idx(n); - - int n_left = n; - std::iota(idx.begin(), idx.end(), 0); - - for (size_t v = 0u; v < viruses.size(); ++v) - { - - if (viruses_dist_funs[v]) - { - - viruses_dist_funs[v](*viruses[v], this); - - } else { - - // Picking how many - int nsampled; - if (prevalence_virus_as_proportion[v]) - { - nsampled = static_cast(std::floor(prevalence_virus[v] * size())); - } - else - { - nsampled = static_cast(prevalence_virus[v]); - } - - if (nsampled > static_cast(size())) - throw std::range_error("There are only " + std::to_string(size()) + - " individuals in the population. Cannot add the virus to " + std::to_string(nsampled)); - - - VirusPtr virus = viruses[v]; - - while (nsampled > 0) - { - - int loc = static_cast(floor(runif() * (n_left--))); - - Agent & agent = population[idx[loc]]; - - // Adding action - agent.add_virus( - virus, - const_cast * >(this), - virus->state_init, - virus->queue_init - ); - - // Adjusting sample - nsampled--; - std::swap(idx[loc], idx[n_left]); - - } - - } - - // Apply the actions - actions_run(); - } - -} - -template -inline void Model::dist_tools() -{ - - // Starting first infection - int n = size(); - std::vector< size_t > idx(n); - for (epiworld_fast_uint t = 0; t < tools.size(); ++t) - { - - if (tools_dist_funs[t]) - { - - tools_dist_funs[t](*tools[t], this); - - } else { - - // Picking how many - int nsampled; - if (prevalence_tool_as_proportion[t]) - { - nsampled = static_cast(std::floor(prevalence_tool[t] * size())); - } - else - { - nsampled = static_cast(prevalence_tool[t]); - } - - if (nsampled > static_cast(size())) - throw std::range_error("There are only " + std::to_string(size()) + - " individuals in the population. Cannot add the tool to " + std::to_string(nsampled)); - - ToolPtr tool = tools[t]; - - int n_left = n; - std::iota(idx.begin(), idx.end(), 0); - while (nsampled > 0) - { - int loc = static_cast(floor(runif() * n_left--)); - - population[idx[loc]].add_tool( - tool, - const_cast< Model * >(this), - tool->state_init, tool->queue_init - ); - - nsampled--; - - std::swap(idx[loc], idx[n_left]); - - } - - } - - // Apply the actions - actions_run(); - - } - -} - -// template -// inline void Model::dist_entities() -// { - -// // Starting first infection -// int n = size(); -// std::vector< size_t > idx(n); -// for (epiworld_fast_uint e = 0; e < entities.size(); ++e) -// { - -// if (entities_dist_funs[e]) -// { - -// entities_dist_funs[e](entities[e], this); - -// } else { - -// // Picking how many -// int nsampled; -// if (prevalence_entity_as_proportion[e]) -// { -// nsampled = static_cast(std::floor(prevalence_entity[e] * size())); -// } -// else -// { -// nsampled = static_cast(prevalence_entity[e]); -// } - -// if (nsampled > static_cast(size())) -// throw std::range_error("There are only " + std::to_string(size()) + -// " individuals in the population. Cannot add the entity to " + std::to_string(nsampled)); - -// Entity & entity = entities[e]; - -// int n_left = n; -// std::iota(idx.begin(), idx.end(), 0); -// while (nsampled > 0) -// { -// int loc = static_cast(floor(runif() * n_left--)); - -// population[idx[loc]].add_entity(entity, this, entity.state_init, entity.queue_init); - -// nsampled--; - -// std::swap(idx[loc], idx[n_left]); - -// } - -// } - -// // Apply the actions -// actions_run(); - -// } - -// } - -template -inline void Model::chrono_start() { - time_start = std::chrono::steady_clock::now(); -} - -template -inline void Model::chrono_end() { - time_end = std::chrono::steady_clock::now(); - time_elapsed += (time_end - time_start); - n_replicates++; -} - -template -inline void Model::set_backup() -{ - - if (population_backup.size() == 0u) - population_backup = population; - - if (entities_backup.size() == 0u) - entities_backup = entities; - -} - -// template -// inline void Model::restore_backup() -// { - -// // Restoring the data -// population = *population_backup; -// entities = *entities_backup; - -// // And correcting the pointer -// for (auto & p : population) -// p.model = this; - -// for (auto & e : entities) -// e.model = this; - -// } - -template -inline std::mt19937 & Model::get_rand_endgine() -{ - return engine; -} - -template -inline epiworld_double Model::runif() { - // CHECK_INIT() - return runifd(engine); -} - -template -inline epiworld_double Model::runif(epiworld_double a, epiworld_double b) { - // CHECK_INIT() - return runifd(engine) * (b - a) + a; -} - -template -inline epiworld_double Model::rnorm() { - // CHECK_INIT() - return rnormd(engine); -} - -template -inline epiworld_double Model::rnorm(epiworld_double mean, epiworld_double sd) { - // CHECK_INIT() - return rnormd(engine) * sd + mean; -} - -template -inline epiworld_double Model::rgamma() { - return rgammad(engine); -} - -template -inline epiworld_double Model::rgamma(epiworld_double alpha, epiworld_double beta) { - auto old_param = rgammad.param(); - rgammad.param(std::gamma_distribution<>::param_type(alpha, beta)); - epiworld_double ans = rgammad(engine); - rgammad.param(old_param); - return ans; -} - -template -inline epiworld_double Model::rexp() { - return rexpd(engine); -} - -template -inline epiworld_double Model::rexp(epiworld_double lambda) { - auto old_param = rexpd.param(); - rexpd.param(std::exponential_distribution<>::param_type(lambda)); - epiworld_double ans = rexpd(engine); - rexpd.param(old_param); - return ans; -} - -template -inline epiworld_double Model::rlognormal() { - return rlognormald(engine); -} - -template -inline epiworld_double Model::rlognormal(epiworld_double mean, epiworld_double shape) { - auto old_param = rlognormald.param(); - rlognormald.param(std::lognormal_distribution<>::param_type(mean, shape)); - epiworld_double ans = rlognormald(engine); - rlognormald.param(old_param); - return ans; -} - -template -inline int Model::rbinom() { - return rbinomd(engine); -} - -template -inline int Model::rbinom(int n, epiworld_double p) { - auto old_param = rbinomd.param(); - rbinomd.param(std::binomial_distribution<>::param_type(n, p)); - epiworld_double ans = rbinomd(engine); - rbinomd.param(old_param); - return ans; -} - -template -inline void Model::seed(size_t s) { - this->engine.seed(s); -} - -template -inline void Model::add_virus(Virus & v, epiworld_double preval) -{ - - if (preval > 1.0) - throw std::range_error("Prevalence of virus cannot be above 1.0"); - - if (preval < 0.0) - throw std::range_error("Prevalence of virus cannot be negative"); - - // Checking the state - epiworld_fast_int init_, post_, rm_; - v.get_state(&init_, &post_, &rm_); - - if (init_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -init- state." - ); - else if (post_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -post- state." - ); - - // Recording the variant - db.record_virus(v); - - // Adding new virus - viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(preval); - prevalence_virus_as_proportion.push_back(true); - viruses_dist_funs.push_back(nullptr); - -} - -template -inline void Model::add_virus_n(Virus & v, epiworld_fast_uint preval) -{ - - // Checking the ids - epiworld_fast_int init_, post_, rm_; - v.get_state(&init_, &post_, &rm_); - - if (init_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -init- state." - ); - else if (post_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -post- state." - ); - - // Setting the id - db.record_virus(v); - - // Adding new virus - viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(preval); - prevalence_virus_as_proportion.push_back(false); - viruses_dist_funs.push_back(nullptr); - -} - -template -inline void Model::add_virus_fun(Virus & v, VirusToAgentFun fun) -{ - - // Checking the ids - epiworld_fast_int init_, post_, rm_; - v.get_state(&init_, &post_, &rm_); - - if (init_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -init- state." - ); - else if (post_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -post- state." - ); - - // Setting the id - db.record_virus(v); - // v.set_id(viruses.size()); - - // Adding new virus - viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(0.0); - prevalence_virus_as_proportion.push_back(false); - viruses_dist_funs.push_back(fun); - -} - -template -inline void Model::add_tool(Tool & t, epiworld_double preval) -{ - - if (preval > 1.0) - throw std::range_error("Prevalence of tool cannot be above 1.0"); - - if (preval < 0.0) - throw std::range_error("Prevalence of tool cannot be negative"); - - db.record_tool(t); - - // Adding the tool to the model (and database.) - tools.push_back(std::make_shared< Tool >(t)); - prevalence_tool.push_back(preval); - prevalence_tool_as_proportion.push_back(true); - tools_dist_funs.push_back(nullptr); - -} - -template -inline void Model::add_tool_n(Tool & t, epiworld_fast_uint preval) -{ - - db.record_tool(t); - - tools.push_back(std::make_shared >(t)); - prevalence_tool.push_back(preval); - prevalence_tool_as_proportion.push_back(false); - tools_dist_funs.push_back(nullptr); - -} - -template -inline void Model::add_tool_fun(Tool & t, ToolToAgentFun fun) -{ - - db.record_tool(t); - - tools.push_back(std::make_shared >(t)); - prevalence_tool.push_back(0.0); - prevalence_tool_as_proportion.push_back(false); - tools_dist_funs.push_back(fun); -} - - -template -inline void Model::add_entity(Entity e) -{ - - e.model = this; - e.id = entities.size(); - entities.push_back(e); - -} - -template -inline void Model::rm_virus(size_t virus_pos) -{ - - if (viruses.size() <= virus_pos) - throw std::range_error( - std::string("The specified virus (") + - std::to_string(virus_pos) + - std::string(") is out of range. ") + - std::string("There are only ") + - std::to_string(viruses.size()) + - std::string(" viruses.") - ); - - // Flipping with the last one - std::swap(viruses[virus_pos], viruses[viruses.size() - 1]); - std::swap(viruses_dist_funs[virus_pos], viruses_dist_funs[viruses.size() - 1]); - std::swap(prevalence_virus[virus_pos], prevalence_virus[viruses.size() - 1]); - std::vector::swap( - prevalence_virus_as_proportion[virus_pos], - prevalence_virus_as_proportion[viruses.size() - 1] - ); - - viruses.pop_back(); - viruses_dist_funs.pop_back(); - prevalence_virus.pop_back(); - prevalence_virus_as_proportion.pop_back(); - - return; - -} - -template -inline void Model::rm_tool(size_t tool_pos) -{ - - if (tools.size() <= tool_pos) - throw std::range_error( - std::string("The specified tool (") + - std::to_string(tool_pos) + - std::string(") is out of range. ") + - std::string("There are only ") + - std::to_string(tools.size()) + - std::string(" tools.") - ); - - // Flipping with the last one - std::swap(tools[tool_pos], tools[tools.size() - 1]); - std::swap(tools_dist_funs[tool_pos], tools_dist_funs[tools.size() - 1]); - std::swap(prevalence_tool[tool_pos], prevalence_tool[tools.size() - 1]); - - /* There's an error on windows: - https://github.com/UofUEpiBio/epiworldR/actions/runs/4801482395/jobs/8543744180#step:6:84 - - More clear here: - https://stackoverflow.com/questions/58660207/why-doesnt-stdswap-work-on-vectorbool-elements-under-clang-win - */ - std::vector::swap( - prevalence_tool_as_proportion[tool_pos], - prevalence_tool_as_proportion[tools.size() - 1] - ); - - // auto old = prevalence_tool_as_proportion[tool_pos]; - // prevalence_tool_as_proportion[tool_pos] = prevalence_tool_as_proportion[tools.size() - 1]; - // prevalence_tool_as_proportion[tools.size() - 1] = old; - - - tools.pop_back(); - tools_dist_funs.pop_back(); - prevalence_tool.pop_back(); - prevalence_tool_as_proportion.pop_back(); - - return; - -} - -template -inline void Model::load_agents_entities_ties( - std::string fn, - int skip - ) -{ - - int i,j; - std::ifstream filei(fn); - - if (!filei) - throw std::logic_error("The file " + fn + " was not found."); - - int linenum = 0; - std::vector< epiworld_fast_uint > source_; - std::vector< std::vector< epiworld_fast_uint > > target_(entities.size()); - - target_.reserve(1e5); - - while (!filei.eof()) - { - - if (linenum++ < skip) - continue; - - filei >> i >> j; - - // Looking for exceptions - if (filei.bad()) - throw std::logic_error( - "I/O error while reading the file " + - fn - ); - - if (filei.fail()) - break; - - if (i >= static_cast(this->size())) - throw std::range_error( - "The agent["+std::to_string(linenum)+"] = " + std::to_string(i) + - " is above the max id " + std::to_string(this->size() - 1) - ); - - if (j >= static_cast(this->entities.size())) - throw std::range_error( - "The entity["+std::to_string(linenum)+"] = " + std::to_string(j) + - " is above the max id " + std::to_string(this->entities.size() - 1) - ); - - target_[j].push_back(i); - - population[i].add_entity(entities[j], nullptr); - - } - - // // Iterating over entities - // for (size_t e = 0u; e < entities.size(); ++e) - // { - - // // This entity will have individuals assigned to it, so we add it - // if (target_[e].size() > 0u) - // { - - // // Filling in the gaps - // prevalence_entity[e] = static_cast(target_[e].size()); - // prevalence_entity_as_proportion[e] = false; - - // // Generating the assignment function - // auto who = target_[e]; - // entities_dist_funs[e] = - // [who](Entity & e, Model* m) -> void { - - // for (auto w : who) - // m->population[w].add_entity(e, m, e.state_init, e.queue_init); - - // return; - - // }; - - // } - - // } - - return; - -} - -template -inline void Model::agents_from_adjlist( - std::string fn, - int size, - int skip, - bool directed - ) { - - AdjList al; - al.read_edgelist(fn, size, skip, directed); - this->agents_from_adjlist(al); - -} - -template -inline void Model::agents_from_edgelist( - const std::vector< int > & source, - const std::vector< int > & target, - int size, - bool directed -) { - - AdjList al(source, target, size, directed); - agents_from_adjlist(al); - -} - -template -inline void Model::agents_from_adjlist(AdjList al) { - - // Resizing the people - agents_empty_graph(al.vcount()); - - const auto & tmpdat = al.get_dat(); - - for (size_t i = 0u; i < tmpdat.size(); ++i) - { - - // population[i].id = i; - population[i].model = this; - - for (const auto & link: tmpdat[i]) - { - - population[i].add_neighbor( - population[link.first], - true, true - ); - - } - - } - - #ifdef EPI_DEBUG - for (auto & p: population) - { - if (p.id >= static_cast(al.vcount())) - throw std::logic_error( - "Agent's id cannot be negative above or equal to the number of agents!"); - } - #endif - -} - -template -inline bool Model::is_directed() const -{ - if (population.size() == 0u) - throw std::logic_error("The population hasn't been initialized."); - - return directed; -} - -template -inline int Model::today() const { - - if (ndays == 0) - return 0; - - return this->current_date; -} - -template -inline void Model::next() { - - db.record(); - ++this->current_date; - - // Advancing the progress bar - if ((this->current_date >= 1) && verbose) - pb.next(); - - return ; -} - -template -inline void Model::run( - epiworld_fast_uint ndays, - int seed -) -{ - - if (size() == 0u) - throw std::logic_error("There's no agents in this model!"); - - if (nstates == 0u) - throw std::logic_error( - std::string("No states registered in this model. ") + - std::string("At least one state should be included. See the function -Model::add_state()-") - ); - - // Setting up the number of steps - this->ndays = ndays; - - if (seed >= 0) - engine.seed(seed); - - array_double_tmp.resize(std::max( - size(), - static_cast(1024 * 1024) - )); - - - array_virus_tmp.resize(1024); - - // Checking whether the proposed state in/out/removed - // are valid - epiworld_fast_int _init, _end, _removed; - int nstate_int = static_cast(nstates); - for (auto & v : viruses) - { - v->get_state(&_init, &_end, &_removed); - - // Negative unspecified state - if (((_init != -99) && (_init < 0)) || (_init >= nstate_int)) - throw std::range_error("States must be between 0 and " + - std::to_string(nstates - 1)); - - // Negative unspecified state - if (((_end != -99) && (_end < 0)) || (_end >= nstate_int)) - throw std::range_error("States must be between 0 and " + - std::to_string(nstates - 1)); - - if (((_removed != -99) && (_removed < 0)) || (_removed >= nstate_int)) - throw std::range_error("States must be between 0 and " + - std::to_string(nstates - 1)); - - } - - for (auto & t : tools) - { - t->get_state(&_init, &_end); - - // Negative unspecified state - if (((_init != -99) && (_init < 0)) || (_init >= nstate_int)) - throw std::range_error("States must be between 0 and " + - std::to_string(nstates - 1)); - - // Negative unspecified state - if (((_end != -99) && (_end < 0)) || (_end >= nstate_int)) - throw std::range_error("States must be between 0 and " + - std::to_string(nstates - 1)); - - } - - // Starting first infection and tools - reset(); - - // Initializing the simulation - chrono_start(); - EPIWORLD_RUN((*this)) - { - - #ifdef EPI_DEBUG - db.n_transmissions_potential = 0; - db.n_transmissions_today = 0; - #endif - - // We can execute these components in whatever order the - // user needs. - this->update_state(); - - // We start with the global actions - this->run_global_actions(); - - // In this case we are applying degree sequence rewiring - // to change the network just a bit. - this->rewire(); - - // This locks all the changes - this->next(); - - // Mutation must happen at the very end of all - this->mutate_virus(); - - } - - // The last reaches the end... - this->current_date--; - - chrono_end(); - -} - -template -inline void Model::run_multiple( - epiworld_fast_uint ndays, - epiworld_fast_uint nexperiments, - int seed_, - std::function*)> fun, - bool reset, - bool verbose, - int nthreads -) -{ - - if (seed_ >= 0) - this->seed(seed_); - - // Seeds will be reproducible by default - std::vector< int > seeds_n(nexperiments); - // #ifdef EPI_DEBUG - // std::fill( - // seeds_n.begin(), - // seeds_n.end(), - // std::floor( - // runif() * static_cast(std::numeric_limits::max()) - // ) - // ); - // #else - for (auto & s : seeds_n) - { - s = static_cast( - std::floor( - runif() * static_cast(std::numeric_limits::max()) - ) - ); - } - // #endif - - EPI_DEBUG_NOTIFY_ACTIVE() - - bool old_verb = this->verbose; - verbose_off(); - - // Setting up backup - if (reset) - set_backup(); - - #ifdef _OPENMP - - omp_set_num_threads(nthreads); - - // Generating copies of the model - std::vector< Model * > these; - for (size_t i = 0; i < static_cast(std::max(nthreads - 1, 0)); ++i) - these.push_back(clone_ptr()); - - // Figuring out how many replicates - std::vector< size_t > nreplicates(nthreads, 0); - std::vector< size_t > nreplicates_csum(nthreads, 0); - size_t sums = 0u; - for (int i = 0; i < nthreads; ++i) - { - nreplicates[i] = static_cast( - std::floor(nexperiments/nthreads) - ); - - // This takes the cumsum - nreplicates_csum[i] = sums; - - sums += nreplicates[i]; - - } - - if (sums < nexperiments) - nreplicates[nthreads - 1] += (nexperiments - sums); - - Progress pb_multiple( - nreplicates[0u], - EPIWORLD_PROGRESS_BAR_WIDTH - ); - - if (verbose) - { - - printf_epiworld( - "Starting multiple runs (%i) using %i thread(s)\n", - static_cast(nexperiments), - static_cast(nthreads) - ); - - pb_multiple.start(); - - } - - #ifdef EPI_DEBUG - // Checking the initial state of all the models. Throw an - // exception if they are not the same. - for (size_t i = 1; i < static_cast(std::max(nthreads - 1, 0)); ++i) - { - - if (db != these[i]->db) - { - throw std::runtime_error( - "The initial state of the models is not the same" - ); - } - } - #endif - - #pragma omp parallel shared(these, nreplicates, nreplicates_csum, seeds_n) \ - firstprivate(nexperiments, nthreads, fun, reset, verbose, pb_multiple, ndays) \ - default(shared) - { - - auto iam = omp_get_thread_num(); - - for (size_t n = 0u; n < nreplicates[iam]; ++n) - { - size_t sim_id = nreplicates_csum[iam] + n; - if (iam == 0) - { - - // Initializing the seed - run(ndays, seeds_n[sim_id]); - - if (fun) - fun(n, this); - - // Only the first one prints - if (verbose) - pb_multiple.next(); - - } else { - - // Initializing the seed - these[iam - 1]->run(ndays, seeds_n[sim_id]); - - if (fun) - fun(sim_id, these[iam - 1]); - - } - - } - - } - - // Adjusting the number of replicates - n_replicates += (nexperiments - nreplicates[0u]); - - for (auto & ptr : these) - delete ptr; - - #else - // if (reset) - // set_backup(); - - Progress pb_multiple( - nexperiments, - EPIWORLD_PROGRESS_BAR_WIDTH - ) - ; - if (verbose) - { - - printf_epiworld( - "Starting multiple runs (%i)\n", - static_cast(nexperiments) - ); - - pb_multiple.start(); - - } - - for (size_t n = 0u; n < nexperiments; ++n) - { - - run(ndays, seeds_n[n]); - - if (fun) - fun(n, this); - - if (verbose) - pb_multiple.next(); - - } - #endif - - if (verbose) - pb_multiple.end(); - - if (old_verb) - verbose_on(); - - return; - -} - -template -inline void Model::update_state() { - - // Next state - if (use_queuing) - { - int i = -1; - for (auto & p: population) - if (queue[++i] > 0) - { - if (state_fun[p.state]) - state_fun[p.state](&p, this); - } - - } - else - { - - for (auto & p: population) - if (state_fun[p.state]) - state_fun[p.state](&p, this); - - } - - actions_run(); - -} - - - -template -inline void Model::mutate_virus() { - - if (use_queuing) - { - - int i = -1; - for (auto & p: population) - { - - if (queue[++i] == 0) - continue; - - if (p.n_viruses > 0u) - for (auto & v : p.get_viruses()) - v->mutate(this); - - } - - } - else - { - - for (auto & p: population) - { - - if (p.n_viruses > 0u) - for (auto & v : p.get_viruses()) - v->mutate(this); - - } - - } - - -} - -template -inline size_t Model::get_n_viruses() const { - return db.size(); -} - -template -inline size_t Model::get_n_tools() const { - return tools.size(); -} - -template -inline epiworld_fast_uint Model::get_ndays() const { - return ndays; -} - -template -inline epiworld_fast_uint Model::get_n_replicates() const -{ - return n_replicates; -} - -template -inline void Model::set_ndays(epiworld_fast_uint ndays) { - this->ndays = ndays; -} - -template -inline bool Model::get_verbose() const { - return verbose; -} - -template -inline void Model::verbose_on() { - verbose = true; -} - -template -inline void Model::verbose_off() { - verbose = false; -} - -template -inline void Model::set_rewire_fun( - std::function>*,Model*,epiworld_double)> fun - ) { - rewire_fun = fun; -} - -template -inline void Model::set_rewire_prop(epiworld_double prop) -{ - - if (prop < 0.0) - throw std::range_error("Proportions cannot be negative."); - - if (prop > 1.0) - throw std::range_error("Proportions cannot be above 1.0."); - - rewire_prop = prop; -} - -template -inline epiworld_double Model::get_rewire_prop() const { - return rewire_prop; -} - -template -inline void Model::rewire() { - - if (rewire_fun) - rewire_fun(&population, this, rewire_prop); -} - - -template -inline void Model::write_data( - std::string fn_virus_info, - std::string fn_virus_hist, - std::string fn_tool_info, - std::string fn_tool_hist, - std::string fn_total_hist, - std::string fn_transmission, - std::string fn_transition, - std::string fn_reproductive_number, - std::string fn_generation_time - ) const -{ - - db.write_data( - fn_virus_info, fn_virus_hist, - fn_tool_info, fn_tool_hist, - fn_total_hist, fn_transmission, fn_transition, - fn_reproductive_number, fn_generation_time - ); - -} - -template -inline void Model::write_edgelist( - std::string fn - ) const -{ - - // Figuring out the writing sequence - std::vector< const Agent * > wseq(size()); - for (const auto & p: population) - wseq[p.id] = &p; - - std::ofstream efile(fn, std::ios_base::out); - efile << "source target\n"; - if (this->is_directed()) - { - - for (const auto & p : wseq) - { - for (auto & n : p->neighbors) - efile << p->id << " " << n << "\n"; - } - - } else { - - for (const auto & p : wseq) - { - for (auto & n : p->neighbors) - if (static_cast(p->id) <= static_cast(n)) - efile << p->id << " " << n << "\n"; - } - - } - -} - -template -inline void Model::write_edgelist( -std::vector< int > & source, -std::vector< int > & target -) const { - - // Figuring out the writing sequence - std::vector< const Agent * > wseq(size()); - for (const auto & p: population) - wseq[p.id] = &p; - - if (this->is_directed()) - { - - for (const auto & p : wseq) - { - for (auto & n : p->neighbors) - { - source.push_back(static_cast(p->id)); - target.push_back(static_cast(n)); - } - } - - } else { - - for (const auto & p : wseq) - { - for (auto & n : p->neighbors) { - if (static_cast(p->id) <= static_cast(n)) { - source.push_back(static_cast(p->id)); - target.push_back(static_cast(n)); - } - } - } - - } - - -} - -template -inline std::map & Model::params() -{ - return parameters; -} - -template -inline void Model::reset() { - - // Restablishing people - pb = Progress(ndays, 80); - - if (population_backup.size() != 0u) - { - population = population_backup; - - #ifdef EPI_DEBUG - for (size_t i = 0; i < population.size(); ++i) - { - - if (population[i] != population_backup[i]) - throw std::logic_error("Model::reset population doesn't match."); - - } - #endif - - } - else - { - for (auto & p : population) - p.reset(); - - #ifdef EPI_DEBUG - for (auto & a: population) - { - if (a.get_state() != 0u) - throw std::logic_error("Model::reset population doesn't match." - "Some agents are not in the baseline state."); - } - #endif - } - - if (entities_backup.size() != 0) - { - entities = entities_backup; - - #ifdef EPI_DEBUG - for (size_t i = 0; i < entities.size(); ++i) - { - - if (entities[i] != entities_backup[i]) - throw std::logic_error("Model::reset entities don't match."); - - } - #endif - - } - else - { - for (auto & e: entities) - e.reset(); - } - - current_date = 0; - - db.reset(); - - // This also clears the queue - if (use_queuing) - queue.reset(); - - // Re distributing tools and virus - dist_virus(); - dist_tools(); - - // Recording the original state (at time 0) and advancing - // to time 1 - next(); - - -} - -// Too big to keep here -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//model-meat-print.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODEL_MEAT_PRINT_HPP -#define EPIWORLD_MODEL_MEAT_PRINT_HPP - -template -inline void Model::print(bool lite) const -{ - - // Horizontal line - std::string line = ""; - for (epiworld_fast_uint i = 0u; i < 80u; ++i) - line += "_"; - - // Prints a message if debugging is on - EPI_DEBUG_NOTIFY_ACTIVE() - - printf_epiworld("%s\n",line.c_str()); - - if (lite) - { - // Printing the name of the model - printf_epiworld("%s", name.c_str()); - - // Printing the number of agents, viruses, and tools - printf_epiworld( - "\nIt features %i agents, %i virus(es), and %i tool(s).\n", - static_cast(size()), - static_cast(get_n_viruses()), - static_cast(get_n_tools()) - ); - - printf_epiworld( - "The model has %i states.", - static_cast(nstates) - ); - - if (today() != 0) - { - printf_epiworld( - "\nThe final distribution is: " - ); - - int nstate_int = static_cast(nstates); - - for (int i = 0u; i < nstate_int; ++i) - { - printf_epiworld( - "%i %s%s", - static_cast(db.today_total[ i ]), - states_labels[i].c_str(), - ( - i == (nstate_int - 2) - ) ? ", and " : ( - (i == (nstate_int - 1)) ? ".\n" : ", " - ) - ); - } - } else { - printf_epiworld(" The model hasn't been run yet.\n"); - } - - return; - } - - printf_epiworld("%s\n%s\n\n",line.c_str(), "SIMULATION STUDY"); - - printf_epiworld("Name of the model : %s\n", (this->name == "") ? std::string("(none)").c_str() : name.c_str()); - printf_epiworld("Population size : %i\n", static_cast(size())); - - auto ncols = get_agents_data_ncols(); - - if (ncols > 0) - { - printf_epiworld("Agents' data loaded : yes (%i columns/features)\n", static_cast(ncols)); - } - else - { - printf_epiworld("Agents' data : (none)\n"); - } - - printf_epiworld("Number of entities : %i\n", static_cast(entities.size())); - printf_epiworld("Days (duration) : %i (of %i)\n", today(), static_cast(ndays)); - printf_epiworld("Number of viruses : %i\n", static_cast(db.get_n_viruses())); - if (n_replicates > 0u) - { - std::string abbr; - epiworld_double elapsed; - epiworld_double total; - get_elapsed("auto", &elapsed, &total, &abbr, false); - printf_epiworld("Last run elapsed t : %.2f%s\n", elapsed, abbr.c_str()); - if (n_replicates > 1u) - { - printf_epiworld("Total elapsed t : %.2f%s (%i runs)\n", total, abbr.c_str(), static_cast(n_replicates)); - } - - // Elapsed time in speed - get_elapsed("microseconds", &elapsed, &total, &abbr, false); - printf_epiworld("Last run speed : %.2f million agents x day / second\n", - static_cast(this->size()) * - static_cast(this->get_ndays()) / - static_cast(elapsed) - ); - if (n_replicates > 1u) - { - printf_epiworld("Average run speed : %.2f million agents x day / second\n", - static_cast(this->size()) * - static_cast(this->get_ndays()) * - static_cast(n_replicates) / - static_cast(total) - ); - } - - } else { - printf_epiworld("Last run elapsed t : -\n"); - } - - - if (rewire_fun) - { - printf_epiworld("Rewiring : on (%.2f)\n\n", rewire_prop); - } else { - printf_epiworld("Rewiring : off\n\n"); - } - - // Printing global actions - printf_epiworld("Global actions:\n"); - for (auto & a : global_actions) - { - if (a.get_day() < 0) - { - printf_epiworld(" - %s (runs daily)\n", a.get_name().c_str()); - } else { - printf_epiworld(" - %s (day %i)\n", a.get_name().c_str(), a.get_day()); - } - } - - if (global_actions.size() == 0u) - { - printf_epiworld(" (none)\n"); - } - - printf_epiworld("\nVirus(es):\n"); - size_t n_viruses_model = viruses.size(); - for (size_t i = 0u; i < n_viruses_model; ++i) - { - - if ((n_viruses_model > 10) && (i >= 10)) - { - printf_epiworld(" ...and %li more viruses...\n", n_viruses_model - i); - break; - } - - if (i < n_viruses_model) - { - - if (prevalence_virus_as_proportion[i]) - { - - printf_epiworld( - " - %s (baseline prevalence: %.2f%%)\n", - viruses[i]->get_name().c_str(), - prevalence_virus[i] * 100.00 - ); - - } - else - { - - printf_epiworld( - " - %s (baseline prevalence: %i seeds)\n", - viruses[i]->get_name().c_str(), - static_cast(prevalence_virus[i]) - ); - - } - - } else { - - printf_epiworld( - " - %s (originated in the model...)\n", - viruses[i]->get_name().c_str() - ); - - } - - } - - if (viruses.size() == 0u) - { - printf_epiworld(" (none)\n"); - } - - printf_epiworld("\nTool(s):\n"); - size_t n_tools_model = tools.size(); - for (size_t i = 0u; i < tools.size(); ++i) - { - - if ((n_tools_model > 10) && (i >= 10)) - { - printf_epiworld(" ...and %li more tools...\n", n_tools_model - i); - break; - } - - if (i < n_tools_model) - { - if (prevalence_tool_as_proportion[i]) - { - - printf_epiworld( - " - %s (baseline prevalence: %.2f%%)\n", - tools[i]->get_name().c_str(), - prevalence_tool[i] * 100.0 - ); - - } - else - { - - printf_epiworld( - " - %s (baseline prevalence: %i seeds)\n", - tools[i]->get_name().c_str(), - static_cast(prevalence_tool[i]) - ); - - } - - } else { - - printf_epiworld( - " - %s (originated in the model...)\n", - tools[i]->get_name().c_str() - ); - - } - - - } - - if (tools.size() == 0u) - { - printf_epiworld(" (none)\n"); - } - - // Information about the parameters included - printf_epiworld("\nModel parameters:\n"); - epiworld_fast_uint nchar = 0u; - for (auto & p : parameters) - if (p.first.length() > nchar) - nchar = p.first.length(); - - std::string fmt = " - %-" + std::to_string(nchar + 1) + "s: "; - for (auto & p : parameters) - { - std::string fmt_tmp = fmt; - if (std::fabs(p.second) < 0.0001) - fmt_tmp += "%.1e\n"; - else - fmt_tmp += "%.4f\n"; - - printf_epiworld( - fmt_tmp.c_str(), - p.first.c_str(), - p.second - ); - - } - - if (parameters.size() == 0u) - { - printf_epiworld(" (none)\n"); - } - - nchar = 0u; - for (auto & p : states_labels) - if (p.length() > nchar) - nchar = p.length(); - - - - if (today() != 0) - { - fmt = - std::string(" - (%") + - std::to_string(std::to_string(nstates).length()) + - std::string("d) %-") + std::to_string(nchar) + - std::string("s : %") + - std::to_string(std::to_string(size()).length()) + - std::string("i -> %i\n"); - } else { - fmt = - std::string(" - (%") + - std::to_string(std::to_string(nstates).length()) + - std::string("d) %-") + std::to_string(nchar) + - std::string("s : %i\n"); - } - - if (today() != 0) - { - printf_epiworld("\nDistribution of the population at time %i:\n", today()); - for (size_t s = 0u; s < nstates; ++s) - { - - printf_epiworld( - fmt.c_str(), - s, - states_labels[s].c_str(), - db.hist_total_counts[s], - db.today_total[ s ] - ); - - } - // else - // { - - // printf_epiworld( - // fmt.c_str(), - // s, - // states_labels[s].c_str(), - // db.today_total[ s ] - // ); - - // } - } - - if (today() != 0) - (void) db.transition_probability(true); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//model-meat-print.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -template -inline Model && Model::clone() const { - - // Step 1: Regen the individuals and make sure that: - // - Neighbors point to the right place - // - DB is pointing to the right place - Model res(*this); - - // Removing old neighbors - for (auto & p: res.population) - p.neighbors.clear(); - - // Rechecking individuals - for (epiworld_fast_uint p = 0u; p < size(); ++p) - { - // Making room - const Agent & agent_this = population[p]; - Agent & agent_res = res.population[p]; - - // Agent pointing to the right model and agent - agent_res.model = &res; - agent_res.viruses.agent = &agent_res; - agent_res.tools.agent = &agent_res; - - // Readding - std::vector< Agent * > neigh = agent_this.neighbors; - for (epiworld_fast_uint n = 0u; n < neigh.size(); ++n) - { - // Point to the right neighbors - int loc = res.population_ids[neigh[n]->get_id()]; - agent_res.add_neighbor(res.population[loc], true, true); - - } - - } - - return res; - -} - - - -template -inline void Model::add_state( - std::string lab, - UpdateFun fun -) -{ - - // Checking it doesn't match - for (auto & s : states_labels) - if (s == lab) - throw std::logic_error("state \"" + s + "\" already registered."); - - states_labels.push_back(lab); - state_fun.push_back(fun); - nstates++; - -} - - -template -inline const std::vector< std::string > & -Model::get_states() const -{ - return states_labels; -} - -template -inline const std::vector< UpdateFun > & -Model::get_state_fun() const -{ - return state_fun; -} - -template -inline void Model::print_state_codes() const -{ - - // Horizontal line - std::string line = ""; - for (epiworld_fast_uint i = 0u; i < 80u; ++i) - line += "_"; - - printf_epiworld("\n%s\nstates CODES\n\n", line.c_str()); - - epiworld_fast_uint nchar = 0u; - for (auto & p : states_labels) - if (p.length() > nchar) - nchar = p.length(); - - std::string fmt = " %2i = %-" + std::to_string(nchar + 1 + 4) + "s\n"; - for (epiworld_fast_uint i = 0u; i < nstates; ++i) - { - - printf_epiworld( - fmt.c_str(), - i, - (states_labels[i] + " (S)").c_str() - ); - - } - -} - - - -template -inline epiworld_double Model::add_param( - epiworld_double initial_value, - std::string pname - ) { - - if (parameters.find(pname) == parameters.end()) - parameters[pname] = initial_value; - - return initial_value; - -} - -template -inline void Model::read_params(std::string fn) -{ - - std::ifstream paramsfile(fn); - - if (!paramsfile) - throw std::logic_error("The file " + fn + " was not found."); - - std::regex pattern("^([^:]+)\\s*[:]\\s*([0-9]+|[0-9]*\\.[0-9]+)?\\s*$"); - - std::string line; - std::smatch match; - auto empty = std::sregex_iterator(); - - while (std::getline(paramsfile, line)) - { - - // Is it a comment or an empty line? - if (std::regex_match(line, std::regex("^([*].+|//.+|#.+|\\s*)$"))) - continue; - - // Finding the patter, if it doesn't match, then error - std::regex_match(line, match, pattern); - - if (match.empty()) - throw std::logic_error("The line does not match parameters:\n" + line); - - // Capturing the number - std::string anumber = match[2u].str() + match[3u].str(); - epiworld_double tmp_num = static_cast( - std::strtod(anumber.c_str(), nullptr) - ); - - add_param( - tmp_num, - std::regex_replace( - match[1u].str(), - std::regex("^\\s+|\\s+$"), - "") - ); - - } - -} - -template -inline epiworld_double Model::get_param(std::string pname) -{ - if (parameters.find(pname) == parameters.end()) - throw std::logic_error("The parameter " + pname + " does not exists."); - - return parameters[pname]; -} - -template -inline void Model::set_param(std::string pname, epiworld_double value) -{ - if (parameters.find(pname) == parameters.end()) - throw std::logic_error("The parameter " + pname + " does not exists."); - - parameters[pname] = value; - - return; - -} - -// // Same as before but using the size_t method -// template -// inline void Model::set_param(size_t k, epiworld_double value) -// { -// if (k >= parameters.size()) -// throw std::logic_error("The parameter index " + std::to_string(k) + " does not exists."); - -// // Access the k-th element of the std::unordered_map parameters - - -// *(parameters.begin() + k) = value; - -// return; -// } - -template -inline epiworld_double Model::par(std::string pname) -{ - return parameters[pname]; -} - -#define DURCAST(tunit,txtunit) {\ - elapsed = std::chrono::duration_cast(\ - time_end - time_start).count(); \ - elapsed_total = std::chrono::duration_cast(time_elapsed).count(); \ - abbr_unit = txtunit;} - -template -inline void Model::get_elapsed( - std::string unit, - epiworld_double * last_elapsed, - epiworld_double * total_elapsed, - std::string * unit_abbr, - bool print -) const { - - // Preparing the result - epiworld_double elapsed, elapsed_total; - std::string abbr_unit; - - // Figuring out the length - if (unit == "auto") - { - - size_t tlength = std::to_string( - static_cast(floor(time_elapsed.count())) - ).length(); - - if (tlength <= 1) - unit = "nanoseconds"; - else if (tlength <= 3) - unit = "microseconds"; - else if (tlength <= 6) - unit = "milliseconds"; - else if (tlength <= 8) - unit = "seconds"; - else if (tlength <= 9) - unit = "minutes"; - else - unit = "hours"; - - } - - if (unit == "nanoseconds") DURCAST(nanoseconds,"ns") - else if (unit == "microseconds") DURCAST(microseconds,"\xC2\xB5s") - else if (unit == "milliseconds") DURCAST(milliseconds,"ms") - else if (unit == "seconds") DURCAST(seconds,"s") - else if (unit == "minutes") DURCAST(minutes,"m") - else if (unit == "hours") DURCAST(hours,"h") - else - throw std::range_error("The time unit " + unit + " is not supported."); - - - if (last_elapsed != nullptr) - *last_elapsed = elapsed; - if (total_elapsed != nullptr) - *total_elapsed = elapsed_total; - if (unit_abbr != nullptr) - *unit_abbr = abbr_unit; - - if (!print) - return; - - if (n_replicates > 1u) - { - printf_epiworld("last run elapsed time : %.2f%s\n", - elapsed, abbr_unit.c_str()); - printf_epiworld("total elapsed time : %.2f%s\n", - elapsed_total, abbr_unit.c_str()); - printf_epiworld("total runs : %i\n", - static_cast(n_replicates)); - printf_epiworld("mean run elapsed time : %.2f%s\n", - elapsed_total/static_cast(n_replicates), abbr_unit.c_str()); - - } else { - printf_epiworld("last run elapsed time : %.2f%s.\n", elapsed, abbr_unit.c_str()); - } -} - -template -inline void Model::set_user_data(std::vector< std::string > names) -{ - db.set_user_data(names); -} - -template -inline void Model::add_user_data(epiworld_fast_uint j, epiworld_double x) -{ - db.add_user_data(j, x); -} - -template -inline void Model::add_user_data(std::vector x) -{ - db.add_user_data(x); -} - -template -inline UserData & Model::get_user_data() -{ - return db.get_user_data(); -} - -template -inline void Model::add_global_action( - std::function*)> fun, - std::string name, - int date -) -{ - - global_actions.push_back( - GlobalAction( - fun, - name, - date - ) - ); - -} - -template -inline void Model::add_global_action( - GlobalAction action -) -{ - global_actions.push_back(action); -} - -template -GlobalAction & Model::get_global_action( - std::string name -) -{ - - for (auto & a : global_actions) - if (a.name == name) - return a; - - throw std::logic_error("The global action " + name + " was not found."); - -} - -template -GlobalAction & Model::get_global_action( - size_t index -) -{ - - if (index >= global_actions.size()) - throw std::range_error("The index " + std::to_string(index) + " is out of range."); - - return global_actions[index]; - -} - -// Remove implementation -template -inline void Model::rm_global_action( - std::string name -) -{ - - for (auto it = global_actions.begin(); it != global_actions.end(); ++it) - { - if (it->get_name() == name) - { - global_actions.erase(it); - return; - } - } - - throw std::logic_error("The global action " + name + " was not found."); - -} - -// Same as above, but the index implementation -template -inline void Model::rm_global_action( - size_t index -) -{ - - if (index >= global_actions.size()) - throw std::range_error("The index " + std::to_string(index) + " is out of range."); - - global_actions.erase(global_actions.begin() + index); - -} - -template -inline void Model::run_global_actions() -{ - - for (auto & action: global_actions) - { - action(this, today()); - actions_run(); - } - -} - -template -inline void Model::queuing_on() -{ - use_queuing = true; -} - -template -inline void Model::queuing_off() -{ - use_queuing = false; -} - -template -inline bool Model::is_queuing_on() const -{ - return use_queuing; -} - -template -inline Queue & Model::get_queue() -{ - return queue; -} - -template -inline const std::vector< VirusPtr > & Model::get_viruses() const -{ - return viruses; -} - -template -const std::vector< ToolPtr > & Model::get_tools() const -{ - return tools; -} - -template -inline Virus & Model::get_virus(size_t id) -{ - - if (viruses.size() <= id) - throw std::length_error("The specified id for the virus is out of range"); - - return *viruses[id]; - -} - -template -inline Tool & Model::get_tool(size_t id) -{ - - if (tools.size() <= id) - throw std::length_error("The specified id for the tools is out of range"); - - return *tools[id]; - -} - - -template -inline void Model::set_agents_data(double * data_, size_t ncols_) -{ - agents_data = data_; - agents_data_ncols = ncols_; -} - -template -inline double * Model::get_agents_data() { - return this->agents_data; -} - -template -inline size_t Model::get_agents_data_ncols() const { - return this->agents_data_ncols; -} - - -template -inline void Model::set_name(std::string name) -{ - this->name = name; -} - -template -inline std::string Model::get_name() const -{ - return this->name; -} - -#define VECT_MATCH(a, b, c) \ - EPI_DEBUG_FAIL_AT_TRUE(a.size() != b.size(), c) \ - for (size_t __i = 0u; __i < a.size(); ++__i) \ - {\ - EPI_DEBUG_FAIL_AT_TRUE(a[__i] != b[__i], c) \ - } - -template -inline bool Model::operator==(const Model & other) const -{ - EPI_DEBUG_FAIL_AT_TRUE(name != other.name, "names don't match") - EPI_DEBUG_FAIL_AT_TRUE(db != other.db, "database don't match") - - VECT_MATCH(population, other.population, "population doesn't match") - - EPI_DEBUG_FAIL_AT_TRUE( - using_backup != other.using_backup, - "Model:: using_backup don't match" - ) - - if ((population_backup.size() != 0) & (other.population_backup.size() != 0)) - { - - // False is population_backup.size() != other.population_backup.size() - if (population_backup.size() != other.population_backup.size()) - return false; - - for (size_t i = 0u; i < population_backup.size(); ++i) - { - if (population_backup[i] != other.population_backup[i]) - return false; - } - - } else if ((population_backup.size() == 0) & (other.population_backup.size() != 0)) { - return false; - } else if ((population_backup.size() != 0) & (other.population_backup.size() == 0)) - { - return false; - } - - EPI_DEBUG_FAIL_AT_TRUE( - agents_data != other.agents_data, - "Model:: agents_data don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - agents_data_ncols != other.agents_data_ncols, - "Model:: agents_data_ncols don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - directed != other.directed, - "Model:: directed don't match" - ) - - // Viruses ----------------------------------------------------------------- - EPI_DEBUG_FAIL_AT_TRUE( - viruses.size() != other.viruses.size(), - "Model:: viruses.size() don't match" - ) - - for (size_t i = 0u; i < viruses.size(); ++i) - { - EPI_DEBUG_FAIL_AT_TRUE( - *viruses[i] != *other.viruses[i], - "Model:: *viruses[i] don't match" - ) - - } - - VECT_MATCH( - prevalence_virus, - other.prevalence_virus, - "virus prevalence don't match" - ) - - VECT_MATCH( - prevalence_virus_as_proportion, - other.prevalence_virus_as_proportion, - "virus prevalence as prop don't match" - ) - - // Tools ------------------------------------------------------------------- - EPI_DEBUG_FAIL_AT_TRUE( - tools.size() != other.tools.size(), - "Model:: tools.size() don't match" - ) - - for (size_t i = 0u; i < tools.size(); ++i) - { - EPI_DEBUG_FAIL_AT_TRUE( - *tools[i] != *other.tools[i], - "Model:: *tools[i] don't match" - ) - - } - - VECT_MATCH( - prevalence_tool, - other.prevalence_tool, - "tools prevalence don't match" - ) - - VECT_MATCH( - prevalence_tool_as_proportion, - other.prevalence_tool_as_proportion, - "tools as prop don't match" - ) - - VECT_MATCH( - entities, - other.entities, - "entities don't match" - ) - - if ((entities_backup.size() != 0) & (other.entities_backup.size() != 0)) - { - - for (size_t i = 0u; i < entities_backup.size(); ++i) - { - - EPI_DEBUG_FAIL_AT_TRUE( - entities_backup[i] != other.entities_backup[i], - "Model:: entities_backup[i] don't match" - ) - - } - - } else if ((entities_backup.size() == 0) & (other.entities_backup.size() != 0)) { - EPI_DEBUG_FAIL_AT_TRUE(true, "entities_backup don't match") - } else if ((entities_backup.size() != 0) & (other.entities_backup.size() == 0)) - { - EPI_DEBUG_FAIL_AT_TRUE(true, "entities_backup don't match") - } - - EPI_DEBUG_FAIL_AT_TRUE( - rewire_prop != other.rewire_prop, - "Model:: rewire_prop don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - parameters.size() != other.parameters.size(), - "Model:: () don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - parameters != other.parameters, - "Model:: parameters don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - ndays != other.ndays, - "Model:: ndays don't match" - ) - - VECT_MATCH( - states_labels, - other.states_labels, - "state labels don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - nstates != other.nstates, - "Model:: nstates don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - verbose != other.verbose, - "Model:: verbose don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - current_date != other.current_date, - "Model:: current_date don't match" - ) - - VECT_MATCH(global_actions, other.global_actions, "global action don't match"); - - EPI_DEBUG_FAIL_AT_TRUE( - queue != other.queue, - "Model:: queue don't match" - ) - - - EPI_DEBUG_FAIL_AT_TRUE( - use_queuing != other.use_queuing, - "Model:: use_queuing don't match" - ) - - return true; - -} - -#undef VECT_MATCH -#undef DURCAST -#undef CASES_PAR -#undef CASE_PAR -#undef CHECK_INIT -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/model-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/viruses-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_VIRUSES_BONES_HPP -#define EPIWORLD_VIRUSES_BONES_HPP - -template -class Virus; - -template -class Agent; - -/** - * @brief Set of viruses (useful for building iterators) - * - * @tparam TSeq - */ -template -class Viruses { - friend class Virus; - friend class Agent; -private: - std::vector< VirusPtr > * dat; - const epiworld_fast_uint * n_viruses; - -public: - - Viruses() = delete; - Viruses(Agent & p) : dat(&p.viruses), n_viruses(&p.n_viruses) {}; - - typename std::vector< VirusPtr >::iterator begin(); - typename std::vector< VirusPtr >::iterator end(); - - VirusPtr & operator()(size_t i); - VirusPtr & operator[](size_t i); - - size_t size() const noexcept; - - void print() const noexcept; - -}; - -template -inline typename std::vector< VirusPtr >::iterator Viruses::begin() -{ - - if (*n_viruses == 0u) - return dat->end(); - - return dat->begin(); -} - -template -inline typename std::vector< VirusPtr >::iterator Viruses::end() -{ - - #ifdef EPI_DEBUG - if (dat->size() < *n_viruses) - throw EPI_DEBUG_ERROR(std::logic_error, "Viruses:: The end of the virus is out of range"); - #endif - - return begin() + *n_viruses; -} - -template -inline VirusPtr & Viruses::operator()(size_t i) -{ - - if (i >= *n_viruses) - throw std::range_error("Virus index out of range."); - - return dat->operator[](i); - -} - -template -inline VirusPtr & Viruses::operator[](size_t i) -{ - - return dat->operator[](i); - -} - -template -inline size_t Viruses::size() const noexcept -{ - return *n_viruses; -} - -template -inline void Viruses::print() const noexcept -{ - - if (*n_viruses == 0u) - { - printf_epiworld("List of viruses (none)\n"); - return; - } - - printf_epiworld("List of viruses (%i): ", *n_viruses); - - // Printing the name of each virus separated by a comma - for (size_t i = 0u; i < *n_viruses; ++i) - { - if (i == *n_viruses - 1u) - { - printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); - } else - { - printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); - } - } - - printf_epiworld("\n"); - -} - -/** - * @brief Set of Viruses (const) (useful for iterators) - * - * @tparam TSeq - */ -template -class Viruses_const { - friend class Virus; - friend class Agent; -private: - const std::vector< VirusPtr > * dat; - const epiworld_fast_uint * n_viruses; - -public: - - Viruses_const() = delete; - Viruses_const(const Agent & p) : dat(&p.viruses), n_viruses(&p.n_viruses) {}; - - typename std::vector< VirusPtr >::const_iterator begin() const; - typename std::vector< VirusPtr >::const_iterator end() const; - - const VirusPtr & operator()(size_t i); - const VirusPtr & operator[](size_t i); - - size_t size() const noexcept; - - void print() const noexcept; - -}; - -template -inline typename std::vector< VirusPtr >::const_iterator Viruses_const::begin() const { - - if (*n_viruses == 0u) - return dat->end(); - - return dat->begin(); -} - -template -inline typename std::vector< VirusPtr >::const_iterator Viruses_const::end() const { - - #ifdef EPI_DEBUG - if (dat->size() < *n_viruses) - throw EPI_DEBUG_ERROR(std::logic_error, "Viruses_const:: The end of the virus is out of range"); - #endif - return begin() + *n_viruses; -} - -template -inline const VirusPtr & Viruses_const::operator()(size_t i) -{ - - if (i >= *n_viruses) - throw std::range_error("Virus index out of range."); - - return dat->operator[](i); - -} - -template -inline const VirusPtr & Viruses_const::operator[](size_t i) -{ - - return dat->operator[](i); - -} - -template -inline size_t Viruses_const::size() const noexcept -{ - return *n_viruses; -} - -template -inline void Viruses_const::print() const noexcept -{ - - if (*n_viruses == 0u) - { - printf_epiworld("List of viruses (none)\n"); - return; - } - - printf_epiworld("List of viruses (%i): ", *n_viruses); - - // Printing the name of each virus separated by a comma - for (size_t i = 0u; i < *n_viruses; ++i) - { - if (i == *n_viruses - 1u) - { - printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); - } else - { - printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); - } - } - - printf_epiworld("\n"); - -} - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/viruses-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/virus-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_VIRUS_HPP -#define EPIWORLD_VIRUS_HPP - -template -class Agent; - -template -class Virus; - -template -class Model; - -/** - * @brief Virus - * - * @tparam TSeq - * @details - * Raw transmisibility of a virus should be a function of its genetic - * sequence. Nonetheless, transmisibility can be reduced as a result of - * having one or more tools to fight the virus. Because of this, transmisibility - * should be a function of the agent. - */ -template -class Virus { - friend class Agent; - friend class Model; - friend class DataBase; - friend void default_add_virus(Action & a, Model * m); - friend void default_rm_virus(Action & a, Model * m); -private: - - Agent * agent = nullptr; - int pos_in_agent = -99; ///< Location in the agent - int agent_exposure_number = -99; - - std::shared_ptr baseline_sequence = nullptr; - std::shared_ptr virus_name = nullptr; - int date = -99; - int id = -99; - bool active = true; - MutFun mutation_fun = nullptr; - PostRecoveryFun post_recovery_fun = nullptr; - VirusFun probability_of_infecting_fun = nullptr; - VirusFun probability_of_recovery_fun = nullptr; - VirusFun probability_of_death_fun = nullptr; - VirusFun incubation_fun = nullptr; - - // Setup parameters - std::vector< epiworld_double * > params = {}; - std::vector< epiworld_double > data = {}; - - epiworld_fast_int state_init = -99; ///< Change of state when added to agent. - epiworld_fast_int state_post = -99; ///< Change of state when removed from agent. - epiworld_fast_int state_removed = -99; ///< Change of state when agent is removed - - epiworld_fast_int queue_init = Queue::Everyone; ///< Change of state when added to agent. - epiworld_fast_int queue_post = -Queue::Everyone; ///< Change of state when removed from agent. - epiworld_fast_int queue_removed = -99; ///< Change of state when agent is removed - -public: - Virus(std::string name = "unknown virus"); - - void mutate(Model * model); - void set_mutation(MutFun fun); - - std::shared_ptr get_sequence(); - void set_sequence(TSeq sequence); - - Agent * get_agent(); - void set_agent(Agent * p, epiworld_fast_uint idx); - - void set_date(int d); - int get_date() const; - - void set_id(int idx); - int get_id() const; - - /** - * @name Get and set the tool functions - * - * @param v The virus over which to operate - * @param fun the function to be used - * - * @return epiworld_double - */ - ///@{ - epiworld_double get_prob_infecting(Model * model); - epiworld_double get_prob_recovery(Model * model); - epiworld_double get_prob_death(Model * model); - epiworld_double get_incubation(Model * model); - - void post_recovery(Model * model); - void set_post_recovery(PostRecoveryFun fun); - void set_post_immunity(epiworld_double prob); - void set_post_immunity(epiworld_double * prob); - - void set_prob_infecting_fun(VirusFun fun); - void set_prob_recovery_fun(VirusFun fun); - void set_prob_death_fun(VirusFun fun); - void set_incubation_fun(VirusFun fun); - - void set_prob_infecting(const epiworld_double * prob); - void set_prob_recovery(const epiworld_double * prob); - void set_prob_death(const epiworld_double * prob); - void set_incubation(const epiworld_double * prob); - - void set_prob_infecting(epiworld_double prob); - void set_prob_recovery(epiworld_double prob); - void set_prob_death(epiworld_double prob); - void set_incubation(epiworld_double prob); - ///@} - - - void set_name(std::string name); - std::string get_name() const; - - std::vector< epiworld_double > & get_data(); - - /** - * @name Get and set the state and queue - * - * After applied, viruses can change the state and affect - * the queue of agents. These function sets the default values, - * which are retrieved when adding or removing a virus does not - * specify a change in state or in queue. - * - * @param init After the virus/tool is added to the agent. - * @param end After the virus/tool is removed. - * @param removed After the agent (Agent) is removed. - */ - ///@{ - void set_state( - epiworld_fast_int init, - epiworld_fast_int end, - epiworld_fast_int removed = -99 - ); - - void set_queue( - epiworld_fast_int init, - epiworld_fast_int end, - epiworld_fast_int removed = -99 - ); - - void get_state( - epiworld_fast_int * init, - epiworld_fast_int * end, - epiworld_fast_int * removed = nullptr - ); - - void get_queue( - epiworld_fast_int * init, - epiworld_fast_int * end, - epiworld_fast_int * removed = nullptr - ); - ///@} - - bool operator==(const Virus & other) const; - bool operator!=(const Virus & other) const {return !operator==(other);}; - - void print() const; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/virus-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/virus-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_VIRUS_MEAT_HPP -#define EPIWORLD_VIRUS_MEAT_HPP - -/** - * @brief Factory function of VirusFun base on logit - * - * @tparam TSeq - * @param vars Vector indicating the position of the variables to use. - * @param coefs Vector of coefficients. - * @return VirusFun - */ -template -inline VirusFun virus_fun_logit( - std::vector< int > vars, - std::vector< double > coefs, - Model * model, - bool logit = true -) { - - // Checking that there are features - if (coefs.size() == 0u) - throw std::logic_error( - "The -coefs- argument should feature at least one element." - ); - - if (coefs.size() != vars.size()) - throw std::length_error( - std::string("The length of -coef- (") + - std::to_string(coefs.size()) + - std::string(") and -vars- (") + - std::to_string(vars.size()) + - std::string(") should match. ") - ); - - // Checking that there are variables in the model - if (model != nullptr) - { - - size_t K = model->get_agents_data_ncols(); - for (const auto & var: vars) - { - if ((var >= static_cast(K)) | (var < 0)) - throw std::range_error( - std::string("The variable ") + - std::to_string(var) + - std::string(" is out of range.") + - std::string(" The agents only feature ") + - std::to_string(K) + - std::string("variables (features).") - ); - } - - } - - std::vector< epiworld_double > coefs_f; - for (auto c: coefs) - coefs_f.push_back(static_cast(c)); - - VirusFun fun_infect = [coefs_f,vars,logit]( - Agent * agent, - Virus & virus, - Model * model - ) -> epiworld_double { - - size_t K = coefs_f.size(); - epiworld_double res = 0.0; - - #pragma omp simd reduction(+:res) - for (size_t i = 0u; i < K; ++i) - res += agent->operator[](vars.at(i)) * coefs_f.at(i); - - return 1.0/(1.0 + std::exp(-res)); - - }; - - return fun_infect; - -} - -template -inline Virus::Virus(std::string name) { - set_name(name); -} - -template -inline void Virus::mutate( - Model * model -) { - - if (mutation_fun) - if (mutation_fun(agent, *this, model)) - model->get_db().record_virus(*this); - - return; - -} - -template -inline void Virus::set_mutation( - MutFun fun -) { - mutation_fun = MutFun(fun); -} - -template -inline std::shared_ptr Virus::get_sequence() -{ - - return baseline_sequence; - -} - -template -inline void Virus::set_sequence(TSeq sequence) -{ - - baseline_sequence = std::make_shared(sequence); - return; - -} - -template -inline Agent * Virus::get_agent() -{ - - return agent; - -} - -template -inline void Virus::set_agent(Agent * p, epiworld_fast_uint idx) -{ - - #ifdef EPI_DEBUG - if (idx >= p->viruses.size()) - { - printf_epiworld( - "[epi-debug]Virus::set_agent id to set up is outside of range." - ); - } - #endif - - agent = p; - pos_in_agent = static_cast(idx); - -} - -template -inline void Virus::set_id(int idx) -{ - - id = idx; - return; - -} - -template -inline int Virus::get_id() const -{ - - return id; - -} - -template -inline void Virus::set_date(int d) -{ - - date = d; - return; - -} - -template -inline int Virus::get_date() const -{ - - return date; - -} - -template -inline epiworld_double Virus::get_prob_infecting( - Model * model -) -{ - - if (probability_of_infecting_fun) - return probability_of_infecting_fun(agent, *this, model); - - return EPI_DEFAULT_VIRUS_PROB_INFECTION; - -} - - - -template -inline epiworld_double Virus::get_prob_recovery( - Model * model -) -{ - - if (probability_of_recovery_fun) - return probability_of_recovery_fun(agent, *this, model); - - return EPI_DEFAULT_VIRUS_PROB_RECOVERY; - -} - - - -template -inline epiworld_double Virus::get_prob_death( - Model * model -) -{ - - if (probability_of_death_fun) - return probability_of_death_fun(agent, *this, model); - - return EPI_DEFAULT_VIRUS_PROB_DEATH; - -} - -template -inline epiworld_double Virus::get_incubation( - Model * model -) -{ - - if (incubation_fun) - return incubation_fun(agent, *this, model); - - return EPI_DEFAULT_INCUBATION_DAYS; - -} - -template -inline void Virus::set_prob_infecting_fun(VirusFun fun) -{ - probability_of_infecting_fun = fun; -} - -template -inline void Virus::set_prob_recovery_fun(VirusFun fun) -{ - probability_of_recovery_fun = fun; -} - -template -inline void Virus::set_prob_death_fun(VirusFun fun) -{ - probability_of_death_fun = fun; -} - -template -inline void Virus::set_incubation_fun(VirusFun fun) -{ - incubation_fun = fun; -} - -template -inline void Virus::set_prob_infecting(const epiworld_double * prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return *prob; - }; - - probability_of_infecting_fun = tmpfun; -} - -template -inline void Virus::set_prob_recovery(const epiworld_double * prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return *prob; - }; - - probability_of_recovery_fun = tmpfun; -} - -template -inline void Virus::set_prob_death(const epiworld_double * prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return *prob; - }; - - probability_of_death_fun = tmpfun; -} - -template -inline void Virus::set_incubation(const epiworld_double * prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return *prob; - }; - - incubation_fun = tmpfun; -} - -template -inline void Virus::set_prob_infecting(epiworld_double prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return prob; - }; - - probability_of_infecting_fun = tmpfun; -} - -template -inline void Virus::set_prob_recovery(epiworld_double prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return prob; - }; - - probability_of_recovery_fun = tmpfun; -} - -template -inline void Virus::set_prob_death(epiworld_double prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return prob; - }; - - probability_of_death_fun = tmpfun; -} - -template -inline void Virus::set_incubation(epiworld_double prob) -{ - VirusFun tmpfun = - [prob](Agent *, Virus &, Model *) - { - return prob; - }; - - incubation_fun = tmpfun; -} - -template -inline void Virus::set_post_recovery(PostRecoveryFun fun) -{ - if (post_recovery_fun) - { - printf_epiworld( - "Warning: a PostRecoveryFun is alreay in place (overwriting)." - ); - } - - post_recovery_fun = fun; -} - -template -inline void Virus::post_recovery( - Model * model -) -{ - - if (post_recovery_fun) - post_recovery_fun(agent, *this, model); - - return; - -} - -template -inline void Virus::set_post_immunity( - epiworld_double prob -) -{ - - if (post_recovery_fun) - { - - std::string msg = - std::string( - "You cannot set post immunity when a post_recovery " - ) + - std::string( - "function is already in place. Redesign the post_recovery function." - ); - - throw std::logic_error(msg); - - } - - // To make sure that we keep registering the virus - ToolPtr __no_reinfect = std::make_shared>( - "Immunity (" + *virus_name + ")" - ); - - __no_reinfect->set_susceptibility_reduction(prob); - __no_reinfect->set_death_reduction(0.0); - __no_reinfect->set_transmission_reduction(0.0); - __no_reinfect->set_recovery_enhancer(0.0); - - PostRecoveryFun tmpfun = - [__no_reinfect]( - Agent * p, Virus &, Model * m - ) - { - - // Have we registered the tool? - if (__no_reinfect->get_id() == -99) - m->get_db().record_tool(*__no_reinfect); - - p->add_tool(__no_reinfect, m); - - return; - - }; - - post_recovery_fun = tmpfun; - -} - -template -inline void Virus::set_post_immunity( - epiworld_double * prob -) -{ - - if (post_recovery_fun) - { - - std::string msg = - std::string( - "You cannot set post immunity when a post_recovery " - ) + - std::string( - "function is already in place. Redesign the post_recovery function." - ); - - throw std::logic_error(msg); - - } - - // To make sure that we keep registering the virus - ToolPtr __no_reinfect = std::make_shared>( - "Immunity (" + *virus_name + ")" - ); - - __no_reinfect->set_susceptibility_reduction(prob); - __no_reinfect->set_death_reduction(0.0); - __no_reinfect->set_transmission_reduction(0.0); - __no_reinfect->set_recovery_enhancer(0.0); - - PostRecoveryFun tmpfun = - [__no_reinfect](Agent * p, Virus &, Model * m) - { - - // Have we registered the tool? - if (__no_reinfect->get_id() == -99) - m->get_db().record_tool(*__no_reinfect); - - p->add_tool(__no_reinfect, m); - - return; - - }; - - post_recovery_fun = tmpfun; - -} - -template -inline void Virus::set_name(std::string name) -{ - - if (name == "") - virus_name = nullptr; - else - virus_name = std::make_shared(name); - -} - -template -inline std::string Virus::get_name() const -{ - - if (virus_name) - return *virus_name; - - return "unknown virus"; - -} - -template -inline std::vector< epiworld_double > & Virus::get_data() { - return data; -} - -template -inline void Virus::set_state( - epiworld_fast_int init, - epiworld_fast_int end, - epiworld_fast_int removed -) -{ - state_init = init; - state_post = end; - state_removed = removed; -} - -template -inline void Virus::set_queue( - epiworld_fast_int init, - epiworld_fast_int end, - epiworld_fast_int removed -) -{ - - queue_init = init; - queue_post = end; - queue_removed = removed; - -} - -template -inline void Virus::get_state( - epiworld_fast_int * init, - epiworld_fast_int * end, - epiworld_fast_int * removed -) -{ - - if (init != nullptr) - *init = state_init; - - if (end != nullptr) - *end = state_post; - - if (removed != nullptr) - *removed = state_removed; - -} - -template -inline void Virus::get_queue( - epiworld_fast_int * init, - epiworld_fast_int * end, - epiworld_fast_int * removed -) -{ - - if (init != nullptr) - *init = queue_init; - - if (end != nullptr) - *end = queue_post; - - if (removed != nullptr) - *removed = queue_removed; - -} - -template<> -inline bool Virus>::operator==( - const Virus> & other - ) const -{ - - - EPI_DEBUG_FAIL_AT_TRUE( - baseline_sequence->size() != other.baseline_sequence->size(), - "Virus:: baseline_sequence don't match" - ) - - for (size_t i = 0u; i < baseline_sequence->size(); ++i) - { - - EPI_DEBUG_FAIL_AT_TRUE( - baseline_sequence->operator[](i) != other.baseline_sequence->operator[](i), - "Virus:: baseline_sequence[i] don't match" - ) - - } - - EPI_DEBUG_FAIL_AT_TRUE( - virus_name != other.virus_name, - "Virus:: virus_name don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_init != other.state_init, - "Virus:: state_init don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_post != other.state_post, - "Virus:: state_post don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_removed != other.state_removed, - "Virus:: state_removed don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_init != other.queue_init, - "Virus:: queue_init don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_post != other.queue_post, - "Virus:: queue_post don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_removed != other.queue_removed, - "Virus:: queue_removed don't match" - ) - - return true; - -} - -template -inline bool Virus::operator==(const Virus & other) const -{ - - EPI_DEBUG_FAIL_AT_TRUE( - *baseline_sequence != *other.baseline_sequence, - "Virus:: baseline_sequence don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - virus_name != other.virus_name, - "Virus:: virus_name don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_init != other.state_init, - "Virus:: state_init don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_post != other.state_post, - "Virus:: state_post don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - state_removed != other.state_removed, - "Virus:: state_removed don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_init != other.queue_init, - "Virus:: queue_init don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_post != other.queue_post, - "Virus:: queue_post don't match" - ) - - EPI_DEBUG_FAIL_AT_TRUE( - queue_removed != other.queue_removed, - "Virus:: queue_removed don't match" - ) - - return true; - -} - -template -inline void Virus::print() const -{ - - printf_epiworld("Virus : %s\n", virus_name->c_str()); - printf_epiworld("Id : %s\n", (id < 0)? std::string("(empty)").c_str() : std::to_string(id).c_str()); - printf_epiworld("state_init : %i\n", state_init); - printf_epiworld("state_post : %i\n", state_post); - printf_epiworld("state_removed : %i\n", state_removed); - printf_epiworld("queue_init : %i\n", queue_init); - printf_epiworld("queue_post : %i\n", queue_post); - printf_epiworld("queue_removed : %i\n", queue_removed); - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/virus-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/tools-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_TOOLS_BONES_HPP -#define EPIWORLD_TOOLS_BONES_HPP - -template -class Tool; - -template -class Agent; - -// #define ToolPtr std::shared_ptr< Tool > - -/** - * @brief Set of tools (useful for building iterators) - * - * @tparam TSeq - */ -template -class Tools { - friend class Tool; - friend class Agent; -private: - std::vector< ToolPtr > * dat; - const epiworld_fast_uint * n_tools; - -public: - - Tools() = delete; - Tools(Agent & p) : dat(&p.tools), n_tools(&p.n_tools) {}; - - typename std::vector< ToolPtr >::iterator begin(); - typename std::vector< ToolPtr >::iterator end(); - - ToolPtr & operator()(size_t i); - ToolPtr & operator[](size_t i); - - size_t size() const noexcept; - - void print() const noexcept; - -}; - -template -inline typename std::vector< ToolPtr >::iterator Tools::begin() -{ - - if (*n_tools == 0u) - return dat->end(); - - return dat->begin(); -} - -template -inline typename std::vector< ToolPtr >::iterator Tools::end() -{ - - return begin() + *n_tools; -} - -template -inline ToolPtr & Tools::operator()(size_t i) -{ - - if (i >= *n_tools) - throw std::range_error("Tool index out of range."); - - return dat->operator[](i); - -} - -template -inline ToolPtr & Tools::operator[](size_t i) -{ - - return dat->operator[](i); - -} - -template -inline size_t Tools::size() const noexcept -{ - return *n_tools; -} - -template -inline void Tools::print() const noexcept -{ - if (*n_tools == 0u) - { - printf_epiworld("List of tools (none)\n"); - return; - } - - printf_epiworld("List of tools (%i): ", *n_tools); - - // Printing the name of each virus separated by a comma - for (size_t i = 0u; i < *n_tools; ++i) - { - if (i == *n_tools - 1u) - { - printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); - } else - { - printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); - } - } - - printf_epiworld("\n"); - -} - -/** - * @brief Set of Tools (const) (useful for iterators) - * - * @tparam TSeq - */ -template -class Tools_const { - friend class Tool; - friend class Agent; -private: - const std::vector< ToolPtr > * dat; - const epiworld_fast_uint * n_tools; - -public: - - Tools_const() = delete; - Tools_const(const Agent & p) : dat(&p.tools), n_tools(&p.n_tools) {}; - - typename std::vector< ToolPtr >::const_iterator begin() const; - typename std::vector< ToolPtr >::const_iterator end() const; - - const ToolPtr & operator()(size_t i); - const ToolPtr & operator[](size_t i); - - size_t size() const noexcept; - - void print() const noexcept; - -}; - -template -inline typename std::vector< ToolPtr >::const_iterator Tools_const::begin() const { - - if (*n_tools == 0u) - return dat->end(); - - return dat->begin(); -} - -template -inline typename std::vector< ToolPtr >::const_iterator Tools_const::end() const { - - return begin() + *n_tools; -} - -template -inline const ToolPtr & Tools_const::operator()(size_t i) -{ - - if (i >= *n_tools) - throw std::range_error("Tool index out of range."); - - return dat->operator[](i); - -} - -template -inline const ToolPtr & Tools_const::operator[](size_t i) -{ - - return dat->operator[](i); - -} - -template -inline size_t Tools_const::size() const noexcept -{ - return *n_tools; -} - -template -inline void Tools_const::print() const noexcept -{ - if (*n_tools == 0u) - { - printf_epiworld("List of tools (none)\n"); - return; - } - - printf_epiworld("List of tools (%i): ", *n_tools); - - // Printing the name of each virus separated by a comma - for (size_t i = 0u; i < *n_tools; ++i) - { - if (i == *n_tools - 1u) - { - printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); - } else - { - printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); - } - } - - printf_epiworld("\n"); - -} - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/tools-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/tool-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -#ifndef EPIWORLD_TOOL_BONES_HPP -#define EPIWORLD_TOOL_BONES_HPP - -template -class Virus; - -template -class Agent; - -template -class Model; - -template -class Tool; - -/** - * @brief Tools for defending the agent against the virus - * - * @tparam TSeq Type of sequence - */ -template -class Tool { - friend class Agent; - friend class Model; - friend void default_add_tool(Action & a, Model * m); - friend void default_rm_tool(Action & a, Model * m); -private: - - Agent * agent = nullptr; - int pos_in_agent = -99; ///< Location in the agent - - int date = -99; - int id = -99; - std::shared_ptr tool_name = nullptr; - std::shared_ptr sequence = nullptr; - ToolFun susceptibility_reduction_fun = nullptr; - ToolFun transmission_reduction_fun = nullptr; - ToolFun recovery_enhancer_fun = nullptr; - ToolFun death_reduction_fun = nullptr; - - // Setup parameters - std::vector< epiworld_double * > params; - - epiworld_fast_int state_init = -99; - epiworld_fast_int state_post = -99; - - epiworld_fast_int queue_init = Queue::NoOne; ///< Change of state when added to agent. - epiworld_fast_int queue_post = Queue::NoOne; ///< Change of state when removed from agent. - - void set_agent(Agent * p, size_t idx); - -public: - Tool(std::string name = "unknown tool"); - // Tool(TSeq d, std::string name = "unknown tool"); - - void set_sequence(TSeq d); - void set_sequence(std::shared_ptr d); - std::shared_ptr get_sequence(); - - /** - * @name Get and set the tool functions - * - * @param v The virus over which to operate - * @param fun the function to be used - * - * @return epiworld_double - */ - ///@{ - epiworld_double get_susceptibility_reduction(VirusPtr v, Model * model); - epiworld_double get_transmission_reduction(VirusPtr v, Model * model); - epiworld_double get_recovery_enhancer(VirusPtr v, Model * model); - epiworld_double get_death_reduction(VirusPtr v, Model * model); - - void set_susceptibility_reduction_fun(ToolFun fun); - void set_transmission_reduction_fun(ToolFun fun); - void set_recovery_enhancer_fun(ToolFun fun); - void set_death_reduction_fun(ToolFun fun); - - void set_susceptibility_reduction(epiworld_double * prob); - void set_transmission_reduction(epiworld_double * prob); - void set_recovery_enhancer(epiworld_double * prob); - void set_death_reduction(epiworld_double * prob); - - void set_susceptibility_reduction(epiworld_double prob); - void set_transmission_reduction(epiworld_double prob); - void set_recovery_enhancer(epiworld_double prob); - void set_death_reduction(epiworld_double prob); - ///@} - - void set_name(std::string name); - std::string get_name() const; - - Agent * get_agent(); - int get_id() const; - void set_id(int id); - void set_date(int d); - int get_date() const; - - void set_state(epiworld_fast_int init, epiworld_fast_int post); - void set_queue(epiworld_fast_int init, epiworld_fast_int post); - void get_state(epiworld_fast_int * init, epiworld_fast_int * post); - void get_queue(epiworld_fast_int * init, epiworld_fast_int * post); - - bool operator==(const Tool & other) const; - bool operator!=(const Tool & other) const {return !operator==(other);}; - - void print() const; - -}; - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/tool-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/tool-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -#ifndef EPIWORLD_TOOLS_MEAT_HPP -#define EPIWORLD_TOOLS_MEAT_HPP - -/** - * @brief Factory function of ToolFun base on logit - * - * @tparam TSeq - * @param vars Vector indicating the position of the variables to use. - * @param coefs Vector of coefficients. - * @return ToolFun - */ -template -inline ToolFun tool_fun_logit( - std::vector< int > vars, - std::vector< double > coefs, - Model * model -) { - - // Checking that there are features - if (coefs.size() == 0u) - throw std::logic_error( - "The -coefs- argument should feature at least one element." - ); - - if (coefs.size() != vars.size()) - throw std::length_error( - std::string("The length of -coef- (") + - std::to_string(coefs.size()) + - std::string(") and -vars- (") + - std::to_string(vars.size()) + - std::string(") should match. ") - ); - - // Checking that there are variables in the model - if (model != nullptr) - { - - size_t K = model->get_agents_data_ncols(); - for (const auto & var: vars) - { - if ((var >= static_cast(K)) | (var < 0)) - throw std::range_error( - std::string("The variable ") + - std::to_string(var) + - std::string(" is out of range.") + - std::string(" The agents only feature ") + - std::to_string(K) + - std::string("variables (features).") - ); - } - - } - - std::vector< epiworld_double > coefs_f; - for (auto c: coefs) - coefs_f.push_back(static_cast(c)); - - ToolFun fun_ = [coefs_f,vars]( - Tool& tool, - Agent * agent, - VirusPtr virus, - Model * model - ) -> epiworld_double { - - size_t K = coefs_f.size(); - epiworld_double res = 0.0; - - #pragma omp simd reduction(+:res) - for (size_t i = 0u; i < K; ++i) - res += agent->operator[](vars.at(i)) * coefs_f.at(i); - - return 1.0/(1.0 + std::exp(-res)); - - }; - - return fun_; - -} - -template -inline Tool::Tool(std::string name) -{ - set_name(name); -} - -// template -// inline Tool::Tool(TSeq d, std::string name) { -// sequence = std::make_shared(d); -// tool_name = std::make_shared(name); -// } - -template -inline void Tool::set_sequence(TSeq d) { - sequence = std::make_shared(d); -} - -template -inline void Tool::set_sequence(std::shared_ptr d) { - sequence = d; -} - -template -inline std::shared_ptr Tool::get_sequence() { - return sequence; -} - -template -inline epiworld_double Tool::get_susceptibility_reduction( - VirusPtr v, - Model * model -) -{ - - if (susceptibility_reduction_fun) - return susceptibility_reduction_fun(*this, this->agent, v, model); - - return DEFAULT_TOOL_CONTAGION_REDUCTION; - -} - -template -inline epiworld_double Tool::get_transmission_reduction( - VirusPtr v, - Model * model -) -{ - - if (transmission_reduction_fun) - return transmission_reduction_fun(*this, this->agent, v, model); - - return DEFAULT_TOOL_TRANSMISSION_REDUCTION; - -} - -template -inline epiworld_double Tool::get_recovery_enhancer( - VirusPtr v, - Model * model -) -{ - - if (recovery_enhancer_fun) - return recovery_enhancer_fun(*this, this->agent, v, model); - - return DEFAULT_TOOL_RECOVERY_ENHANCER; - -} - -template -inline epiworld_double Tool::get_death_reduction( - VirusPtr v, - Model * model -) -{ - - if (death_reduction_fun) - return death_reduction_fun(*this, this->agent, v, model); - - return DEFAULT_TOOL_DEATH_REDUCTION; - -} - -template -inline void Tool::set_susceptibility_reduction_fun( - ToolFun fun -) -{ - susceptibility_reduction_fun = fun; -} - -template -inline void Tool::set_transmission_reduction_fun( - ToolFun fun -) -{ - transmission_reduction_fun = fun; -} - -template -inline void Tool::set_recovery_enhancer_fun( - ToolFun fun -) -{ - recovery_enhancer_fun = fun; -} - -template -inline void Tool::set_death_reduction_fun( - ToolFun fun -) -{ - death_reduction_fun = fun; -} - -template -inline void Tool::set_susceptibility_reduction(epiworld_double * prob) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return *prob; - }; - - susceptibility_reduction_fun = tmpfun; - -} - -// EPIWORLD_SET_LAMBDA(susceptibility_reduction) -template -inline void Tool::set_transmission_reduction(epiworld_double * prob) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return *prob; - }; - - transmission_reduction_fun = tmpfun; - -} - -// EPIWORLD_SET_LAMBDA(transmission_reduction) -template -inline void Tool::set_recovery_enhancer(epiworld_double * prob) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return *prob; - }; - - recovery_enhancer_fun = tmpfun; - -} - -// EPIWORLD_SET_LAMBDA(recovery_enhancer) -template -inline void Tool::set_death_reduction(epiworld_double * prob) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return *prob; - }; - - death_reduction_fun = tmpfun; - -} - -// EPIWORLD_SET_LAMBDA(death_reduction) - -// #undef EPIWORLD_SET_LAMBDA -template -inline void Tool::set_susceptibility_reduction( - epiworld_double prob -) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return prob; - }; - - susceptibility_reduction_fun = tmpfun; - -} - -template -inline void Tool::set_transmission_reduction( - epiworld_double prob -) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return prob; - }; - - transmission_reduction_fun = tmpfun; - -} - -template -inline void Tool::set_recovery_enhancer( - epiworld_double prob -) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return prob; - }; - - recovery_enhancer_fun = tmpfun; - -} - -template -inline void Tool::set_death_reduction( - epiworld_double prob -) -{ - - ToolFun tmpfun = - [prob](Tool &, Agent *, VirusPtr, Model *) - { - return prob; - }; - - death_reduction_fun = tmpfun; - -} - -template -inline void Tool::set_name(std::string name) -{ - if (name != "") - tool_name = std::make_shared(name); -} - -template -inline std::string Tool::get_name() const { - - if (tool_name) - return *tool_name; - - return "unknown tool"; - -} - -template -inline Agent * Tool::get_agent() -{ - return this->agent; -} - -template -inline void Tool::set_agent(Agent * p, size_t idx) -{ - agent = p; - pos_in_agent = static_cast(idx); -} - -template -inline int Tool::get_id() const { - return id; -} - - -template -inline void Tool::set_id(int id) -{ - this->id = id; -} - -template -inline void Tool::set_date(int d) -{ - this->date = d; -} - -template -inline int Tool::get_date() const -{ - return date; -} - -template -inline void Tool::set_state( - epiworld_fast_int init, - epiworld_fast_int end -) -{ - state_init = init; - state_post = end; -} - -template -inline void Tool::set_queue( - epiworld_fast_int init, - epiworld_fast_int end -) -{ - queue_init = init; - queue_post = end; -} - -template -inline void Tool::get_state( - epiworld_fast_int * init, - epiworld_fast_int * post -) -{ - if (init != nullptr) - *init = state_init; - - if (post != nullptr) - *post = state_post; - -} - -template -inline void Tool::get_queue( - epiworld_fast_int * init, - epiworld_fast_int * post -) -{ - if (init != nullptr) - *init = queue_init; - - if (post != nullptr) - *post = queue_post; - -} - -template<> -inline bool Tool>::operator==( - const Tool> & other - ) const -{ - - if (sequence->size() != other.sequence->size()) - return false; - - for (size_t i = 0u; i < sequence->size(); ++i) - { - if (sequence->operator[](i) != other.sequence->operator[](i)) - return false; - } - - if (tool_name != other.tool_name) - return false; - - if (state_init != other.state_init) - return false; - - if (state_post != other.state_post) - return false; - - if (queue_init != other.queue_init) - return false; - - if (queue_post != other.queue_post) - return false; - - - return true; - -} - -template -inline bool Tool::operator==(const Tool & other) const -{ - if (*sequence != *other.sequence) - return false; - - if (tool_name != other.tool_name) - return false; - - if (state_init != other.state_init) - return false; - - if (state_post != other.state_post) - return false; - - if (queue_init != other.queue_init) - return false; - - if (queue_post != other.queue_post) - return false; - - return true; - -} - - -template -inline void Tool::print() const -{ - - printf_epiworld("Tool : %s\n", tool_name->c_str()); - printf_epiworld("Id : %s\n", (id < 0)? std::string("(empty)").c_str() : std::to_string(id).c_str()); - printf_epiworld("state_init : %i\n", state_init); - printf_epiworld("state_post : %i\n", state_post); - printf_epiworld("queue_init : %i\n", queue_init); - printf_epiworld("queue_post : %i\n", queue_post); - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/tool-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/entity-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_ENTITY_BONES_HPP -#define EPIWORLD_ENTITY_BONES_HPP - -template -class Model; - -template -class Agent; - -template -class AgentsSample; - -template -inline void default_add_entity(Action & a, Model * m); - -template -inline void default_rm_entity(Action & a, Model * m); - -template -class Entity { - friend class Agent; - friend class AgentsSample; - friend class Model; - friend void default_add_entity(Action & a, Model * m); - friend void default_rm_entity(Action & a, Model * m); -private: - - Model * model; - - int id = -1; - std::vector< size_t > agents; ///< Vector of agents - std::vector< size_t > agents_location; ///< Location where the entity is stored in the agent - size_t n_agents = 0u; - - /** - * @name Auxiliary variables for AgentsSample iterators - * - * @details These variables+objects are used by the AgentsSample - * class for building efficient iterators over agents. The idea is to - * reduce the memory allocation, so only during the first call of - * AgentsSample::AgentsSample(Entity) these vectors are allocated. - */ - ///@{ - std::vector< Agent * > sampled_agents; - size_t sampled_agents_n = 0u; - std::vector< size_t > sampled_agents_left; - size_t sampled_agents_left_n = 0u; - // int date_last_add_or_remove = -99; ///< Last time the entity added or removed an agent - ///@} - - int max_capacity = -1; - std::string entity_name = "Unknown entity"; - - std::vector< epiworld_double > location = {0.0}; ///< An arbitrary vector for location - - epiworld_fast_int state_init = -99; - epiworld_fast_int state_post = -99; - - epiworld_fast_int queue_init = 0; ///< Change of state when added to agent. - epiworld_fast_int queue_post = 0; ///< Change of state when removed from agent. - -public: - - // Entity() = delete; - // Entity(Entity & e) = delete; - // Entity(const Entity & e); - // Entity(Entity && e); - Entity(std::string name) : entity_name(name) {}; - // Entity & operator=(const Entity & e); - - void add_agent(Agent & p, Model * model); - void add_agent(Agent * p, Model * model); - void rm_agent(size_t idx); - size_t size() const noexcept; - void set_location(std::vector< epiworld_double > loc); - std::vector< epiworld_double > & get_location(); - - typename std::vector< Agent * >::iterator begin(); - typename std::vector< Agent * >::iterator end(); - - typename std::vector< Agent * >::const_iterator begin() const; - typename std::vector< Agent * >::const_iterator end() const; - - Agent * operator[](size_t i); - - int get_id() const noexcept; - const std::string & get_name() const noexcept; - - void set_state(epiworld_fast_int init, epiworld_fast_int post); - void set_queue(epiworld_fast_int init, epiworld_fast_int post); - void get_state(epiworld_fast_int * init, epiworld_fast_int * post); - void get_queue(epiworld_fast_int * init, epiworld_fast_int * post); - - void reset(); - - bool operator==(const Entity & other) const; - bool operator!=(const Entity & other) const {return !operator==(other);}; - -}; - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/entity-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/entity-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_ENTITY_MEAT_HPP -#define EPIWORLD_ENTITY_MEAT_HPP - -// template -// inline Entity::Entity(const Entity & e) : -// model(e.model), -// id(e.id), -// agents(0u), -// agents_location(0u), -// n_agents(0), -// sampled_agents(0u), -// sampled_agents_n(0u), -// sampled_agents_left(0u), -// sampled_agents_left_n(0u), -// max_capacity(e.max_capacity), -// entity_name(e.entity_name), -// location(e.location), -// state_init(e.state_init), -// state_post(e.state_post), -// queue_init(e.queue_init), -// queue_post(e.queue_post) -// { - -// } - -template -inline void Entity::add_agent( - Agent & p, - Model * model - ) -{ - - // Need to add it to the actions, through the individual - p.add_entity(*this, model); - -} - -template -inline void Entity::add_agent( - Agent * p, - Model * model - ) -{ - p->add_entity(*this, model); -} - -template -inline void Entity::rm_agent(size_t idx) -{ - if (idx >= n_agents) - throw std::out_of_range( - "Trying to remove agent "+ std::to_string(idx) + - " out of " + std::to_string(n_agents) - ); - - model->population[idx].rm_entity(*this); - - return; -} - -template -inline size_t Entity::size() const noexcept -{ - return n_agents; -} - -template -inline void Entity::set_location(std::vector< epiworld_double > loc) -{ - location = loc; -} - -template -inline std::vector< epiworld_double > & Entity::get_location() -{ - return location; -} - -template -inline typename std::vector< Agent * >::iterator Entity::begin() -{ - - if (n_agents == 0) - return agents.end(); - - return agents.begin(); - -} - -template -inline typename std::vector< Agent * >::iterator Entity::end() -{ - return agents.begin() + n_agents; -} - -template -inline typename std::vector< Agent * >::const_iterator Entity::begin() const -{ - - if (n_agents == 0) - return agents.end(); - - return agents.begin(); - -} - -template -inline typename std::vector< Agent * >::const_iterator Entity::end() const -{ - return agents.begin() + n_agents; -} - -template -inline Agent * Entity::operator[](size_t i) -{ - if (n_agents <= i) - throw std::logic_error("There are not that many agents in this entity."); - - return &model->get_agents()[i]; -} - -template -inline int Entity::get_id() const noexcept -{ - return id; -} - -template -inline const std::string & Entity::get_name() const noexcept -{ - return entity_name; -} - -template -inline void Entity::set_state( - epiworld_fast_int init, - epiworld_fast_int end -) -{ - state_init = init; - state_post = end; -} - -template -inline void Entity::set_queue( - epiworld_fast_int init, - epiworld_fast_int end -) -{ - queue_init = init; - queue_post = end; -} - -template -inline void Entity::get_state( - epiworld_fast_int * init, - epiworld_fast_int * post -) -{ - if (init != nullptr) - *init = state_init; - - if (post != nullptr) - *post = state_post; - -} - -template -inline void Entity::get_queue( - epiworld_fast_int * init, - epiworld_fast_int * post -) -{ - if (init != nullptr) - *init = queue_init; - - if (post != nullptr) - *post = queue_post; - -} - -template -inline void Entity::reset() -{ - sampled_agents.clear(); - sampled_agents_n = 0u; - sampled_agents_left.clear(); - sampled_agents_left_n = 0u; -} - -template -inline bool Entity::operator==(const Entity & other) const -{ - - if (id != other.id) - return false; - - if (n_agents != other.n_agents) - return false; - - for (size_t i = 0u; i < n_agents; ++i) - { - if (agents[i] != other.agents[i]) - return false; - } - - - if (max_capacity != other.max_capacity) - return false; - - if (entity_name != other.entity_name) - return false; - - if (location.size() != other.location.size()) - return false; - - for (size_t i = 0u; i < location.size(); ++i) - { - - if (location[i] != other.location[i]) - return false; - - } - - if (state_init != other.state_init) - return false; - - if (state_post != other.state_post) - return false; - - if (queue_init != other.queue_init) - return false; - - if (queue_post != other.queue_post) - return false; - - return true; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/entity-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/entities-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_ENTITIES_BONES_HPP -#define EPIWORLD_ENTITIES_BONES_HPP - -template -class Virus; - -template -class Agent; - - -/** - * @brief Set of Entities (useful for building iterators) - * - * @tparam TSeq - */ -template -class Entities { - friend class Entity; - friend class Agent; -private: - std::vector< Entity * > dat; - const size_t n_entities; - -public: - - Entities() = delete; - Entities(Agent & p); - - typename std::vector< Entity * >::iterator begin(); - typename std::vector< Entity * >::iterator end(); - - Entity & operator()(size_t i); - Entity & operator[](size_t i); - - size_t size() const noexcept; - - bool operator==(const Entities & other) const; - -}; - -template -inline Entities::Entities(Agent & p) : - n_entities(p.get_n_entities()) -{ - - dat.reserve(n_entities); - for (size_t i = 0u; i < n_entities; ++i) - dat.push_back(&p.get_entity(i)); - -} - -template -inline typename std::vector< Entity* >::iterator Entities::begin() -{ - - if (n_entities == 0u) - return dat.end(); - - return dat.begin(); -} - -template -inline typename std::vector< Entity* >::iterator Entities::end() -{ - - return begin() + n_entities; -} - -template -inline Entity & Entities::operator()(size_t i) -{ - - if (i >= n_entities) - throw std::range_error("Entity index out of range."); - - return *dat[i]; - -} - -template -inline Entity & Entities::operator[](size_t i) -{ - - return *dat[i]; - -} - -template -inline size_t Entities::size() const noexcept -{ - return n_entities; -} - -template -inline bool Entities::operator==(const Entities & other) const -{ - - if (n_entities != other.n_entities) - return false; - - for (size_t i = 0u; i < dat.size(); ++i) - { - if (dat[i] != other.dat[i]) - return false; - } - - return true; -} - -/** - * @brief Set of Entities (const) (useful for iterators) - * - * @tparam TSeq - */ -template -class Entities_const { - friend class Virus; - friend class Agent; -private: - const std::vector< Entity* > dat; - const size_t n_entities; - -public: - - Entities_const() = delete; - Entities_const(const Agent & p); - - typename std::vector< Entity* >::const_iterator begin(); - typename std::vector< Entity* >::const_iterator end(); - - const Entity & operator()(size_t i); - const Entity & operator[](size_t i); - - size_t size() const noexcept; - - bool operator==(const Entities_const & other) const; - -}; - -template -inline Entities_const::Entities_const(const Agent & p) : - n_entities(p.get_n_entities()) -{ - - dat.reserve(n_entities); - for (size_t i = 0u; i < n_entities; ++i) - dat.push_back(&p.get_entity(i)); - -} - -template -inline typename std::vector< Entity* >::const_iterator Entities_const::begin() { - - if (n_entities == 0u) - return dat.end(); - - return dat.begin(); -} - -template -inline typename std::vector< Entity* >::const_iterator Entities_const::end() { - - return begin() + n_entities; -} - -template -inline const Entity & Entities_const::operator()(size_t i) -{ - - if (i >= n_entities) - throw std::range_error("Entity index out of range."); - - return *dat[i]; - -} - -template -inline const Entity & Entities_const::operator[](size_t i) -{ - - return *dat[i]; - -} - -template -inline size_t Entities_const::size() const noexcept -{ - return n_entities; -} - -template -inline bool Entities_const::operator==(const Entities_const & other) const -{ - - if (n_entities != other.n_entities) - return false; - - for (size_t i = 0u; i < dat.size(); ++i) - { - if (dat[i] != other.dat[i]) - return false; - } - - return true; -} - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/entities-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/agent-meat-state.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_PERSON_MEAT_STATE_HPP -#define EPIWORLD_PERSON_MEAT_STATE_HPP - -// template -// class Model; - -// template -// class Agent; - - -/** - * @file agent-meat-state.hpp - * @author George G. Vega Yon (g.vegayon en gmail) - * @brief Sampling functions are getting big, so we keep them in a separate file. - * @version 0.1 - * @date 2022-06-15 - * - * @copyright Copyright (c) 2022 - * - */ -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//agent-meat-virus-sampling.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_AGENT_MEAT_VIRUS_SAMPLING -#define EPIWORLD_AGENT_MEAT_VIRUS_SAMPLING - -/** - * @brief Functions for sampling viruses - * - */ -namespace sampler { - -/** - * @brief Make a function to sample from neighbors - * - * This is akin to the function default_update_susceptible, with the difference - * that it will create a function that supports excluding states from the sampling - * frame. For example, individuals who have acquired a virus can be excluded if - * in incubation state. - * - * @tparam TSeq - * @param exclude unsigned vector of states that need to be excluded from the sampling - * @return Virus* of the selected virus. If none selected (or none - * available,) returns a nullptr; - */ -template -inline std::function*,Model*)> make_update_susceptible( - std::vector< epiworld_fast_uint > exclude = {} - ) -{ - - - if (exclude.size() == 0u) - { - - std::function*,Model*)> sampler = - [](Agent * p, Model * m) -> void - { - - if (p->get_n_viruses() > 0u) - throw std::logic_error( - std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + - std::string(" has ") + std::to_string(p->get_n_viruses()) + - std::string(" viruses.") - ); - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - - return; - }; - - return sampler; - - } else { - - // Making room for the query - std::shared_ptr> exclude_agent_bool = - std::make_shared>(0); - - std::shared_ptr> exclude_agent_bool_idx = - std::make_shared>(exclude); - - std::function*,Model*)> sampler = - [exclude_agent_bool,exclude_agent_bool_idx](Agent * p, Model * m) -> void - { - - // The first time we call it, we need to initialize the vector - if (exclude_agent_bool->size() == 0u) - { - - exclude_agent_bool->resize(m->get_states().size(), false); - for (auto s : *exclude_agent_bool_idx) - { - if (s >= exclude_agent_bool->size()) - throw std::logic_error( - std::string("You are trying to exclude a state that is out of range: ") + - std::to_string(s) + std::string(". There are only ") + - std::to_string(exclude_agent_bool->size()) + - std::string(" states in the model.") - ); - - exclude_agent_bool->operator[](s) = true; - - } - - } - - if (p->get_n_viruses() > 0u) - throw std::logic_error( - std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + - std::string(" has ") + std::to_string(p->get_n_viruses()) + - std::string(" viruses.") - ); - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - - // If the state is in the list, exclude it - if (exclude_agent_bool->operator[](neighbor->get_state())) - continue; - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - - return; - - }; - - return sampler; - - } - -} - -/** - * @brief Make a function to sample from neighbors - * - * This is akin to the function default_update_susceptible, with the difference - * that it will create a function that supports excluding states from the sampling - * frame. For example, individuals who have acquired a virus can be excluded if - * in incubation state. - * - * @tparam TSeq - * @param exclude unsigned vector of states that need to be excluded from the sampling - * @return Virus* of the selected virus. If none selected (or none - * available,) returns a nullptr; - */ -template -inline std::function*(Agent*,Model*)> make_sample_virus_neighbors( - std::vector< epiworld_fast_uint > exclude = {} -) -{ - if (exclude.size() == 0u) - { - - std::function*(Agent*,Model*)> res = - [](Agent * p, Model * m) -> Virus* { - - if (p->get_n_viruses() > 0u) - throw std::logic_error( - std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + - std::string(" has ") + std::to_string(p->get_n_viruses()) + - std::string(" viruses.") - ); - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return nullptr; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return nullptr; - - return m->array_virus_tmp[which]; - - }; - - return res; - - - } else { - - // Making room for the query - std::shared_ptr> exclude_agent_bool = - std::make_shared>(0); - - std::shared_ptr> exclude_agent_bool_idx = - std::make_shared>(exclude); - - - std::function*(Agent*,Model*)> res = - [exclude_agent_bool,exclude_agent_bool_idx](Agent * p, Model * m) -> Virus* { - - // The first time we call it, we need to initialize the vector - if (exclude_agent_bool->size() == 0u) - { - - exclude_agent_bool->resize(m->get_states().size(), false); - for (auto s : *exclude_agent_bool_idx) - { - if (s >= exclude_agent_bool->size()) - throw std::logic_error( - std::string("You are trying to exclude a state that is out of range: ") + - std::to_string(s) + std::string(". There are only ") + - std::to_string(exclude_agent_bool->size()) + - std::string(" states in the model.") - ); - - exclude_agent_bool->operator[](s) = true; - - } - - } - - if (p->get_n_viruses() > 0u) - throw std::logic_error( - std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + - std::string(" has ") + std::to_string(p->get_n_viruses()) + - std::string(" viruses.") - ); - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - - // If the state is in the list, exclude it - if (exclude_agent_bool->operator[](neighbor->get_state())) - continue; - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return nullptr; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return nullptr; - - return m->array_virus_tmp[which]; - - }; - - return res; - - } - -} - -/** - * @brief Sample from neighbors pool of viruses (at most one) - * - * This function samples at most one virus from the pool of - * viruses from its neighbors. If no virus is selected, the function - * returns a `nullptr`, otherwise it returns a pointer to the - * selected virus. - * - * This can be used to build a new update function (EPI_NEW_UPDATEFUN.) - * - * @tparam TSeq - * @param p Pointer to person - * @param m Pointer to the model - * @return Virus* of the selected virus. If none selected (or none - * available,) returns a nullptr; - */ -template -inline Virus * sample_virus_single(Agent * p, Model * m) -{ - - if (p->get_n_viruses() > 0u) - throw std::logic_error( - std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + - std::string(" has ") + std::to_string(p->get_n_viruses()) + - std::string(" viruses.") - ); - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - #ifdef EPI_DEBUG - int _vcount_neigh = 0; - #endif - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= m->array_virus_tmp.size()) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - #ifdef EPI_DEBUG - if ( - (m->array_double_tmp[nviruses_tmp - 1] < 0.0) | - (m->array_double_tmp[nviruses_tmp - 1] > 1.0) - ) - { - printf_epiworld( - "[epi-debug] Agent %i's virus %i has transmission prob outside of [0, 1]: %.4f!\n", - static_cast(neighbor->get_id()), - static_cast(_vcount_neigh++), - m->array_double_tmp[nviruses_tmp - 1] - ); - } - #endif - - } - } - - - // No virus to compute - if (nviruses_tmp == 0u) - return nullptr; - - #ifdef EPI_DEBUG - m->get_db().n_transmissions_potential++; - #endif - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return nullptr; - - #ifdef EPI_DEBUG - m->get_db().n_transmissions_today++; - #endif - - return m->array_virus_tmp[which]; - -} - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//agent-meat-virus-sampling.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - - -template -inline void default_update_susceptible( - Agent * p, - Model * m - ) -{ - - Virus * virus = sampler::sample_virus_single(p, m); - - if (virus == nullptr) - return; - - p->add_virus(*virus, m); - - return; - -} - -template -inline void default_update_exposed(Agent * p, Model * m) { - - if (p->get_n_viruses() == 0u) - throw std::logic_error( - std::string("Using the -default_update_exposed- on agents WITHOUT viruses makes no sense! ") + - std::string("Agent id ") + std::to_string(p->get_id()) + std::string(" has no virus registered.") - ); - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Die - m->array_double_tmp[n_events++] = - v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - - #ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } - #else - if (n_events == 0u) - return; - #endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - if ((which % 2) == 0) // If odd - { - - size_t which_v = std::ceil(which / 2); - p->rm_agent_by_virus(which_v, m); - - } else { - - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - } - - return ; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/agent-meat-state.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/agent-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_PERSON_BONES_HPP -#define EPIWORLD_PERSON_BONES_HPP - -template -class Model; - -template -class Virus; - -template -class Viruses; - -template -class Viruses_const; - -template -class Tool; - -template -class Tools; - -template -class Tools_const; - -template -class Queue; - -template -struct Action; - -template -class Entity; - -template -class Entities; - -template -inline void default_add_virus(Action & a, Model * m); - -template -inline void default_add_tool(Action & a, Model * m); - -template -inline void default_add_entity(Action & a, Model * m); - -template -inline void default_rm_virus(Action & a, Model * m); - -template -inline void default_rm_tool(Action & a, Model * m); - -template -inline void default_rm_entity(Action & a, Model * m); - - - -/** - * @brief Agent (agents) - * - * @tparam TSeq Sequence type (should match `TSeq` across the model) - */ -template -class Agent { - friend class Model; - friend class Virus; - friend class Viruses; - friend class Viruses_const; - friend class Tool; - friend class Tools; - friend class Tools_const; - friend class Queue; - friend class Entities; - friend class AgentsSample; - friend void default_add_virus(Action & a, Model * m); - friend void default_add_tool(Action & a, Model * m); - friend void default_add_entity(Action & a, Model * m); - friend void default_rm_virus(Action & a, Model * m); - friend void default_rm_tool(Action & a, Model * m); - friend void default_rm_entity(Action & a, Model * m); -private: - - Model * model; - - std::vector< size_t > neighbors; - std::vector< size_t > neighbors_locations; - size_t n_neighbors = 0u; - - std::vector< size_t > entities; - std::vector< size_t > entities_locations; - size_t n_entities = 0u; - - epiworld_fast_uint state = 0u; - epiworld_fast_uint state_prev = 0u; ///< For accounting, if need to undo a change. - - int state_last_changed = -1; ///< Last time the agent was updated. - int id = -1; - - std::vector< VirusPtr > viruses; - epiworld_fast_uint n_viruses = 0u; - - std::vector< ToolPtr > tools; - epiworld_fast_uint n_tools = 0u; - - ActionFun add_virus_ = default_add_virus; - ActionFun add_tool_ = default_add_tool; - ActionFun add_entity_ = default_add_entity; - - ActionFun rm_virus_ = default_rm_virus; - ActionFun rm_tool_ = default_rm_tool; - ActionFun rm_entity_ = default_rm_entity; - - epiworld_fast_uint action_counter = 0u; - - std::vector< Agent * > sampled_agents; - size_t sampled_agents_n = 0u; - std::vector< size_t > sampled_agents_left; - size_t sampled_agents_left_n = 0u; - int date_last_build_sample = -99; - -public: - - Agent(); - Agent(Agent && p); - Agent(const Agent & p); - Agent & operator=(const Agent & other_agent); - - /** - * @name Add/Remove Virus/Tool - * - * Any of these is ultimately reflected at the end of the iteration. - * - * @param tool Tool to add - * @param virus Virus to add - * @param state_new state after the change - * @param queue - */ - ///@{ - void add_tool( - ToolPtr tool, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void add_tool( - Tool tool, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void add_virus( - VirusPtr virus, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void add_virus( - Virus virus, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void add_entity( - Entity & entity, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_tool( - epiworld_fast_uint tool_idx, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_tool( - ToolPtr & tool, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_virus( - epiworld_fast_uint virus_idx, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_virus( - VirusPtr & virus, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_entity( - epiworld_fast_uint entity_idx, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_entity( - Entity & entity, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); - - void rm_agent_by_virus( - epiworld_fast_uint virus_idx, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); ///< Agent removed by virus - - void rm_agent_by_virus( - VirusPtr & virus, - Model * model, - epiworld_fast_int state_new = -99, - epiworld_fast_int queue = -99 - ); ///< Agent removed by virus - ///@} - - /** - * @name Get the rates (multipliers) for the agent - * - * @param v A pointer to a virus. - * @return epiworld_double - */ - ///@{ - epiworld_double get_susceptibility_reduction(VirusPtr v, Model * model); - epiworld_double get_transmission_reduction(VirusPtr v, Model * model); - epiworld_double get_recovery_enhancer(VirusPtr v, Model * model); - epiworld_double get_death_reduction(VirusPtr v, Model * model); - ///@} - - int get_id() const; ///< Id of the individual - - VirusPtr & get_virus(int i); - Viruses get_viruses(); - const Viruses_const get_viruses() const; - size_t get_n_viruses() const noexcept; - - ToolPtr & get_tool(int i); - Tools get_tools(); - const Tools_const get_tools() const; - size_t get_n_tools() const noexcept; - - void mutate_virus(); - void add_neighbor( - Agent & p, - bool check_source = true, - bool check_target = true - ); - - /** - * @brief Swaps neighbors between the current agent and agent `other` - * - * @param other - * @param n_this - * @param n_other - */ - void swap_neighbors( - Agent & other, - size_t n_this, - size_t n_other - ); - - std::vector< Agent * > get_neighbors(); - size_t get_n_neighbors() const; - - void change_state( - Model * model, - epiworld_fast_uint new_state, - epiworld_fast_int queue = 0 - ); - - const epiworld_fast_uint & get_state() const; - - void reset(); - - bool has_tool(epiworld_fast_uint t) const; - bool has_tool(std::string name) const; - bool has_tool(const Tool & t) const; - bool has_virus(epiworld_fast_uint t) const; - bool has_virus(std::string name) const; - bool has_virus(const Virus & v) const; - - void print(Model * model, bool compressed = false) const; - - /** - * @brief Access the j-th column of the agent - * - * If an external array has been specified, then these two - * functions can be used to access additional agent's features - * not included in the model. - * - * The `operator[]` method is with no boundary check, whereas - * the `operator()` method checks boundaries. The former can result - * in a segfault. - * - * - * @param j - * @return double& - */ - ///@{ - double & operator()(size_t j); - double & operator[](size_t j); - double operator()(size_t j) const; - double operator[](size_t j) const; - ///@} - - Entities get_entities(); - const Entities_const get_entities() const; - const Entity & get_entity(size_t i) const; - Entity & get_entity(size_t i); - size_t get_n_entities() const; - - bool operator==(const Agent & other) const; - bool operator!=(const Agent & other) const {return !operator==(other);}; - -}; - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/agent-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/agent-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_PERSON_MEAT_HPP -#define EPIWORLD_PERSON_MEAT_HPP - -// template -// inline bool IN(Ta & a, std::vector< Ta > & b); - -#define CHECK_COALESCE_(proposed_, virus_tool_, alt_) \ - if (static_cast(proposed_) == -99) {\ - if (static_cast(virus_tool_) == -99) \ - (proposed_) = (alt_);\ - else (proposed_) = (virus_tool_);} - -// To large to add directly here -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//agent-actions-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_AGENT_ACTIONS_MEAT_HPP -#define EPIWORLD_AGENT_ACTIONS_MEAT_HPP - -template -inline void default_add_virus(Action & a, Model * m) -{ - - Agent * p = a.agent; - VirusPtr v = a.virus; - - CHECK_COALESCE_(a.new_state, v->state_init, p->get_state()) - CHECK_COALESCE_(a.queue, v->queue_init, 1) - - // Has a agent? If so, we need to register the transmission - if (v->get_agent()) - { - - // ... only if not the same agent - if (v->get_agent()->get_id() != p->get_id()) - m->get_db().record_transmission( - v->get_agent()->get_id(), - p->get_id(), - v->get_id(), - v->get_date() - ); - - } - - // Update virus accounting - p->n_viruses++; - size_t n_viruses = p->n_viruses; - - if (n_viruses <= p->viruses.size()) - p->viruses[n_viruses - 1] = std::make_shared< Virus >(*v); - else - p->viruses.push_back(std::make_shared< Virus >(*v)); - - n_viruses--; - - // Notice that both agent and date can be changed in this case - // as only the sequence is a shared_ptr itself. - #ifdef EPI_DEBUG - if (n_viruses >= p->viruses.size()) - { - throw std::logic_error( - "[epi-debug]::default_add_virus Index for new virus out of range." - ); - } - #endif - p->viruses[n_viruses]->set_agent(p, n_viruses); - p->viruses[n_viruses]->set_date(m->today()); - - #ifdef EPI_DEBUG - m->get_db().today_virus.at(v->get_id()).at(p->state)++; - #else - m->get_db().today_virus[v->get_id()][p->state]++; - #endif - -} - -template -inline void default_add_tool(Action & a, Model * m) -{ - - Agent * p = a.agent; - ToolPtr t = a.tool; - - CHECK_COALESCE_(a.new_state, t->state_init, p->get_state()) - CHECK_COALESCE_(a.queue, t->queue_init, Queue::NoOne) - - // Update tool accounting - p->n_tools++; - size_t n_tools = p->n_tools; - - if (n_tools <= p->tools.size()) - p->tools[n_tools - 1] = std::make_shared< Tool >(*t); - else - p->tools.push_back(std::make_shared< Tool >(*t)); - - n_tools--; - - p->tools[n_tools]->set_date(m->today()); - p->tools[n_tools]->set_agent(p, n_tools); - - m->get_db().today_tool[t->get_id()][p->state]++; - -} - -template -inline void default_rm_virus(Action & a, Model * model) -{ - - Agent * p = a.agent; - VirusPtr & v = a.agent->viruses[a.virus->pos_in_agent]; - - CHECK_COALESCE_(a.new_state, v->state_post, p->get_state()) - CHECK_COALESCE_(a.queue, v->queue_post, -Queue::Everyone) - - if (--p->n_viruses > 0) - { - // The new virus will change positions - p->viruses[p->n_viruses]->pos_in_agent = v->pos_in_agent; - std::swap( - p->viruses[p->n_viruses], // Moving to the end - p->viruses[v->pos_in_agent] // Moving to the beginning - ); - } - - // Calling the virus action over the removed virus - v->post_recovery(model); - - return; - -} - -template -inline void default_rm_tool(Action & a, Model * /*m*/) -{ - - Agent * p = a.agent; - ToolPtr & t = a.agent->tools[a.tool->pos_in_agent]; - - CHECK_COALESCE_(a.new_state, t->state_post, p->get_state()) - CHECK_COALESCE_(a.queue, t->queue_post, Queue::NoOne) - - if (--p->n_tools > 0) - { - p->tools[p->n_tools]->pos_in_agent = t->pos_in_agent; - std::swap( - p->tools[t->pos_in_agent], - p->tools[p->n_tools] - ); - } - - return; - -} - -template -inline void default_add_entity(Action & a, Model *) -{ - - Agent * p = a.agent; - Entity * e = a.entity; - - CHECK_COALESCE_(a.new_state, e->state_post, p->get_state()) - CHECK_COALESCE_(a.queue, e->queue_post, Queue::NoOne) - - // Checking the agent and the entity are not linked - if ((p->get_n_entities() > 0) && (e->size() > 0)) - { - - if (p->get_n_entities() > e->size()) // Slower search through the agent - { - for (size_t i = 0u; i < e->size(); ++i) - if(e->operator[](i)->get_id() == p->get_id()) - throw std::logic_error("An entity cannot be reassigned to an agent."); - } - else // Slower search through the entity - { - for (size_t i = 0u; i < p->get_n_entities(); ++i) - if(p->get_entity(i).get_id() == e->get_id()) - throw std::logic_error("An entity cannot be reassigned to an agent."); - } - - // It means that agent and entity were not associated. - } - - // Adding the entity to the agent - if (++p->n_entities <= p->entities.size()) - { - - p->entities[p->n_entities - 1] = e->get_id(); - p->entities_locations[p->n_entities - 1] = e->n_agents; - - } else - { - p->entities.push_back(e->get_id()); - p->entities_locations.push_back(e->n_agents); - } - - // Adding the agent to the entity - // Adding the entity to the agent - if (++e->n_agents <= e->agents.size()) - { - - e->agents[e->n_agents - 1] = p->get_id(); - // Adjusted by '-1' since the list of entities in the agent just grew. - e->agents_location[e->n_agents - 1] = p->n_entities - 1; - - } else - { - e->agents.push_back(p->get_id()); - e->agents_location.push_back(p->n_entities - 1); - } - - // Today was the last modification - // e->date_last_add_or_remove = m->today(); - -} - -template -inline void default_rm_entity(Action & a, Model * m) -{ - - Agent * p = a.agent; - Entity * e = a.entity; - size_t idx_agent_in_entity = a.idx_agent; - size_t idx_entity_in_agent = a.idx_object; - - CHECK_COALESCE_(a.new_state, e->state_post, p->get_state()) - CHECK_COALESCE_(a.queue, e->queue_post, Queue::NoOne) - - if (--p->n_entities > 0) - { - - // When we move the end entity to the new location, the - // moved entity needs to reflect the change, i.e., where the - // entity will now be located in the agent - size_t agent_location_in_last_entity = p->entities_locations[p->n_entities]; - Entity * last_entity = &m->get_entities()[p->entities[p->n_entities]]; ///< Last entity of the agent - - // The end entity will be located where the removed was - last_entity->agents_location[agent_location_in_last_entity] = idx_entity_in_agent; - - // We now make the swap - std::swap( - p->entities[p->n_entities], - p->entities[idx_entity_in_agent] - ); - - } - - if (--e->n_agents > 0) - { - - // When we move the end agent to the new location, the - // moved agent needs to reflect the change, i.e., where the - // agent will now be located in the entity - size_t entity_location_in_last_agent = e->agents_location[e->n_agents]; - Agent * last_agent = &m->get_agents()[e->agents[e->n_agents]]; ///< Last agent of the entity - - // The end entity will be located where the removed was - last_agent->entities_locations[entity_location_in_last_agent] = idx_agent_in_entity; - - // We now make the swap - std::swap( - e->agents[e->n_agents], - e->agents[idx_agent_in_entity] - ); - - } - - // Setting the date of the last removal - // e->date_last_add_or_remove = m->today(); - - return; - -} -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//agent-actions-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -template -inline Agent::Agent() {} - -template -inline Agent::Agent(Agent && p) : - model(p.model), - neighbors(std::move(p.neighbors)), - neighbors_locations(std::move(p.neighbors_locations)), - n_neighbors(p.n_neighbors), - entities(std::move(p.entities)), - entities_locations(std::move(p.entities_locations)), - n_entities(p.n_entities), - state(p.state), - state_prev(p.state_prev), - state_last_changed(p.state_last_changed), - id(p.id), - viruses(std::move(p.viruses)), /// Needs to be adjusted - n_viruses(p.n_viruses), - tools(std::move(p.tools)), /// Needs to be adjusted - n_tools(p.n_tools), - add_virus_(std::move(p.add_virus_)), - add_tool_(std::move(p.add_tool_)), - add_entity_(std::move(p.add_entity_)), - rm_virus_(std::move(p.rm_virus_)), - rm_tool_(std::move(p.rm_tool_)), - rm_entity_(std::move(p.rm_entity_)), - action_counter(p.action_counter) -{ - - state = p.state; - id = p.id; - - // Dealing with the virus - - int loc = 0; - for (auto & v : viruses) - { - - // Will create a copy of the virus, with the exeption of - // the virus code - v->agent = this; - v->pos_in_agent = loc++; - - } - - loc = 0; - for (auto & t : tools) - { - - // Will create a copy of the virus, with the exeption of - // the virus code - t->agent = this; - t->pos_in_agent = loc++; - - } - -} - -template -inline Agent::Agent(const Agent & p) : - model(p.model), - neighbors(p.neighbors), - neighbors_locations(p.neighbors_locations), - n_neighbors(p.n_neighbors), - entities(p.entities), - entities_locations(p.entities_locations), - n_entities(p.n_entities), - sampled_agents(0u), - sampled_agents_n(0u), - sampled_agents_left_n(0u), - date_last_build_sample(-99) -{ - - state = p.state; - id = p.id; - - // Dealing with the virus - viruses.resize(p.get_n_viruses(), nullptr); - n_viruses = viruses.size(); - for (size_t i = 0u; i < n_viruses; ++i) - { - - // Will create a copy of the virus, with the exeption of - // the virus code - viruses[i] = std::make_shared>(*p.viruses[i]); - viruses[i]->set_agent(this, i); - - } - - tools.resize(p.get_n_tools(), nullptr); - n_tools = tools.size(); - for (size_t i = 0u; i < n_tools; ++i) - { - - // Will create a copy of the virus, with the exeption of - // the virus code - tools[i] = std::make_shared>(*p.tools[i]); - tools[i]->set_agent(this, i); - - } - - add_virus_ = p.add_virus_; - add_tool_ = p.add_tool_; - rm_virus_ = p.rm_virus_; - rm_tool_ = p.rm_tool_; - -} - -template -inline Agent & Agent::operator=( - const Agent & other_agent -) -{ - - model = other_agent.model; - - neighbors = other_agent.neighbors; - neighbors_locations = other_agent.neighbors_locations; - n_neighbors = other_agent.n_neighbors; - - entities = other_agent.entities; - entities_locations = other_agent.entities_locations; - n_entities = other_agent.n_entities; - - sampled_agents.clear(); - sampled_agents_n = 0; - sampled_agents_left_n = 0; - date_last_build_sample = -99; - - // neighbors = other_agent.neighbors; - // entities = other_agent.entities; - // entities_locations = other_agent.entities_locations; - // n_entities = other_agent.n_entities; - state = other_agent.state; - state_prev = other_agent.state_prev; - state_last_changed = other_agent.state_last_changed; - id = other_agent.id; - - // viruses = other_agent.viruses; - n_viruses = other_agent.n_viruses; - viruses.resize(n_viruses, nullptr); - for (size_t i = 0u; i < n_viruses; ++i) - { - viruses[i] = std::make_shared>(*other_agent.viruses[i]); - viruses[i]->set_agent(this, i); - } - - // tools = other_agent.tools; - n_tools = other_agent.n_tools; - for (size_t i = 0u; i < n_tools; ++i) - { - tools[i] = std::make_shared>(*other_agent.tools[i]); - tools[i]->set_agent(this, i); - } - - add_virus_ = other_agent.add_virus_; - add_tool_ = other_agent.add_tool_; - add_entity_ = other_agent.add_entity_; - rm_virus_ = other_agent.rm_virus_; - rm_tool_ = other_agent.rm_tool_; - rm_entity_ = other_agent.rm_entity_; - action_counter = other_agent.action_counter; - - return *this; - -} - -template -inline void Agent::add_tool( - ToolPtr tool, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) { - - // Checking the virus exists - if (tool->get_id() >= static_cast(model->get_db().get_n_tools())) - throw std::range_error("The tool with id: " + std::to_string(tool->get_id()) + - " has not been registered. There are only " + std::to_string(model->get_n_tools()) + - " included in the model."); - - - model->actions_add( - this, nullptr, tool, nullptr, state_new, queue, add_tool_, -1, -1 - ); - -} - -template -inline void Agent::add_tool( - Tool tool, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - ToolPtr tool_ptr = std::make_shared< Tool >(tool); - add_tool(tool_ptr, model, state_new, queue); -} - -template -inline void Agent::add_virus( - VirusPtr virus, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - // Checking the virus exists - if (virus->get_id() >= static_cast(model->get_db().get_n_viruses())) - throw std::range_error("The virus with id: " + std::to_string(virus->get_id()) + - " has not been registered. There are only " + std::to_string(model->get_n_viruses()) + - " included in the model."); - - model->actions_add( - this, virus, nullptr, nullptr, state_new, queue, add_virus_, -1, -1 - ); - -} - -template -inline void Agent::add_virus( - Virus virus, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - VirusPtr virus_ptr = std::make_shared< Virus >(virus); - add_virus(virus_ptr, model, state_new, queue); -} - -template -inline void Agent::add_entity( - Entity & entity, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (model != nullptr) - { - - model->actions_add( - this, nullptr, nullptr, &entity, state_new, queue, add_entity_, -1, -1 - ); - - } - else // If no model is passed, then we assume that we only need to add the - // model entity - { - - Action a( - this, nullptr, nullptr, &entity, state_new, queue, add_entity_, - -1, -1 - ); - - default_add_entity(a, model); /* passing model makes nothing */ - - } - -} - -template -inline void Agent::rm_tool( - epiworld_fast_uint tool_idx, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (tool_idx >= n_tools) - throw std::range_error( - "The Tool you want to remove is out of range. This Agent only has " + - std::to_string(n_tools) + " tools." - ); - - model->actions_add( - this, nullptr, tools[tool_idx], nullptr, state_new, queue, rm_tool_, -1, -1 - ); - -} - -template -inline void Agent::rm_tool( - ToolPtr & tool, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (tool->agent != this) - throw std::logic_error("Cannot remove a virus from another agent!"); - - model->actions_add( - this, nullptr, tool, nullptr, state_new, queue, rm_tool_, -1, -1 - ); - -} - -template -inline void Agent::rm_virus( - epiworld_fast_uint virus_idx, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - if (virus_idx >= n_viruses) - throw std::range_error( - "The Virus you want to remove is out of range. This Agent only has " + - std::to_string(n_viruses) + " viruses." - ); - else if (n_viruses == 0u) - throw std::logic_error( - "There is no virus to remove here!" - ); - - #ifdef EPI_DEBUG - if (viruses[virus_idx]->pos_in_agent >= static_cast(n_viruses)) - { - throw std::logic_error( - "[epi-debug]::rm_virus the position in the agent is wrong." - ); - } - #endif - - model->actions_add( - this, viruses[virus_idx], nullptr, nullptr, state_new, queue, - default_rm_virus, -1, -1 - ); - -} - -template -inline void Agent::rm_virus( - VirusPtr & virus, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (virus->agent != this) - throw std::logic_error("Cannot remove a virus from another agent!"); - - model->actions_add( - this, virus, nullptr, nullptr, state_new, queue, - default_rm_virus, -1, -1 - ); - - -} - -template -inline void Agent::rm_entity( - epiworld_fast_uint entity_idx, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (entity_idx >= n_entities) - throw std::range_error( - "The Entity you want to remove is out of range. This Agent only has " + - std::to_string(n_entities) + " entitites." - ); - else if (n_entities == 0u) - throw std::logic_error( - "There is entity to remove here!" - ); - - model->actions_add( - this, nullptr, nullptr, model->entities[entity_idx], state_new, queue, - default_rm_entity, entities_locations[entity_idx], entity_idx - ); -} - -template -inline void Agent::rm_entity( - Entity & entity, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - // Looking for entity location in the agent - int entity_idx = -1; - for (size_t i = 0u; i < n_entities; ++i) - { - if (entities[i] == entity->get_id()) - entity_idx = i; - } - - if (entity_idx == -1) - throw std::logic_error( - "The agent " + std::to_string(id) + " is not associated with entity \"" + - entity.get_name() + "\"." - ); - - - model->actions_add( - this, nullptr, nullptr, entities[entity_idx], state_new, queue, - default_rm_entity, entities_locations[entity_idx], entity_idx - ); -} - -template -inline void Agent::rm_agent_by_virus( - epiworld_fast_uint virus_idx, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (state_new == -99) - state_new = state; - - if (virus_idx >= n_viruses) - throw std::range_error( - std::string("The virus trying to remove the agent is out of range. ") + - std::string("This agent has only ") + std::to_string(n_viruses) + - std::string(" and you are trying to remove virus # ") + - std::to_string(virus_idx) + std::string(".") - ); - - // Removing viruses - for (size_t i = 0u; i < n_viruses; ++i) - { - if (i != virus_idx) - rm_virus(i, model); - } - - // Changing state to new_state - VirusPtr & v = viruses[virus_idx]; - epiworld_fast_int dead_state, dead_queue; - v->get_state(nullptr, nullptr, &dead_state); - v->get_queue(nullptr, nullptr, &dead_queue); - - if (queue != -99) - dead_queue = queue; - - change_state( - model, - // Either preserve the current state or apply a new one - (dead_state < 0) ? state : static_cast(dead_state), - - // By default, it will be removed from the queue... unless the user - // says the contrary! - (dead_queue == -99) ? Queue::NoOne : dead_queue - ); - -} - -template -inline void Agent::rm_agent_by_virus( - VirusPtr & virus, - Model * model, - epiworld_fast_int state_new, - epiworld_fast_int queue -) -{ - - if (virus->get_agent() == nullptr) - throw std::logic_error("The virus trying to remove the agent has no host."); - - if (virus->get_agent()->id != id) - throw std::logic_error("Viruses can only remove their hosts'."); - - rm_agent_by_virus( - virus->pos_in_agent, - model, - state_new, - queue - ); - -} - -template -inline epiworld_double Agent::get_susceptibility_reduction( - VirusPtr v, - Model * model -) { - - return model->susceptibility_reduction_mixer(this, v, model); -} - -template -inline epiworld_double Agent::get_transmission_reduction( - VirusPtr v, - Model * model -) { - return model->transmission_reduction_mixer(this, v, model); -} - -template -inline epiworld_double Agent::get_recovery_enhancer( - VirusPtr v, - Model * model -) { - return model->recovery_enhancer_mixer(this, v, model); -} - -template -inline epiworld_double Agent::get_death_reduction( - VirusPtr v, - Model * model -) { - return model->death_reduction_mixer(this, v, model); -} - -template -inline int Agent::get_id() const -{ - return id; -} - -template -inline Viruses Agent::get_viruses() { - - return Viruses(*this); - -} - -template -inline const Viruses_const Agent::get_viruses() const { - - return Viruses_const(*this); - -} - -template -inline VirusPtr & Agent::get_virus(int i) { - return viruses.at(i); -} - -template -inline size_t Agent::get_n_viruses() const noexcept -{ - return n_viruses; -} - -template -inline Tools Agent::get_tools() { - return Tools(*this); -} - -template -inline const Tools_const Agent::get_tools() const { - return Tools_const(*this); -} - -template -inline ToolPtr & Agent::get_tool(int i) -{ - return tools.at(i); -} - -template -inline size_t Agent::get_n_tools() const noexcept -{ - return n_tools; -} - -template -inline void Agent::mutate_virus() -{ - - for (auto & v : viruses) - v->mutate(); - -} - -template -inline void Agent::add_neighbor( - Agent & p, - bool check_source, - bool check_target -) { - // Can we find the neighbor? - bool found = false; - if (check_source) - { - - for (auto & n: neighbors) - if (static_cast(n) == p.get_id()) - { - found = true; - break; - } - - } - - // Three things going on here: - // - Where in the neighbor will this be - // - What is the neighbor's id - // - Increasing the number of neighbors - if (!found) - { - - neighbors_locations.push_back(p.get_n_neighbors()); - neighbors.push_back(p.get_id()); - n_neighbors++; - - } - - - found = false; - if (check_target) - { - - for (auto & n: p.neighbors) - if (static_cast(n) == id) - { - found = true; - break; - } - - } - - if (!found) - { - - p.neighbors_locations.push_back(n_neighbors - 1); - p.neighbors.push_back(id); - p.n_neighbors++; - - } - - -} - -template -inline void Agent::swap_neighbors( - Agent & other, - size_t n_this, - size_t n_other -) -{ - - // Getting the agents - auto & pop = model->population; - auto & neigh_this = pop[neighbors[n_this]]; - auto & neigh_other = pop[other.neighbors[n_other]]; - - // Getting the locations in the neighbors - size_t loc_this_in_neigh = neighbors_locations[n_this]; - size_t loc_other_in_neigh = other.neighbors_locations[n_other]; - - // Changing ids - std::swap(neighbors[n_this], other.neighbors[n_other]); - - if (!model->directed) - { - std::swap( - neigh_this.neighbors[loc_this_in_neigh], - neigh_other.neighbors[loc_other_in_neigh] - ); - - // Changing the locations - std::swap(neighbors_locations[n_this], other.neighbors_locations[n_other]); - - std::swap( - neigh_this.neighbors_locations[loc_this_in_neigh], - neigh_other.neighbors_locations[loc_other_in_neigh] - ); - } - -} - -template -inline std::vector< Agent *> Agent::get_neighbors() -{ - std::vector< Agent * > res(n_neighbors, nullptr); - for (size_t i = 0u; i < n_neighbors; ++i) - res[i] = &model->population[neighbors[i]]; - - return res; -} - -template -inline size_t Agent::get_n_neighbors() const -{ - return n_neighbors; -} - -template -inline void Agent::change_state( - Model * model, - epiworld_fast_uint new_state, - epiworld_fast_int queue - ) -{ - - model->actions_add( - this, nullptr, nullptr, nullptr, new_state, queue, nullptr, -1, -1 - ); - - return; - -} - -template -inline const epiworld_fast_uint & Agent::get_state() const { - return state; -} - -template -inline void Agent::reset() -{ - - this->viruses.clear(); - n_viruses = 0u; - - this->tools.clear(); - n_tools = 0u; - - this->state = 0u; - this->state_prev = 0u; - - this->state_last_changed = -1; - -} - -template -inline bool Agent::has_tool(epiworld_fast_uint t) const -{ - - for (auto & tool : tools) - if (tool->get_id() == static_cast(t)) - return true; - - return false; - -} - -template -inline bool Agent::has_tool(std::string name) const -{ - - for (auto & tool : tools) - if (tool->get_name() == name) - return true; - - return false; - -} - -template -inline bool Agent::has_tool(const Tool & tool) const -{ - - return has_tool(tool.get_id()); - -} - -template -inline bool Agent::has_virus(epiworld_fast_uint t) const -{ - for (auto & v : viruses) - if (v->get_id() == static_cast(t)) - return true; - - return false; -} - -template -inline bool Agent::has_virus(std::string name) const -{ - - for (auto & v : viruses) - if (v->get_name() == name) - return true; - - return false; - -} - -template -inline bool Agent::has_virus(const Virus & virus) const -{ - - return has_virus(virus.get_id()); - -} - -template -inline void Agent::print( - Model * model, - bool compressed - ) const -{ - - if (compressed) - { - printf_epiworld( - "Agent: %i, state: %s (%lu), Nvirus: %lu, NTools: %lu, NNeigh: %lu\n", - id, model->states_labels[state].c_str(), state, n_viruses, n_tools, neighbors.size() - ); - } - else { - - printf_epiworld("Information about agent id %i\n", this->id); - printf_epiworld(" State : %s (%lu)\n", model->states_labels[state].c_str(), state); - printf_epiworld(" Virus count : %lu\n", n_viruses); - printf_epiworld(" Tool count : %lu\n", n_tools); - printf_epiworld(" Neigh. count : %lu\n", neighbors.size()); - - size_t nfeats = model->get_agents_data_ncols(); - if (nfeats > 0) - { - - printf_epiworld("This model includes features (%lu): [ ", nfeats); - - int max_to_show = static_cast((nfeats > 10)? 10 : nfeats); - - for (int k = 0; k < max_to_show; ++k) - { - printf_epiworld("%.2f", this->operator[](k)); - - if (k != (max_to_show - 1)) - { - printf_epiworld(", "); - } else { - printf_epiworld(" ]\n"); - } - - } - - } - - } - - return; - -} - -template -inline double & Agent::operator()(size_t j) -{ - - if (model->agents_data_ncols <= j) - throw std::logic_error("The requested feature of the agent is out of range."); - - return *(model->agents_data + j * model->size() + id); - -} - -template -inline double & Agent::operator[](size_t j) -{ - return *(model->agents_data + j * model->size() + id); -} - -template -inline double Agent::operator()(size_t j) const -{ - - if (model->agents_data_ncols <= j) - throw std::logic_error("The requested feature of the agent is out of range."); - - return *(model->agents_data + j * model->size() + id); - -} - -template -inline double Agent::operator[](size_t j) const -{ - return *(model->agents_data + j * model->size() + id); -} - -template -inline Entities Agent::get_entities() -{ - return Entities(*this); -} - -template -inline const Entities_const Agent::get_entities() const -{ - return Entities_const(*this); -} - -template -inline const Entity & Agent::get_entity(size_t i) const -{ - if (i >= n_entities) - throw std::range_error("Trying to get to an agent's entity outside of the range."); - - return model->entities[entities[i]]; -} - -template -inline Entity & Agent::get_entity(size_t i) -{ - if (i >= n_entities) - throw std::range_error("Trying to get to an agent's entity outside of the range."); - - return model->entities[entities[i]]; -} - -template -inline size_t Agent::get_n_entities() const -{ - return n_entities; -} - -template -inline bool Agent::operator==(const Agent & other) const -{ - - EPI_DEBUG_FAIL_AT_TRUE( - n_neighbors != other.n_neighbors, - "Agent:: n_eighbors don't match" - ) - - - for (size_t i = 0u; i < n_neighbors; ++i) - { - EPI_DEBUG_FAIL_AT_TRUE( - neighbors[i] != other.neighbors[i], - "Agent:: neighbor[i] don't match" - ) - } - - EPI_DEBUG_FAIL_AT_TRUE( - n_entities != other.n_entities, - "Agent:: n_entities don't match" - ) - - - for (size_t i = 0u; i < n_entities; ++i) - { - EPI_DEBUG_FAIL_AT_TRUE( - entities[i] != other.entities[i], - "Agent:: entities[i] don't match" - ) - } - - EPI_DEBUG_FAIL_AT_TRUE( - state != other.state, - "Agent:: state don't match" - ) - - - EPI_DEBUG_FAIL_AT_TRUE( - state_prev != other.state_prev, - "Agent:: state_prev don't match" - ) - - - // EPI_DEBUG_FAIL_AT_TRUE( - // state_last_changed != other.state_last_changed, - // "Agent:: state_last_changed don't match" - // ) ///< Last time the agent was updated. - - - EPI_DEBUG_FAIL_AT_TRUE( - n_viruses != other.n_viruses, - "Agent:: n_viruses don't match" - ) - - - for (size_t i = 0u; i < n_viruses; ++i) - { - - EPI_DEBUG_FAIL_AT_TRUE( - *viruses[i] != *other.viruses[i], - "Agent:: viruses[i] don't match" - ) - - } - - EPI_DEBUG_FAIL_AT_TRUE(n_tools != other.n_tools, "Agent:: n_tools don't match") - - for (size_t i = 0u; i < n_tools; ++i) - { - - EPI_DEBUG_FAIL_AT_TRUE( - tools[i] != other.tools[i], - "Agent:: tools[i] don't match" - ) - - } - - return true; - -} - -#undef CHECK_COALESCE_ - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/agent-meat.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/agentssample-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_AGENTS_BONES_HPP -#define EPIWORLD_AGENTS_BONES_HPP - -class SAMPLETYPE { -public: - static const int MODEL = 0; - static const int ENTITY = 1; - static const int AGENT = 2; -}; - -// template -// class Agent; - -// template -// class Model; - -// template -// class Entity; - -/** - * @brief Sample of agents - * - * This class allows sampling agents from Entity and Model. - * - * @tparam TSeq - */ -template -class AgentsSample { -private: - - size_t sample_size = 0u; - - std::vector< Agent* > * agents = nullptr; ///< Pointer to sample of agents - size_t * agents_n = nullptr; ///< Size of sample of agents - - std::vector< size_t > * agents_left = nullptr; ///< Pointer to agents left (iota) - size_t * agents_left_n = nullptr; ///< Size of agents left - - Model * model = nullptr; ///< Extracts runif() and (if the case) population. - Entity * entity = nullptr; /// - Agent * agent = nullptr; - - int sample_type = SAMPLETYPE::AGENT; - - void sample_n(size_t n); ///< Backbone function for sampling - - -public: - - // Not available (for now) - AgentsSample() = delete; ///< Default constructor - AgentsSample(const AgentsSample & a) = delete; ///< Copy constructor - AgentsSample(AgentsSample && a) = delete; ///< Move constructor - - AgentsSample(Model & model_, size_t n, bool truncate = false); - AgentsSample(Model * model, Entity & entity_, size_t n, bool truncate = false); - AgentsSample(Model * model, Agent & agent_, size_t n, bool truncate = false); - - ~AgentsSample(); - - typename std::vector< Agent * >::iterator begin(); - typename std::vector< Agent * >::iterator end(); - - Agent * operator[](size_t n); - Agent * operator()(size_t n); - size_t size() const noexcept; - -}; - -template -inline AgentsSample::AgentsSample( - Model & model_, - size_t n, - bool truncate - ) { - - if (truncate) - { - - if (n > model_.size()) - n = model_.size(); - - } else if (n > model_.size()) - throw std::logic_error( - "There are only " + std::to_string(model_.size()) + " agents. You cannot " + - "sample " + std::to_string(n)); - - sample_size = n; - model = &model_; - sample_type = SAMPLETYPE::MODEL; - - agents = &model_.sampled_population; - agents_n = &model_.sampled_population_n; - - agents_left = &model_.population_left; - agents_left_n = &model_.population_left_n; - - sample_n(n); - - return; - -} - -template -inline AgentsSample::AgentsSample( - Model * model, - Entity & entity_, - size_t n, bool truncate - ) { - - if (truncate) - { - - if (n > entity_.size()) - n = entity_.size(); - - } else if (n > entity_.size()) - throw std::logic_error( - "There are only " + std::to_string(entity_.size()) + " agents. You cannot " + - "sample " + std::to_string(n)); - - sample_size = n; - model = &entity_.model; - sample_type = SAMPLETYPE::ENTITY; - - agents = &entity_.sampled_agents; - agents_n = &entity_.sampled_agents_n; - - agents_left = &entity_.sampled_agents_left; - agents_left_n = &entity_.sampled_agents_left_n; - - sample_n(n); - - return; - -} - -/** - * @brief Sample from the agent's entities - * - * For example, how many individuals the agent contacts in a given point in time. - * - * @tparam TSeq - * @param agent_ - * @param n Sample size - * @param truncate If the agent has fewer than `n` connections, then truncate = true - * will automatically reduce the number of possible samples. Otherwise, if false, then - * it returns an error. - */ -template -inline AgentsSample::AgentsSample( - Model * model, - Agent & agent_, - size_t n, - bool truncate - ) -{ - - sample_type = SAMPLETYPE::AGENT; - - agent = &agent_; - - agents = &agent_.sampled_agents; - agents_n = &agent_.sampled_agents_n; - - agents_left = &agent_.sampled_agents_left; - agents_left_n = &agent_.sampled_agents_left_n; - - // Computing the cumulative sum of counts across entities - size_t agents_in_entities = 0; - Entities entities_a = agent->get_entities(); - - std::vector< int > cum_agents_count(entities_a.size(), 0); - int idx = -1; - for (auto & e : entities_a) - { - if (++idx == 0) - cum_agents_count[idx] = (e->size() - 1u); - else - cum_agents_count[idx] = ( - (e->size() - 1u) + - cum_agents_count[idx - 1] - ); - - agents_in_entities += (e->size() - 1u); - } - - if (truncate) - { - - if (n > agents_in_entities) - n = agents_in_entities; - - } else if (n > agents_in_entities) - throw std::logic_error( - "There are only " + std::to_string(agents_in_entities) + " agents. You cannot " + - "sample " + std::to_string(n)); - - sample_size = n; - - if (agents->size() < n) - agents->resize(n); - - for (size_t i = 0u; i < n; ++i) - { - int jth = std::floor(model->runif() * agents_in_entities); - for (size_t e = 0u; e < cum_agents_count.size(); ++e) - { - // Are we in the limit? - if (jth <= cum_agents_count[e]) - { - if (e == 0) // From the first group - agents->operator[](i) = entities_a[e][jth]; - else - agents->operator[](i) = entities_a[e][jth - cum_agents_count[e - 1]]; - - break; - } - - } - } - - return; - -} - -template -inline AgentsSample::~AgentsSample() {} - -template -inline size_t AgentsSample::size() const noexcept -{ - return this->sample_size; -} - -template -inline Agent * AgentsSample::operator[](size_t i) -{ - - return agents->operator[](i); - -} - -template -inline Agent * AgentsSample::operator()(size_t i) -{ - - if (i >= this->sample_size) - throw std::range_error("The requested agent is out of range."); - - return agents->operator[](i); - -} - -template -inline typename std::vector< Agent * >::iterator AgentsSample::begin() -{ - - if (sample_size > 0u) - return agents->begin(); - else - return agents->end(); - -} - -template -inline typename std::vector< Agent * >::iterator AgentsSample::end() -{ - - return agents->begin() + sample_size; - -} - -template -inline void AgentsSample::sample_n(size_t n) -{ - - // Checking if the size of the entity has changed (or hasn't been initialized) - if (sample_type == SAMPLETYPE::MODEL) - { - - if (model->size() != agents_left->size()) - { - agents_left->resize(model->size(), 0u); - std::iota(agents_left->begin(), agents_left->end(), 0u); - } - - - } else if (sample_type == SAMPLETYPE::ENTITY) { - - if (entity->size() != agents_left->size()) - { - - agents_left->resize(entity->size(), 0u); - std::iota(agents_left->begin(), agents_left->end(), 0u); - - } - - } - - // Restart the counter of agents left - *agents_left_n = agents_left->size(); - - if (agents->size() < sample_size) - agents->resize(sample_size, nullptr); - - if (sample_type == SAMPLETYPE::MODEL) - { - - for (size_t i = 0u; i < n; ++i) - { - - size_t ith = agents_left->operator[](model->runif() * ((*agents_left_n)--)); - agents->operator[](i) = &model->population[ith]; - - // Updating list - std::swap(agents_left->operator[](ith), agents_left->operator[](*agents_left_n)); - - } - - } else if (sample_type == SAMPLETYPE::ENTITY) { - - for (size_t i = 0u; i < n; ++i) - { - - size_t ith = agents_left->operator[](model->runif() * (--(*agents_left_n))); - agents->operator[](i) = entity->agents[ith]; - - // Updating list - std::swap(agents_left->operator[](ith), agents_left->operator[](*agents_left_n)); - - } - - } - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/agentssample-bones.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld/models/models.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_HPP -#define EPIWORLD_MODELS_HPP - -namespace epimodels { - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/globalactions.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_GLOBALACTIONS_HPP -#define EPIWORLD_GLOBALACTIONS_HPP - -// This function creates a global action that distributes a tool -// to agents with probability p. -/** - * @brief Global action that distributes a tool to agents with probability p. - * - * @tparam TSeq Sequence type (should match `TSeq` across the model) - * @param p Probability of distributing the tool. - * @param tool Tool function. - * @return std::function*)> - */ -template -inline std::function*)> globalaction_tool( - Tool & tool, - double p -) { - - std::function*)> fun = [p,&tool]( - Model * model - ) -> void { - - for (auto & agent : model->get_agents()) - { - - // Check if the agent has the tool - if (agent.has_tool(tool)) - continue; - - // Adding the tool - if (model->runif() < p) - agent.add_tool(tool, model); - - - } - - #ifdef EPIWORLD_DEBUG - tool.print(); - #endif - - return; - - - }; - - return fun; - -} - -// Same function as above, but p is now a function of a vector of coefficients -// and a vector of variables. -/** - * @brief Global action that distributes a tool to agents with probability - * p = 1 / (1 + exp(-\sum_i coef_i * agent(vars_i))). - * - * @tparam TSeq Sequence type (should match `TSeq` across the model) - * @param coefs Vector of coefficients. - * @param vars Vector of variables. - * @param tool_fun Tool function. - * @return std::function*)> - */ -template -inline std::function*)> globalaction_tool_logit( - Tool & tool, - std::vector< size_t > vars, - std::vector< double > coefs -) { - - std::function*)> fun = [coefs,vars,&tool]( - Model * model - ) -> void { - - for (auto & agent : model->get_agents()) - { - - // Check if the agent has the tool - if (agent.has_tool(tool)) - continue; - - // Computing the probability using a logit. Uses OpenMP reduction - // to sum the coefficients. - double p = 0.0; - #pragma omp parallel for reduction(+:p) - for (size_t i = 0u; i < coefs.size(); ++i) - p += coefs.at(i) * agent(vars[i]); - - p = 1.0 / (1.0 + std::exp(-p)); - - // Adding the tool - if (model->runif() < p) - agent.add_tool(tool, model); - - - } - - #ifdef EPIWORLD_DEBUG - tool.print(); - #endif - - return; - - - }; - - return fun; - -} - -// A global action that updates a parameter in the model. -/** - * @brief Global action that updates a parameter in the model. - * - * @tparam TSeq Sequence type (should match `TSeq` across the model) - * @param param Parameter to update. - * @param value Value to update the parameter to. - * @return std::function*)> - */ -template -inline std::function*)> globalaction_set_param( - std::string param, - double value -) { - - std::function*)> fun = [value,param]( - Model * model - ) -> void { - - model->set_param(param, value); - - return; - - - }; - - return fun; - -} -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/globalactions.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sis.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SIS_HPP -#define EPIWORLD_MODELS_SIS_HPP - -/** - * @brief Template for a Susceptible-Infected-Susceptible (SIS) model - * - * @param vname std::string Name of the virus - * @param initial_prevalence epiworld_double Initial prevalence - * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system - * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system - */ -template -class ModelSIS : public epiworld::Model -{ - -public: - - ModelSIS() {}; - - ModelSIS( - ModelSIS & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - - ModelSIS( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - -}; - -template -inline ModelSIS::ModelSIS( - ModelSIS & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ) -{ - - model.set_name("Susceptible-Infected-Susceptible (SIS)"); - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Infected", epiworld::default_update_exposed); - - // Setting up parameters - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(recovery_rate, "Recovery rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1,0,0); - - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_recovery(&model("Recovery rate")); - virus.set_prob_death(0.0); - - model.add_virus(virus, prevalence); - - return; - -} - -template -inline ModelSIS::ModelSIS( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ) -{ - - ModelSIS( - *this, - vname, - prevalence, - transmission_rate, - recovery_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sis.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sir.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_SIR_H -#define EPIWORLD_SIR_H - -/** - * @brief Template for a Susceptible-Infected-Removed (SIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param initial_prevalence epiworld_double Initial prevalence - * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system - * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system - */ -template -class ModelSIR : public epiworld::Model -{ -public: - - ModelSIR() {}; - - ModelSIR( - ModelSIR & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - - ModelSIR( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - -}; - -template -inline ModelSIR::ModelSIR( - ModelSIR & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ) -{ - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Infected", epiworld::default_update_exposed); - model.add_state("Recovered"); - - // Setting up parameters - model.add_param(recovery_rate, "Recovery rate"); - model.add_param(transmission_rate, "Transmission rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1,2,2); - - virus.set_prob_recovery(&model("Recovery rate")); - virus.set_prob_infecting(&model("Transmission rate")); - - model.add_virus(virus, prevalence); - - model.set_name("Susceptible-Infected-Recovered (SIR)"); - - return; - -} - -template -inline ModelSIR::ModelSIR( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ) -{ - - ModelSIR( - *this, - vname, - prevalence, - transmission_rate, - recovery_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sir.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/seir.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SEIR_HPP -#define EPIWORLD_MODELS_SEIR_HPP - -/** - * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence epiworld_double Initial prevalence the immune system - * @param transmission_rate epiworld_double Transmission rate of the virus - * @param avg_incubation_days epiworld_double Average incubation days of the virus. - * @param recovery_rate epiworld_double Recovery rate of the virus. - */ -template -class ModelSEIR : public epiworld::Model -{ -private: - static const int SUSCEPTIBLE = 0; - static const int EXPOSED = 1; - static const int INFECTED = 2; - static const int REMOVED = 3; - -public: - - ModelSEIR() {}; - - ModelSEIR( - ModelSEIR & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ); - - ModelSEIR( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ); - - epiworld::UpdateFun update_exposed_seir = []( - epiworld::Agent * p, - epiworld::Model * m - ) -> void { - - // Getting the virus - auto v = p->get_virus(0); - - // Does the agent become infected? - if (m->runif() < 1.0/(v->get_incubation(m))) - p->change_state(m, ModelSEIR::INFECTED); - - return; - }; - - - epiworld::UpdateFun update_infected_seir = []( - epiworld::Agent * p, - epiworld::Model * m - ) -> void { - // Does the agent recover? - if (m->runif() < (m->par("Recovery rate"))) - p->rm_virus(0, m); - - return; - }; - -}; - - -template -inline ModelSEIR::ModelSEIR( - ModelSEIR & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ) -{ - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Exposed", model.update_exposed_seir); - model.add_state("Infected", model.update_infected_seir); - model.add_state("Removed"); - - // Setting up parameters - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(avg_incubation_days, "Incubation days"); - model.add_param(recovery_rate, "Recovery rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(ModelSEIR::EXPOSED, ModelSEIR::REMOVED, ModelSEIR::REMOVED); - - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_incubation(&model("Incubation days")); - - // Adding the tool and the virus - model.add_virus(virus, prevalence); - - model.set_name("Susceptible-Exposed-Infected-Removed (SEIR)"); - - return; - -} - -template -inline ModelSEIR::ModelSEIR( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ) -{ - - ModelSEIR( - *this, - vname, - prevalence, - transmission_rate, - avg_incubation_days, - recovery_rate - ); - - return; - -} - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/seir.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/surveillance.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SURVEILLANCE_HPP -#define EPIWORLD_MODELS_SURVEILLANCE_HPP - -template -class ModelSURV : public epiworld::Model { - -private: - // state - static const int SUSCEPTIBLE = 0; - static const int LATENT = 1; - static const int SYMPTOMATIC = 2; - static const int SYMPTOMATIC_ISOLATED = 3; // sampled and discovered - static const int ASYMPTOMATIC = 4; - static const int ASYMPTOMATIC_ISOLATED = 5; - static const int RECOVERED = 6; - static const int REMOVED = 7; - -public: - - /** - * @name Construct a new ModelSURV object - * - * The ModelSURV class simulates a survaillence model where agents can be - * isolated, even if asyptomatic. - * - * @param vname String. Name of the virus - * @param prevalence Integer. Number of initial cases of the virus. - * @param efficacy_vax Double. Efficacy of the vaccine (1 - P(acquire the disease)). - * @param latent_period Double. Shape parameter of a `Gamma(latent_period, 1)` - * distribution. This coincides with the expected number of latent days. - * @param infect_period Double. Shape parameter of a `Gamma(infected_period, 1)` - * distribution. This coincides with the expected number of infectious days. - * @param prob_symptoms Double. Probability of generating symptoms. - * @param prop_vaccinated Double. Probability of vaccination. Coincides with - * the initial prevalence of vaccinated individuals. - * @param prop_vax_redux_transm Double. Factor by which the vaccine reduces - * transmissibility. - * @param prop_vax_redux_infect Double. Factor by which the vaccine reduces - * the chances of becoming infected. - * @param surveillance_prob Double. Probability of testing an agent. - * @param prob_transmission Double. Raw transmission probability. - * @param prob_death Double. Raw probability of death for symptomatic individuals. - * @param prob_noreinfect Double. Probability of no re-infection. - * - * @details - * This model features the following states: - * - * - Susceptible - * - Latent - * - Symptomatic - * - Symptomatic isolated - * - Asymptomatic - * - Asymptomatic isolated - * - Recovered - * - Removed - * - * @returns An object of class `epiworld_surv` - * - */ - ///@{ - ModelSURV() {}; - - ModelSURV( - ModelSURV & model, - std::string vname, - epiworld_fast_uint prevalence = 50, - epiworld_double efficacy_vax = 0.9, - epiworld_double latent_period = 3u, - epiworld_double infect_period = 6u, - epiworld_double prob_symptoms = 0.6, - epiworld_double prop_vaccinated = 0.25, - epiworld_double prop_vax_redux_transm = 0.5, - epiworld_double prop_vax_redux_infect = 0.5, - epiworld_double surveillance_prob = 0.001, - epiworld_double prob_transmission = 1.0, - epiworld_double prob_death = 0.001, - epiworld_double prob_noreinfect = 0.9 - ); - - ModelSURV( - std::string vname, - epiworld_fast_uint prevalence = 50, - epiworld_double efficacy_vax = 0.9, - epiworld_double latent_period = 3u, - epiworld_double infect_period = 6u, - epiworld_double prob_symptoms = 0.6, - epiworld_double prop_vaccinated = 0.25, - epiworld_double prop_vax_redux_transm = 0.5, - epiworld_double prop_vax_redux_infect = 0.5, - epiworld_double surveillance_prob = 0.001, - epiworld_double prob_transmission = 1.0, - epiworld_double prob_death = 0.001, - epiworld_double prob_noreinfect = 0.9 - ); - ///@} - -}; - -template -inline ModelSURV::ModelSURV( - ModelSURV & model, - std::string vname, - epiworld_fast_uint prevalence, - epiworld_double efficacy_vax, - epiworld_double latent_period, - epiworld_double infect_period, - epiworld_double prob_symptoms, - epiworld_double prop_vaccinated, - epiworld_double prop_vax_redux_transm, - epiworld_double prop_vax_redux_infect, - epiworld_double surveillance_prob, - epiworld_double prob_transmission, - epiworld_double prob_death, - epiworld_double prob_noreinfect - ) -{ - - EPI_NEW_UPDATEFUN_LAMBDA(surveillance_update_susceptible, TSeq) { - - // This computes the prob of getting any neighbor variant - epiworld_fast_uint nviruses_tmp = 0u; - for (auto & neighbor: p->get_neighbors()) - { - - for (size_t i = 0u; i < neighbor->get_n_viruses(); ++i) - { - - auto & v = neighbor->get_virus(i); - - /* And it is a function of susceptibility_reduction as well */ - epiworld_double tmp_transmission = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) - ; - - m->array_double_tmp[nviruses_tmp] = tmp_transmission; - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - } - - // No virus to compute on - if (nviruses_tmp == 0) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - return; - - }; - - - epiworld::UpdateFun surveillance_update_exposed = - [](epiworld::Agent * p, epiworld::Model * m) -> void - { - - epiworld::VirusPtr & v = p->get_virus(0u); - epiworld_double p_die = v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); - - epiworld_fast_uint days_since_exposed = m->today() - v->get_date(); - epiworld_fast_uint state = p->get_state(); - - // Figuring out latent period - if (v->get_data().size() == 0u) - { - epiworld_double latent_days = m->rgamma(m->par("Latent period"), 1.0); - v->get_data().push_back(latent_days); - - v->get_data().push_back( - m->rgamma(m->par("Infect period"), 1.0) + latent_days - ); - } - - // If still latent, nothing happens - if (days_since_exposed <= v->get_data()[0u]) - return; - - // If past days infected + latent, then bye. - if (days_since_exposed >= v->get_data()[1u]) - { - p->rm_virus(0, m); - return; - } - - // If it is infected, then it can be asymptomatic or symptomatic - if (state == ModelSURV::LATENT) - { - - // Will be symptomatic? - if (EPI_RUNIF() < m->par("Prob of symptoms")) - p->change_state(m, ModelSURV::SYMPTOMATIC); - else - p->change_state(m, ModelSURV::ASYMPTOMATIC); - - return; - - } - - // Otherwise, it can be removed - if (EPI_RUNIF() < p_die) - { - p->change_state(m, ModelSURV::REMOVED, -1); - return; - } - - return; - - }; - - std::vector< epiworld_fast_uint > exposed_state = { - SYMPTOMATIC, - SYMPTOMATIC_ISOLATED, - ASYMPTOMATIC, - ASYMPTOMATIC_ISOLATED, - LATENT - }; - - epiworld::GlobalFun surveillance_program = - [exposed_state]( - epiworld::Model* m - ) -> void - { - - // How many will we find - std::binomial_distribution<> bdist(m->size(), m->par("Surveilance prob.")); - int nsampled = bdist(m->get_rand_endgine()); - - int to_go = nsampled + 1; - - epiworld_double ndetected = 0.0; - epiworld_double ndetected_asympt = 0.0; - - auto & pop = m->get_agents(); - std::vector< bool > sampled(m->size(), false); - - while (to_go-- > 0) - { - - // Who is the lucky one - epiworld_fast_uint i = static_cast(std::floor(EPI_RUNIF() * m->size())); - - if (sampled[i]) - continue; - - sampled[i] = true; - epiworld::Agent * p = &pop[i]; - - // If still exposed for the next term - if (epiworld::IN(p->get_state(), exposed_state )) - { - - ndetected += 1.0; - if (p->get_state() == ModelSURV::ASYMPTOMATIC) - { - ndetected_asympt += 1.0; - p->change_state(m, ModelSURV::ASYMPTOMATIC_ISOLATED); - } - else - { - p->change_state(m, ModelSURV::SYMPTOMATIC_ISOLATED); - } - - } - - } - - // Writing the user data - std::vector< int > totals; - m->get_db().get_today_total(nullptr,&totals); - m->add_user_data( - { - static_cast(nsampled), - ndetected, - ndetected_asympt, - static_cast(totals[ModelSURV::ASYMPTOMATIC]) - } - ); - - - }; - - model.add_state("Susceptible", surveillance_update_susceptible); - model.add_state("Latent", surveillance_update_exposed); - model.add_state("Symptomatic", surveillance_update_exposed); - model.add_state("Symptomatic isolated", surveillance_update_exposed); - model.add_state("Asymptomatic", surveillance_update_exposed); - model.add_state("Asymptomatic isolated", surveillance_update_exposed); - model.add_state("Recovered"); - model.add_state("Removed"); - - // General model parameters - model.add_param(latent_period, "Latent period"); - model.add_param(infect_period, "Infect period"); - model.add_param(prob_symptoms, "Prob of symptoms"); - model.add_param(surveillance_prob, "Surveilance prob."); - model.add_param(efficacy_vax, "Vax efficacy"); - model.add_param(prop_vax_redux_transm, "Vax redux transmission"); - model.add_param(prob_transmission, "Prob of transmission"); - model.add_param(prob_death, "Prob. death"); - model.add_param(prob_noreinfect, "Prob. no reinfect"); - - // Virus ------------------------------------------------------------------ - epiworld::Virus covid("Covid19"); - covid.set_state(LATENT, RECOVERED, REMOVED); - covid.set_post_immunity(&model("Prob. no reinfect")); - covid.set_prob_death(&model("Prob. death")); - - epiworld::VirusFun ptransmitfun = []( - epiworld::Agent * p, - epiworld::Virus & v, - epiworld::Model * m - ) -> epiworld_double - { - // No chance of infecting - epiworld_fast_uint s = p->get_state(); - if (s == ModelSURV::LATENT) - return static_cast(0.0); - else if (s == ModelSURV::SYMPTOMATIC_ISOLATED) - return static_cast(0.0); - else if (s == ModelSURV::ASYMPTOMATIC_ISOLATED) - return static_cast(0.0); - - // Otherwise - return m->par("Prob of transmission"); - }; - - covid.set_prob_infecting_fun(ptransmitfun); - - model.add_virus_n(covid, prevalence); - - model.set_user_data({"nsampled", "ndetected", "ndetected_asympt", "nasymptomatic"}); - model.add_global_action(surveillance_program, "Surveilance program", -1); - - // Vaccine tool ----------------------------------------------------------- - epiworld::Tool vax("Vaccine"); - vax.set_susceptibility_reduction(&model("Vax efficacy")); - vax.set_transmission_reduction(&model("Vax redux transmission")); - - model.add_tool(vax, prop_vaccinated); - - model.set_name("Surveillance"); - - return; - -} - -template -inline ModelSURV::ModelSURV( - std::string vname, - epiworld_fast_uint prevalence, - epiworld_double efficacy_vax, - epiworld_double latent_period, - epiworld_double infect_period, - epiworld_double prob_symptoms, - epiworld_double prop_vaccinated, - epiworld_double prop_vax_redux_transm, - epiworld_double prop_vax_redux_infect, - epiworld_double surveillance_prob, - epiworld_double prob_transmission, - epiworld_double prob_death, - epiworld_double prob_noreinfect - ) -{ - - ModelSURV( - *this, - vname, - prevalence, - efficacy_vax, - latent_period, - infect_period, - prob_symptoms, - prop_vaccinated, - prop_vax_redux_transm, - prop_vax_redux_infect, - surveillance_prob, - prob_transmission, - prob_death, - prob_noreinfect - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/surveillance.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sirconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SIRCONNECTED_HPP -#define EPIWORLD_MODELS_SIRCONNECTED_HPP - -template -class ModelSIRCONN : public epiworld::Model -{ -private: - static const int SUSCEPTIBLE = 0; - static const int INFECTED = 1; - static const int RECOVERED = 2; - -public: - - ModelSIRCONN() { - - // tracked_agents_infected.reserve(1e4); - // tracked_agents_infected_next.reserve(1e4); - - }; - - ModelSIRCONN( - ModelSIRCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - - ModelSIRCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ); - - // Tracking who is infected and who is not - // std::vector< epiworld::Agent* > tracked_agents_infected = {}; - // std::vector< epiworld::Agent* > tracked_agents_infected_next = {}; - // std::vector< epiworld_double > tracked_agents_weight = {}; - // std::vector< epiworld_double > tracked_agents_weight_next = {}; - - // int tracked_ninfected = 0; - // int tracked_ninfected_next = 0; - // epiworld_double tracked_current_infect_prob = 0.0; - - void run( - epiworld_fast_uint ndays, - int seed = -1 - ); - - void reset(); - - Model * clone_ptr(); - - -}; - -template -inline void ModelSIRCONN::run( - epiworld_fast_uint ndays, - int seed -) -{ - - // tracked_agents_infected.clear(); - // tracked_agents_infected_next.clear(); - - // tracked_ninfected = 0; - // tracked_ninfected_next = 0; - // tracked_current_infect_prob = 0.0; - - Model::run(ndays, seed); - -} - -template -inline void ModelSIRCONN::reset() -{ - - Model::reset(); - - // Model::set_rand_binom( - // Model::size(), - // static_cast( - // Model::par("Contact rate"))/ - // static_cast(Model::size()) - // ); - - return; - -} - -template -inline Model * ModelSIRCONN::clone_ptr() -{ - - ModelSIRCONN * ptr = new ModelSIRCONN( - *dynamic_cast*>(this) - ); - - return dynamic_cast< Model *>(ptr); - -} - -/** - * @brief Template for a Susceptible-Infected-Removed (SIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence Initial prevalence (proportion) - * @param contact_rate Average number of contacts (interactions) per step. - * @param transmission_rate Probability of transmission - * @param recovery_rate Probability of recovery - */ -template -inline ModelSIRCONN::ModelSIRCONN( - ModelSIRCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate - // epiworld_double prob_reinfection - ) -{ - - - - epiworld::UpdateFun update_susceptible = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Sampling how many individuals - m->set_rand_binom( - m->size(), - static_cast( - m->par("Contact rate"))/ - static_cast(m->size()) - ); - - int ndraw = m->rbinom(); - - if (ndraw == 0) - return; - - // Drawing from the set - int nviruses_tmp = 0; - for (int i = 0; i < ndraw; ++i) - { - // Now selecting who is transmitting the disease - int which = static_cast( - std::floor(m->size() * m->runif()) - ); - - /* There is a bug in which runif() returns 1.0. It is rare, but - * we saw it here. See the Notes section in the C++ manual - * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 - * And the reported bug in GCC: - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 - * - */ - if (which == static_cast(m->size())) - --which; - - // Can't sample itself - if (which == static_cast(p->get_id())) - continue; - - // If the neighbor is infected, then proceed - auto & neighbor = m->get_agents()[which]; - if (neighbor.get_state() == ModelSIRCONN::INFECTED) - { - - for (const VirusPtr & v : neighbor.get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor.get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - - return; - - }; - - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - auto state = p->get_state(); - - if (state == ModelSIRCONN::INFECTED) - { - - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - - #ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } - #else - if (n_events == 0u) - return; - #endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - return ; - - } else - throw std::logic_error("This function can only be applied to infected individuals. (SIR)") ; - - return; - - }; - - // state - model.add_state("Susceptible", update_susceptible); - model.add_state("Infected", update_infected); - model.add_state("Recovered"); - - // Setting up parameters - model.add_param(contact_rate, "Contact rate"); - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(recovery_rate, "Recovery rate"); - // model.add_param(prob_reinfection, "Prob. Reinfection"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1, 2, 2); - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_recovery(&model("Recovery rate")); - - model.add_virus(virus, prevalence); - - model.queuing_off(); // No queuing need - - model.agents_empty_graph(n); - - model.set_name("Susceptible-Infected-Removed (SIR) (connected)"); - - return; - -} - -template -inline ModelSIRCONN::ModelSIRCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate - ) -{ - - ModelSIRCONN( - *this, - vname, - n, - prevalence, - contact_rate, - transmission_rate, - recovery_rate - ); - - return; - -} - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sirconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/seirconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SEIRCONNECTED_HPP -#define EPIWORLD_MODELS_SEIRCONNECTED_HPP - -template -class ModelSEIRCONN : public epiworld::Model -{ -public: - - static const int SUSCEPTIBLE = 0; - static const int EXPOSED = 1; - static const int INFECTED = 2; - static const int RECOVERED = 3; - - - ModelSEIRCONN() {}; - - ModelSEIRCONN( - ModelSEIRCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ); - - ModelSEIRCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ); - - void run( - epiworld_fast_uint ndays, - int seed = -1 - ); - - void reset(); - - Model * clone_ptr(); - -}; - -template -inline void ModelSEIRCONN::run( - epiworld_fast_uint ndays, - int seed -) -{ - - Model::run(ndays, seed); - -} - -template -inline void ModelSEIRCONN::reset() -{ - - Model::reset(); - - Model::set_rand_binom( - Model::size(), - static_cast( - Model::par("Contact rate"))/ - static_cast(Model::size()) - ); - - return; - -} - -template -inline Model * ModelSEIRCONN::clone_ptr() -{ - - ModelSEIRCONN * ptr = new ModelSEIRCONN( - *dynamic_cast*>(this) - ); - - return dynamic_cast< Model *>(ptr); - -} - -/** - * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence Initial prevalence (proportion) - * @param contact_rate Average number of contacts (interactions) per step. - * @param transmission_rate Probability of transmission - * @param recovery_rate Probability of recovery - */ -template -inline ModelSEIRCONN::ModelSEIRCONN( - ModelSEIRCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - // epiworld_double prob_reinfection - ) -{ - - epiworld::UpdateFun update_susceptible = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Sampling how many individuals - int ndraw = m->rbinom(); - - if (ndraw == 0) - return; - - // Drawing from the set - int nviruses_tmp = 0; - for (int i = 0; i < ndraw; ++i) - { - // Now selecting who is transmitting the disease - int which = static_cast( - std::floor(m->size() * m->runif()) - ); - - /* There is a bug in which runif() returns 1.0. It is rare, but - * we saw it here. See the Notes section in the C++ manual - * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 - * And the reported bug in GCC: - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 - * - */ - if (which == static_cast(m->size())) - --which; - - // Can't sample itself - if (which == static_cast(p->get_id())) - continue; - - // If the neighbor is infected, then proceed - auto & neighbor = m->get_agents()[which]; - if (neighbor.get_state() == ModelSEIRCONN::INFECTED) - { - - for (const VirusPtr & v : neighbor.get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor.get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus( - *m->array_virus_tmp[which], - m, - ModelSEIRCONN::EXPOSED - ); - - return; - - }; - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - auto state = p->get_state(); - - if (state == ModelSEIRCONN::EXPOSED) - { - - // Getting the virus - auto & v = p->get_virus(0u); - - // Does the agent become infected? - if (m->runif() < 1.0/(v->get_incubation(m))) - { - - p->change_state(m, ModelSEIRCONN::INFECTED); - return; - - } - - - } else if (state == ModelSEIRCONN::INFECTED) - { - - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - - #ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } - #else - if (n_events == 0u) - return; - #endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - return ; - - } else - throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIR)") ; - - return; - - }; - - // Setting up parameters - model.add_param(contact_rate, "Contact rate"); - model.add_param(transmission_rate, "Prob. Transmission"); - model.add_param(recovery_rate, "Prob. Recovery"); - model.add_param(avg_incubation_days, "Avg. Incubation days"); - - // state - model.add_state("Susceptible", update_susceptible); - model.add_state("Exposed", update_infected); - model.add_state("Infected", update_infected); - model.add_state("Recovered"); - - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state( - ModelSEIRCONN::EXPOSED, - ModelSEIRCONN::RECOVERED, - ModelSEIRCONN::RECOVERED - ); - - virus.set_prob_infecting(&model("Prob. Transmission")); - virus.set_prob_recovery(&model("Prob. Recovery")); - virus.set_incubation(&model("Avg. Incubation days")); - - model.add_virus(virus, prevalence); - - model.queuing_off(); // No queuing need - - // Adding the empty population - model.agents_empty_graph(n); - - model.set_name("Susceptible-Exposed-Infected-Removed (SEIR) (connected)"); - - return; - -} - -template -inline ModelSEIRCONN::ModelSEIRCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate - ) -{ - - ModelSEIRCONN( - *this, - vname, - n, - prevalence, - contact_rate, - transmission_rate, - avg_incubation_days, - recovery_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/seirconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sird.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_SIRD_H -#define EPIWORLD_SIRD_H - -/** - * @brief Template for a Susceptible-Infected-Removed-Deceased (SIRD) model - * - * @param model A Model object where to set up the SIRD. - * @param vname std::string Name of the virus - * @param initial_prevalence epiworld_double Initial prevalence - * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system - * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system - * @param initial_death epiworld_double Initial death_rate of the immune system - */ -template -class ModelSIRD : public epiworld::Model -{ -public: - - ModelSIRD() {}; - - ModelSIRD( - ModelSIRD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - ModelSIRD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - -}; - -template -inline ModelSIRD::ModelSIRD( - ModelSIRD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Infected", epiworld::default_update_exposed); - model.add_state("Recovered"), - model.add_state("Deceased") - ; - - // Setting up parameters - model.add_param(recovery_rate, "Recovery rate"); - model.add_param(transmission_rate, "Transmission rate"), - model.add_param(death_rate, "Death rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1,2,3); - virus.set_prob_recovery(&model("Recovery rate")); - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_death(&model("Death rate")); - - model.add_virus(virus, prevalence); - - model.set_name("Susceptible-Infected-Recovered-Deceased (SIRD)"); - - return; - -} - -template -inline ModelSIRD::ModelSIRD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - ModelSIRD( - *this, - vname, - prevalence, - transmission_rate, - recovery_rate, - death_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sird.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sisd.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SISD_HPP -#define EPIWORLD_MODELS_SISD_HPP - -/** - * @brief Template for a Susceptible-Infected-Susceptible-Deceased (SISD) model - * - * @param vname std::string Name of the virus - * @param initial_prevalence epiworld_double Initial prevalence - * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system - * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system - * @param inital_death epiworld_double Initial death_rate of the immune system - */ -template -class ModelSISD : public epiworld::Model -{ - -public: - - ModelSISD() {}; - - ModelSISD( - ModelSISD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - ModelSISD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - -}; - -template -inline ModelSISD::ModelSISD( - ModelSISD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - model.set_name("Susceptible-Infected-Susceptible-Deceased (SISD)"); - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Infected", epiworld::default_update_exposed); - model.add_state("Deceased"); - - // Setting up parameters - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(recovery_rate, "Recovery rate"); - model.add_param(death_rate, "Death rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1,0,2); - - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_recovery(&model("Recovery rate")); - virus.set_prob_death(0.01); - - model.add_virus(virus, prevalence); - - return; - -} - -template -inline ModelSISD::ModelSISD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - ModelSISD( - *this, - vname, - prevalence, - transmission_rate, - recovery_rate, - death_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sisd.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/seird.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SEIRD_HPP -#define EPIWORLD_MODELS_SEIRD_HPP - -/** - * @brief Template for a Susceptible-Exposed-Infected-Removed-Deceased (SEIRD) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence epiworld_double Initial prevalence the immune system - * @param transmission_rate epiworld_double Transmission rate of the virus - * @param avg_incubation_days epiworld_double Average incubation days of the virus - * @param recovery_rate epiworld_double Recovery rate of the virus. - * @param death_rate epiworld_double Death rate of the virus. - */ -template -class ModelSEIRD : public epiworld::Model -{ -private: - static const int SUSCEPTIBLE = 0; - static const int EXPOSED = 1; - static const int INFECTED = 2; - static const int REMOVED = 3; - static const int DECEASED = 4; - -public: - - ModelSEIRD() {}; - - ModelSEIRD( - ModelSEIRD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - ModelSEIRD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - epiworld::UpdateFun update_exposed_seir = []( - epiworld::Agent * p, - epiworld::Model * m - ) -> void { - - // Getting the virus - auto v = p->get_virus(0); - - // Does the agent become infected? - if (m->runif() < 1.0/(v->get_incubation(m))) - p->change_state(m, ModelSEIRD::INFECTED); - - return; - }; - - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - auto state = p->get_state(); - - if (state == ModelSEIRD::INFECTED) - { - - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Die - m->array_double_tmp[n_events++] = - v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - -#ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } -#else - if (n_events == 0u) - return; -#endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - if ((which % 2) == 0) // If odd - { - - size_t which_v = std::ceil(which / 2); - p->rm_agent_by_virus(which_v, m); - - } else { - - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - } - - return ; - - } else - throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIRD)") ; - - return; - - }; -}; - - - -template -inline ModelSEIRD::ModelSEIRD( - ModelSEIRD & model, - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate -) -{ - - // Adding statuses - model.add_state("Susceptible", epiworld::default_update_susceptible); - model.add_state("Exposed", model.update_exposed_seir); - model.add_state("Infected", model.update_infected); - model.add_state("Removed"); - model.add_state("Deceased"); - - // Setting up parameters - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(avg_incubation_days, "Incubation days"); - model.add_param(recovery_rate, "Recovery rate"); - model.add_param(death_rate, "Death rate"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(ModelSEIRD::EXPOSED, ModelSEIRD::REMOVED, ModelSEIRD::DECEASED); - - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_incubation(&model("Incubation days")); - virus.set_prob_death(&model("Death rate")); - - // Adding the tool and the virus - model.add_virus(virus, prevalence); - - model.set_name("Susceptible-Exposed-Infected-Removed-Deceased (SEIRD)"); - - return; - -} - -template -inline ModelSEIRD::ModelSEIRD( - std::string vname, - epiworld_double prevalence, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate -) -{ - - ModelSEIRD( - *this, - vname, - prevalence, - transmission_rate, - avg_incubation_days, - recovery_rate, - death_rate - ); - - return; - -} - - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/seird.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sirdconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SIRDCONNECTED_HPP -#define EPIWORLD_MODELS_SIRDCONNECTED_HPP - -template -class ModelSIRDCONN : public epiworld::Model -{ -private: - static const int SUSCEPTIBLE = 0; - static const int INFECTED = 1; - static const int RECOVERED = 2; - static const int DECEASED = 3; - -public: - - ModelSIRDCONN() { - - // tracked_agents_infected.reserve(1e4); - // tracked_agents_infected_next.reserve(1e4); - - }; - - ModelSIRDCONN( - ModelSIRDCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - ModelSIRDCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - // Tracking who is infected and who is not - // std::vector< epiworld::Agent* > tracked_agents_infected = {}; - // std::vector< epiworld::Agent* > tracked_agents_infected_next = {}; - // std::vector< epiworld_double > tracked_agents_weight = {}; - // std::vector< epiworld_double > tracked_agents_weight_next = {}; - - // int tracked_ninfected = 0; - // int tracked_ninfected_next = 0; - // epiworld_double tracked_current_infect_prob = 0.0; - - void run( - epiworld_fast_uint ndays, - int seed = -1 - ); - - void reset(); - - Model * clone_ptr(); - - -}; - -template -inline void ModelSIRDCONN::run( - epiworld_fast_uint ndays, - int seed -) -{ - - // tracked_agents_infected.clear(); - // tracked_agents_infected_next.clear(); - - // tracked_ninfected = 0; - // tracked_ninfected_next = 0; - // tracked_current_infect_prob = 0.0; - - Model::run(ndays, seed); - -} - -template -inline void ModelSIRDCONN::reset() -{ - - Model::reset(); - - // Model::set_rand_binom( - // Model::size(), - // static_cast( - // Model::par("Contact rate"))/ - // static_cast(Model::size()) - // ); - - return; - -} - -template -inline Model * ModelSIRDCONN::clone_ptr() -{ - - ModelSIRDCONN * ptr = new ModelSIRDCONN( - *dynamic_cast*>(this) - ); - - return dynamic_cast< Model *>(ptr); - -} - -/** - * @brief Template for a Susceptible-Infected-Removed (SIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence Initial prevalence (proportion) - * @param contact_rate Average number of contacts (interactions) per step. - * @param transmission_rate Probability of transmission - * @param recovery_rate Probability of recovery - * @param death_rate Probability of death - */ -template -inline ModelSIRDCONN::ModelSIRDCONN( - ModelSIRDCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - // epiworld_double prob_reinfection - ) -{ - - - - epiworld::UpdateFun update_susceptible = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Sampling how many individuals - m->set_rand_binom( - m->size(), - static_cast( - m->par("Contact rate"))/ - static_cast(m->size()) - ); - - int ndraw = m->rbinom(); - - if (ndraw == 0) - return; - - // Drawing from the set - int nviruses_tmp = 0; - for (int i = 0; i < ndraw; ++i) - { - // Now selecting who is transmitting the disease - int which = static_cast( - std::floor(m->size() * m->runif()) - ); - - /* There is a bug in which runif() returns 1.0. It is rare, but - * we saw it here. See the Notes section in the C++ manual - * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 - * And the reported bug in GCC: - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 - * - */ - if (which == static_cast(m->size())) - --which; - - // Can't sample itself - if (which == static_cast(p->get_id())) - continue; - - // If the neighbor is infected, then proceed - auto & neighbor = m->get_agents()[which]; - if (neighbor.get_state() == ModelSIRDCONN::INFECTED) - { - - for (const VirusPtr & v : neighbor.get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor.get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - - return; - - }; - - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - auto state = p->get_state(); - - if (state == ModelSIRDCONN::INFECTED) - { - - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Die - m->array_double_tmp[n_events++] = - v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - -#ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } -#else - if (n_events == 0u) - return; -#endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - if ((which % 2) == 0) // If odd - { - - size_t which_v = std::ceil(which / 2); - p->rm_agent_by_virus(which_v, m); - - } else { - - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - } - - return ; - - } else - throw std::logic_error("This function can only be applied to infected individuals. (SIR)") ; - - return; - - }; - - // state - model.add_state("Susceptible", update_susceptible); - model.add_state("Infected", update_infected); - model.add_state("Recovered"); - model.add_state("Deceased"); - - - // Setting up parameters - model.add_param(contact_rate, "Contact rate"); - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(recovery_rate, "Recovery rate"); - model.add_param(death_rate, "Death rate"); - // model.add_param(prob_reinfection, "Prob. Reinfection"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state(1, 2, 3); - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_recovery(&model("Recovery rate")); - virus.set_prob_death(&model("Death rate")); - - model.add_virus(virus, prevalence); - - model.queuing_off(); // No queuing need - - model.agents_empty_graph(n); - - model.set_name("Susceptible-Infected-Removed-Deceased (SIRD) (connected)"); - - return; - -} - -template -inline ModelSIRDCONN::ModelSIRDCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - ModelSIRDCONN( - *this, - vname, - n, - prevalence, - contact_rate, - transmission_rate, - recovery_rate, - death_rate - ); - - return; - -} - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sirdconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/seirdconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_MODELS_SEIRDCONNECTED_HPP -#define EPIWORLD_MODELS_SEIRDCONNECTED_HPP - -template -class ModelSEIRDCONN : public epiworld::Model -{ -public: - - static const int SUSCEPTIBLE = 0; - static const int EXPOSED = 1; - static const int INFECTED = 2; - static const int REMOVED = 3; - static const int DECEASED = 4; - - - ModelSEIRDCONN() {}; - - ModelSEIRDCONN( - ModelSEIRDCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - ModelSEIRDCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - ); - - void run( - epiworld_fast_uint ndays, - int seed = -1 - ); - - void reset(); - - Model * clone_ptr(); - -}; - -template -inline void ModelSEIRDCONN::run( - epiworld_fast_uint ndays, - int seed -) -{ - - Model::run(ndays, seed); - -} - -template -inline void ModelSEIRDCONN::reset() -{ - - Model::reset(); - - Model::set_rand_binom( - Model::size(), - static_cast( - Model::par("Contact rate"))/ - static_cast(Model::size()) - ); - - return; - -} - -template -inline Model * ModelSEIRDCONN::clone_ptr() -{ - - ModelSEIRDCONN * ptr = new ModelSEIRDCONN( - *dynamic_cast*>(this) - ); - - return dynamic_cast< Model *>(ptr); - -} - -/** - * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence Initial prevalence (proportion) - * @param contact_rate Average number of contacts (interactions) per step. - * @param transmission_rate Probability of transmission - * @param recovery_rate Probability of recovery - * @param death_rate Probability of death - */ -template -inline ModelSEIRDCONN::ModelSEIRDCONN( - ModelSEIRDCONN & model, - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - // epiworld_double prob_reinfection - ) -{ - - epiworld::UpdateFun update_susceptible = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Sampling how many individuals - int ndraw = m->rbinom(); - - if (ndraw == 0) - return; - - // Drawing from the set - int nviruses_tmp = 0; - for (int i = 0; i < ndraw; ++i) - { - // Now selecting who is transmitting the disease - int which = static_cast( - std::floor(m->size() * m->runif()) - ); - - /* There is a bug in which runif() returns 1.0. It is rare, but - * we saw it here. See the Notes section in the C++ manual - * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 - * And the reported bug in GCC: - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 - * - */ - if (which == static_cast(m->size())) - --which; - - // Can't sample itself - if (which == static_cast(p->get_id())) - continue; - - // If the neighbor is infected, then proceed - auto & neighbor = m->get_agents()[which]; - if (neighbor.get_state() == ModelSEIRDCONN::INFECTED) - { - - for (const VirusPtr & v : neighbor.get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor.get_transmission_reduction(v, m)) - ; - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - - } - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus( - *m->array_virus_tmp[which], - m, - ModelSEIRDCONN::EXPOSED - ); - - return; - - }; - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - auto state = p->get_state(); - - if (state == ModelSEIRDCONN::EXPOSED) - { - - // Getting the virus - auto & v = p->get_virus(0u); - - // Does the agent become infected? - if (m->runif() < 1.0/(v->get_incubation(m))) - { - - p->change_state(m, ModelSEIRDCONN::INFECTED); - return; - - } - - - } else if (state == ModelSEIRDCONN::INFECTED) - { - - - // Odd: Die, Even: Recover - epiworld_fast_uint n_events = 0u; - for (const auto & v : p->get_viruses()) - { - - // Die - m->array_double_tmp[n_events++] = - v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); - - // Recover - m->array_double_tmp[n_events++] = - 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); - - } - - #ifdef EPI_DEBUG - if (n_events == 0u) - { - printf_epiworld( - "[epi-debug] agent %i has 0 possible events!!\n", - static_cast(p->get_id()) - ); - throw std::logic_error("Zero events in exposed."); - } - #else - if (n_events == 0u) - return; - #endif - - - // Running the roulette - int which = roulette(n_events, m); - - if (which < 0) - return; - - // Which roulette happen? - if ((which % 2) == 0) // If odd - { - - size_t which_v = std::ceil(which / 2); - p->rm_agent_by_virus(which_v, m); - - } else { - - size_t which_v = std::floor(which / 2); - p->rm_virus(which_v, m); - - } - - return ; - - } else - throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIRD)") ; - - return; - - }; - - // Setting up parameters - model.add_param(contact_rate, "Contact rate"); - model.add_param(transmission_rate, "Prob. Transmission"); - model.add_param(recovery_rate, "Prob. Recovery"); - model.add_param(avg_incubation_days, "Avg. Incubation days"); - model.add_param(death_rate, "Death rate"); - - // state - model.add_state("Susceptible", update_susceptible); - model.add_state("Exposed", update_infected); - model.add_state("Infected", update_infected); - model.add_state("Removed"); - model.add_state("Deceased"); - - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state( - ModelSEIRDCONN::EXPOSED, - ModelSEIRDCONN::REMOVED, - ModelSEIRDCONN::DECEASED - ); - - virus.set_prob_infecting(&model("Prob. Transmission")); - virus.set_prob_recovery(&model("Prob. Recovery")); - virus.set_incubation(&model("Avg. Incubation days")); - virus.set_prob_death(&model("Death rate")); - model.add_virus(virus, prevalence); - - model.queuing_off(); // No queuing need - - // Adding the empty population - model.agents_empty_graph(n); - - model.set_name("Susceptible-Exposed-Infected-Removed-Deceased (SEIRD) (connected)"); - - return; - -} - -template -inline ModelSEIRDCONN::ModelSEIRDCONN( - std::string vname, - epiworld_fast_uint n, - epiworld_double prevalence, - epiworld_double contact_rate, - epiworld_double transmission_rate, - epiworld_double avg_incubation_days, - epiworld_double recovery_rate, - epiworld_double death_rate - ) -{ - - ModelSEIRDCONN( - *this, - vname, - n, - prevalence, - contact_rate, - transmission_rate, - avg_incubation_days, - recovery_rate, - death_rate - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/seirdconnected.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/sirlogit.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -// #include "../epiworld.hpp" - -#ifndef EPIWORLD_MODELS_SIRLOGIT_HPP -#define EPIWORLD_MODELS_SIRLOGIT_HPP - -template -class ModelSIRLogit : public epiworld::Model -{ -private: - static const int SUSCEPTIBLE = 0; - static const int INFECTED = 1; - static const int RECOVERED = 2; - -public: - - ModelSIRLogit() {}; - - /** - * @param vname Name of the virus. - * @param coefs_infect Double ptr. Infection coefficients. - * @param coefs_recover Double ptr. Recovery coefficients. - * @param ncoef_infect Unsigned int. Number of infection coefficients. - * @param ncoef_recover Unsigned int. Number of recovery coefficients. - * @param coef_infect_cols Vector. Ids of infection vars. - * @param coef_recover_cols Vector. Ids of recover vars. - */ - ModelSIRLogit( - ModelSIRLogit & model, - std::string vname, - double * data, - size_t ncols, - std::vector< double > coefs_infect, - std::vector< double > coefs_recover, - std::vector< size_t > coef_infect_cols, - std::vector< size_t > coef_recover_cols, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double prevalence - ); - - ModelSIRLogit( - std::string vname, - double * data, - size_t ncols, - std::vector< double > coefs_infect, - std::vector< double > coefs_recover, - std::vector< size_t > coef_infect_cols, - std::vector< size_t > coef_recover_cols, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double prevalence - ); - - void run( - epiworld_fast_uint ndays, - int seed = -1 - ); - - Model * clone_ptr(); - - void reset(); - - std::vector< double > coefs_infect; - std::vector< double > coefs_recover; - std::vector< size_t > coef_infect_cols; - std::vector< size_t > coef_recover_cols; - -}; - - - -template -inline void ModelSIRLogit::run( - epiworld_fast_uint ndays, - int seed -) -{ - - Model::run(ndays, seed); - -} - -template -inline Model * ModelSIRLogit::clone_ptr() -{ - - ModelSIRLogit * ptr = new ModelSIRLogit( - *dynamic_cast*>(this) - ); - - return dynamic_cast< Model *>(ptr); - -} - -template -inline void ModelSIRLogit::reset() -{ - - /* Checking specified columns in the model */ - for (const auto & c : coef_infect_cols) - { - if (c >= Model::agents_data_ncols) - throw std::range_error("Columns specified in coef_infect_cols out of range."); - } - - for (const auto & c : coef_recover_cols) - { - if (c >= Model::agents_data_ncols) - throw std::range_error("Columns specified in coef_recover_cols out of range."); - } - - /* Checking attributes */ - if (coefs_infect.size() != (coef_infect_cols.size() + 1u)) - throw std::logic_error( - "The number of coefficients (infection) doesn't match the number of features. It must be as many features of the agents plus 1 (exposure.)" - ); - - if (coefs_recover.size() != coef_recover_cols.size()) - throw std::logic_error( - "The number of coefficients (recovery) doesn't match the number of features. It must be as many features of the agents." - ); - - Model::reset(); - - return; - -} - -/** - * @brief Template for a Susceptible-Infected-Removed (SIR) model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param prevalence Initial prevalence (proportion) - * @param contact_rate Average number of contacts (interactions) per step. - * @param prob_transmission Probability of transmission - * @param prob_recovery Probability of recovery - */ -template -inline ModelSIRLogit::ModelSIRLogit( - ModelSIRLogit & model, - std::string vname, - double * data, - size_t ncols, - std::vector< double > coefs_infect, - std::vector< double > coefs_recover, - std::vector< size_t > coef_infect_cols, - std::vector< size_t > coef_recover_cols, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double prevalence - ) -{ - - if (coef_infect_cols.size() == 0u) - throw std::logic_error("No columns specified for coef_infect_cols."); - - if (coef_recover_cols.size() == 0u) - throw std::logic_error("No columns specified for coef_recover_cols."); - - // Saving the variables - model.set_agents_data( - data, ncols - ); - - model.coefs_infect = coefs_infect; - model.coefs_recover = coefs_recover; - model.coef_infect_cols = coef_infect_cols; - model.coef_recover_cols = coef_recover_cols; - - epiworld::UpdateFun update_susceptible = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Getting the right type - ModelSIRLogit * _m = dynamic_cast*>(m); - - // Exposure coefficient - const double coef_exposure = _m->coefs_infect[0u]; - - // This computes the prob of getting any neighbor variant - size_t nviruses_tmp = 0u; - - double baseline = 0.0; - for (size_t k = 0u; k < _m->coef_infect_cols.size(); ++k) - baseline += p->operator[](k) * _m->coefs_infect[k + 1u]; - - for (auto & neighbor: p->get_neighbors()) - { - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - #ifdef EPI_DEBUG - if (nviruses_tmp >= m->array_virus_tmp.size()) - throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); - #endif - - /* And it is a function of susceptibility_reduction as well */ - m->array_double_tmp[nviruses_tmp] = - baseline + - (1.0 - p->get_susceptibility_reduction(v, m)) * - v->get_prob_infecting(m) * - (1.0 - neighbor->get_transmission_reduction(v, m)) * - coef_exposure - ; - - // Applying the plogis function - m->array_double_tmp[nviruses_tmp] = 1.0/ - (1.0 + std::exp(-m->array_double_tmp[nviruses_tmp])); - - m->array_virus_tmp[nviruses_tmp++] = &(*v); - - } - - } - - // No virus to compute - if (nviruses_tmp == 0u) - return; - - // Running the roulette - int which = roulette(nviruses_tmp, m); - - if (which < 0) - return; - - p->add_virus(*m->array_virus_tmp[which], m); - - return; - - }; - - epiworld::UpdateFun update_infected = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void - { - - // Getting the right type - ModelSIRLogit * _m = dynamic_cast*>(m); - - // Computing recovery probability once - double prob = 0.0; - #pragma omp simd reduction(+:prob) - for (size_t i = 0u; i < _m->coefs_recover.size(); ++i) - prob += p->operator[](i) * _m->coefs_recover[i]; - - // Computing logis - prob = 1.0/(1.0 + std::exp(-prob)); - - if (prob > m->runif()) - p->rm_virus(0, m); - - return; - - }; - - // state - model.add_state("Susceptible", update_susceptible); - model.add_state("Infected", update_infected); - model.add_state("Recovered"); - - // Setting up parameters - // model.add_param(contact_rate, "Contact rate"); - model.add_param(transmission_rate, "Transmission rate"); - model.add_param(recovery_rate, "Recovery rate"); - // model.add_param(prob_reinfection, "Prob. Reinfection"); - - // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); - virus.set_state( - ModelSIRLogit::INFECTED, - ModelSIRLogit::RECOVERED, - ModelSIRLogit::RECOVERED - ); - - virus.set_prob_infecting(&model("Transmission rate")); - virus.set_prob_recovery(&model("Recovery rate")); - - // virus.set_prob - - model.add_virus(virus, prevalence); - - model.set_name("Susceptible-Infected-Removed (SIR) (logit)"); - - return; - -} - -template -inline ModelSIRLogit::ModelSIRLogit( - std::string vname, - double * data, - size_t ncols, - std::vector< double > coefs_infect, - std::vector< double > coefs_recover, - std::vector< size_t > coef_infect_cols, - std::vector< size_t > coef_recover_cols, - epiworld_double transmission_rate, - epiworld_double recovery_rate, - epiworld_double prevalence - ) -{ - - ModelSIRLogit( - *this, - vname, - data, - ncols, - coefs_infect, - coefs_recover, - coef_infect_cols, - coef_recover_cols, - transmission_rate, - recovery_rate, - prevalence - ); - - return; - -} - - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/sirlogit.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - Start of -include/epiworld//models/diffnet.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - -#ifndef EPIWORLD_DIFFNET_H -#define EPIWORLD_DIFFNET_H - -/** - * @brief Template for a Network Diffusion Model - * - * @param model A Model object where to set up the SIR. - * @param vname std::string Name of the virus - * @param initial_prevalence epiworld_double Initial prevalence - * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system - * @param initial_recovery epiworld_double Initial recovery rate of the immune system - */ -template -class ModelDiffNet : public epiworld::Model -{ -private: -public: - - ModelDiffNet() {}; - - ModelDiffNet( - ModelDiffNet & model, - std::string innovation_name, - epiworld_double prevalence, - epiworld_double prob_adopt, - bool normalize_exposure = true, - double * agents_data = nullptr, - size_t data_ncols = 0u, - std::vector< size_t > data_cols = {}, - std::vector< double > params = {} - ); - - ModelDiffNet( - std::string innovation_name, - epiworld_double prevalence, - epiworld_double prob_adopt, - bool normalize_exposure = true, - double * agents_data = nullptr, - size_t data_ncols = 0u, - std::vector< size_t > data_cols = {}, - std::vector< double > params = {} - ); - - static const int NONADOPTER = 0; - static const int ADOPTER = 1; - - bool normalize_exposure = true; - std::vector< size_t > data_cols; - std::vector< double > params; -}; - -template -inline ModelDiffNet::ModelDiffNet( - ModelDiffNet & model, - std::string innovation_name, - epiworld_double prevalence, - epiworld_double prob_adopt, - bool normalize_exposure, - double * agents_data, - size_t data_ncols, - std::vector< size_t > data_cols, - std::vector< double > params - ) -{ - - // Adding additional parameters - this->normalize_exposure = normalize_exposure; - this->data_cols = data_cols; - this->params = params; - - epiworld::UpdateFun update_non_adopters = []( - epiworld::Agent * p, epiworld::Model * m - ) -> void { - - // Measuring exposure - // If the neighbor is infected, then proceed - size_t nviruses = m->get_n_viruses(); - std::vector< Virus* > innovations(nviruses, {}); - std::vector< bool > stored(nviruses, false); - std::vector< double > exposure(nviruses, 0.0); - - ModelDiffNet * diffmodel = dynamic_cast*>(m); - - Agent & agent = *p; - - // For each one of the possible innovations, we have to compute - // the adoption probability, which is a function of exposure - for (auto & neighbor: agent.get_neighbors()) - { - - if (neighbor->get_state() == ModelDiffNet::ADOPTER) - { - - for (const VirusPtr & v : neighbor->get_viruses()) - { - - /* And it is a function of susceptibility_reduction as well */ - double p_i = - (1.0 - agent.get_susceptibility_reduction(v, m)) * - (1.0 - agent.get_transmission_reduction(v, m)) - ; - - size_t vid = v->get_id(); - if (!stored[vid]) - { - stored[vid] = true; - innovations[vid] = &(*v); - } - exposure[vid] += p_i; - - } - - } - - } - - // Computing probability of adoption - for (size_t i = 0u; i < nviruses; ++i) - { - - if (diffmodel->normalize_exposure) - exposure.at(i) /= agent.get_n_neighbors(); - - for (auto & j: diffmodel->data_cols) - exposure.at(i) += agent(j) * diffmodel->params.at(j); - - // Baseline probability of adoption - double p = m->get_viruses()[i]->get_prob_infecting(m); - exposure.at(i) += std::log(p) - std::log(1.0 - p); - - // Computing as log - exposure.at(i) = 1.0/(1.0 + std::exp(-exposure.at(i))); - - } - - // Running the roulette to see is an innovation is adopted - int which = roulette(exposure, m); - - // No innovation was adopted - if (which < 0) - return; - - // Otherwise, it is adopted from any of the neighbors - agent.add_virus( - *innovations.at(which), - m, - ModelDiffNet::ADOPTER - ); - - return; - - }; - - // Adding agents data - model.set_agents_data(agents_data, data_ncols); - - // Adding statuses - model.add_state("Non adopters", update_non_adopters); - model.add_state("Adopters"); - - // Adding parameters - std::string parname = std::string("Prob. Adopting ") + innovation_name; - model.add_param(prob_adopt, parname); - - // Preparing the virus ------------------------------------------- - epiworld::Virus innovation(innovation_name); - innovation.set_state(1,1,1); - - innovation.set_prob_infecting(&model(parname)); - - model.add_virus(innovation, prevalence); - - model.set_name( - std::string("Diffusion of Innovations - ") + innovation_name); - - return; - -} - -template -inline ModelDiffNet::ModelDiffNet( - std::string innovation_name, - epiworld_double prevalence, - epiworld_double prob_adopt, - bool normalize_exposure, - double * agents_data, - size_t data_ncols, - std::vector< size_t > data_cols, - std::vector< double > params - ) -{ - - ModelDiffNet( - *this, - innovation_name, - prevalence, - prob_adopt, - normalize_exposure, - agents_data, - data_ncols, - data_cols, - params - ); - - return; - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld//models/diffnet.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - - -} - -#endif -/*////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - End of -include/epiworld/models/models.hpp- - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////*/ - - - - - -} - -#endif diff --git a/inst/include/epiworld/adjlist-bones.hpp b/inst/include/epiworld/adjlist-bones.hpp new file mode 100644 index 00000000..ae988639 --- /dev/null +++ b/inst/include/epiworld/adjlist-bones.hpp @@ -0,0 +1,74 @@ +#ifndef EPIWORLD_ADJLIST_BONES_HPP +#define EPIWORLD_ADJLIST_BONES_HPP + +class AdjList { +private: + + std::vector> dat; + bool directed; + epiworld_fast_uint N = 0; + epiworld_fast_uint E = 0; + +public: + + AdjList() {}; + + /** + * @brief Construct a new Adj List object + * + * @details + * Ids in the network are assume to range from `0` to `size - 1`. + * + * @param source Unsigned int vector with the source + * @param target Unsigned int vector with the target + * @param size Number of vertices in the network. + * @param directed Bool true if the network is directed + */ + AdjList( + const std::vector< int > & source, + const std::vector< int > & target, + int size, + bool directed + ); + + AdjList(AdjList && a); // Move constructor + AdjList(const AdjList & a); // Copy constructor + AdjList& operator=(const AdjList& a); + + + /** + * @brief Read an edgelist + * + * Ids in the network are assume to range from `0` to `size - 1`. + * + * @param fn Path to the file + * @param skip Number of lines to skip (e.g., 1 if there's a header) + * @param directed `true` if the network is directed + * @param size Number of vertices in the network. + */ + void read_edgelist( + std::string fn, + int size, + int skip = 0, + bool directed = true + ); + + std::map operator()( + epiworld_fast_uint i + ) const; + + void print(epiworld_fast_uint limit = 20u) const; + size_t vcount() const; ///< Number of vertices/nodes in the network. + size_t ecount() const; ///< Number of edges/arcs/ties in the network. + + std::vector> & get_dat() { + return dat; + }; + + bool is_directed() const; ///< `true` if the network is directed. + +}; + + +#endif + diff --git a/inst/include/epiworld/adjlist-meat.hpp b/inst/include/epiworld/adjlist-meat.hpp new file mode 100644 index 00000000..1f03e9ae --- /dev/null +++ b/inst/include/epiworld/adjlist-meat.hpp @@ -0,0 +1,222 @@ +#ifndef EPIWORLD_ADJLIST_MEAT_HPP +#define EPIWORLD_ADJLIST_MEAT_HPP + +inline AdjList::AdjList( + const std::vector< int > & source, + const std::vector< int > & target, + int size, + bool directed +) : directed(directed) { + + + dat.resize(size, std::map({})); + int max_id = size - 1; + + int i,j; + for (int m = 0; m < static_cast(source.size()); ++m) + { + + i = source[m]; + j = target[m]; + + if (i > max_id) + throw std::range_error( + "The source["+std::to_string(m)+"] = " + std::to_string(i) + + " is above the max_id " + std::to_string(max_id) + ); + + if (j > max_id) + throw std::range_error( + "The target["+std::to_string(m)+"] = " + std::to_string(j) + + " is above the max_id " + std::to_string(max_id) + ); + + // Adding nodes + if (dat[i].find(j) == dat[i].end()) + dat[i].insert(std::pair(j, 1u)); + else + dat[i][j]++; + + if (!directed) + { + + if (dat[j].find(i) == dat[j].end()) + dat[j].insert(std::pair(i, 1u)); + else + dat[j][i]++; + + } + + E++; + + } + + N = size; + + return; + +} + + +inline AdjList::AdjList(AdjList && a) : + dat(std::move(a.dat)), + directed(a.directed), + N(a.N), + E(a.E) +{ + +} + +inline AdjList::AdjList(const AdjList & a) : + dat(a.dat), + directed(a.directed), + N(a.N), + E(a.E) +{ + +} + +inline AdjList& AdjList::operator=(const AdjList& a) +{ + if (this == &a) + return *this; + + this->dat = a.dat; + this->directed = a.directed; + this->N = a.N; + this->E = a.E; + + return *this; +} + +inline void AdjList::read_edgelist( + std::string fn, + int size, + int skip, + bool directed +) { + + int i,j; + std::ifstream filei(fn); + + if (!filei) + throw std::logic_error("The file " + fn + " was not found."); + + int linenum = 0; + std::vector< int > source_; + std::vector< int > target_; + + source_.reserve(1e5); + target_.reserve(1e5); + + int max_id = size - 1; + + while (!filei.eof()) + { + + if (linenum++ < skip) + continue; + + filei >> i >> j; + + // Looking for exceptions + if (filei.bad()) + throw std::logic_error( + "I/O error while reading the file " + + fn + ); + + if (filei.fail()) + break; + + if (i > max_id) + throw std::range_error( + "The source["+std::to_string(linenum)+"] = " + std::to_string(i) + + " is above the max_id " + std::to_string(max_id) + ); + + if (j > max_id) + throw std::range_error( + "The target["+std::to_string(linenum)+"] = " + std::to_string(j) + + " is above the max_id " + std::to_string(max_id) + ); + + source_.push_back(i); + target_.push_back(j); + + } + + // Now using the right constructor + *this = AdjList(source_, target_, size, directed); + + return; + +} + +inline std::map AdjList::operator()( + epiworld_fast_uint i + ) const { + + if (i >= N) + throw std::range_error( + "The vertex id " + std::to_string(i) + " is not in the network." + ); + + return dat[i]; + +} + +inline void AdjList::print(epiworld_fast_uint limit) const { + + + epiworld_fast_uint counter = 0; + printf_epiworld("Nodeset:\n"); + int i = -1; + for (auto & n : dat) + { + + if (counter++ > limit) + break; + + printf_epiworld(" % 3i: {", ++i); + int niter = 0; + for (auto n_n : n) + if (++niter < static_cast(n.size())) + { + printf_epiworld("%i, ", static_cast(n_n.first)); + } + else { + printf_epiworld("%i}\n", static_cast(n_n.first)); + } + } + + if (limit < dat.size()) + { + printf_epiworld( + " (... skipping %i records ...)\n", + static_cast(dat.size() - limit) + ); + } + +} + +inline size_t AdjList::vcount() const +{ + return N; +} + +inline size_t AdjList::ecount() const +{ + return E; +} + +inline bool AdjList::is_directed() const { + + if (dat.size() == 0u) + throw std::logic_error("The edgelist is empty."); + + return directed; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agent-actions-meat.hpp b/inst/include/epiworld/agent-actions-meat.hpp new file mode 100644 index 00000000..20cc829c --- /dev/null +++ b/inst/include/epiworld/agent-actions-meat.hpp @@ -0,0 +1,260 @@ +#ifndef EPIWORLD_AGENT_ACTIONS_MEAT_HPP +#define EPIWORLD_AGENT_ACTIONS_MEAT_HPP + +template +inline void default_add_virus(Action & a, Model * m) +{ + + Agent * p = a.agent; + VirusPtr v = a.virus; + + CHECK_COALESCE_(a.new_state, v->state_init, p->get_state()) + CHECK_COALESCE_(a.queue, v->queue_init, 1) + + // Has a agent? If so, we need to register the transmission + if (v->get_agent()) + { + + // ... only if not the same agent + if (v->get_agent()->get_id() != p->get_id()) + m->get_db().record_transmission( + v->get_agent()->get_id(), + p->get_id(), + v->get_id(), + v->get_date() + ); + + } + + // Update virus accounting + p->n_viruses++; + size_t n_viruses = p->n_viruses; + + if (n_viruses <= p->viruses.size()) + p->viruses[n_viruses - 1] = std::make_shared< Virus >(*v); + else + p->viruses.push_back(std::make_shared< Virus >(*v)); + + n_viruses--; + + // Notice that both agent and date can be changed in this case + // as only the sequence is a shared_ptr itself. + #ifdef EPI_DEBUG + if (n_viruses >= p->viruses.size()) + { + throw std::logic_error( + "[epi-debug]::default_add_virus Index for new virus out of range." + ); + } + #endif + p->viruses[n_viruses]->set_agent(p, n_viruses); + p->viruses[n_viruses]->set_date(m->today()); + + #ifdef EPI_DEBUG + m->get_db().today_virus.at(v->get_id()).at(p->state)++; + #else + m->get_db().today_virus[v->get_id()][p->state]++; + #endif + +} + +template +inline void default_add_tool(Action & a, Model * m) +{ + + Agent * p = a.agent; + ToolPtr t = a.tool; + + CHECK_COALESCE_(a.new_state, t->state_init, p->get_state()) + CHECK_COALESCE_(a.queue, t->queue_init, Queue::NoOne) + + // Update tool accounting + p->n_tools++; + size_t n_tools = p->n_tools; + + if (n_tools <= p->tools.size()) + p->tools[n_tools - 1] = std::make_shared< Tool >(*t); + else + p->tools.push_back(std::make_shared< Tool >(*t)); + + n_tools--; + + p->tools[n_tools]->set_date(m->today()); + p->tools[n_tools]->set_agent(p, n_tools); + + m->get_db().today_tool[t->get_id()][p->state]++; + +} + +template +inline void default_rm_virus(Action & a, Model * model) +{ + + Agent * p = a.agent; + VirusPtr & v = a.agent->viruses[a.virus->pos_in_agent]; + + CHECK_COALESCE_(a.new_state, v->state_post, p->get_state()) + CHECK_COALESCE_(a.queue, v->queue_post, -Queue::Everyone) + + if (--p->n_viruses > 0) + { + // The new virus will change positions + p->viruses[p->n_viruses]->pos_in_agent = v->pos_in_agent; + std::swap( + p->viruses[p->n_viruses], // Moving to the end + p->viruses[v->pos_in_agent] // Moving to the beginning + ); + } + + // Calling the virus action over the removed virus + v->post_recovery(model); + + return; + +} + +template +inline void default_rm_tool(Action & a, Model * /*m*/) +{ + + Agent * p = a.agent; + ToolPtr & t = a.agent->tools[a.tool->pos_in_agent]; + + CHECK_COALESCE_(a.new_state, t->state_post, p->get_state()) + CHECK_COALESCE_(a.queue, t->queue_post, Queue::NoOne) + + if (--p->n_tools > 0) + { + p->tools[p->n_tools]->pos_in_agent = t->pos_in_agent; + std::swap( + p->tools[t->pos_in_agent], + p->tools[p->n_tools] + ); + } + + return; + +} + +template +inline void default_add_entity(Action & a, Model *) +{ + + Agent * p = a.agent; + Entity * e = a.entity; + + CHECK_COALESCE_(a.new_state, e->state_post, p->get_state()) + CHECK_COALESCE_(a.queue, e->queue_post, Queue::NoOne) + + // Checking the agent and the entity are not linked + if ((p->get_n_entities() > 0) && (e->size() > 0)) + { + + if (p->get_n_entities() > e->size()) // Slower search through the agent + { + for (size_t i = 0u; i < e->size(); ++i) + if(e->operator[](i)->get_id() == p->get_id()) + throw std::logic_error("An entity cannot be reassigned to an agent."); + } + else // Slower search through the entity + { + for (size_t i = 0u; i < p->get_n_entities(); ++i) + if(p->get_entity(i).get_id() == e->get_id()) + throw std::logic_error("An entity cannot be reassigned to an agent."); + } + + // It means that agent and entity were not associated. + } + + // Adding the entity to the agent + if (++p->n_entities <= p->entities.size()) + { + + p->entities[p->n_entities - 1] = e->get_id(); + p->entities_locations[p->n_entities - 1] = e->n_agents; + + } else + { + p->entities.push_back(e->get_id()); + p->entities_locations.push_back(e->n_agents); + } + + // Adding the agent to the entity + // Adding the entity to the agent + if (++e->n_agents <= e->agents.size()) + { + + e->agents[e->n_agents - 1] = p->get_id(); + // Adjusted by '-1' since the list of entities in the agent just grew. + e->agents_location[e->n_agents - 1] = p->n_entities - 1; + + } else + { + e->agents.push_back(p->get_id()); + e->agents_location.push_back(p->n_entities - 1); + } + + // Today was the last modification + // e->date_last_add_or_remove = m->today(); + +} + +template +inline void default_rm_entity(Action & a, Model * m) +{ + + Agent * p = a.agent; + Entity * e = a.entity; + size_t idx_agent_in_entity = a.idx_agent; + size_t idx_entity_in_agent = a.idx_object; + + CHECK_COALESCE_(a.new_state, e->state_post, p->get_state()) + CHECK_COALESCE_(a.queue, e->queue_post, Queue::NoOne) + + if (--p->n_entities > 0) + { + + // When we move the end entity to the new location, the + // moved entity needs to reflect the change, i.e., where the + // entity will now be located in the agent + size_t agent_location_in_last_entity = p->entities_locations[p->n_entities]; + Entity * last_entity = &m->get_entities()[p->entities[p->n_entities]]; ///< Last entity of the agent + + // The end entity will be located where the removed was + last_entity->agents_location[agent_location_in_last_entity] = idx_entity_in_agent; + + // We now make the swap + std::swap( + p->entities[p->n_entities], + p->entities[idx_entity_in_agent] + ); + + } + + if (--e->n_agents > 0) + { + + // When we move the end agent to the new location, the + // moved agent needs to reflect the change, i.e., where the + // agent will now be located in the entity + size_t entity_location_in_last_agent = e->agents_location[e->n_agents]; + Agent * last_agent = &m->get_agents()[e->agents[e->n_agents]]; ///< Last agent of the entity + + // The end entity will be located where the removed was + last_agent->entities_locations[entity_location_in_last_agent] = idx_agent_in_entity; + + // We now make the swap + std::swap( + e->agents[e->n_agents], + e->agents[idx_agent_in_entity] + ); + + } + + // Setting the date of the last removal + // e->date_last_add_or_remove = m->today(); + + return; + +} +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agent-bones.hpp b/inst/include/epiworld/agent-bones.hpp new file mode 100644 index 00000000..422f071f --- /dev/null +++ b/inst/include/epiworld/agent-bones.hpp @@ -0,0 +1,332 @@ +#ifndef EPIWORLD_PERSON_BONES_HPP +#define EPIWORLD_PERSON_BONES_HPP + +template +class Model; + +template +class Virus; + +template +class Viruses; + +template +class Viruses_const; + +template +class Tool; + +template +class Tools; + +template +class Tools_const; + +template +class Queue; + +template +struct Action; + +template +class Entity; + +template +class Entities; + +template +inline void default_add_virus(Action & a, Model * m); + +template +inline void default_add_tool(Action & a, Model * m); + +template +inline void default_add_entity(Action & a, Model * m); + +template +inline void default_rm_virus(Action & a, Model * m); + +template +inline void default_rm_tool(Action & a, Model * m); + +template +inline void default_rm_entity(Action & a, Model * m); + + + +/** + * @brief Agent (agents) + * + * @tparam TSeq Sequence type (should match `TSeq` across the model) + */ +template +class Agent { + friend class Model; + friend class Virus; + friend class Viruses; + friend class Viruses_const; + friend class Tool; + friend class Tools; + friend class Tools_const; + friend class Queue; + friend class Entities; + friend class AgentsSample; + friend void default_add_virus(Action & a, Model * m); + friend void default_add_tool(Action & a, Model * m); + friend void default_add_entity(Action & a, Model * m); + friend void default_rm_virus(Action & a, Model * m); + friend void default_rm_tool(Action & a, Model * m); + friend void default_rm_entity(Action & a, Model * m); +private: + + Model * model; + + std::vector< size_t > neighbors; + std::vector< size_t > neighbors_locations; + size_t n_neighbors = 0u; + + std::vector< size_t > entities; + std::vector< size_t > entities_locations; + size_t n_entities = 0u; + + epiworld_fast_uint state = 0u; + epiworld_fast_uint state_prev = 0u; ///< For accounting, if need to undo a change. + + int state_last_changed = -1; ///< Last time the agent was updated. + int id = -1; + + std::vector< VirusPtr > viruses; + epiworld_fast_uint n_viruses = 0u; + + std::vector< ToolPtr > tools; + epiworld_fast_uint n_tools = 0u; + + ActionFun add_virus_ = default_add_virus; + ActionFun add_tool_ = default_add_tool; + ActionFun add_entity_ = default_add_entity; + + ActionFun rm_virus_ = default_rm_virus; + ActionFun rm_tool_ = default_rm_tool; + ActionFun rm_entity_ = default_rm_entity; + + epiworld_fast_uint action_counter = 0u; + + std::vector< Agent * > sampled_agents; + size_t sampled_agents_n = 0u; + std::vector< size_t > sampled_agents_left; + size_t sampled_agents_left_n = 0u; + int date_last_build_sample = -99; + +public: + + Agent(); + Agent(Agent && p); + Agent(const Agent & p); + Agent & operator=(const Agent & other_agent); + + /** + * @name Add/Remove Virus/Tool + * + * Any of these is ultimately reflected at the end of the iteration. + * + * @param tool Tool to add + * @param virus Virus to add + * @param state_new state after the change + * @param queue + */ + ///@{ + void add_tool( + ToolPtr tool, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void add_tool( + Tool tool, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void add_virus( + VirusPtr virus, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void add_virus( + Virus virus, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void add_entity( + Entity & entity, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_tool( + epiworld_fast_uint tool_idx, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_tool( + ToolPtr & tool, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_virus( + epiworld_fast_uint virus_idx, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_virus( + VirusPtr & virus, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_entity( + epiworld_fast_uint entity_idx, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_entity( + Entity & entity, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); + + void rm_agent_by_virus( + epiworld_fast_uint virus_idx, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); ///< Agent removed by virus + + void rm_agent_by_virus( + VirusPtr & virus, + Model * model, + epiworld_fast_int state_new = -99, + epiworld_fast_int queue = -99 + ); ///< Agent removed by virus + ///@} + + /** + * @name Get the rates (multipliers) for the agent + * + * @param v A pointer to a virus. + * @return epiworld_double + */ + ///@{ + epiworld_double get_susceptibility_reduction(VirusPtr v, Model * model); + epiworld_double get_transmission_reduction(VirusPtr v, Model * model); + epiworld_double get_recovery_enhancer(VirusPtr v, Model * model); + epiworld_double get_death_reduction(VirusPtr v, Model * model); + ///@} + + int get_id() const; ///< Id of the individual + + VirusPtr & get_virus(int i); + Viruses get_viruses(); + const Viruses_const get_viruses() const; + size_t get_n_viruses() const noexcept; + + ToolPtr & get_tool(int i); + Tools get_tools(); + const Tools_const get_tools() const; + size_t get_n_tools() const noexcept; + + void mutate_virus(); + void add_neighbor( + Agent & p, + bool check_source = true, + bool check_target = true + ); + + /** + * @brief Swaps neighbors between the current agent and agent `other` + * + * @param other + * @param n_this + * @param n_other + */ + void swap_neighbors( + Agent & other, + size_t n_this, + size_t n_other + ); + + std::vector< Agent * > get_neighbors(); + size_t get_n_neighbors() const; + + void change_state( + Model * model, + epiworld_fast_uint new_state, + epiworld_fast_int queue = 0 + ); + + const epiworld_fast_uint & get_state() const; + + void reset(); + + bool has_tool(epiworld_fast_uint t) const; + bool has_tool(std::string name) const; + bool has_tool(const Tool & t) const; + bool has_virus(epiworld_fast_uint t) const; + bool has_virus(std::string name) const; + bool has_virus(const Virus & v) const; + + void print(Model * model, bool compressed = false) const; + + /** + * @brief Access the j-th column of the agent + * + * If an external array has been specified, then these two + * functions can be used to access additional agent's features + * not included in the model. + * + * The `operator[]` method is with no boundary check, whereas + * the `operator()` method checks boundaries. The former can result + * in a segfault. + * + * + * @param j + * @return double& + */ + ///@{ + double & operator()(size_t j); + double & operator[](size_t j); + double operator()(size_t j) const; + double operator[](size_t j) const; + ///@} + + Entities get_entities(); + const Entities_const get_entities() const; + const Entity & get_entity(size_t i) const; + Entity & get_entity(size_t i); + size_t get_n_entities() const; + + bool operator==(const Agent & other) const; + bool operator!=(const Agent & other) const {return !operator==(other);}; + +}; + + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agent-meat-state.hpp b/inst/include/epiworld/agent-meat-state.hpp new file mode 100644 index 00000000..e45c5646 --- /dev/null +++ b/inst/include/epiworld/agent-meat-state.hpp @@ -0,0 +1,105 @@ +#ifndef EPIWORLD_PERSON_MEAT_STATE_HPP +#define EPIWORLD_PERSON_MEAT_STATE_HPP + +// template +// class Model; + +// template +// class Agent; + + +/** + * @file agent-meat-state.hpp + * @author George G. Vega Yon (g.vegayon en gmail) + * @brief Sampling functions are getting big, so we keep them in a separate file. + * @version 0.1 + * @date 2022-06-15 + * + * @copyright Copyright (c) 2022 + * + */ +#include "agent-meat-virus-sampling.hpp" + + +template +inline void default_update_susceptible( + Agent * p, + Model * m + ) +{ + + Virus * virus = sampler::sample_virus_single(p, m); + + if (virus == nullptr) + return; + + p->add_virus(*virus, m); + + return; + +} + +template +inline void default_update_exposed(Agent * p, Model * m) { + + if (p->get_n_viruses() == 0u) + throw std::logic_error( + std::string("Using the -default_update_exposed- on agents WITHOUT viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + std::string(" has no virus registered.") + ); + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Die + m->array_double_tmp[n_events++] = + v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + if ((which % 2) == 0) // If odd + { + + size_t which_v = std::ceil(which / 2); + p->rm_agent_by_virus(which_v, m); + + } else { + + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + } + + return ; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agent-meat-virus-sampling.hpp b/inst/include/epiworld/agent-meat-virus-sampling.hpp new file mode 100644 index 00000000..82096206 --- /dev/null +++ b/inst/include/epiworld/agent-meat-virus-sampling.hpp @@ -0,0 +1,441 @@ +#ifndef EPIWORLD_AGENT_MEAT_VIRUS_SAMPLING +#define EPIWORLD_AGENT_MEAT_VIRUS_SAMPLING + +/** + * @brief Functions for sampling viruses + * + */ +namespace sampler { + +/** + * @brief Make a function to sample from neighbors + * + * This is akin to the function default_update_susceptible, with the difference + * that it will create a function that supports excluding states from the sampling + * frame. For example, individuals who have acquired a virus can be excluded if + * in incubation state. + * + * @tparam TSeq + * @param exclude unsigned vector of states that need to be excluded from the sampling + * @return Virus* of the selected virus. If none selected (or none + * available,) returns a nullptr; + */ +template +inline std::function*,Model*)> make_update_susceptible( + std::vector< epiworld_fast_uint > exclude = {} + ) +{ + + + if (exclude.size() == 0u) + { + + std::function*,Model*)> sampler = + [](Agent * p, Model * m) -> void + { + + if (p->get_n_viruses() > 0u) + throw std::logic_error( + std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + + std::string(" has ") + std::to_string(p->get_n_viruses()) + + std::string(" viruses.") + ); + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + + return; + }; + + return sampler; + + } else { + + // Making room for the query + std::shared_ptr> exclude_agent_bool = + std::make_shared>(0); + + std::shared_ptr> exclude_agent_bool_idx = + std::make_shared>(exclude); + + std::function*,Model*)> sampler = + [exclude_agent_bool,exclude_agent_bool_idx](Agent * p, Model * m) -> void + { + + // The first time we call it, we need to initialize the vector + if (exclude_agent_bool->size() == 0u) + { + + exclude_agent_bool->resize(m->get_states().size(), false); + for (auto s : *exclude_agent_bool_idx) + { + if (s >= exclude_agent_bool->size()) + throw std::logic_error( + std::string("You are trying to exclude a state that is out of range: ") + + std::to_string(s) + std::string(". There are only ") + + std::to_string(exclude_agent_bool->size()) + + std::string(" states in the model.") + ); + + exclude_agent_bool->operator[](s) = true; + + } + + } + + if (p->get_n_viruses() > 0u) + throw std::logic_error( + std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + + std::string(" has ") + std::to_string(p->get_n_viruses()) + + std::string(" viruses.") + ); + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + + // If the state is in the list, exclude it + if (exclude_agent_bool->operator[](neighbor->get_state())) + continue; + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + + return; + + }; + + return sampler; + + } + +} + +/** + * @brief Make a function to sample from neighbors + * + * This is akin to the function default_update_susceptible, with the difference + * that it will create a function that supports excluding states from the sampling + * frame. For example, individuals who have acquired a virus can be excluded if + * in incubation state. + * + * @tparam TSeq + * @param exclude unsigned vector of states that need to be excluded from the sampling + * @return Virus* of the selected virus. If none selected (or none + * available,) returns a nullptr; + */ +template +inline std::function*(Agent*,Model*)> make_sample_virus_neighbors( + std::vector< epiworld_fast_uint > exclude = {} +) +{ + if (exclude.size() == 0u) + { + + std::function*(Agent*,Model*)> res = + [](Agent * p, Model * m) -> Virus* { + + if (p->get_n_viruses() > 0u) + throw std::logic_error( + std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + + std::string(" has ") + std::to_string(p->get_n_viruses()) + + std::string(" viruses.") + ); + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return nullptr; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return nullptr; + + return m->array_virus_tmp[which]; + + }; + + return res; + + + } else { + + // Making room for the query + std::shared_ptr> exclude_agent_bool = + std::make_shared>(0); + + std::shared_ptr> exclude_agent_bool_idx = + std::make_shared>(exclude); + + + std::function*(Agent*,Model*)> res = + [exclude_agent_bool,exclude_agent_bool_idx](Agent * p, Model * m) -> Virus* { + + // The first time we call it, we need to initialize the vector + if (exclude_agent_bool->size() == 0u) + { + + exclude_agent_bool->resize(m->get_states().size(), false); + for (auto s : *exclude_agent_bool_idx) + { + if (s >= exclude_agent_bool->size()) + throw std::logic_error( + std::string("You are trying to exclude a state that is out of range: ") + + std::to_string(s) + std::string(". There are only ") + + std::to_string(exclude_agent_bool->size()) + + std::string(" states in the model.") + ); + + exclude_agent_bool->operator[](s) = true; + + } + + } + + if (p->get_n_viruses() > 0u) + throw std::logic_error( + std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + + std::string(" has ") + std::to_string(p->get_n_viruses()) + + std::string(" viruses.") + ); + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + + // If the state is in the list, exclude it + if (exclude_agent_bool->operator[](neighbor->get_state())) + continue; + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return nullptr; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return nullptr; + + return m->array_virus_tmp[which]; + + }; + + return res; + + } + +} + +/** + * @brief Sample from neighbors pool of viruses (at most one) + * + * This function samples at most one virus from the pool of + * viruses from its neighbors. If no virus is selected, the function + * returns a `nullptr`, otherwise it returns a pointer to the + * selected virus. + * + * This can be used to build a new update function (EPI_NEW_UPDATEFUN.) + * + * @tparam TSeq + * @param p Pointer to person + * @param m Pointer to the model + * @return Virus* of the selected virus. If none selected (or none + * available,) returns a nullptr; + */ +template +inline Virus * sample_virus_single(Agent * p, Model * m) +{ + + if (p->get_n_viruses() > 0u) + throw std::logic_error( + std::string("Using the -default_update_susceptible- on agents WITH viruses makes no sense! ") + + std::string("Agent id ") + std::to_string(p->get_id()) + + std::string(" has ") + std::to_string(p->get_n_viruses()) + + std::string(" viruses.") + ); + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + #ifdef EPI_DEBUG + int _vcount_neigh = 0; + #endif + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= m->array_virus_tmp.size()) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + #ifdef EPI_DEBUG + if ( + (m->array_double_tmp[nviruses_tmp - 1] < 0.0) | + (m->array_double_tmp[nviruses_tmp - 1] > 1.0) + ) + { + printf_epiworld( + "[epi-debug] Agent %i's virus %i has transmission prob outside of [0, 1]: %.4f!\n", + static_cast(neighbor->get_id()), + static_cast(_vcount_neigh++), + m->array_double_tmp[nviruses_tmp - 1] + ); + } + #endif + + } + } + + + // No virus to compute + if (nviruses_tmp == 0u) + return nullptr; + + #ifdef EPI_DEBUG + m->get_db().n_transmissions_potential++; + #endif + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return nullptr; + + #ifdef EPI_DEBUG + m->get_db().n_transmissions_today++; + #endif + + return m->array_virus_tmp[which]; + +} + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agent-meat.hpp b/inst/include/epiworld/agent-meat.hpp new file mode 100644 index 00000000..4b5b5191 --- /dev/null +++ b/inst/include/epiworld/agent-meat.hpp @@ -0,0 +1,1014 @@ +#ifndef EPIWORLD_PERSON_MEAT_HPP +#define EPIWORLD_PERSON_MEAT_HPP + +// template +// inline bool IN(Ta & a, std::vector< Ta > & b); + +#define CHECK_COALESCE_(proposed_, virus_tool_, alt_) \ + if (static_cast(proposed_) == -99) {\ + if (static_cast(virus_tool_) == -99) \ + (proposed_) = (alt_);\ + else (proposed_) = (virus_tool_);} + +// To large to add directly here +#include "agent-actions-meat.hpp" + +template +inline Agent::Agent() {} + +template +inline Agent::Agent(Agent && p) : + model(p.model), + neighbors(std::move(p.neighbors)), + neighbors_locations(std::move(p.neighbors_locations)), + n_neighbors(p.n_neighbors), + entities(std::move(p.entities)), + entities_locations(std::move(p.entities_locations)), + n_entities(p.n_entities), + state(p.state), + state_prev(p.state_prev), + state_last_changed(p.state_last_changed), + id(p.id), + viruses(std::move(p.viruses)), /// Needs to be adjusted + n_viruses(p.n_viruses), + tools(std::move(p.tools)), /// Needs to be adjusted + n_tools(p.n_tools), + add_virus_(std::move(p.add_virus_)), + add_tool_(std::move(p.add_tool_)), + add_entity_(std::move(p.add_entity_)), + rm_virus_(std::move(p.rm_virus_)), + rm_tool_(std::move(p.rm_tool_)), + rm_entity_(std::move(p.rm_entity_)), + action_counter(p.action_counter) +{ + + state = p.state; + id = p.id; + + // Dealing with the virus + + int loc = 0; + for (auto & v : viruses) + { + + // Will create a copy of the virus, with the exeption of + // the virus code + v->agent = this; + v->pos_in_agent = loc++; + + } + + loc = 0; + for (auto & t : tools) + { + + // Will create a copy of the virus, with the exeption of + // the virus code + t->agent = this; + t->pos_in_agent = loc++; + + } + +} + +template +inline Agent::Agent(const Agent & p) : + model(p.model), + neighbors(p.neighbors), + neighbors_locations(p.neighbors_locations), + n_neighbors(p.n_neighbors), + entities(p.entities), + entities_locations(p.entities_locations), + n_entities(p.n_entities), + sampled_agents(0u), + sampled_agents_n(0u), + sampled_agents_left_n(0u), + date_last_build_sample(-99) +{ + + state = p.state; + id = p.id; + + // Dealing with the virus + viruses.resize(p.get_n_viruses(), nullptr); + n_viruses = viruses.size(); + for (size_t i = 0u; i < n_viruses; ++i) + { + + // Will create a copy of the virus, with the exeption of + // the virus code + viruses[i] = std::make_shared>(*p.viruses[i]); + viruses[i]->set_agent(this, i); + + } + + tools.resize(p.get_n_tools(), nullptr); + n_tools = tools.size(); + for (size_t i = 0u; i < n_tools; ++i) + { + + // Will create a copy of the virus, with the exeption of + // the virus code + tools[i] = std::make_shared>(*p.tools[i]); + tools[i]->set_agent(this, i); + + } + + add_virus_ = p.add_virus_; + add_tool_ = p.add_tool_; + rm_virus_ = p.rm_virus_; + rm_tool_ = p.rm_tool_; + +} + +template +inline Agent & Agent::operator=( + const Agent & other_agent +) +{ + + model = other_agent.model; + + neighbors = other_agent.neighbors; + neighbors_locations = other_agent.neighbors_locations; + n_neighbors = other_agent.n_neighbors; + + entities = other_agent.entities; + entities_locations = other_agent.entities_locations; + n_entities = other_agent.n_entities; + + sampled_agents.clear(); + sampled_agents_n = 0; + sampled_agents_left_n = 0; + date_last_build_sample = -99; + + // neighbors = other_agent.neighbors; + // entities = other_agent.entities; + // entities_locations = other_agent.entities_locations; + // n_entities = other_agent.n_entities; + state = other_agent.state; + state_prev = other_agent.state_prev; + state_last_changed = other_agent.state_last_changed; + id = other_agent.id; + + // viruses = other_agent.viruses; + n_viruses = other_agent.n_viruses; + viruses.resize(n_viruses, nullptr); + for (size_t i = 0u; i < n_viruses; ++i) + { + viruses[i] = std::make_shared>(*other_agent.viruses[i]); + viruses[i]->set_agent(this, i); + } + + // tools = other_agent.tools; + n_tools = other_agent.n_tools; + for (size_t i = 0u; i < n_tools; ++i) + { + tools[i] = std::make_shared>(*other_agent.tools[i]); + tools[i]->set_agent(this, i); + } + + add_virus_ = other_agent.add_virus_; + add_tool_ = other_agent.add_tool_; + add_entity_ = other_agent.add_entity_; + rm_virus_ = other_agent.rm_virus_; + rm_tool_ = other_agent.rm_tool_; + rm_entity_ = other_agent.rm_entity_; + action_counter = other_agent.action_counter; + + return *this; + +} + +template +inline void Agent::add_tool( + ToolPtr tool, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) { + + // Checking the virus exists + if (tool->get_id() >= static_cast(model->get_db().get_n_tools())) + throw std::range_error("The tool with id: " + std::to_string(tool->get_id()) + + " has not been registered. There are only " + std::to_string(model->get_n_tools()) + + " included in the model."); + + + model->actions_add( + this, nullptr, tool, nullptr, state_new, queue, add_tool_, -1, -1 + ); + +} + +template +inline void Agent::add_tool( + Tool tool, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + ToolPtr tool_ptr = std::make_shared< Tool >(tool); + add_tool(tool_ptr, model, state_new, queue); +} + +template +inline void Agent::add_virus( + VirusPtr virus, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + // Checking the virus exists + if (virus->get_id() >= static_cast(model->get_db().get_n_viruses())) + throw std::range_error("The virus with id: " + std::to_string(virus->get_id()) + + " has not been registered. There are only " + std::to_string(model->get_n_viruses()) + + " included in the model."); + + model->actions_add( + this, virus, nullptr, nullptr, state_new, queue, add_virus_, -1, -1 + ); + +} + +template +inline void Agent::add_virus( + Virus virus, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + VirusPtr virus_ptr = std::make_shared< Virus >(virus); + add_virus(virus_ptr, model, state_new, queue); +} + +template +inline void Agent::add_entity( + Entity & entity, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (model != nullptr) + { + + model->actions_add( + this, nullptr, nullptr, &entity, state_new, queue, add_entity_, -1, -1 + ); + + } + else // If no model is passed, then we assume that we only need to add the + // model entity + { + + Action a( + this, nullptr, nullptr, &entity, state_new, queue, add_entity_, + -1, -1 + ); + + default_add_entity(a, model); /* passing model makes nothing */ + + } + +} + +template +inline void Agent::rm_tool( + epiworld_fast_uint tool_idx, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (tool_idx >= n_tools) + throw std::range_error( + "The Tool you want to remove is out of range. This Agent only has " + + std::to_string(n_tools) + " tools." + ); + + model->actions_add( + this, nullptr, tools[tool_idx], nullptr, state_new, queue, rm_tool_, -1, -1 + ); + +} + +template +inline void Agent::rm_tool( + ToolPtr & tool, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (tool->agent != this) + throw std::logic_error("Cannot remove a virus from another agent!"); + + model->actions_add( + this, nullptr, tool, nullptr, state_new, queue, rm_tool_, -1, -1 + ); + +} + +template +inline void Agent::rm_virus( + epiworld_fast_uint virus_idx, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + if (virus_idx >= n_viruses) + throw std::range_error( + "The Virus you want to remove is out of range. This Agent only has " + + std::to_string(n_viruses) + " viruses." + ); + else if (n_viruses == 0u) + throw std::logic_error( + "There is no virus to remove here!" + ); + + #ifdef EPI_DEBUG + if (viruses[virus_idx]->pos_in_agent >= static_cast(n_viruses)) + { + throw std::logic_error( + "[epi-debug]::rm_virus the position in the agent is wrong." + ); + } + #endif + + model->actions_add( + this, viruses[virus_idx], nullptr, nullptr, state_new, queue, + default_rm_virus, -1, -1 + ); + +} + +template +inline void Agent::rm_virus( + VirusPtr & virus, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (virus->agent != this) + throw std::logic_error("Cannot remove a virus from another agent!"); + + model->actions_add( + this, virus, nullptr, nullptr, state_new, queue, + default_rm_virus, -1, -1 + ); + + +} + +template +inline void Agent::rm_entity( + epiworld_fast_uint entity_idx, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (entity_idx >= n_entities) + throw std::range_error( + "The Entity you want to remove is out of range. This Agent only has " + + std::to_string(n_entities) + " entitites." + ); + else if (n_entities == 0u) + throw std::logic_error( + "There is entity to remove here!" + ); + + model->actions_add( + this, nullptr, nullptr, model->entities[entity_idx], state_new, queue, + default_rm_entity, entities_locations[entity_idx], entity_idx + ); +} + +template +inline void Agent::rm_entity( + Entity & entity, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + // Looking for entity location in the agent + int entity_idx = -1; + for (size_t i = 0u; i < n_entities; ++i) + { + if (entities[i] == entity->get_id()) + entity_idx = i; + } + + if (entity_idx == -1) + throw std::logic_error( + "The agent " + std::to_string(id) + " is not associated with entity \"" + + entity.get_name() + "\"." + ); + + + model->actions_add( + this, nullptr, nullptr, entities[entity_idx], state_new, queue, + default_rm_entity, entities_locations[entity_idx], entity_idx + ); +} + +template +inline void Agent::rm_agent_by_virus( + epiworld_fast_uint virus_idx, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (state_new == -99) + state_new = state; + + if (virus_idx >= n_viruses) + throw std::range_error( + std::string("The virus trying to remove the agent is out of range. ") + + std::string("This agent has only ") + std::to_string(n_viruses) + + std::string(" and you are trying to remove virus # ") + + std::to_string(virus_idx) + std::string(".") + ); + + // Removing viruses + for (size_t i = 0u; i < n_viruses; ++i) + { + if (i != virus_idx) + rm_virus(i, model); + } + + // Changing state to new_state + VirusPtr & v = viruses[virus_idx]; + epiworld_fast_int dead_state, dead_queue; + v->get_state(nullptr, nullptr, &dead_state); + v->get_queue(nullptr, nullptr, &dead_queue); + + if (queue != -99) + dead_queue = queue; + + change_state( + model, + // Either preserve the current state or apply a new one + (dead_state < 0) ? state : static_cast(dead_state), + + // By default, it will be removed from the queue... unless the user + // says the contrary! + (dead_queue == -99) ? Queue::NoOne : dead_queue + ); + +} + +template +inline void Agent::rm_agent_by_virus( + VirusPtr & virus, + Model * model, + epiworld_fast_int state_new, + epiworld_fast_int queue +) +{ + + if (virus->get_agent() == nullptr) + throw std::logic_error("The virus trying to remove the agent has no host."); + + if (virus->get_agent()->id != id) + throw std::logic_error("Viruses can only remove their hosts'."); + + rm_agent_by_virus( + virus->pos_in_agent, + model, + state_new, + queue + ); + +} + +template +inline epiworld_double Agent::get_susceptibility_reduction( + VirusPtr v, + Model * model +) { + + return model->susceptibility_reduction_mixer(this, v, model); +} + +template +inline epiworld_double Agent::get_transmission_reduction( + VirusPtr v, + Model * model +) { + return model->transmission_reduction_mixer(this, v, model); +} + +template +inline epiworld_double Agent::get_recovery_enhancer( + VirusPtr v, + Model * model +) { + return model->recovery_enhancer_mixer(this, v, model); +} + +template +inline epiworld_double Agent::get_death_reduction( + VirusPtr v, + Model * model +) { + return model->death_reduction_mixer(this, v, model); +} + +template +inline int Agent::get_id() const +{ + return id; +} + +template +inline Viruses Agent::get_viruses() { + + return Viruses(*this); + +} + +template +inline const Viruses_const Agent::get_viruses() const { + + return Viruses_const(*this); + +} + +template +inline VirusPtr & Agent::get_virus(int i) { + return viruses.at(i); +} + +template +inline size_t Agent::get_n_viruses() const noexcept +{ + return n_viruses; +} + +template +inline Tools Agent::get_tools() { + return Tools(*this); +} + +template +inline const Tools_const Agent::get_tools() const { + return Tools_const(*this); +} + +template +inline ToolPtr & Agent::get_tool(int i) +{ + return tools.at(i); +} + +template +inline size_t Agent::get_n_tools() const noexcept +{ + return n_tools; +} + +template +inline void Agent::mutate_virus() +{ + + for (auto & v : viruses) + v->mutate(); + +} + +template +inline void Agent::add_neighbor( + Agent & p, + bool check_source, + bool check_target +) { + // Can we find the neighbor? + bool found = false; + if (check_source) + { + + for (auto & n: neighbors) + if (static_cast(n) == p.get_id()) + { + found = true; + break; + } + + } + + // Three things going on here: + // - Where in the neighbor will this be + // - What is the neighbor's id + // - Increasing the number of neighbors + if (!found) + { + + neighbors_locations.push_back(p.get_n_neighbors()); + neighbors.push_back(p.get_id()); + n_neighbors++; + + } + + + found = false; + if (check_target) + { + + for (auto & n: p.neighbors) + if (static_cast(n) == id) + { + found = true; + break; + } + + } + + if (!found) + { + + p.neighbors_locations.push_back(n_neighbors - 1); + p.neighbors.push_back(id); + p.n_neighbors++; + + } + + +} + +template +inline void Agent::swap_neighbors( + Agent & other, + size_t n_this, + size_t n_other +) +{ + + // Getting the agents + auto & pop = model->population; + auto & neigh_this = pop[neighbors[n_this]]; + auto & neigh_other = pop[other.neighbors[n_other]]; + + // Getting the locations in the neighbors + size_t loc_this_in_neigh = neighbors_locations[n_this]; + size_t loc_other_in_neigh = other.neighbors_locations[n_other]; + + // Changing ids + std::swap(neighbors[n_this], other.neighbors[n_other]); + + if (!model->directed) + { + std::swap( + neigh_this.neighbors[loc_this_in_neigh], + neigh_other.neighbors[loc_other_in_neigh] + ); + + // Changing the locations + std::swap(neighbors_locations[n_this], other.neighbors_locations[n_other]); + + std::swap( + neigh_this.neighbors_locations[loc_this_in_neigh], + neigh_other.neighbors_locations[loc_other_in_neigh] + ); + } + +} + +template +inline std::vector< Agent *> Agent::get_neighbors() +{ + std::vector< Agent * > res(n_neighbors, nullptr); + for (size_t i = 0u; i < n_neighbors; ++i) + res[i] = &model->population[neighbors[i]]; + + return res; +} + +template +inline size_t Agent::get_n_neighbors() const +{ + return n_neighbors; +} + +template +inline void Agent::change_state( + Model * model, + epiworld_fast_uint new_state, + epiworld_fast_int queue + ) +{ + + model->actions_add( + this, nullptr, nullptr, nullptr, new_state, queue, nullptr, -1, -1 + ); + + return; + +} + +template +inline const epiworld_fast_uint & Agent::get_state() const { + return state; +} + +template +inline void Agent::reset() +{ + + this->viruses.clear(); + n_viruses = 0u; + + this->tools.clear(); + n_tools = 0u; + + this->state = 0u; + this->state_prev = 0u; + + this->state_last_changed = -1; + +} + +template +inline bool Agent::has_tool(epiworld_fast_uint t) const +{ + + for (auto & tool : tools) + if (tool->get_id() == static_cast(t)) + return true; + + return false; + +} + +template +inline bool Agent::has_tool(std::string name) const +{ + + for (auto & tool : tools) + if (tool->get_name() == name) + return true; + + return false; + +} + +template +inline bool Agent::has_tool(const Tool & tool) const +{ + + return has_tool(tool.get_id()); + +} + +template +inline bool Agent::has_virus(epiworld_fast_uint t) const +{ + for (auto & v : viruses) + if (v->get_id() == static_cast(t)) + return true; + + return false; +} + +template +inline bool Agent::has_virus(std::string name) const +{ + + for (auto & v : viruses) + if (v->get_name() == name) + return true; + + return false; + +} + +template +inline bool Agent::has_virus(const Virus & virus) const +{ + + return has_virus(virus.get_id()); + +} + +template +inline void Agent::print( + Model * model, + bool compressed + ) const +{ + + if (compressed) + { + printf_epiworld( + "Agent: %i, state: %s (%lu), Nvirus: %lu, NTools: %lu, NNeigh: %lu\n", + id, model->states_labels[state].c_str(), state, n_viruses, n_tools, neighbors.size() + ); + } + else { + + printf_epiworld("Information about agent id %i\n", this->id); + printf_epiworld(" State : %s (%lu)\n", model->states_labels[state].c_str(), state); + printf_epiworld(" Virus count : %lu\n", n_viruses); + printf_epiworld(" Tool count : %lu\n", n_tools); + printf_epiworld(" Neigh. count : %lu\n", neighbors.size()); + + size_t nfeats = model->get_agents_data_ncols(); + if (nfeats > 0) + { + + printf_epiworld("This model includes features (%lu): [ ", nfeats); + + int max_to_show = static_cast((nfeats > 10)? 10 : nfeats); + + for (int k = 0; k < max_to_show; ++k) + { + printf_epiworld("%.2f", this->operator[](k)); + + if (k != (max_to_show - 1)) + { + printf_epiworld(", "); + } else { + printf_epiworld(" ]\n"); + } + + } + + } + + } + + return; + +} + +template +inline double & Agent::operator()(size_t j) +{ + + if (model->agents_data_ncols <= j) + throw std::logic_error("The requested feature of the agent is out of range."); + + return *(model->agents_data + j * model->size() + id); + +} + +template +inline double & Agent::operator[](size_t j) +{ + return *(model->agents_data + j * model->size() + id); +} + +template +inline double Agent::operator()(size_t j) const +{ + + if (model->agents_data_ncols <= j) + throw std::logic_error("The requested feature of the agent is out of range."); + + return *(model->agents_data + j * model->size() + id); + +} + +template +inline double Agent::operator[](size_t j) const +{ + return *(model->agents_data + j * model->size() + id); +} + +template +inline Entities Agent::get_entities() +{ + return Entities(*this); +} + +template +inline const Entities_const Agent::get_entities() const +{ + return Entities_const(*this); +} + +template +inline const Entity & Agent::get_entity(size_t i) const +{ + if (i >= n_entities) + throw std::range_error("Trying to get to an agent's entity outside of the range."); + + return model->entities[entities[i]]; +} + +template +inline Entity & Agent::get_entity(size_t i) +{ + if (i >= n_entities) + throw std::range_error("Trying to get to an agent's entity outside of the range."); + + return model->entities[entities[i]]; +} + +template +inline size_t Agent::get_n_entities() const +{ + return n_entities; +} + +template +inline bool Agent::operator==(const Agent & other) const +{ + + EPI_DEBUG_FAIL_AT_TRUE( + n_neighbors != other.n_neighbors, + "Agent:: n_eighbors don't match" + ) + + + for (size_t i = 0u; i < n_neighbors; ++i) + { + EPI_DEBUG_FAIL_AT_TRUE( + neighbors[i] != other.neighbors[i], + "Agent:: neighbor[i] don't match" + ) + } + + EPI_DEBUG_FAIL_AT_TRUE( + n_entities != other.n_entities, + "Agent:: n_entities don't match" + ) + + + for (size_t i = 0u; i < n_entities; ++i) + { + EPI_DEBUG_FAIL_AT_TRUE( + entities[i] != other.entities[i], + "Agent:: entities[i] don't match" + ) + } + + EPI_DEBUG_FAIL_AT_TRUE( + state != other.state, + "Agent:: state don't match" + ) + + + EPI_DEBUG_FAIL_AT_TRUE( + state_prev != other.state_prev, + "Agent:: state_prev don't match" + ) + + + // EPI_DEBUG_FAIL_AT_TRUE( + // state_last_changed != other.state_last_changed, + // "Agent:: state_last_changed don't match" + // ) ///< Last time the agent was updated. + + + EPI_DEBUG_FAIL_AT_TRUE( + n_viruses != other.n_viruses, + "Agent:: n_viruses don't match" + ) + + + for (size_t i = 0u; i < n_viruses; ++i) + { + + EPI_DEBUG_FAIL_AT_TRUE( + *viruses[i] != *other.viruses[i], + "Agent:: viruses[i] don't match" + ) + + } + + EPI_DEBUG_FAIL_AT_TRUE(n_tools != other.n_tools, "Agent:: n_tools don't match") + + for (size_t i = 0u; i < n_tools; ++i) + { + + EPI_DEBUG_FAIL_AT_TRUE( + tools[i] != other.tools[i], + "Agent:: tools[i] don't match" + ) + + } + + return true; + +} + +#undef CHECK_COALESCE_ + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/agentssample-bones.hpp b/inst/include/epiworld/agentssample-bones.hpp new file mode 100644 index 00000000..165c4985 --- /dev/null +++ b/inst/include/epiworld/agentssample-bones.hpp @@ -0,0 +1,340 @@ +#ifndef EPIWORLD_AGENTS_BONES_HPP +#define EPIWORLD_AGENTS_BONES_HPP + +class SAMPLETYPE { +public: + static const int MODEL = 0; + static const int ENTITY = 1; + static const int AGENT = 2; +}; + +// template +// class Agent; + +// template +// class Model; + +// template +// class Entity; + +/** + * @brief Sample of agents + * + * This class allows sampling agents from Entity and Model. + * + * @tparam TSeq + */ +template +class AgentsSample { +private: + + size_t sample_size = 0u; + + std::vector< Agent* > * agents = nullptr; ///< Pointer to sample of agents + size_t * agents_n = nullptr; ///< Size of sample of agents + + std::vector< size_t > * agents_left = nullptr; ///< Pointer to agents left (iota) + size_t * agents_left_n = nullptr; ///< Size of agents left + + Model * model = nullptr; ///< Extracts runif() and (if the case) population. + Entity * entity = nullptr; /// + Agent * agent = nullptr; + + int sample_type = SAMPLETYPE::AGENT; + + void sample_n(size_t n); ///< Backbone function for sampling + + +public: + + // Not available (for now) + AgentsSample() = delete; ///< Default constructor + AgentsSample(const AgentsSample & a) = delete; ///< Copy constructor + AgentsSample(AgentsSample && a) = delete; ///< Move constructor + + AgentsSample(Model & model_, size_t n, bool truncate = false); + AgentsSample(Model * model, Entity & entity_, size_t n, bool truncate = false); + AgentsSample(Model * model, Agent & agent_, size_t n, bool truncate = false); + + ~AgentsSample(); + + typename std::vector< Agent * >::iterator begin(); + typename std::vector< Agent * >::iterator end(); + + Agent * operator[](size_t n); + Agent * operator()(size_t n); + size_t size() const noexcept; + +}; + +template +inline AgentsSample::AgentsSample( + Model & model_, + size_t n, + bool truncate + ) { + + if (truncate) + { + + if (n > model_.size()) + n = model_.size(); + + } else if (n > model_.size()) + throw std::logic_error( + "There are only " + std::to_string(model_.size()) + " agents. You cannot " + + "sample " + std::to_string(n)); + + sample_size = n; + model = &model_; + sample_type = SAMPLETYPE::MODEL; + + agents = &model_.sampled_population; + agents_n = &model_.sampled_population_n; + + agents_left = &model_.population_left; + agents_left_n = &model_.population_left_n; + + sample_n(n); + + return; + +} + +template +inline AgentsSample::AgentsSample( + Model * model, + Entity & entity_, + size_t n, bool truncate + ) { + + if (truncate) + { + + if (n > entity_.size()) + n = entity_.size(); + + } else if (n > entity_.size()) + throw std::logic_error( + "There are only " + std::to_string(entity_.size()) + " agents. You cannot " + + "sample " + std::to_string(n)); + + sample_size = n; + model = &entity_.model; + sample_type = SAMPLETYPE::ENTITY; + + agents = &entity_.sampled_agents; + agents_n = &entity_.sampled_agents_n; + + agents_left = &entity_.sampled_agents_left; + agents_left_n = &entity_.sampled_agents_left_n; + + sample_n(n); + + return; + +} + +/** + * @brief Sample from the agent's entities + * + * For example, how many individuals the agent contacts in a given point in time. + * + * @tparam TSeq + * @param agent_ + * @param n Sample size + * @param truncate If the agent has fewer than `n` connections, then truncate = true + * will automatically reduce the number of possible samples. Otherwise, if false, then + * it returns an error. + */ +template +inline AgentsSample::AgentsSample( + Model * model, + Agent & agent_, + size_t n, + bool truncate + ) +{ + + sample_type = SAMPLETYPE::AGENT; + + agent = &agent_; + + agents = &agent_.sampled_agents; + agents_n = &agent_.sampled_agents_n; + + agents_left = &agent_.sampled_agents_left; + agents_left_n = &agent_.sampled_agents_left_n; + + // Computing the cumulative sum of counts across entities + size_t agents_in_entities = 0; + Entities entities_a = agent->get_entities(); + + std::vector< int > cum_agents_count(entities_a.size(), 0); + int idx = -1; + for (auto & e : entities_a) + { + if (++idx == 0) + cum_agents_count[idx] = (e->size() - 1u); + else + cum_agents_count[idx] = ( + (e->size() - 1u) + + cum_agents_count[idx - 1] + ); + + agents_in_entities += (e->size() - 1u); + } + + if (truncate) + { + + if (n > agents_in_entities) + n = agents_in_entities; + + } else if (n > agents_in_entities) + throw std::logic_error( + "There are only " + std::to_string(agents_in_entities) + " agents. You cannot " + + "sample " + std::to_string(n)); + + sample_size = n; + + if (agents->size() < n) + agents->resize(n); + + for (size_t i = 0u; i < n; ++i) + { + int jth = std::floor(model->runif() * agents_in_entities); + for (size_t e = 0u; e < cum_agents_count.size(); ++e) + { + // Are we in the limit? + if (jth <= cum_agents_count[e]) + { + if (e == 0) // From the first group + agents->operator[](i) = entities_a[e][jth]; + else + agents->operator[](i) = entities_a[e][jth - cum_agents_count[e - 1]]; + + break; + } + + } + } + + return; + +} + +template +inline AgentsSample::~AgentsSample() {} + +template +inline size_t AgentsSample::size() const noexcept +{ + return this->sample_size; +} + +template +inline Agent * AgentsSample::operator[](size_t i) +{ + + return agents->operator[](i); + +} + +template +inline Agent * AgentsSample::operator()(size_t i) +{ + + if (i >= this->sample_size) + throw std::range_error("The requested agent is out of range."); + + return agents->operator[](i); + +} + +template +inline typename std::vector< Agent * >::iterator AgentsSample::begin() +{ + + if (sample_size > 0u) + return agents->begin(); + else + return agents->end(); + +} + +template +inline typename std::vector< Agent * >::iterator AgentsSample::end() +{ + + return agents->begin() + sample_size; + +} + +template +inline void AgentsSample::sample_n(size_t n) +{ + + // Checking if the size of the entity has changed (or hasn't been initialized) + if (sample_type == SAMPLETYPE::MODEL) + { + + if (model->size() != agents_left->size()) + { + agents_left->resize(model->size(), 0u); + std::iota(agents_left->begin(), agents_left->end(), 0u); + } + + + } else if (sample_type == SAMPLETYPE::ENTITY) { + + if (entity->size() != agents_left->size()) + { + + agents_left->resize(entity->size(), 0u); + std::iota(agents_left->begin(), agents_left->end(), 0u); + + } + + } + + // Restart the counter of agents left + *agents_left_n = agents_left->size(); + + if (agents->size() < sample_size) + agents->resize(sample_size, nullptr); + + if (sample_type == SAMPLETYPE::MODEL) + { + + for (size_t i = 0u; i < n; ++i) + { + + size_t ith = agents_left->operator[](model->runif() * ((*agents_left_n)--)); + agents->operator[](i) = &model->population[ith]; + + // Updating list + std::swap(agents_left->operator[](ith), agents_left->operator[](*agents_left_n)); + + } + + } else if (sample_type == SAMPLETYPE::ENTITY) { + + for (size_t i = 0u; i < n; ++i) + { + + size_t ith = agents_left->operator[](model->runif() * (--(*agents_left_n))); + agents->operator[](i) = entity->agents[ith]; + + // Updating list + std::swap(agents_left->operator[](ith), agents_left->operator[](*agents_left_n)); + + } + + } + + return; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/config.hpp b/inst/include/epiworld/config.hpp new file mode 100644 index 00000000..8c65e067 --- /dev/null +++ b/inst/include/epiworld/config.hpp @@ -0,0 +1,282 @@ +#ifndef EPIWORLD_CONFIG_HPP +#define EPIWORLD_CONFIG_HPP + +#ifndef printf_epiworld + #define printf_epiworld fflush(stdout);printf +#endif + +#ifndef EPIWORLD_MAXNEIGHBORS + #define EPIWORLD_MAXNEIGHBORS 1048576 +#endif + +#ifdef _OPENMP + #include +// #else +// #define omp_get_thread_num() 0 +// #define omp_set_num_threads() 1 +#endif + +#ifndef epiworld_double + #define epiworld_double float +#endif + +#ifndef epiworld_fast_int + #define epiworld_fast_int long long int +#endif + +#ifndef epiworld_fast_uint + #define epiworld_fast_uint unsigned long long int +#endif + +#define EPI_DEFAULT_TSEQ int + +template +class Model; + +template +class Agent; + +template +class PersonTools; + +template +class Virus; + +template +class Viruses; + +template +class Viruses_const; + +template +class Tool; + +template +class Tools; + +template +class Tools_const; + +template +class Entity; + +template +using VirusPtr = std::shared_ptr< Virus< TSeq > >; + +template +using ToolPtr = std::shared_ptr< Tool< TSeq > >; + +template +using ToolFun = std::function&,Agent*,VirusPtr,Model*)>; + +template +using MixerFun = std::function*,VirusPtr,Model*)>; + +template +using MutFun = std::function*,Virus&,Model*)>; + +template +using PostRecoveryFun = std::function*,Virus&,Model*)>; + +template +using VirusFun = std::function*,Virus&,Model*)>; + +template +using UpdateFun = std::function*,Model*)>; + +template +using GlobalFun = std::function*)>; + +template +struct Action; + +template +using ActionFun = std::function&,Model*)>; + +/** + * @brief Decides how to distribute viruses at initialization + */ +template +using VirusToAgentFun = std::function&,Model*)>; + +/** + * @brief Decides how to distribute tools at initialization + */ +template +using ToolToAgentFun = std::function&,Model*)>; + +/** + * @brief Decides how to distribute entities at initialization + */ +template +using EntityToAgentFun = std::function&,Model*)>; + +/** + * @brief Action data for update an agent + * + * @tparam TSeq + */ +template +struct Action { + Agent * agent; + VirusPtr virus; + ToolPtr tool; + Entity * entity; + epiworld_fast_int new_state; + epiworld_fast_int queue; + ActionFun call; + int idx_agent; + int idx_object; +public: +/** + * @brief Construct a new Action object + * + * All the parameters are rather optional. + * + * @param agent_ Agent over who the action will happen + * @param virus_ Virus to add + * @param tool_ Tool to add + * @param virus_idx Index of virus to be removed (if needed) + * @param tool_idx Index of tool to be removed (if needed) + * @param new_state_ Next state + * @param queue_ Efect on the queue + * @param call_ The action call (if needed) + * @param idx_agent_ Location of agent in object. + * @param idx_object_ Location of object in agent. + */ + Action( + Agent * agent_, + VirusPtr virus_, + ToolPtr tool_, + Entity * entity_, + epiworld_fast_int new_state_, + epiworld_fast_int queue_, + ActionFun call_, + int idx_agent_, + int idx_object_ + ) : agent(agent_), virus(virus_), tool(tool_), entity(entity_), + new_state(new_state_), + queue(queue_), call(call_), idx_agent(idx_agent_), idx_object(idx_object_) { + return; + }; +}; + +/** + * @name Constants in epiworld + * + * @details The following are the default values some probabilities and + * rates take when no value has been specified in the model. + */ +///@{ +#ifndef DEFAULT_TOOL_CONTAGION_REDUCTION + #define DEFAULT_TOOL_CONTAGION_REDUCTION 0.0 +#endif + +#ifndef DEFAULT_TOOL_TRANSMISSION_REDUCTION + #define DEFAULT_TOOL_TRANSMISSION_REDUCTION 0.0 +#endif + +#ifndef DEFAULT_TOOL_RECOVERY_ENHANCER + #define DEFAULT_TOOL_RECOVERY_ENHANCER 0.0 +#endif + +#ifndef DEFAULT_TOOL_DEATH_REDUCTION + #define DEFAULT_TOOL_DEATH_REDUCTION 0.0 +#endif + +#ifndef EPI_DEFAULT_VIRUS_PROB_INFECTION + #define EPI_DEFAULT_VIRUS_PROB_INFECTION 1.0 +#endif + +#ifndef EPI_DEFAULT_VIRUS_PROB_RECOVERY + #define EPI_DEFAULT_VIRUS_PROB_RECOVERY 0.1428 +#endif + +#ifndef EPI_DEFAULT_VIRUS_PROB_DEATH + #define EPI_DEFAULT_VIRUS_PROB_DEATH 0.0 +#endif + +#ifndef EPI_DEFAULT_INCUBATION_DAYS + #define EPI_DEFAULT_INCUBATION_DAYS 7.0 +#endif +///@} + +#ifdef EPI_DEBUG + #define EPI_DEBUG_PRINTF printf_epiworld + + #define EPI_DEBUG_ERROR(etype, msg) \ + (etype)("[[epi-debug]] (error) " + std::string(msg)); + + #define EPI_DEBUG_NOTIFY_ACTIVE() \ + EPI_DEBUG_PRINTF("DEBUGGING ON (compiled with EPI_DEBUG defined)%s\n", ""); + + #define EPI_DEBUG_ALL_NON_NEGATIVE(vect) \ + for (auto & v : vect) \ + if (static_cast(v) < 0.0) \ + throw EPI_DEBUG_ERROR(std::logic_error, "A negative value not allowed."); + + #define EPI_DEBUG_SUM_DBL(vect, num) \ + double _epi_debug_sum = 0.0; \ + for (auto & v : vect) \ + { \ + _epi_debug_sum += static_cast(v);\ + if (_epi_debug_sum > static_cast(num)) \ + throw EPI_DEBUG_ERROR(std::logic_error, "The sum of elements not reached."); \ + } + + #define EPI_DEBUG_SUM_INT(vect, num) \ + int _epi_debug_sum = 0; \ + for (auto & v : vect) \ + { \ + _epi_debug_sum += static_cast(v);\ + if (_epi_debug_sum > static_cast(num)) \ + throw EPI_DEBUG_ERROR(std::logic_error, "The sum of elements not reached."); \ + } + + #define EPI_DEBUG_VECTOR_MATCH_INT(a, b, c) \ + if (a.size() != b.size()) {\ + EPI_DEBUG_PRINTF("In '%s'", std::string(c).c_str()); \ + EPI_DEBUG_PRINTF("Size of vector a: %lu\n", (a).size());\ + EPI_DEBUG_PRINTF("Size of vector b: %lu\n", (b).size());\ + throw EPI_DEBUG_ERROR(std::length_error, "The vectors do not match size."); \ + }\ + for (int _i = 0; _i < static_cast(a.size()); ++_i) \ + if (a[_i] != b[_i]) {\ + EPI_DEBUG_PRINTF("In '%s'", std::string(c).c_str()); \ + EPI_DEBUG_PRINTF("Iterating the last 5 values%s:\n", ""); \ + for (int _j = std::max(0, static_cast(_i) - 4); _j <= _i; ++_j) \ + { \ + EPI_DEBUG_PRINTF( \ + "a[%i]: %i; b[%i]: %i\n", \ + _j, \ + static_cast(a[_j]), \ + _j, static_cast(b[_j])); \ + } \ + throw EPI_DEBUG_ERROR(std::logic_error, "The vectors do not match."); \ + } + + #define EPI_DEBUG_FAIL_AT_TRUE(a,b) \ + if (a) \ + {\ + throw EPI_DEBUG_ERROR(std::logic_error, b); \ + } +#else + #define EPI_DEBUG_PRINTF(fmt, ...) + #define EPI_DEBUG_ERROR(fmt, ...) + #define EPI_DEBUG_NOTIFY_ACTIVE() + #define EPI_DEBUG_ALL_NON_NEGATIVE(vect) + #define EPI_DEBUG_SUM_DBL(vect, num) + #define EPI_DEBUG_SUM_INT(vect, num) + #define EPI_DEBUG_VECTOR_MATCH_INT(a, b, c) + #define EPI_DEBUG_FAIL_AT_TRUE(a, b) \ + if (a) \ + return false; +#endif + +#ifdef EPI_DEBUG_NO_THREAD_ID + #define EPI_GET_THREAD_ID() 0 +#else + #define EPI_GET_THREAD_ID() omp_get_thread_num() +#endif + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/database-bones.hpp b/inst/include/epiworld/database-bones.hpp new file mode 100644 index 00000000..be62c671 --- /dev/null +++ b/inst/include/epiworld/database-bones.hpp @@ -0,0 +1,305 @@ +#ifndef EPIWORLD_DATABASE_BONES_HPP +#define EPIWORLD_DATABASE_BONES_HPP + +template +class Model; + +template +class Virus; + +template +class UserData; + +template +inline void default_add_virus(Action & a, Model * m); + +template +inline void default_add_tool(Action & a, Model * m); + +template +inline void default_rm_virus(Action & a, Model * m); + +template +inline void default_rm_tool(Action & a, Model * m); + +/** + * @brief Statistical data about the process + * + * @tparam TSeq + */ +template +class DataBase { + friend class Model; + friend void default_add_virus(Action & a, Model * m); + friend void default_add_tool(Action & a, Model * m); + friend void default_rm_virus(Action & a, Model * m); + friend void default_rm_tool(Action & a, Model * m); +private: + Model * model; + + // Variants information + MapVec_type virus_id; ///< The squence is the key + std::vector< std::string > virus_name; + std::vector< TSeq> virus_sequence; + std::vector< int > virus_origin_date; + std::vector< int > virus_parent_id; + + MapVec_type tool_id; ///< The squence is the key + std::vector< std::string > tool_name; + std::vector< TSeq> tool_sequence; + std::vector< int > tool_origin_date; + + std::function(const TSeq&)> seq_hasher = default_seq_hasher; + std::function seq_writer = default_seq_writer; + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + std::vector< std::vector > today_virus; + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + std::vector< std::vector > today_tool; + + // {Susceptible, Infected, etc.} + std::vector< int > today_total; + + // Totals + int today_total_nviruses_active = 0; + + int sampling_freq = 1; + + // Variants history + std::vector< int > hist_virus_date; + std::vector< int > hist_virus_id; + std::vector< epiworld_fast_uint > hist_virus_state; + std::vector< int > hist_virus_counts; + + // Tools history + std::vector< int > hist_tool_date; + std::vector< int > hist_tool_id; + std::vector< epiworld_fast_uint > hist_tool_state; + std::vector< int > hist_tool_counts; + + // Overall hist + std::vector< int > hist_total_date; + std::vector< int > hist_total_nviruses_active; + std::vector< epiworld_fast_uint > hist_total_state; + std::vector< int > hist_total_counts; + std::vector< int > hist_transition_matrix; + + // Transmission network + std::vector< int > transmission_date; ///< Date of the transmission event + std::vector< int > transmission_source; ///< Id of the source + std::vector< int > transmission_target; ///< Id of the target + std::vector< int > transmission_virus; ///< Id of the variant + std::vector< int > transmission_source_exposure_date; ///< Date when the source acquired the variant + + std::vector< int > transition_matrix; + + UserData user_data; + + void update_state( + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state, + bool undo = false + ); + + void update_virus( + epiworld_fast_uint virus_id, + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state + ); + + void update_tool( + epiworld_fast_uint tool_id, + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state + ); + + void record_transition(epiworld_fast_uint from, epiworld_fast_uint to, bool undo); + + +public: + + #ifdef EPI_DEBUG + int n_transmissions_potential = 0; + int n_transmissions_today = 0; + #endif + + DataBase() = delete; + DataBase(Model & m) : model(&m), user_data(m) {}; + DataBase(const DataBase & db); + // DataBase & operator=(const DataBase & m); + + /** + * @brief Registering a new variant + * + * @param v Pointer to the new virus. + * Since viruses are originated in the agent, the numbers simply move around. + * From the parent virus to the new virus. And the total number of infected + * does not change. + */ + void record_virus(Virus & v); + void record_tool(Tool & t); + void set_seq_hasher(std::function(TSeq)> fun); + void reset(); + Model * get_model(); + void record(); + + const std::vector< TSeq > & get_sequence() const; + const std::vector< int > & get_nexposed() const; + size_t size() const; + + /** + * @name Get recorded information from the model + * + * @param what std::string, The state, e.g., 0, 1, 2, ... + * @return In `get_today_total`, the current counts of `what`. + * @return In `get_today_virus`, the current counts of `what` for + * each virus. + * @return In `get_hist_total`, the time series of `what` + * @return In `get_hist_virus`, the time series of what for each virus. + * @return In `get_hist_total_date` and `get_hist_virus_date` the + * corresponding date + */ + ///@{ + int get_today_total(std::string what) const; + int get_today_total(epiworld_fast_uint what) const; + void get_today_total( + std::vector< std::string > * state = nullptr, + std::vector< int > * counts = nullptr + ) const; + + void get_today_virus( + std::vector< std::string > & state, + std::vector< int > & id, + std::vector< int > & counts + ) const; + + void get_hist_total( + std::vector< int > * date, + std::vector< std::string > * state, + std::vector< int > * counts + ) const; + + void get_hist_virus( + std::vector< int > & date, + std::vector< int > & id, + std::vector< std::string > & state, + std::vector< int > & counts + ) const; + + void get_hist_tool( + std::vector< int > & date, + std::vector< int > & id, + std::vector< std::string > & state, + std::vector< int > & counts + ) const; + + void get_hist_transition_matrix( + std::vector< std::string > & state_from, + std::vector< std::string > & state_to, + std::vector< int > & date, + std::vector< int > & counts, + bool skip_zeros + ) const; + ///@} + + /** + * @brief Get the transmissions object + * + * @param date + * @param source + * @param target + * @param virus + * @param source_exposure_date + */ + ///@{ + void get_transmissions( + std::vector & date, + std::vector & source, + std::vector & target, + std::vector & virus, + std::vector & source_exposure_date + ) const; + + void get_transmissions( + int * date, + int * source, + int * target, + int * virus, + int * source_exposure_date + ) const; + ///@} + + void write_data( + std::string fn_virus_info, + std::string fn_virus_hist, + std::string fn_tool_info, + std::string fn_tool_hist, + std::string fn_total_hist, + std::string fn_transmission, + std::string fn_transition, + std::string fn_reproductive_number, + std::string fn_generation_time + ) const; + + void record_transmission(int i, int j, int virus, int i_expo_date); + + size_t get_n_viruses() const; + size_t get_n_tools() const; + + void set_user_data(std::vector< std::string > names); + void add_user_data(std::vector< epiworld_double > x); + void add_user_data(epiworld_fast_uint j, epiworld_double x); + UserData & get_user_data(); + + + /** + * @brief Computes the reproductive number of each case + * + * @details By definition, whereas it computes R0 (basic reproductive number) + * or Rt/R (the effective reproductive number) will depend on whether the + * virus is allowed to circulate naïvely or not, respectively. + * + * @param fn File where to write out the reproductive number. + */ + ///@{ + MapVec_type reproductive_number() const; + + void reproductive_number( + std::string fn + ) const; + ///@} + + /** + * @brief Calculates the transition probabilities + * + * @return std::vector< epiworld_double > + */ + std::vector< epiworld_double > transition_probability( + bool print = true + ) const; + + bool operator==(const DataBase & other) const; + bool operator!=(const DataBase & other) const {return !operator==(other);}; + + /** + * Calculates the generating time + * @param agent_id,virus_id,time,gentime vectors where to save the values agent_id + */ + ///@{ + void generation_time( + std::vector< int > & agent_id, + std::vector< int > & virus_id, + std::vector< int > & time, + std::vector< int > & gentime + ) const; + + void generation_time( + std::string fn + ) const; + ///@} + +}; + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/database-meat.hpp b/inst/include/epiworld/database-meat.hpp new file mode 100644 index 00000000..926c5227 --- /dev/null +++ b/inst/include/epiworld/database-meat.hpp @@ -0,0 +1,1791 @@ +#ifndef EPIWORLD_DATABASE_MEAT_HPP +#define EPIWORLD_DATABASE_MEAT_HPP + +template +inline void DataBase::reset() +{ + + // Initializing the counts + today_total.resize(model->nstates); + std::fill(today_total.begin(), today_total.end(), 0); + for (auto & p : model->get_agents()) + ++today_total[p.get_state()]; + + #ifdef EPI_DEBUG + // Only the first should be different from zero + { + int n = static_cast(model->size()); + if (today_total[0] != n) + throw std::runtime_error("The number of susceptible agents is not equal to the total number of agents."); + + if (std::accumulate(today_total.begin(), today_total.end(), 0) != n) + throw std::runtime_error("The total number of agents is not equal to the sum of the number of agents in each state."); + + } + #endif + + + transition_matrix.resize(model->nstates * model->nstates); + std::fill(transition_matrix.begin(), transition_matrix.end(), 0); + for (size_t s = 0u; s < model->nstates; ++s) + transition_matrix[s + s * model->nstates] = today_total[s]; + + hist_virus_date.clear(); + hist_virus_id.clear(); + hist_virus_state.clear(); + hist_virus_counts.clear(); + + hist_tool_date.clear(); + hist_tool_id.clear(); + hist_tool_state.clear(); + hist_tool_counts.clear(); + + today_virus.resize(get_n_viruses()); + std::fill(today_virus.begin(), today_virus.begin(), std::vector(model->nstates, 0)); + + today_tool.resize(get_n_tools()); + std::fill(today_tool.begin(), today_tool.begin(), std::vector(model->nstates, 0)); + + hist_total_date.clear(); + hist_total_state.clear(); + hist_total_nviruses_active.clear(); + hist_total_counts.clear(); + hist_transition_matrix.clear(); + + transmission_date.clear(); + transmission_virus.clear(); + transmission_source.clear(); + transmission_target.clear(); + transmission_source_exposure_date.clear(); + + return; + +} + +template +inline DataBase::DataBase(const DataBase & db) : + virus_id(db.virus_id), + virus_name(db.virus_name), + virus_sequence(db.virus_sequence), + virus_origin_date(db.virus_origin_date), + virus_parent_id(db.virus_parent_id), + tool_id(db.tool_id), + tool_name(db.tool_name), + tool_sequence(db.tool_sequence), + tool_origin_date(db.tool_origin_date), + seq_hasher(db.seq_hasher), + seq_writer(db.seq_writer), + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + today_virus(db.today_virus), + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + today_tool(db.today_tool), + // {Susceptible, Infected, etc.} + today_total(db.today_total), + // Totals + today_total_nviruses_active(db.today_total_nviruses_active), + sampling_freq(db.sampling_freq), + // Variants history + hist_virus_date(db.hist_virus_date), + hist_virus_id(db.hist_virus_id), + hist_virus_state(db.hist_virus_state), + hist_virus_counts(db.hist_virus_counts), + // Tools history + hist_tool_date(db.hist_tool_date), + hist_tool_id(db.hist_tool_id), + hist_tool_state(db.hist_tool_state), + hist_tool_counts(db.hist_tool_counts), + // Overall hist + hist_total_date(db.hist_total_date), + hist_total_nviruses_active(db.hist_total_nviruses_active), + hist_total_state(db.hist_total_state), + hist_total_counts(db.hist_total_counts), + hist_transition_matrix(db.hist_transition_matrix), + // Transmission network + transmission_date(db.transmission_date), + transmission_source(db.transmission_source), + transmission_target(db.transmission_target), + transmission_virus(db.transmission_virus), + transmission_source_exposure_date(db.transmission_source_exposure_date), + transition_matrix(db.transition_matrix), + user_data(nullptr) +{} + +// DataBase & DataBase::operator=(const DataBase & m) +// { + +// } + +template +inline Model * DataBase::get_model() { + return model; +} + +template +inline const std::vector< TSeq > & DataBase::get_sequence() const { + return virus_sequence; +} + +template +inline void DataBase::record() +{ + + //////////////////////////////////////////////////////////////////////////// + // DEBUGGING BLOCK + //////////////////////////////////////////////////////////////////////////// + EPI_DEBUG_SUM_INT(today_total, model->size()) + EPI_DEBUG_ALL_NON_NEGATIVE(today_total) + + #ifdef EPI_DEBUG + // Checking whether the sums correspond + std::vector< int > _today_total_cp(today_total.size(), 0); + for (auto & p : model->population) + _today_total_cp[p.get_state()]++; + + EPI_DEBUG_VECTOR_MATCH_INT( + _today_total_cp, today_total, + "Sums of __today_total_cp in database-meat.hpp" + ) + + if (model->today() == 0) + { + if (hist_total_date.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_date should be of length 0.") + if (hist_total_nviruses_active.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_nviruses_active should be of length 0.") + if (hist_total_state.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_state should be of length 0.") + if (hist_total_counts.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_total_counts should be of length 0.") + if (hist_virus_date.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_date should be of length 0.") + if (hist_virus_id.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_id should be of length 0.") + if (hist_virus_state.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_state should be of length 0.") + if (hist_virus_counts.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_virus_counts should be of length 0.") + if (hist_tool_date.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_date should be of length 0.") + if (hist_tool_id.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_id should be of length 0.") + if (hist_tool_state.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_state should be of length 0.") + if (hist_tool_counts.size() != 0) + EPI_DEBUG_ERROR(std::logic_error, "DataBase::record hist_tool_counts should be of length 0.") + } + #endif + //////////////////////////////////////////////////////////////////////////// + + // Only store every now and then + if ((model->today() % sampling_freq) == 0) + { + + // Recording virus's history + for (auto & p : virus_id) + { + + for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) + { + + hist_virus_date.push_back(model->today()); + hist_virus_id.push_back(p.second); + hist_virus_state.push_back(s); + hist_virus_counts.push_back(today_virus[p.second][s]); + + } + + } + + // Recording tool's history + for (auto & p : tool_id) + { + + for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) + { + + hist_tool_date.push_back(model->today()); + hist_tool_id.push_back(p.second); + hist_tool_state.push_back(s); + hist_tool_counts.push_back(today_tool[p.second][s]); + + } + + } + + // Recording the overall history + for (epiworld_fast_uint s = 0u; s < model->nstates; ++s) + { + hist_total_date.push_back(model->today()); + hist_total_nviruses_active.push_back(today_total_nviruses_active); + hist_total_state.push_back(s); + hist_total_counts.push_back(today_total[s]); + } + + for (auto cell : transition_matrix) + hist_transition_matrix.push_back(cell); + + // Now the diagonal must reflect the state + for (size_t s_i = 0u; s_i < model->nstates; ++s_i) + { + + for (size_t s_j = 0u; s_j < model->nstates; ++s_j) + { + + if ((s_i != s_j) && (transition_matrix[s_i + s_j * model->nstates] > 0)) + { + transition_matrix[s_j + s_j * model->nstates] += + transition_matrix[s_i + s_j * model->nstates]; + + transition_matrix[s_i + s_j * model->nstates] = 0; + } + + } + + } + + #ifdef EPI_DEBUG + for (size_t s_i = 0u; s_i < model->nstates; ++s_i) + { + if (transition_matrix[s_i + s_i * model->nstates] != + today_total[s_i]) + throw std::logic_error( + "The diagonal of the updated transition Matrix should match the daily totals" + ); + } + #endif + + } + +} + +template +inline void DataBase::record_virus(Virus & v) +{ + + // If no sequence, then need to add one. This is regardless of the case + if (v.get_sequence() == nullptr) + v.set_sequence(default_sequence( + static_cast(virus_name.size()) + )); + + // Negative id -> virus hasn't been recorded + if (v.get_id() < 0) + { + + + // Generating the hash + std::vector< int > hash = seq_hasher(*v.get_sequence()); + + epiworld_fast_uint new_id = virus_id.size(); + virus_id[hash] = new_id; + virus_name.push_back(v.get_name()); + virus_sequence.push_back(*v.get_sequence()); + virus_origin_date.push_back(model->today()); + + virus_parent_id.push_back(v.get_id()); // Must be -99 + + today_virus.push_back({}); + today_virus[new_id].resize(model->nstates, 0); + + // Updating the variant + v.set_id(new_id); + v.set_date(model->today()); + + today_total_nviruses_active++; + + } else { // In this case, the virus is already on record, need to make sure + // The new sequence is new. + + // Updating registry + std::vector< int > hash = seq_hasher(*v.get_sequence()); + epiworld_fast_uint old_id = v.get_id(); + epiworld_fast_uint new_id; + + // If the sequence is new, then it means that the + if (virus_id.find(hash) == virus_id.end()) + { + + new_id = virus_id.size(); + virus_id[hash] = new_id; + virus_name.push_back(v.get_name()); + virus_sequence.push_back(*v.get_sequence()); + virus_origin_date.push_back(model->today()); + + virus_parent_id.push_back(old_id); + + today_virus.push_back({}); + today_virus[new_id].resize(model->nstates, 0); + + // Updating the variant + v.set_id(new_id); + v.set_date(model->today()); + + today_total_nviruses_active++; + + } else { + + // Finding the id + new_id = virus_id[hash]; + + // Reflecting the change + v.set_id(new_id); + v.set_date(virus_origin_date[new_id]); + + } + + // Moving statistics (only if we are affecting an individual) + if (v.get_agent() != nullptr) + { + // Correcting math + epiworld_fast_uint tmp_state = v.get_agent()->get_state(); + today_virus[old_id][tmp_state]--; + today_virus[new_id][tmp_state]++; + + } + + } + + return; + +} + +template +inline void DataBase::record_tool(Tool & t) +{ + + if (t.get_sequence() == nullptr) + t.set_sequence(default_sequence( + static_cast(tool_name.size()) + )); + + if (t.get_id() < 0) + { + + std::vector< int > hash = seq_hasher(*t.get_sequence()); + epiworld_fast_uint new_id = tool_id.size(); + tool_id[hash] = new_id; + tool_name.push_back(t.get_name()); + tool_sequence.push_back(*t.get_sequence()); + tool_origin_date.push_back(model->today()); + + today_tool.push_back({}); + today_tool[new_id].resize(model->nstates, 0); + + // Updating the tool + t.set_id(new_id); + t.set_date(model->today()); + + } else { + + // Updating registry + std::vector< int > hash = seq_hasher(*t.get_sequence()); + epiworld_fast_uint old_id = t.get_id(); + epiworld_fast_uint new_id; + + if (tool_id.find(hash) == tool_id.end()) + { + + new_id = tool_id.size(); + tool_id[hash] = new_id; + tool_name.push_back(t.get_name()); + tool_sequence.push_back(*t.get_sequence()); + tool_origin_date.push_back(model->today()); + + today_tool.push_back({}); + today_tool[new_id].resize(model->nstates, 0); + + // Updating the tool + t.set_id(new_id); + t.set_date(model->today()); + + } else { + + // Finding the id + new_id = tool_id[hash]; + + // Reflecting the change + t.set_id(new_id); + t.set_date(tool_origin_date[new_id]); + + } + + // Moving statistics (only if we are affecting an individual) + if (t.get_agent() != nullptr) + { + // Correcting math + epiworld_fast_uint tmp_state = t.get_agent()->get_state(); + today_tool[old_id][tmp_state]--; + today_tool[new_id][tmp_state]++; + + } + + } + + + + return; +} + +template +inline size_t DataBase::size() const +{ + return virus_id.size(); +} + +template +inline void DataBase::update_state( + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state, + bool undo +) { + + if (undo) + { + + today_total[prev_state]++; + today_total[new_state]--; + + } else { + + today_total[prev_state]--; + today_total[new_state]++; + + } + + record_transition(prev_state, new_state, undo); + + return; +} + +template +inline void DataBase::update_virus( + epiworld_fast_uint virus_id, + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state +) { + + today_virus[virus_id][prev_state]--; + today_virus[virus_id][new_state]++; + + return; + +} + +template +inline void DataBase::update_tool( + epiworld_fast_uint tool_id, + epiworld_fast_uint prev_state, + epiworld_fast_uint new_state +) { + + + today_tool[tool_id][prev_state]--; + today_tool[tool_id][new_state]++; + + return; + +} + +template +inline void DataBase::record_transition( + epiworld_fast_uint from, + epiworld_fast_uint to, + bool undo +) { + + if (undo) + { + + transition_matrix[to * model->nstates + from]--; + transition_matrix[from * model->nstates + from]++; + + } else { + + transition_matrix[to * model->nstates + from]++; + transition_matrix[from * model->nstates + from]--; + + } + + #ifdef EPI_DEBUG + if (transition_matrix[from * model->nstates + from] < 0) + throw std::logic_error("An entry in transition matrix is negative."); + #endif + +} + +template +inline int DataBase::get_today_total( + std::string what +) const +{ + + for (auto i = 0u; i < model->states_labels.size(); ++i) + { + if (model->states_labels[i] == what) + return today_total[i]; + } + + throw std::range_error("The value '" + what + "' is not in the model."); + +} + +template +inline void DataBase::get_today_total( + std::vector< std::string > * state, + std::vector< int > * counts +) const +{ + if (state != nullptr) + (*state) = model->states_labels; + + if (counts != nullptr) + *counts = today_total; + +} + +template +inline void DataBase::get_today_virus( + std::vector< std::string > & state, + std::vector< int > & id, + std::vector< int > & counts + ) const +{ + + state.resize(today_virus.size(), ""); + id.resize(today_virus.size(), 0); + counts.resize(today_virus.size(),0); + + int n = 0u; + for (epiworld_fast_uint v = 0u; v < today_virus.size(); ++v) + for (epiworld_fast_uint s = 0u; s < model->states_labels.size(); ++s) + { + state[n] = model->states_labels[s]; + id[n] = static_cast(v); + counts[n++] = today_virus[v][s]; + + } + +} + +template +inline void DataBase::get_hist_total( + std::vector< int > * date, + std::vector< std::string > * state, + std::vector< int > * counts +) const +{ + + if (date != nullptr) + *date = hist_total_date; + + if (state != nullptr) + { + state->resize(hist_total_state.size(), ""); + for (epiworld_fast_uint i = 0u; i < hist_total_state.size(); ++i) + state->operator[](i) = model->states_labels[hist_total_state[i]]; + } + + if (counts != nullptr) + *counts = hist_total_counts; + + return; + +} + +template +inline void DataBase::get_hist_virus( + std::vector< int > & date, + std::vector< int > & id, + std::vector< std::string > & state, + std::vector< int > & counts +) const { + + date = hist_virus_date; + std::vector< std::string > labels; + labels = model->states_labels; + + id = hist_virus_id; + state.resize(hist_virus_state.size(), ""); + for (epiworld_fast_uint i = 0u; i < hist_virus_state.size(); ++i) + state[i] = labels[hist_virus_state[i]]; + + counts = hist_virus_counts; + + return; + +} + + +template +inline void DataBase::get_hist_tool( + std::vector< int > & date, + std::vector< int > & id, + std::vector< std::string > & state, + std::vector< int > & counts +) const { + + date = hist_tool_date; + std::vector< std::string > labels; + labels = model->states_labels; + + id = hist_tool_id; + state.resize(hist_tool_state.size(), ""); + for (size_t i = 0u; i < hist_tool_state.size(); ++i) + state[i] = labels[hist_tool_state[i]]; + + counts = hist_tool_counts; + + return; + +} + +template +inline void DataBase::get_hist_transition_matrix( + std::vector< std::string > & state_from, + std::vector< std::string > & state_to, + std::vector< int > & date, + std::vector< int > & counts, + bool skip_zeros +) const +{ + + size_t n = this->hist_transition_matrix.size(); + + // Clearing the previous vectors + state_from.clear(); + state_to.clear(); + date.clear(); + counts.clear(); + + // Reserving space + state_from.reserve(n); + state_to.reserve(n); + date.reserve(n); + counts.reserve(n); + + size_t n_states = model->nstates; + size_t n_steps = model->get_ndays(); + + // If n is zero, then we are done + if (n == 0u) + return; + + for (size_t step = 0u; step <= n_steps; ++step) // The final step counts + { + for (size_t j = 0u; j < n_states; ++j) // Column major storage + { + for (size_t i = 0u; i < n_states; ++i) + { + // Retrieving the value of the day + int v = hist_transition_matrix[ + step * n_states * n_states + // Day of the data + j * n_states + // Column (to) + i // Row (from) + ]; + + // If we are skipping the zeros and it is zero, then don't save + if (skip_zeros && v == 0) + continue; + + state_from.push_back(model->states_labels[i]); + state_to.push_back(model->states_labels[j]); + date.push_back(hist_total_date[step * n_states]); + counts.push_back(v); + + } + + } + } + + return; + + +} + +template +inline void DataBase::get_transmissions( + std::vector & date, + std::vector & source, + std::vector & target, + std::vector & virus, + std::vector & source_exposure_date +) const +{ + + size_t nevents = transmission_date.size(); + + date.resize(nevents); + source.resize(nevents); + target.resize(nevents); + virus.resize(nevents); + source_exposure_date.resize(nevents); + + get_transmissions( + &date[0u], + &source[0u], + &target[0u], + &virus[0u], + &source_exposure_date[0u] + ); + +} + +template +inline void DataBase::get_transmissions( + int * date, + int * source, + int * target, + int * virus, + int * source_exposure_date +) const +{ + + size_t nevents = transmission_date.size(); + + for (size_t i = 0u; i < nevents; ++i) + { + + *(date + i) = transmission_date.at(i); + *(source + i) = transmission_source.at(i); + *(target + i) = transmission_target.at(i); + *(virus + i) = transmission_virus.at(i); + *(source_exposure_date + i) = transmission_source_exposure_date.at(i); + + } + +} + +template +inline void DataBase::write_data( + std::string fn_virus_info, + std::string fn_virus_hist, + std::string fn_tool_info, + std::string fn_tool_hist, + std::string fn_total_hist, + std::string fn_transmission, + std::string fn_transition, + std::string fn_reproductive_number, + std::string fn_generation_time +) const +{ + + if (fn_virus_info != "") + { + std::ofstream file_virus_info(fn_virus_info, std::ios_base::out); + + // Check if the file exists and throw an error if it doesn't + if (!file_virus_info) + { + throw std::runtime_error( + "Could not open file \"" + fn_virus_info + + "\" for writing.") + ; + } + + + file_virus_info << + #ifdef EPI_DEBUG + "thread" << "virus_id " << "virus " << "virus_sequence " << "date_recorded " << "parent\n"; + #else + "virus_id " << "virus " << "virus_sequence " << "date_recorded " << "parent\n"; + #endif + + for (const auto & v : virus_id) + { + int id = v.second; + file_virus_info << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + id << " \"" << + virus_name[id] << "\" " << + seq_writer(virus_sequence[id]) << " " << + virus_origin_date[id] << " " << + virus_parent_id[id] << "\n"; + } + + } + + if (fn_virus_hist != "") + { + std::ofstream file_virus(fn_virus_hist, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_virus) + { + throw std::runtime_error( + "Could not open file \"" + fn_virus_hist + + "\" for writing.") + ; + } + + file_virus << + #ifdef EPI_DEBUG + "thread "<< "date " << "id " << "state " << "n\n"; + #else + "date " << "virus_id virus" << "state " << "n\n"; + #endif + + for (epiworld_fast_uint i = 0; i < hist_virus_id.size(); ++i) + file_virus << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + hist_virus_date[i] << " " << + hist_virus_id[i] << " \"" << + virus_name[hist_virus_id[i]] << "\" " << + model->states_labels[hist_virus_state[i]] << " " << + hist_virus_counts[i] << "\n"; + } + + if (fn_tool_info != "") + { + std::ofstream file_tool_info(fn_tool_info, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_tool_info) + { + throw std::runtime_error( + "Could not open file \"" + fn_tool_info + + "\" for writing.") + ; + } + + file_tool_info << + #ifdef EPI_DEBUG + "thread " << + #endif + "id " << "tool_name " << "tool_sequence " << "date_recorded\n"; + + for (const auto & t : tool_id) + { + int id = t.second; + file_tool_info << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + id << " \"" << + tool_name[id] << "\" " << + seq_writer(tool_sequence[id]) << " " << + tool_origin_date[id] << "\n"; + } + + } + + if (fn_tool_hist != "") + { + std::ofstream file_tool_hist(fn_tool_hist, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_tool_hist) + { + throw std::runtime_error( + "Could not open file \"" + fn_tool_hist + + "\" for writing.") + ; + } + + file_tool_hist << + #ifdef EPI_DEBUG + "thread " << + #endif + "date " << "id " << "state " << "n\n"; + + for (epiworld_fast_uint i = 0; i < hist_tool_id.size(); ++i) + file_tool_hist << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + hist_tool_date[i] << " " << + hist_tool_id[i] << " " << + model->states_labels[hist_tool_state[i]] << " " << + hist_tool_counts[i] << "\n"; + } + + if (fn_total_hist != "") + { + std::ofstream file_total(fn_total_hist, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_total) + { + throw std::runtime_error( + "Could not open file \"" + fn_total_hist + + "\" for writing.") + ; + } + + file_total << + #ifdef EPI_DEBUG + "thread " << + #endif + "date " << "nviruses " << "state " << "counts\n"; + + for (epiworld_fast_uint i = 0; i < hist_total_date.size(); ++i) + file_total << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + hist_total_date[i] << " " << + hist_total_nviruses_active[i] << " \"" << + model->states_labels[hist_total_state[i]] << "\" " << + hist_total_counts[i] << "\n"; + } + + if (fn_transmission != "") + { + std::ofstream file_transmission(fn_transmission, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_transmission) + { + throw std::runtime_error( + "Could not open file \"" + fn_transmission + + "\" for writing.") + ; + } + + file_transmission << + #ifdef EPI_DEBUG + "thread " << + #endif + "date " << "virus_id virus " << "source_exposure_date " << "source " << "target\n"; + + for (epiworld_fast_uint i = 0; i < transmission_target.size(); ++i) + file_transmission << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + transmission_date[i] << " " << + transmission_virus[i] << " \"" << + virus_name[transmission_virus[i]] << "\" " << + transmission_source_exposure_date[i] << " " << + transmission_source[i] << " " << + transmission_target[i] << "\n"; + + } + + if (fn_transition != "") + { + std::ofstream file_transition(fn_transition, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!file_transition) + { + throw std::runtime_error( + "Could not open file \"" + fn_transition + + "\" for writing.") + ; + } + + file_transition << + #ifdef EPI_DEBUG + "thread " << + #endif + "date " << "from " << "to " << "counts\n"; + + int ns = model->nstates; + + for (int i = 0; i <= model->today(); ++i) + { + + for (int from = 0u; from < ns; ++from) + for (int to = 0u; to < ns; ++to) + file_transition << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + i << " " << + model->states_labels[from] << " " << + model->states_labels[to] << " " << + hist_transition_matrix[i * (ns * ns) + to * ns + from] << "\n"; + + } + + } + + if (fn_reproductive_number != "") + reproductive_number(fn_reproductive_number); + + if (fn_generation_time != "") + generation_time(fn_generation_time); + +} + +template +inline void DataBase::record_transmission( + int i, + int j, + int virus, + int i_expo_date +) { + + transmission_date.push_back(model->today()); + transmission_source.push_back(i); + transmission_target.push_back(j); + transmission_virus.push_back(virus); + transmission_source_exposure_date.push_back(i_expo_date); + +} + +template +inline size_t DataBase::get_n_viruses() const +{ + return virus_id.size(); +} + +template +inline size_t DataBase::get_n_tools() const +{ + return tool_id.size(); +} + + +template +inline void DataBase::set_user_data( + std::vector< std::string > names +) +{ + user_data = UserData(names); + user_data.model = model; +} + +template +inline void DataBase::add_user_data( + std::vector< epiworld_double > x +) +{ + + user_data.add(x); + +} + +template +inline void DataBase::add_user_data( + epiworld_fast_uint k, + epiworld_double x +) +{ + + user_data.add(k, x); + +} + +template +inline UserData & DataBase::get_user_data() +{ + return user_data; +} + +template +inline MapVec_type DataBase::reproductive_number() +const { + + // Checking size + MapVec_type map; + + // Number of digits of maxid + for (size_t i = 0u; i < transmission_date.size(); ++i) + { + // Fabricating id + std::vector< int > h = { + transmission_virus[i], + transmission_source[i], + transmission_source_exposure_date[i] + }; + + // Adding to counter + if (map.find(h) == map.end()) + map[h] = 1; + else + map[h]++; + + // The target is added + std::vector< int > h_target = { + transmission_virus[i], + transmission_target[i], + transmission_date[i] + }; + + map[h_target] = 0; + + } + + return map; + +} + +template +inline void DataBase::reproductive_number( + std::string fn +) const { + + + auto map = reproductive_number(); + + std::ofstream fn_file(fn, std::ios_base::out); + + // Repeat the same error if the file doesn't exists + if (!fn_file) + { + throw std::runtime_error( + "Could not open file \"" + fn + + "\" for writing.") + ; + } + + fn_file << + #ifdef EPI_DEBUG + "thread " << + #endif + "virus_id virus source source_exposure_date rt\n"; + + + for (auto & m : map) + fn_file << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + m.first[0u] << " \"" << + virus_name[m.first[0u]] << "\" " << + m.first[1u] << " " << + m.first[2u] << " " << + m.second << "\n"; + + return; + +} + +template +inline std::vector< epiworld_double > DataBase::transition_probability( + bool print +) const { + + auto states_labels = model->get_states(); + size_t n_state = states_labels.size(); + size_t n_days = model->get_ndays(); + std::vector< epiworld_double > res(n_state * n_state, 0.0); + std::vector< epiworld_double > days_to_include(n_state, 0.0); + + for (size_t t = 1; t < n_days; ++t) + { + + for (size_t s_i = 0; s_i < n_state; ++s_i) + { + epiworld_double daily_total = hist_total_counts[(t - 1) * n_state + s_i]; + + if (daily_total == 0) + continue; + + days_to_include[s_i] += 1.0; + + for (size_t s_j = 0u; s_j < n_state; ++s_j) + { + #ifdef EPI_DEBUG + epiworld_double entry = hist_transition_matrix[ + s_i + s_j * n_state + + t * (n_state * n_state) + ]; + + if (entry > daily_total) + throw std::logic_error( + "The entry in hist_transition_matrix cannot have more elememnts than the total" + ); + + res[s_i + s_j * n_state] += (entry / daily_total); + #else + res[s_i + s_j * n_state] += ( + hist_transition_matrix[ + s_i + s_j * n_state + + t * (n_state * n_state) + ] / daily_total + ); + #endif + } + + } + + } + + for (size_t s_i = 0; s_i < n_state; ++s_i) + { + for (size_t s_j = 0; s_j < n_state; ++s_j) + res[s_i + s_j * n_state] /= days_to_include[s_i]; + } + + if (print) + { + + size_t nchar = 0u; + for (auto & l : states_labels) + if (l.length() > nchar) + nchar = l.length(); + + std::string fmt = " - %-" + std::to_string(nchar) + "s"; + + printf_epiworld("\nTransition Probabilities:\n"); + for (size_t s_i = 0u; s_i < n_state; ++s_i) + { + printf_epiworld(fmt.c_str(), states_labels[s_i].c_str()); + for (size_t s_j = 0u; s_j < n_state; ++s_j) + { + if (std::isnan(res[s_i + s_j * n_state])) + { + printf_epiworld(" -"); + } else { + printf_epiworld(" % 4.2f", res[s_i + s_j * n_state]); + } + } + printf_epiworld("\n"); + } + + printf_epiworld("\n"); + + } + + return res; + + +} + +#define VECT_MATCH(a, b, c) \ + EPI_DEBUG_FAIL_AT_TRUE(a.size() != b.size(), c) \ + for (size_t __i = 0u; __i < a.size(); ++__i) \ + {\ + EPI_DEBUG_FAIL_AT_TRUE(a[__i] != b[__i], c) \ + } + +template<> +inline bool DataBase>::operator==(const DataBase> & other) const +{ + VECT_MATCH( + virus_name, other.virus_name, + "DataBase:: virus_name don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + virus_sequence.size() != other.virus_sequence.size(), + "DataBase:: virus_sequence don't match." + ) + + for (size_t i = 0u; i < virus_sequence.size(); ++i) + { + VECT_MATCH( + virus_sequence[i], other.virus_sequence[i], + "DataBase:: virus_sequence[i] don't match" + ) + } + + VECT_MATCH( + virus_origin_date, + other.virus_origin_date, + "DataBase:: virus_origin_date[i] don't match" + ) + + VECT_MATCH( + virus_parent_id, + other.virus_parent_id, + "DataBase:: virus_parent_id[i] don't match" + ) + + VECT_MATCH( + tool_name, + other.tool_name, + "DataBase:: tool_name[i] don't match" + ) + + VECT_MATCH( + tool_sequence, + other.tool_sequence, + "DataBase:: tool_sequence[i] don't match" + ) + + VECT_MATCH( + tool_origin_date, + other.tool_origin_date, + "DataBase:: tool_origin_date[i] don't match" + ) + + + EPI_DEBUG_FAIL_AT_TRUE( + sampling_freq != other.sampling_freq, + "DataBase:: sampling_freq don't match." + ) + + // Variants history + VECT_MATCH( + hist_virus_date, + other.hist_virus_date, + "DataBase:: hist_virus_date[i] don't match" + ) + + VECT_MATCH( + hist_virus_id, + other.hist_virus_id, + "DataBase:: hist_virus_id[i] don't match" + ) + + VECT_MATCH( + hist_virus_state, + other.hist_virus_state, + "DataBase:: hist_virus_state[i] don't match" + ) + + VECT_MATCH( + hist_virus_counts, + other.hist_virus_counts, + "DataBase:: hist_virus_counts[i] don't match" + ) + + // Tools history + VECT_MATCH( + hist_tool_date, + other.hist_tool_date, + "DataBase:: hist_tool_date[i] don't match" + ) + + VECT_MATCH( + hist_tool_id, + other.hist_tool_id, + "DataBase:: hist_tool_id[i] don't match" + ) + + VECT_MATCH( + hist_tool_state, + other.hist_tool_state, + "DataBase:: hist_tool_state[i] don't match" + ) + + VECT_MATCH( + hist_tool_counts, + other.hist_tool_counts, + "DataBase:: hist_tool_counts[i] don't match" + ) + + // Overall hist + VECT_MATCH( + hist_total_date, + other.hist_total_date, + "DataBase:: hist_total_date[i] don't match" + ) + + VECT_MATCH( + hist_total_nviruses_active, + other.hist_total_nviruses_active, + "DataBase:: hist_total_nviruses_active[i] don't match" + ) + + VECT_MATCH( + hist_total_state, + other.hist_total_state, + "DataBase:: hist_total_state[i] don't match" + ) + + VECT_MATCH( + hist_total_counts, + other.hist_total_counts, + "DataBase:: hist_total_counts[i] don't match" + ) + + VECT_MATCH( + hist_transition_matrix, + other.hist_transition_matrix, + "DataBase:: hist_transition_matrix[i] don't match" + ) + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + EPI_DEBUG_FAIL_AT_TRUE( + today_virus.size() != other.today_virus.size(), + "DataBase:: today_virus don't match." + ) + + for (size_t i = 0u; i < today_virus.size(); ++i) + { + VECT_MATCH( + today_virus[i], other.today_virus[i], + "DataBase:: today_virus[i] don't match" + ) + } + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + if (today_tool.size() != other.today_tool.size()) + return false; + + for (size_t i = 0u; i < today_tool.size(); ++i) + { + VECT_MATCH( + today_tool[i], other.today_tool[i], + "DataBase:: today_tool[i] don't match" + ) + } + + // {Susceptible, Infected, etc.} + VECT_MATCH( + today_total, other.today_total, + "DataBase:: today_total don't match" + ) + + // Totals + EPI_DEBUG_FAIL_AT_TRUE( + today_total_nviruses_active != other.today_total_nviruses_active, + "DataBase:: today_total_nviruses_active don't match." + ) + + // Transmission network + VECT_MATCH( + transmission_date, + other.transmission_date, ///< Date of the transmission eve, + "DataBase:: transmission_date[i] don't match" + ) + + VECT_MATCH( + transmission_source, + other.transmission_source, ///< Id of the sour, + "DataBase:: transmission_source[i] don't match" + ) + + VECT_MATCH( + transmission_target, + other.transmission_target, ///< Id of the targ, + "DataBase:: transmission_target[i] don't match" + ) + + VECT_MATCH( + transmission_virus, + other.transmission_virus, ///< Id of the varia, + "DataBase:: transmission_virus[i] don't match" + ) + + VECT_MATCH( + transmission_source_exposure_date, + other.transmission_source_exposure_date, ///< Date when the source acquired the varia, + "DataBase:: transmission_source_exposure_date[i] don't match" + ) + + + VECT_MATCH( + transition_matrix, + other.transition_matrix, + "DataBase:: transition_matrix[i] don't match" + ) + + + return true; + +} + +template +inline bool DataBase::operator==(const DataBase & other) const +{ + VECT_MATCH( + virus_name, + other.virus_name, + "DataBase:: virus_name[i] don't match" + ) + + VECT_MATCH( + virus_sequence, + other.virus_sequence, + "DataBase:: virus_sequence[i] don't match" + ) + + VECT_MATCH( + virus_origin_date, + other.virus_origin_date, + "DataBase:: virus_origin_date[i] don't match" + ) + + VECT_MATCH( + virus_parent_id, + other.virus_parent_id, + "DataBase:: virus_parent_id[i] don't match" + ) + + VECT_MATCH( + tool_name, + other.tool_name, + "DataBase:: tool_name[i] don't match" + ) + + VECT_MATCH( + tool_sequence, + other.tool_sequence, + "DataBase:: tool_sequence[i] don't match" + ) + + VECT_MATCH( + tool_origin_date, + other.tool_origin_date, + "DataBase:: tool_origin_date[i] don't match" + ) + + + EPI_DEBUG_FAIL_AT_TRUE( + sampling_freq != other.sampling_freq, + "DataBase:: sampling_freq don't match." + ) + + // Variants history + VECT_MATCH( + hist_virus_date, + other.hist_virus_date, + "DataBase:: hist_virus_date[i] don't match" + ) + + VECT_MATCH( + hist_virus_id, + other.hist_virus_id, + "DataBase:: hist_virus_id[i] don't match" + ) + + VECT_MATCH( + hist_virus_state, + other.hist_virus_state, + "DataBase:: hist_virus_state[i] don't match" + ) + + VECT_MATCH( + hist_virus_counts, + other.hist_virus_counts, + "DataBase:: hist_virus_counts[i] don't match" + ) + + // Tools history + VECT_MATCH( + hist_tool_date, + other.hist_tool_date, + "DataBase:: hist_tool_date[i] don't match" + ) + + VECT_MATCH( + hist_tool_id, + other.hist_tool_id, + "DataBase:: hist_tool_id[i] don't match" + ) + + VECT_MATCH( + hist_tool_state, + other.hist_tool_state, + "DataBase:: hist_tool_state[i] don't match" + ) + + VECT_MATCH( + hist_tool_counts, + other.hist_tool_counts, + "DataBase:: hist_tool_counts[i] don't match" + ) + + // Overall hist + VECT_MATCH( + hist_total_date, + other.hist_total_date, + "DataBase:: hist_total_date[i] don't match" + ) + + VECT_MATCH( + hist_total_nviruses_active, + other.hist_total_nviruses_active, + "DataBase:: hist_total_nviruses_active[i] don't match" + ) + + VECT_MATCH( + hist_total_state, + other.hist_total_state, + "DataBase:: hist_total_state[i] don't match" + ) + + VECT_MATCH( + hist_total_counts, + other.hist_total_counts, + "DataBase:: hist_total_counts[i] don't match" + ) + + VECT_MATCH( + hist_transition_matrix, + other.hist_transition_matrix, + "DataBase:: hist_transition_matrix[i] don't match" + ) + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + EPI_DEBUG_FAIL_AT_TRUE( + today_virus.size() != other.today_virus.size(), + "DataBase:: today_virus.size() don't match." + ) + + for (size_t i = 0u; i < today_virus.size(); ++i) + { + VECT_MATCH( + today_virus[i], other.today_virus[i], + "DataBase:: today_virus[i] don't match" + ) + } + + // {Variant 1: {state 1, state 2, etc.}, Variant 2: {...}, ...} + EPI_DEBUG_FAIL_AT_TRUE( + today_tool.size() != other.today_tool.size(), + "DataBase:: today_tool.size() don't match." + ) + + for (size_t i = 0u; i < today_tool.size(); ++i) + { + VECT_MATCH( + today_tool[i], other.today_tool[i], + "DataBase:: today_tool[i] don't match" + ) + } + + // {Susceptible, Infected, etc.} + VECT_MATCH( + today_total, other.today_total, + "DataBase:: today_total[i] don't match" + ) + + // Totals + EPI_DEBUG_FAIL_AT_TRUE( + today_total_nviruses_active != other.today_total_nviruses_active, + "DataBase:: today_total_nviruses_active don't match." + ) + + // Transmission network + VECT_MATCH( ///< Date of the transmission eve + transmission_date, + other.transmission_date, + "DataBase:: transmission_date[i] don't match" + ) + + VECT_MATCH( ///< Id of the sour + transmission_source, + other.transmission_source, + "DataBase:: transmission_source[i] don't match" + ) + + VECT_MATCH( ///< Id of the targ + transmission_target, + other.transmission_target, + "DataBase:: transmission_target[i] don't match" + ) + + VECT_MATCH( ///< Id of the varia + transmission_virus, + other.transmission_virus, + "DataBase:: transmission_virus[i] don't match" + ) + + VECT_MATCH( ///< Date when the source acquired the varia + transmission_source_exposure_date, + other.transmission_source_exposure_date, + "DataBase:: transmission_source_exposure_date[i] don't match" + ) + + VECT_MATCH( + transition_matrix, + other.transition_matrix, + "DataBase:: transition_matrix[i] don't match" + ) + + return true; + +} + +template +inline void DataBase::generation_time( + std::vector< int > & agent_id, + std::vector< int > & virus_id, + std::vector< int > & time, + std::vector< int > & gentime +) const { + + size_t nevents = transmission_date.size(); + + agent_id.reserve(nevents); + virus_id.reserve(nevents); + time.reserve(nevents); + gentime.reserve(nevents); + + // Iterating through the individuals + for (size_t i = 0u; i < nevents; ++i) + { + int agent_id_i = transmission_target[i]; + agent_id.push_back(agent_id_i); + virus_id.push_back(transmission_virus[i]); + time.push_back(transmission_date[i]); + + bool found = false; + for (size_t j = i; j < nevents; ++j) + { + + if (transmission_source[j] == agent_id_i) + { + gentime.push_back(transmission_date[j] - time[i]); + found = true; + break; + } + + } + + // If there's no transmission, we set the generation time to + // minus 1; + if (!found) + gentime.push_back(-1); + + } + + agent_id.shrink_to_fit(); + virus_id.shrink_to_fit(); + time.shrink_to_fit(); + gentime.shrink_to_fit(); + + return; + +} + +template +inline void DataBase::generation_time( + std::string fn +) const +{ + + std::vector< int > agent_id; + std::vector< int > virus_id; + std::vector< int > time; + std::vector< int > gentime; + + generation_time(agent_id, virus_id, time, gentime); + + std::ofstream fn_file(fn, std::ios_base::out); + + // Throw an error if the file doesn't exists using throw + if (!fn_file) + { + throw std::runtime_error( + "DataBase::generation_time: " + "Cannot open file " + fn + "." + ); + } + + + + fn_file << + #ifdef EPI_DEBUG + "thread " << + #endif + "virus source source_exposure_date gentime\n"; + + size_t n = agent_id.size(); + for (size_t i = 0u; i < n; ++i) + fn_file << + #ifdef EPI_DEBUG + EPI_GET_THREAD_ID() << " " << + #endif + virus_id[i] << " " << + agent_id[i] << " " << + time[i] << " " << + gentime[i] << "\n"; + + return; + +} + +#undef VECT_MATCH + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/entities-bones.hpp b/inst/include/epiworld/entities-bones.hpp new file mode 100644 index 00000000..d3c97ef9 --- /dev/null +++ b/inst/include/epiworld/entities-bones.hpp @@ -0,0 +1,208 @@ +#ifndef EPIWORLD_ENTITIES_BONES_HPP +#define EPIWORLD_ENTITIES_BONES_HPP + +template +class Virus; + +template +class Agent; + + +/** + * @brief Set of Entities (useful for building iterators) + * + * @tparam TSeq + */ +template +class Entities { + friend class Entity; + friend class Agent; +private: + std::vector< Entity * > dat; + const size_t n_entities; + +public: + + Entities() = delete; + Entities(Agent & p); + + typename std::vector< Entity * >::iterator begin(); + typename std::vector< Entity * >::iterator end(); + + Entity & operator()(size_t i); + Entity & operator[](size_t i); + + size_t size() const noexcept; + + bool operator==(const Entities & other) const; + +}; + +template +inline Entities::Entities(Agent & p) : + n_entities(p.get_n_entities()) +{ + + dat.reserve(n_entities); + for (size_t i = 0u; i < n_entities; ++i) + dat.push_back(&p.get_entity(i)); + +} + +template +inline typename std::vector< Entity* >::iterator Entities::begin() +{ + + if (n_entities == 0u) + return dat.end(); + + return dat.begin(); +} + +template +inline typename std::vector< Entity* >::iterator Entities::end() +{ + + return begin() + n_entities; +} + +template +inline Entity & Entities::operator()(size_t i) +{ + + if (i >= n_entities) + throw std::range_error("Entity index out of range."); + + return *dat[i]; + +} + +template +inline Entity & Entities::operator[](size_t i) +{ + + return *dat[i]; + +} + +template +inline size_t Entities::size() const noexcept +{ + return n_entities; +} + +template +inline bool Entities::operator==(const Entities & other) const +{ + + if (n_entities != other.n_entities) + return false; + + for (size_t i = 0u; i < dat.size(); ++i) + { + if (dat[i] != other.dat[i]) + return false; + } + + return true; +} + +/** + * @brief Set of Entities (const) (useful for iterators) + * + * @tparam TSeq + */ +template +class Entities_const { + friend class Virus; + friend class Agent; +private: + const std::vector< Entity* > dat; + const size_t n_entities; + +public: + + Entities_const() = delete; + Entities_const(const Agent & p); + + typename std::vector< Entity* >::const_iterator begin(); + typename std::vector< Entity* >::const_iterator end(); + + const Entity & operator()(size_t i); + const Entity & operator[](size_t i); + + size_t size() const noexcept; + + bool operator==(const Entities_const & other) const; + +}; + +template +inline Entities_const::Entities_const(const Agent & p) : + n_entities(p.get_n_entities()) +{ + + dat.reserve(n_entities); + for (size_t i = 0u; i < n_entities; ++i) + dat.push_back(&p.get_entity(i)); + +} + +template +inline typename std::vector< Entity* >::const_iterator Entities_const::begin() { + + if (n_entities == 0u) + return dat.end(); + + return dat.begin(); +} + +template +inline typename std::vector< Entity* >::const_iterator Entities_const::end() { + + return begin() + n_entities; +} + +template +inline const Entity & Entities_const::operator()(size_t i) +{ + + if (i >= n_entities) + throw std::range_error("Entity index out of range."); + + return *dat[i]; + +} + +template +inline const Entity & Entities_const::operator[](size_t i) +{ + + return *dat[i]; + +} + +template +inline size_t Entities_const::size() const noexcept +{ + return n_entities; +} + +template +inline bool Entities_const::operator==(const Entities_const & other) const +{ + + if (n_entities != other.n_entities) + return false; + + for (size_t i = 0u; i < dat.size(); ++i) + { + if (dat[i] != other.dat[i]) + return false; + } + + return true; +} + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/entity-bones.hpp b/inst/include/epiworld/entity-bones.hpp new file mode 100644 index 00000000..b5666a42 --- /dev/null +++ b/inst/include/epiworld/entity-bones.hpp @@ -0,0 +1,102 @@ +#ifndef EPIWORLD_ENTITY_BONES_HPP +#define EPIWORLD_ENTITY_BONES_HPP + +template +class Model; + +template +class Agent; + +template +class AgentsSample; + +template +inline void default_add_entity(Action & a, Model * m); + +template +inline void default_rm_entity(Action & a, Model * m); + +template +class Entity { + friend class Agent; + friend class AgentsSample; + friend class Model; + friend void default_add_entity(Action & a, Model * m); + friend void default_rm_entity(Action & a, Model * m); +private: + + Model * model; + + int id = -1; + std::vector< size_t > agents; ///< Vector of agents + std::vector< size_t > agents_location; ///< Location where the entity is stored in the agent + size_t n_agents = 0u; + + /** + * @name Auxiliary variables for AgentsSample iterators + * + * @details These variables+objects are used by the AgentsSample + * class for building efficient iterators over agents. The idea is to + * reduce the memory allocation, so only during the first call of + * AgentsSample::AgentsSample(Entity) these vectors are allocated. + */ + ///@{ + std::vector< Agent * > sampled_agents; + size_t sampled_agents_n = 0u; + std::vector< size_t > sampled_agents_left; + size_t sampled_agents_left_n = 0u; + // int date_last_add_or_remove = -99; ///< Last time the entity added or removed an agent + ///@} + + int max_capacity = -1; + std::string entity_name = "Unknown entity"; + + std::vector< epiworld_double > location = {0.0}; ///< An arbitrary vector for location + + epiworld_fast_int state_init = -99; + epiworld_fast_int state_post = -99; + + epiworld_fast_int queue_init = 0; ///< Change of state when added to agent. + epiworld_fast_int queue_post = 0; ///< Change of state when removed from agent. + +public: + + // Entity() = delete; + // Entity(Entity & e) = delete; + // Entity(const Entity & e); + // Entity(Entity && e); + Entity(std::string name) : entity_name(name) {}; + // Entity & operator=(const Entity & e); + + void add_agent(Agent & p, Model * model); + void add_agent(Agent * p, Model * model); + void rm_agent(size_t idx); + size_t size() const noexcept; + void set_location(std::vector< epiworld_double > loc); + std::vector< epiworld_double > & get_location(); + + typename std::vector< Agent * >::iterator begin(); + typename std::vector< Agent * >::iterator end(); + + typename std::vector< Agent * >::const_iterator begin() const; + typename std::vector< Agent * >::const_iterator end() const; + + Agent * operator[](size_t i); + + int get_id() const noexcept; + const std::string & get_name() const noexcept; + + void set_state(epiworld_fast_int init, epiworld_fast_int post); + void set_queue(epiworld_fast_int init, epiworld_fast_int post); + void get_state(epiworld_fast_int * init, epiworld_fast_int * post); + void get_queue(epiworld_fast_int * init, epiworld_fast_int * post); + + void reset(); + + bool operator==(const Entity & other) const; + bool operator!=(const Entity & other) const {return !operator==(other);}; + +}; + + +#endif diff --git a/inst/include/epiworld/entity-meat.hpp b/inst/include/epiworld/entity-meat.hpp new file mode 100644 index 00000000..3220848e --- /dev/null +++ b/inst/include/epiworld/entity-meat.hpp @@ -0,0 +1,241 @@ +#ifndef EPIWORLD_ENTITY_MEAT_HPP +#define EPIWORLD_ENTITY_MEAT_HPP + +// template +// inline Entity::Entity(const Entity & e) : +// model(e.model), +// id(e.id), +// agents(0u), +// agents_location(0u), +// n_agents(0), +// sampled_agents(0u), +// sampled_agents_n(0u), +// sampled_agents_left(0u), +// sampled_agents_left_n(0u), +// max_capacity(e.max_capacity), +// entity_name(e.entity_name), +// location(e.location), +// state_init(e.state_init), +// state_post(e.state_post), +// queue_init(e.queue_init), +// queue_post(e.queue_post) +// { + +// } + +template +inline void Entity::add_agent( + Agent & p, + Model * model + ) +{ + + // Need to add it to the actions, through the individual + p.add_entity(*this, model); + +} + +template +inline void Entity::add_agent( + Agent * p, + Model * model + ) +{ + p->add_entity(*this, model); +} + +template +inline void Entity::rm_agent(size_t idx) +{ + if (idx >= n_agents) + throw std::out_of_range( + "Trying to remove agent "+ std::to_string(idx) + + " out of " + std::to_string(n_agents) + ); + + model->population[idx].rm_entity(*this); + + return; +} + +template +inline size_t Entity::size() const noexcept +{ + return n_agents; +} + +template +inline void Entity::set_location(std::vector< epiworld_double > loc) +{ + location = loc; +} + +template +inline std::vector< epiworld_double > & Entity::get_location() +{ + return location; +} + +template +inline typename std::vector< Agent * >::iterator Entity::begin() +{ + + if (n_agents == 0) + return agents.end(); + + return agents.begin(); + +} + +template +inline typename std::vector< Agent * >::iterator Entity::end() +{ + return agents.begin() + n_agents; +} + +template +inline typename std::vector< Agent * >::const_iterator Entity::begin() const +{ + + if (n_agents == 0) + return agents.end(); + + return agents.begin(); + +} + +template +inline typename std::vector< Agent * >::const_iterator Entity::end() const +{ + return agents.begin() + n_agents; +} + +template +inline Agent * Entity::operator[](size_t i) +{ + if (n_agents <= i) + throw std::logic_error("There are not that many agents in this entity."); + + return &model->get_agents()[i]; +} + +template +inline int Entity::get_id() const noexcept +{ + return id; +} + +template +inline const std::string & Entity::get_name() const noexcept +{ + return entity_name; +} + +template +inline void Entity::set_state( + epiworld_fast_int init, + epiworld_fast_int end +) +{ + state_init = init; + state_post = end; +} + +template +inline void Entity::set_queue( + epiworld_fast_int init, + epiworld_fast_int end +) +{ + queue_init = init; + queue_post = end; +} + +template +inline void Entity::get_state( + epiworld_fast_int * init, + epiworld_fast_int * post +) +{ + if (init != nullptr) + *init = state_init; + + if (post != nullptr) + *post = state_post; + +} + +template +inline void Entity::get_queue( + epiworld_fast_int * init, + epiworld_fast_int * post +) +{ + if (init != nullptr) + *init = queue_init; + + if (post != nullptr) + *post = queue_post; + +} + +template +inline void Entity::reset() +{ + sampled_agents.clear(); + sampled_agents_n = 0u; + sampled_agents_left.clear(); + sampled_agents_left_n = 0u; +} + +template +inline bool Entity::operator==(const Entity & other) const +{ + + if (id != other.id) + return false; + + if (n_agents != other.n_agents) + return false; + + for (size_t i = 0u; i < n_agents; ++i) + { + if (agents[i] != other.agents[i]) + return false; + } + + + if (max_capacity != other.max_capacity) + return false; + + if (entity_name != other.entity_name) + return false; + + if (location.size() != other.location.size()) + return false; + + for (size_t i = 0u; i < location.size(); ++i) + { + + if (location[i] != other.location[i]) + return false; + + } + + if (state_init != other.state_init) + return false; + + if (state_post != other.state_post) + return false; + + if (queue_init != other.queue_init) + return false; + + if (queue_post != other.queue_post) + return false; + + return true; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/epiworld-macros.hpp b/inst/include/epiworld/epiworld-macros.hpp new file mode 100644 index 00000000..0fdf87b3 --- /dev/null +++ b/inst/include/epiworld/epiworld-macros.hpp @@ -0,0 +1,104 @@ +#ifndef EPIWORLD_MACROS_HPP +#define EPIWORLD_MACROS_HPP + + + +/** + * @brief Helper macro to define a new tool + * + */ +#define EPI_NEW_TOOL(fname,tseq) inline epiworld_double \ +(fname)(\ + epiworld::Tool< tseq > & t, \ + epiworld::Agent< tseq > * p, \ + std::shared_ptr> v, \ + epiworld::Model< tseq > * m\ + ) + +/** + * @brief Create a Tool within a function + * + */ +#define EPI_NEW_TOOL_LAMBDA(funname,tseq) \ + epiworld::ToolFun funname = \ + [](epiworld::Tool & t, \ + epiworld::Agent * p, \ + std::shared_ptr> v, \ + epiworld::Model * m) -> epiworld_double + +/** + * @brief Helper macro for accessing model parameters + * + */ +#define EPI_PARAMS(i) m->operator()(i) + +/** + * @brief Helper macro for defining Mutation Functions + * + */ +#define EPI_NEW_MUTFUN(funname,tseq) inline bool \ + (funname)(\ + epiworld::Agent * p, \ + epiworld::Virus & v, \ + epiworld::Model * m ) + +#define EPI_NEW_MUTFUN_LAMBDA(funname,tseq) \ + epiworld::MutFun funname = \ + [](epiworld::Agent * p, \ + epiworld::Virus & v, \ + epiworld::Model * m) -> void + +#define EPI_NEW_POSTRECOVERYFUN(funname,tseq) inline void \ + (funname)( \ + epiworld::Agent * p, \ + epiworld::Virus & v, \ + epiworld::Model * m\ + ) + +#define EPI_NEW_POSTRECOVERYFUN_LAMBDA(funname,tseq) \ + epiworld::PostRecoveryFun funname = \ + [](epiworld::Agent * p, \ + epiworld::Virus & v , \ + epiworld::Model * m) -> void + +#define EPI_NEW_VIRUSFUN(funname,tseq) inline epiworld_double \ + (funname)( \ + epiworld::Agent * p, \ + epiworld::Virus & v, \ + epiworld::Model * m\ + ) + +#define EPI_NEW_VIRUSFUN_LAMBDA(funname,TSeq) \ + epiworld::VirusFun funname = \ + [](epiworld::Agent * p, \ + epiworld::Virus & v, \ + epiworld::Model * m) -> epiworld_double + +#define EPI_RUNIF() m->runif() + +#define EPIWORLD_RUN(a) \ + if (a.get_verbose()) \ + { \ + printf_epiworld("Running the model...\n");\ + } \ + for (epiworld_fast_uint niter = 0; niter < a.get_ndays(); ++niter) + +#define EPI_TOKENPASTE(a,b) a ## b +#define MPAR(num) *(m->EPI_TOKENPASTE(p,num)) + +#define EPI_NEW_UPDATEFUN(funname,tseq) inline void \ + (funname)(epiworld::Agent * p, epiworld::Model * m) + +#define EPI_NEW_UPDATEFUN_LAMBDA(funname,tseq) \ + epiworld::UpdateFun funname = \ + [](epiworld::Agent * p, epiworld::Model * m) -> void + +#define EPI_NEW_GLOBALFUN(funname,tseq) inline void \ + (funname)(epiworld::Model* m) + +#define EPI_NEW_GLOBALFUN_LAMBDA(funname,tseq) \ + epiworld::GlobalFun funname = \ + [](epiworld::Model* m) -> void + + +#endif diff --git a/inst/include/epiworld/epiworld.hpp b/inst/include/epiworld/epiworld.hpp new file mode 100644 index 00000000..3644e5df --- /dev/null +++ b/inst/include/epiworld/epiworld.hpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef EPIWORLD_HPP +#define EPIWORLD_HPP + +namespace epiworld { + + #include "config.hpp" + #include "epiworld-macros.hpp" + + #include "misc.hpp" + #include "progress.hpp" + + // #include "math/summary-stats.hpp" + + #include "math/lfmcmc.hpp" + + #include "userdata-bones.hpp" + #include "userdata-meat.hpp" + + #include "seq_processing.hpp" + + #include "database-bones.hpp" + #include "database-meat.hpp" + #include "adjlist-bones.hpp" + #include "adjlist-meat.hpp" + + #include "randgraph.hpp" + + #include "queue-bones.hpp" + + #include "globalactions-bones.hpp" + #include "globalactions-meat.hpp" + + #include "model-bones.hpp" + #include "model-meat.hpp" + + #include "viruses-bones.hpp" + + #include "virus-bones.hpp" + #include "virus-meat.hpp" + + #include "tools-bones.hpp" + + #include "tool-bones.hpp" + #include "tool-meat.hpp" + + #include "entity-bones.hpp" + #include "entity-meat.hpp" + + #include "entities-bones.hpp" + + #include "agent-meat-state.hpp" + #include "agent-bones.hpp" + #include "agent-meat.hpp" + + #include "agentssample-bones.hpp" + + #include "models/models.hpp" + + + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/globalactions-bones.hpp b/inst/include/epiworld/globalactions-bones.hpp new file mode 100644 index 00000000..22cb254c --- /dev/null +++ b/inst/include/epiworld/globalactions-bones.hpp @@ -0,0 +1,53 @@ +#ifndef EPIWORLD_GLOBALACTIONS_BONES_HPP +#define EPIWORLD_GLOBALACTIONS_BONES_HPP + +// template +// using GlobalFun = std::function*)>; + +/** + * @brief Template for a Global Action + * @details Global actions are functions that Model executes + * at the end of a day. + * + */ +template +class GlobalAction +{ +private: + GlobalFun fun = nullptr; + std::string name = "A global action"; + int day = -99; +public: + + GlobalAction() {}; + + /** + * @brief Construct a new Global Action object + * + * @param fun A function that takes a Model * as argument and returns void. + * @param name A descriptive name for the action. + * @param day The day when the action will be executed. If negative, it will be executed every day. + */ + GlobalAction(GlobalFun fun, std::string name, int day = -99); + + ~GlobalAction() {}; + + void operator()(Model * m, int day); + + void set_name(std::string name); + std::string get_name() const; + + void set_day(int day); + int get_day() const; + + void print() const; + + // Comparison operators + bool operator==(const GlobalAction & other) const; + bool operator!=(const GlobalAction & other) const; + +}; + + + +#endif diff --git a/inst/include/epiworld/globalactions-meat.hpp b/inst/include/epiworld/globalactions-meat.hpp new file mode 100644 index 00000000..815d0576 --- /dev/null +++ b/inst/include/epiworld/globalactions-meat.hpp @@ -0,0 +1,78 @@ +#ifndef EPIWORLD_GLOBALACTIONS_MEAT_HPP +#define EPIWORLD_GLOBALACTIONS_MEAT_HPP + +template +inline GlobalAction::GlobalAction( + GlobalFun fun, + std::string name, + int day + ) +{ + this->fun = fun; + this->name = name; + this->day = day; +} + +template +inline void GlobalAction::operator()(Model * m, int day) +{ + + if (this->fun == nullptr) + return; + + // Actions apply if day is negative or if day is equal to the day of the action + if (this->day < 0 || this->day == day) + this->fun(m); + + return; + +} + +template +inline void GlobalAction::set_name(std::string name) +{ + this->name = name; +} + +template +inline std::string GlobalAction::get_name() const +{ + return this->name; +} + +template +inline void GlobalAction::set_day(int day) +{ + this->day = day; +} + +template +inline int GlobalAction::get_day() const +{ + return this->day; +} + +template +inline void GlobalAction::print() const +{ + printf_epiworld( + "Global action: %s\n" + " - Day: %i\n", + this->name.c_str(), + this->day + ); +} + +template +inline bool GlobalAction::operator==(const GlobalAction & other) const +{ + return (this->name == other.name) && (this->day == other.day); +} + +template +inline bool GlobalAction::operator!=(const GlobalAction & other) const +{ + return !(*this == other); +} + +#endif diff --git a/inst/include/epiworld/math/lfmcmc.hpp b/inst/include/epiworld/math/lfmcmc.hpp new file mode 100644 index 00000000..675d16b6 --- /dev/null +++ b/inst/include/epiworld/math/lfmcmc.hpp @@ -0,0 +1,7 @@ +#ifndef EPIWORLD_LFMCMC_HPP +#define EPIWORLD_LFMCMC_HPP + +#include "lfmcmc/lfmcmc-bones.hpp" +#include "lfmcmc/lfmcmc-meat.hpp" + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/math/lfmcmc/lfmcmc-bones.hpp b/inst/include/epiworld/math/lfmcmc/lfmcmc-bones.hpp new file mode 100755 index 00000000..6c494b95 --- /dev/null +++ b/inst/include/epiworld/math/lfmcmc/lfmcmc-bones.hpp @@ -0,0 +1,247 @@ +#ifndef EPIWORLD_LFMCMC_BONES_HPP +#define EPIWORLD_LFMCMC_BONES_HPP + +#ifndef epiworld_double + #define epiworld_double float +#endif + + +template +class LFMCMC; + +template +using LFMCMCSimFun = std::function&,LFMCMC*)>; + +template +using LFMCMCSummaryFun = std::function&,const TData&,LFMCMC*)>; + +template +using LFMCMCProposalFun = std::function&,const std::vector< epiworld_double >&,LFMCMC*)>; + +template +using LFMCMCKernelFun = std::function&,const std::vector< epiworld_double >&,epiworld_double,LFMCMC*)>; + +/** + * @brief Proposal function + * @param params_now Vector where to save the new parameters. + * @param params_prev Vector of reference parameters. + * @param m LFMCMC model. + * @tparam TData + */ +template +inline void proposal_fun_normal( + std::vector< epiworld_double >& params_now, + const std::vector< epiworld_double >& params_prev, + LFMCMC* m +); + +/** + * @brief Factory for a reflective normal kernel + * + * @details Reflective kernel corrects proposals by forcing them to be + * within prespecified boundaries. + * + * @tparam TData + * @param scale Scale of the normal kernel + * @param lb Lower bound (applies the same to all parameters) + * @param ub Upper bound (applies the same to all parameters) + * @return LFMCMCProposalFun + */ +template +inline LFMCMCProposalFun make_proposal_norm_reflective( + epiworld_double scale, + epiworld_double lb = std::numeric_limits::min(), + epiworld_double ub = std::numeric_limits::max() +); + +/** + * @brief Uniform proposal kernel + * + * Proposals are made within a radious 1 of the current + * state of the parameters. + * + * @param params_now Where to write the new parameters + * @param params_prev Reference parameters + * @tparam TData + * @param m LFMCMC model. + */ +template +inline void proposal_fun_unif( + std::vector< epiworld_double >& params_now, + const std::vector< epiworld_double >& params_prev, + LFMCMC* m +); + +/** + * @brief Uses the uniform kernel with euclidean distance + * + * @param stats_now Vector of current statistics based on + * simulated data. + * @param stats_obs Vector of observed statistics + * @param epsilon Epsilon parameter + * @param m LFMCMC model. + * @return epiworld_double + */ +template +inline epiworld_double kernel_fun_uniform( + const std::vector< epiworld_double >& stats_now, + const std::vector< epiworld_double >& stats_obs, + epiworld_double epsilon, + LFMCMC* m +); + +/** + * @brief Gaussian kernel + * + * @tparam TData + * @param epsilon + * @param m + * @return epiworld_double + */ +template +inline epiworld_double kernel_fun_gaussian( + const std::vector< epiworld_double >& stats_now, + const std::vector< epiworld_double >& stats_obs, + epiworld_double epsilon, + LFMCMC* m +); + +/** + * @brief Likelihood-Free Markov Chain Monte Carlo + * + * @tparam TData Type of data that is generated + */ +template +class LFMCMC { +private: + + // Random number sampling + std::mt19937 * engine = nullptr; + + std::shared_ptr< std::uniform_real_distribution<> > runifd = + std::make_shared< std::uniform_real_distribution<> >(0.0, 1.0); + + std::shared_ptr< std::normal_distribution<> > rnormd = + std::make_shared< std::normal_distribution<> >(0.0); + + std::shared_ptr< std::gamma_distribution<> > rgammad = + std::make_shared< std::gamma_distribution<> >(); + + // Process data + TData * observed_data; + + // Information about the size of the problem + size_t n_samples; + size_t n_statistics; + size_t n_parameters; + + epiworld_double epsilon; + + std::vector< epiworld_double > params_now; + std::vector< epiworld_double > params_prev; + std::vector< epiworld_double > params_init; + + std::vector< epiworld_double > observed_stats; ///< Observed statistics + + std::vector< epiworld_double > sampled_params; ///< Sampled Parameters + std::vector< epiworld_double > sampled_stats; ///< Sampled statistics + std::vector< epiworld_double > sampled_stats_prob; ///< Sampled statistics + std::vector< bool > sampled_accepted; ///< Indicator of accepted statistics + + std::vector< epiworld_double > accepted_params; ///< Posterior distribution (accepted samples) + std::vector< epiworld_double > accepted_stats; ///< Posterior distribution (accepted samples) + std::vector< epiworld_double > accepted_params_prob; ///< Posterior probability + + std::vector< epiworld_double > drawn_prob; ///< Drawn probabilities (runif()) + std::vector< TData > * sampled_data = nullptr; + + // Functions + LFMCMCSimFun simulation_fun; + LFMCMCSummaryFun summary_fun; + LFMCMCProposalFun proposal_fun = proposal_fun_normal; + LFMCMCKernelFun kernel_fun = kernel_fun_uniform; + + // Misc + std::vector< std::string > names_parameters; + std::vector< std::string > names_statistics; + + std::chrono::time_point time_start; + std::chrono::time_point time_end; + + // std::chrono::milliseconds + std::chrono::duration time_elapsed = + std::chrono::duration::zero(); + + inline void get_elapsed( + std::string unit, + epiworld_double * last_elapsed, + std::string * unit_abbr, + bool print + ); + + void chrono_start(); + void chrono_end(); + +public: + + void run( + std::vector< epiworld_double > param_init, + size_t n_samples_, + epiworld_double epsilon_ + ); + + LFMCMC() {}; + LFMCMC(TData & observed_data_) : observed_data(&observed_data_) {}; + ~LFMCMC() {}; + + void set_observed_data(TData & observed_data_) {observed_data = &observed_data_;}; + void set_proposal_fun(LFMCMCProposalFun fun); + void set_simulation_fun(LFMCMCSimFun fun); + void set_summary_fun(LFMCMCSummaryFun fun); + void set_kernel_fun(LFMCMCKernelFun fun); + + /** + * @name Random number generation + * + * @param eng + */ + ///@{ + void set_rand_engine(std::mt19937 & eng); + std::mt19937 & get_rand_endgine(); + void seed(epiworld_fast_uint s); + void set_rand_gamma(epiworld_double alpha, epiworld_double beta); + epiworld_double runif(); + epiworld_double rnorm(); + epiworld_double rgamma(); + epiworld_double runif(epiworld_double lb, epiworld_double ub); + epiworld_double rnorm(epiworld_double mean, epiworld_double sd); + epiworld_double rgamma(epiworld_double alpha, epiworld_double beta); + ///@} + + // Accessing parameters of the function + size_t get_n_samples() const {return n_samples;}; + size_t get_n_statistics() const {return n_statistics;}; + size_t get_n_parameters() const {return n_parameters;}; + epiworld_double get_epsilon() const {return epsilon;}; + + const std::vector< epiworld_double > & get_params_now() {return params_now;}; + const std::vector< epiworld_double > & get_params_prev() {return params_prev;}; + const std::vector< epiworld_double > & get_params_init() {return params_init;}; + const std::vector< epiworld_double > & get_statistics_obs() {return observed_stats;}; + const std::vector< epiworld_double > & get_statistics_hist() {return sampled_stats;}; + const std::vector< bool > & get_statistics_accepted() {return sampled_accepted;}; + const std::vector< epiworld_double > & get_posterior_lf_prob() {return accepted_params_prob;}; + const std::vector< epiworld_double > & get_drawn_prob() {return drawn_prob;}; + std::vector< TData > * get_sampled_data() {return sampled_data;}; + + void set_par_names(std::vector< std::string > names); + void set_stats_names(std::vector< std::string > names); + + std::vector< epiworld_double > get_params_mean(); + std::vector< epiworld_double > get_stats_mean(); + + void print() ; + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/math/lfmcmc/lfmcmc-meat-print.hpp b/inst/include/epiworld/math/lfmcmc/lfmcmc-meat-print.hpp new file mode 100755 index 00000000..38cbf56b --- /dev/null +++ b/inst/include/epiworld/math/lfmcmc/lfmcmc-meat-print.hpp @@ -0,0 +1,210 @@ +#ifndef LFMCMC_MEAT_PRINT_HPP +#define LFMCMC_MEAT_PRINT_HPP + +template +inline void LFMCMC::print() +{ + std::vector< epiworld_double > summ_params(n_parameters * 3, 0.0); + std::vector< epiworld_double > summ_stats(n_statistics * 3, 0.0); + + for (size_t k = 0u; k < n_parameters; ++k) + { + + // Retrieving the relevant parameter + std::vector< epiworld_double > par_i(n_samples); + for (size_t i = 0u; i < n_samples; ++i) + { + par_i[i] = accepted_params[i * n_parameters + k]; + summ_params[k * 3] += par_i[i]/n_samples; + } + + // Computing the 95% Credible interval + std::sort(par_i.begin(), par_i.end()); + + summ_params[k * 3 + 1u] = + par_i[std::floor(.025 * static_cast(n_samples))]; + summ_params[k * 3 + 2u] = + par_i[std::floor(.975 * static_cast(n_samples))]; + + } + + for (size_t k = 0u; k < n_statistics; ++k) + { + + // Retrieving the relevant parameter + std::vector< epiworld_double > stat_k(n_samples); + for (size_t i = 0u; i < n_samples; ++i) + { + stat_k[i] = accepted_stats[i * n_statistics + k]; + summ_stats[k * 3] += stat_k[i]/n_samples; + } + + // Computing the 95% Credible interval + std::sort(stat_k.begin(), stat_k.end()); + + summ_stats[k * 3 + 1u] = + stat_k[std::floor(.025 * static_cast(n_samples))]; + summ_stats[k * 3 + 2u] = + stat_k[std::floor(.975 * static_cast(n_samples))]; + + } + + printf_epiworld("___________________________________________\n\n"); + printf_epiworld("LIKELIHOOD-FREE MARKOV CHAIN MONTE CARLO\n\n"); + + printf_epiworld("N Samples : %ld\n", n_samples); + + std::string abbr; + epiworld_double elapsed; + get_elapsed("auto", &elapsed, &abbr, false); + printf_epiworld("Elapsed t : %.2f%s\n\n", elapsed, abbr.c_str()); + + //////////////////////////////////////////////////////////////////////////// + // PARAMETERS + //////////////////////////////////////////////////////////////////////////// + printf_epiworld("Parameters:\n"); + + // Figuring out format + std::string fmt_params; + + int nchar_par_num = 0; + for (auto & n : summ_params) + { + + int tmp_nchar = std::floor(std::log10(std::abs(n))); + if (nchar_par_num < tmp_nchar) + nchar_par_num = tmp_nchar; + } + nchar_par_num += 5; // 1 for neg padd, 2 for decimals, 1 the decimal point, and one b/c log(<10) < 1. + std::string charlen = std::to_string(nchar_par_num); + + if (names_parameters.size() != 0u) + { + int nchar_par = 0; + for (auto & n : names_parameters) + { + int tmp_nchar = n.length(); + if (nchar_par < tmp_nchar) + nchar_par = tmp_nchar; + } + + fmt_params = std::string(" -%-") + + std::to_string(nchar_par) + + std::string("s : % ") + charlen + + std::string(".2f [% ") + charlen + + std::string(".2f, % ") + charlen + + std::string(".2f] (initial : % ") + + charlen + std::string(".2f)\n"); + + for (size_t k = 0u; k < n_parameters; ++k) + { + printf_epiworld( + fmt_params.c_str(), + names_parameters[k].c_str(), + summ_params[k * 3], + summ_params[k * 3 + 1u], + summ_params[k * 3 + 2u], + params_init[k] + ); + } + + + } else { + + fmt_params = std::string(" [%-2ld]: % ") + charlen + + std::string(".2f [% ") + charlen + + std::string(".2f, % ") + charlen + + std::string(".2f] (initial : % ") + charlen + + std::string(".2f)\n"); + + for (size_t k = 0u; k < n_parameters; ++k) + { + + printf_epiworld( + fmt_params.c_str(), + k, + summ_params[k * 3], + summ_params[k * 3 + 1u], + summ_params[k * 3 + 2u], + params_init[k] + ); + } + + } + + //////////////////////////////////////////////////////////////////////////// + // Statistics + //////////////////////////////////////////////////////////////////////////// + printf_epiworld("\nStatistics:\n"); + int nchar = 0; + for (auto & s : summ_stats) + { + int tmp_nchar = std::floor(std::log10(std::abs(s))); + if (nchar < tmp_nchar) + nchar = tmp_nchar; + } + + nchar += 5; // See above + + std::string nchar_char = std::to_string(nchar); + + // Figuring out format + std::string fmt_stats; + if (names_statistics.size() != 0u) + { + int nchar_stats = 0; + for (auto & n : names_statistics) + { + int tmp_nchar = n.length(); + if (nchar_stats < tmp_nchar) + nchar_stats = tmp_nchar; + } + + fmt_stats = std::string(" -%-") + + std::to_string(nchar_stats) + + std::string("s : % ") + nchar_char + + std::string(".2f [% ") + nchar_char + + std::string(".2f, % ") + nchar_char + + std::string(".2f] (Observed: % ") + nchar_char + + std::string(".2f)\n"); + + for (size_t k = 0u; k < n_statistics; ++k) + { + printf_epiworld( + fmt_stats.c_str(), + names_statistics[k].c_str(), + summ_stats[k * 3], + summ_stats[k * 3 + 1u], + summ_stats[k * 3 + 2u], + observed_stats[k] + ); + } + + + } else { + + fmt_stats = std::string(" [%-2ld] : % ") + + nchar_char + + std::string(".2f [% ") + nchar_char + + std::string(".2f, % ") + nchar_char + + std::string(".2f] (Observed: % ") + nchar_char + + std::string(".2f)\n"); + + for (size_t k = 0u; k < n_statistics; ++k) + { + printf_epiworld( + fmt_stats.c_str(), + k, + summ_stats[k * 3], + summ_stats[k * 3 + 1u], + summ_stats[k * 3 + 2u], + observed_stats[k] + ); + } + + } + + printf_epiworld("___________________________________________\n\n"); +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/math/lfmcmc/lfmcmc-meat.hpp b/inst/include/epiworld/math/lfmcmc/lfmcmc-meat.hpp new file mode 100755 index 00000000..ea57b9a4 --- /dev/null +++ b/inst/include/epiworld/math/lfmcmc/lfmcmc-meat.hpp @@ -0,0 +1,528 @@ +#ifndef EPIWORLD_LFMCMC_MEAT_HPP +#define EPIWORLD_LFMCMC_MEAT_HPP + +#include "lfmcmc-bones.hpp" + +/** + * @brief Proposal function + * @param params_now Vector where to save the new parameters. + * @param params_prev Vector of reference parameters. + * @param m LFMCMC model. + * @tparam TData + */ +template +inline void proposal_fun_normal( + std::vector< epiworld_double >& params_now, + const std::vector< epiworld_double >& params_prev, + LFMCMC* m +) { + + for (size_t p = 0u; p < m->get_n_parameters(); ++p) + params_now[p] = params_prev[p] + m->rnorm(); + + return; +} + +/** + * @brief Factory for a reflective normal kernel + * + * @details Reflective kernel corrects proposals by forcing them to be + * within prespecified boundaries. + * + * @tparam TData + * @param scale Scale of the normal kernel + * @param lb Lower bound (applies the same to all parameters) + * @param ub Upper bound (applies the same to all parameters) + * @return LFMCMCProposalFun + */ +template +inline LFMCMCProposalFun make_proposal_norm_reflective( + epiworld_double scale, + epiworld_double lb, + epiworld_double ub +) { + + LFMCMCProposalFun fun = + [scale,lb,ub]( + std::vector< epiworld_double >& params_now, + const std::vector< epiworld_double >& params_prev, + LFMCMC* m + ) { + + // Making the proposal + for (size_t p = 0u; p < m->get_n_parameters(); ++p) + params_now[p] = params_prev[p] + m->rnorm() * scale; + + // Checking boundaries + epiworld_double d = ub - lb; + int odd; + epiworld_double d_above, d_below; + for (auto & p : params_now) + { + + // Correcting if parameter goes above the upper bound + if (p > ub) + { + d_above = p - ub; + odd = static_cast(std::floor(d_above / d)) % 2; + d_above = d_above - std::floor(d_above / d) * d; + + p = (lb + d_above) * odd + + (ub - d_above) * (1 - odd); + + // Correcting if parameter goes below upper bound + } else if (p < lb) + { + d_below = lb - p; + int odd = static_cast(std::floor(d_below / d)) % 2; + d_below = d_below - std::floor(d_below / d) * d; + + p = (ub - d_below) * odd + + (lb + d_below) * (1 - odd); + } + + } + + return; + + }; + + return fun; +} + +/** + * @brief Uniform proposal kernel + * + * Proposals are made within a radious 1 of the current + * state of the parameters. + * + * @param params_now Where to write the new parameters + * @param params_prev Reference parameters + * @tparam TData + * @param m LFMCMC model. + */ +template +inline void proposal_fun_unif( + std::vector< epiworld_double >& params_now, + const std::vector< epiworld_double >& params_prev, + LFMCMC* m +) { + + for (size_t p = 0u; p < m->get_n_parameters(); ++p) + params_now[p] = (params_prev[p] + m->runif(-1.0, 1.0)); + + return; +} + +/** + * @brief Uses the uniform kernel with euclidean distance + * + * @param stats_now Vector of current statistics based on + * simulated data. + * @param stats_obs Vector of observed statistics + * @param epsilon Epsilon parameter + * @param m LFMCMC model. + * @return epiworld_double + */ +template +inline epiworld_double kernel_fun_uniform( + const std::vector< epiworld_double >& stats_now, + const std::vector< epiworld_double >& stats_obs, + epiworld_double epsilon, + LFMCMC* m +) { + + epiworld_double ans = 0.0; + for (size_t p = 0u; p < m->get_n_parameters(); ++p) + ans += std::pow(stats_obs[p] - stats_now[p], 2.0); + + return std::sqrt(ans) < epsilon ? 1.0 : 0.0; + +} + +#define sqrt2pi() 2.5066282746310002416 + +/** + * @brief Gaussian kernel + * + * @tparam TData + * @param epsilon + * @param m + * @return epiworld_double + */ +template +inline epiworld_double kernel_fun_gaussian( + const std::vector< epiworld_double >& stats_now, + const std::vector< epiworld_double >& stats_obs, + epiworld_double epsilon, + LFMCMC* m +) { + + epiworld_double ans = 0.0; + for (size_t p = 0u; p < m->get_n_parameters(); ++p) + ans += std::pow(stats_obs[p] - stats_now[p], 2.0); + + return std::exp( + -.5 * (ans/std::pow(1 + std::pow(epsilon, 2.0)/3.0, 2.0)) + ) / sqrt2pi() ; + +} + + +template +inline void LFMCMC::set_proposal_fun(LFMCMCProposalFun fun) +{ + proposal_fun = fun; +} + +template +inline void LFMCMC::set_simulation_fun(LFMCMCSimFun fun) +{ + simulation_fun = fun; +} + +template +inline void LFMCMC::set_summary_fun(LFMCMCSummaryFun fun) +{ + summary_fun = fun; +} + +template +inline void LFMCMC::set_kernel_fun(LFMCMCKernelFun fun) +{ + kernel_fun = fun; +} + + +template +inline void LFMCMC::run( + std::vector< epiworld_double > params_init_, + size_t n_samples_, + epiworld_double epsilon_ + ) +{ + + // Starting timing + chrono_start(); + + // Setting the baseline parameters of the model + n_samples = n_samples_; + epsilon = epsilon_; + params_init = params_init_; + n_parameters = params_init_.size(); + + params_now.resize(n_parameters); + params_prev.resize(n_parameters); + + if (sampled_data != nullptr) + sampled_data->resize(n_samples); + + params_prev = params_init; + params_now = params_init; + + // Computing the baseline sufficient statistics + summary_fun(observed_stats, *observed_data, this); + n_statistics = observed_stats.size(); + + // Reserving size + drawn_prob.resize(n_samples); + sampled_accepted.resize(n_samples, false); + sampled_stats.resize(n_samples * n_statistics); + sampled_stats_prob.resize(n_samples); + + accepted_params.resize(n_samples * n_parameters); + accepted_stats.resize(n_samples * n_statistics); + accepted_params_prob.resize(n_samples); + + TData data_i = simulation_fun(params_init, this); + + std::vector< epiworld_double > proposed_stats_i; + summary_fun(proposed_stats_i, data_i, this); + accepted_params_prob[0u] = kernel_fun(proposed_stats_i, observed_stats, epsilon, this); + + // Recording statistics + for (size_t i = 0u; i < n_statistics; ++i) + sampled_stats[i] = proposed_stats_i[i]; + + for (size_t k = 0u; k < n_statistics; ++k) + accepted_params[k] = proposed_stats_i[k]; + + for (size_t i = 1u; i < n_samples; ++i) + { + // Step 1: Generate a proposal and store it in params_now + proposal_fun(params_now, params_prev, this); + + // Step 2: Using params_now, simulate data + TData data_i = simulation_fun(params_now, this); + + // Are we storing the data? + if (sampled_data != nullptr) + sampled_data->operator[](i) = data_i; + + // Step 3: Generate the summary statistics of the data + summary_fun(proposed_stats_i, data_i, this); + + // Step 4: Compute the hastings ratio using the kernel function + epiworld_double hr = kernel_fun(proposed_stats_i, observed_stats, epsilon, this); + sampled_stats_prob[i] = hr; + + // Storing data + for (size_t k = 0u; k < n_statistics; ++k) + sampled_stats[i * n_statistics + k] = proposed_stats_i[k]; + + // Running Hastings ratio + epiworld_double r = runif(); + drawn_prob[i] = r; + + // Step 5: Update if likely + if (r < std::min(static_cast(1.0), hr / accepted_params_prob[i - 1u])) + { + accepted_params_prob[i] = hr; + sampled_accepted[i] = true; + + for (size_t k = 0u; k < n_statistics; ++k) + accepted_stats[i * n_statistics + k] = + proposed_stats_i[k]; + + params_prev = params_now; + + } else + { + + for (size_t k = 0u; k < n_statistics; ++k) + accepted_stats[i * n_statistics + k] = + accepted_stats[(i - 1) * n_statistics + k]; + + accepted_params_prob[i] = accepted_params_prob[i - 1u]; + } + + + for (size_t k = 0u; k < n_parameters; ++k) + accepted_params[i * n_parameters + k] = params_prev[k]; + + } + + // End timing + chrono_end(); + +} + + +template +inline epiworld_double LFMCMC::runif() +{ + return runifd->operator()(*engine); +} + +template +inline epiworld_double LFMCMC::runif( + epiworld_double lb, + epiworld_double ub +) +{ + return runifd->operator()(*engine) * (ub - lb) + lb; +} + +template +inline epiworld_double LFMCMC::rnorm() +{ + return rnormd->operator()(*engine); +} + +template +inline epiworld_double LFMCMC::rnorm( + epiworld_double mean, + epiworld_double sd + ) +{ + return (rnormd->operator()(*engine)) * sd + mean; +} + +template +inline epiworld_double LFMCMC::rgamma() +{ + return rgammad->operator()(*engine); +} + +template +inline epiworld_double LFMCMC::rgamma( + epiworld_double alpha, + epiworld_double beta + ) +{ + + auto old_param = rgammad->param(); + + rgammad->param(std::gamma_distribution<>::param_type(alpha, beta)); + + epiworld_double ans = rgammad->operator()(*engine); + + rgammad->param(old_param); + + return ans; + +} + +template +inline void LFMCMC::seed(epiworld_fast_uint s) { + + this->engine->seed(s); + +} + +template +inline void LFMCMC::set_rand_engine(std::mt19937 & eng) +{ + engine = ŋ +} + +template +inline void LFMCMC::set_rand_gamma(epiworld_double alpha, epiworld_double beta) +{ + rgammad = std::make_shared>(alpha,beta); +} + +template +inline std::mt19937 & LFMCMC::get_rand_endgine() +{ + return *engine; +} + +// Step 1: Simulate data + +// Step 2: Compute the sufficient statistics + +// Step 3: Compute the hastings-ratio + +// Step 4: Accept/reject, and go back to step 1 + +#define DURCAST(tunit,txtunit) {\ + elapsed = std::chrono::duration_cast(\ + time_end - time_start).count(); \ + abbr_unit = txtunit;} + +template +inline void LFMCMC::get_elapsed( + std::string unit, + epiworld_double * last_elapsed, + std::string * unit_abbr, + bool print +) { + + // Preparing the result + epiworld_double elapsed; + std::string abbr_unit; + + // Figuring out the length + if (unit == "auto") + { + + size_t tlength = std::to_string( + static_cast(floor(time_elapsed.count())) + ).length(); + + if (tlength <= 1) + unit = "nanoseconds"; + else if (tlength <= 3) + unit = "microseconds"; + else if (tlength <= 6) + unit = "milliseconds"; + else if (tlength <= 8) + unit = "seconds"; + else if (tlength <= 9) + unit = "minutes"; + else + unit = "hours"; + + } + + if (unit == "nanoseconds") DURCAST(nanoseconds,"ns") + else if (unit == "microseconds") DURCAST(microseconds,"\xC2\xB5s") + else if (unit == "milliseconds") DURCAST(milliseconds,"ms") + else if (unit == "seconds") DURCAST(seconds,"s") + else if (unit == "minutes") DURCAST(minutes,"m") + else if (unit == "hours") DURCAST(hours,"h") + else + throw std::range_error("The time unit " + unit + " is not supported."); + + + if (last_elapsed != nullptr) + *last_elapsed = elapsed; + if (unit_abbr != nullptr) + *unit_abbr = abbr_unit; + + if (!print) + return; + + printf_epiworld("Elapsed time : %.2f%s.\n", elapsed, abbr_unit.c_str()); +} + +#undef DURCAST + +#include "lfmcmc-meat-print.hpp" + +template +inline void LFMCMC::chrono_start() { + time_start = std::chrono::steady_clock::now(); +} + +template +inline void LFMCMC::chrono_end() { + time_end = std::chrono::steady_clock::now(); + time_elapsed += (time_end - time_start); +} + +template +inline void LFMCMC::set_par_names(std::vector< std::string > names) +{ + + if (names.size() != n_parameters) + throw std::length_error("The number of names to add differs from the number of parameters in the model."); + + names_parameters = names; + +} +template +inline void LFMCMC::set_stats_names(std::vector< std::string > names) +{ + + if (names.size() != n_statistics) + throw std::length_error("The number of names to add differs from the number of statistics in the model."); + + names_statistics = names; + +} + +template +inline std::vector< epiworld_double > LFMCMC::get_params_mean() +{ + std::vector< epiworld_double > res(this->n_parameters, 0.0); + + for (size_t k = 0u; k < n_parameters; ++k) + { + for (size_t i = 0u; i < n_samples; ++i) + res[k] += (this->accepted_params[k + n_parameters * i])/ + static_cast< epiworld_double >(n_samples); + } + + return res; + +} + +template +inline std::vector< epiworld_double > LFMCMC::get_stats_mean() +{ + std::vector< epiworld_double > res(this->n_statistics, 0.0); + + for (size_t k = 0u; k < n_statistics; ++k) + { + for (size_t i = 0u; i < n_samples; ++i) + res[k] += (this->accepted_stats[k + n_statistics * i])/ + static_cast< epiworld_double >(n_samples); + } + + return res; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/misc.hpp b/inst/include/epiworld/misc.hpp new file mode 100644 index 00000000..dbeb73d1 --- /dev/null +++ b/inst/include/epiworld/misc.hpp @@ -0,0 +1,265 @@ +#ifndef EPIWORLD_MISC_HPP +#define EPIWORLD_MISC_HPP + +template +class Model; + +template +class Agent; + +// Relevant for anything using vecHasher function ------------------------------ +/** + * @brief Vector hasher + * @tparam T + */ +template +struct vecHasher { + std::size_t operator()(std::vector< T > const& dat) const noexcept { + + std::hash< T > hasher; + std::size_t hash = hasher(dat[0u]); + + // ^ makes bitwise XOR + // 0x9e3779b9 is a 32 bit constant (comes from the golden ratio) + // << is a shift operator, something like lhs * 2^(rhs) + if (dat.size() > 1u) + for (epiworld_fast_uint i = 1u; i < dat.size(); ++i) + hash ^= hasher(dat[i]) + 0x9e3779b9 + (hash<<6) + (hash>>2); + + return hash; + + } +}; + +template +using MapVec_type = std::unordered_map< std::vector< Ta >, Tb, vecHasher>; + +/** + * @name Default sequence initializers + * + * @details + * If the user does not provide a default sequence, this function is used when + * a sequence needs to be initialized. Some examples: `Agent`, `Virus`, and + * `Tool` need a default sequence. + * + * @tparam TSeq + * @return TSeq + */ +///@{ +template +inline TSeq default_sequence(int seq_count); + +// Making it 'static' so that we don't have problems when including the +// header. This is important during the linkage, e.g., in R. +// See https://en.cppreference.com/w/cpp/language/storage_duration#Linkage +// static int _n_sequences_created = 0; + +template<> +inline bool default_sequence(int seq_count) { + + if (seq_count == 2) + throw std::logic_error("Maximum number of sequence created."); + + return seq_count++ ? false : true; +} + +template<> +inline int default_sequence(int seq_count) { + return seq_count++; +} + +template<> +inline epiworld_double default_sequence(int seq_count) { + return static_cast(seq_count++); +} + +template<> +inline std::vector default_sequence(int seq_count) { + + if (seq_count == 2) + throw std::logic_error("Maximum number of sequence created."); + + return {seq_count++ ? false : true}; +} + +template<> +inline std::vector default_sequence(int seq_count) { + return {seq_count++}; +} + +template<> +inline std::vector default_sequence(int seq_count) { + return {static_cast(seq_count++)}; +} +///@} + +/** + * @brief Check whether `a` is included in `b` + * + * @tparam Ta Type of `a`. Could be int, epiworld_double, etc. + * @param a Scalar of class `Ta`. + * @param b Vector `std::vector` of class `Ta`. + * @return `true` if `a in b`, and `false` otherwise. + */ +template +inline bool IN(const Ta & a, const std::vector< Ta > & b) noexcept +{ + for (const auto & i : b) + if (a == i) + return true; + + return false; +} + +/** + * @brief Conditional Weighted Sampling + * + * @details + * The sampling function will draw one of `{-1, 0,...,probs.size() - 1}` in a + * weighted fashion. The probabilities are drawn given that either one or none + * of the cases is drawn; in the latter returns -1. + * + * @param probs Vector of probabilities. + * @param m A `Model`. This is used to draw random uniform numbers. + * @return int If -1 then it means that none got sampled, otherwise the index + * of the entry that got drawn. + */ +template +inline int roulette( + const std::vector< TDbl > & probs, + Model * m + ) +{ + + // Step 1: Computing the prob on none + TDbl p_none = 1.0; + std::vector< int > certain_infection; + certain_infection.reserve(probs.size()); + + for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) + { + p_none *= (1.0 - probs[p]); + + if (probs[p] > (1 - 1e-100)) + certain_infection.push_back(p); + + } + + TDbl r = static_cast(m->runif()); + // If there are one or more probs that go close to 1, sample + // uniformly + if (certain_infection.size() > 0) + return certain_infection[std::floor(r * certain_infection.size())]; + + // Step 2: Calculating the prob of none or single + std::vector< TDbl > probs_only_p(probs.size()); + TDbl p_none_or_single = p_none; + for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) + { + probs_only_p[p] = probs[p] * (p_none / (1.0 - probs[p])); + p_none_or_single += probs_only_p[p]; + } + + // Step 3: Roulette + TDbl cumsum = p_none/p_none_or_single; + if (r < cumsum) + { + return -1; + } + + for (epiworld_fast_uint p = 0u; p < probs.size(); ++p) + { + // If it yield here, then bingo, the individual will acquire the disease + cumsum += probs_only_p[p]/(p_none_or_single); + if (r < cumsum) + return static_cast(p); + + } + + + #ifdef EPI_DEBUG + printf_epiworld("[epi-debug] roulette::cumsum = %.4f\n", cumsum); + #endif + + return static_cast(probs.size() - 1u); + +} + +template +inline int roulette(std::vector< double > & probs, Model * m) +{ + return roulette(probs, m); +} + +template +inline int roulette(std::vector< float > & probs, Model * m) +{ + return roulette(probs, m); +} + + +template +inline int roulette( + epiworld_fast_uint nelements, + Model * m + ) +{ + + if ((nelements * 2) > m->array_double_tmp.size()) + { + throw std::logic_error( + "Trying to sample from more data than there is in roulette!" + + std::to_string(nelements) + " vs " + + std::to_string(m->array_double_tmp.size()) + ); + } + + // Step 1: Computing the prob on none + epiworld_double p_none = 1.0; + epiworld_fast_uint ncertain = 0u; + // std::vector< int > certain_infection; + for (epiworld_fast_uint p = 0u; p < nelements; ++p) + { + p_none *= (1.0 - m->array_double_tmp[p]); + + if (m->array_double_tmp[p] > (1 - 1e-100)) + m->array_double_tmp[nelements + ncertain++] = p; + // certain_infection.push_back(p); + + } + + epiworld_double r = m->runif(); + // If there are one or more probs that go close to 1, sample + // uniformly + if (ncertain > 0u) + return m->array_double_tmp[nelements + std::floor(ncertain * r)]; // certain_infection[std::floor(r * certain_infection.size())]; + + // Step 2: Calculating the prob of none or single + // std::vector< epiworld_double > probs_only_p; + epiworld_double p_none_or_single = p_none; + for (epiworld_fast_uint p = 0u; p < nelements; ++p) + { + m->array_double_tmp[nelements + p] = + m->array_double_tmp[p] * (p_none / (1.0 - m->array_double_tmp[p])); + p_none_or_single += m->array_double_tmp[nelements + p]; + } + + // Step 3: Roulette + epiworld_double cumsum = p_none/p_none_or_single; + if (r < cumsum) + return -1; + + for (epiworld_fast_uint p = 0u; p < nelements; ++p) + { + // If it yield here, then bingo, the individual will acquire the disease + cumsum += m->array_double_tmp[nelements + p]/(p_none_or_single); + if (r < cumsum) + return static_cast(p); + + } + + return static_cast(nelements - 1u); + +} + +#endif diff --git a/inst/include/epiworld/model-bones.hpp b/inst/include/epiworld/model-bones.hpp new file mode 100644 index 00000000..b913a383 --- /dev/null +++ b/inst/include/epiworld/model-bones.hpp @@ -0,0 +1,709 @@ +#ifndef EPIWORLD_MODEL_HPP +#define EPIWORLD_MODEL_HPP + +template +class Agent; + +template +class AgentsSample; + +template +class Virus; + +template +class Viruses; + +template +class Viruses_const; + +template +class Tool; + +class AdjList; + +template +class DataBase; + +template +class Queue; + +template +struct Action; + +template +class GlobalAction; + +template +inline epiworld_double susceptibility_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model* m + ); +template +inline epiworld_double transmission_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model* m + ); +template +inline epiworld_double recovery_enhancer_mixer_default( + Agent* p, + VirusPtr v, + Model* m + ); +template +inline epiworld_double death_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model* m + ); + +template +inline std::function*)> make_save_run( + std::string fmt = "%03lu-episimulation.csv", + bool total_hist = true, + bool virus_info = false, + bool virus_hist = false, + bool tool_info = false, + bool tool_hist = false, + bool transmission = false, + bool transition = false, + bool reproductive = false, + bool generation = false + ); + +// template +// class VirusPtr; + +// template +// class ToolPtr; + +/** + * @brief Core class of epiworld. + * + * The model class provides the wrapper that puts together `Agent`, `Virus`, and + * `Tools`. + * + * @tparam TSeq Type of sequence. In principle, users can build models in which + * virus and human sequence is represented as numeric vectors (if needed.) + */ +template +class Model { + friend class Agent; + friend class AgentsSample; + friend class DataBase; + friend class Queue; +protected: + + std::string name = ""; ///< Name of the model + + DataBase db = DataBase(*this); + + std::vector< Agent > population = {}; + + bool using_backup = true; + std::vector< Agent > population_backup = {}; + + + /** + * @name Auxiliary variables for AgentsSample iterators + * + * @details These variables+objects are used by the AgentsSample + * class for building efficient iterators over agents. The idea is to + * reduce the memory allocation, so only during the first call of + * AgentsSample::AgentsSample(Model) these vectors are allocated. + */ + ///@{ + std::vector< Agent * > sampled_population; + size_t sampled_population_n = 0u; + std::vector< size_t > population_left; + size_t population_left_n = 0u; + ///@} + + /** + * @name Agents features + * + * @details Optionally, a model can include an external data source + * pointing to agents information. The data can then be access through + * the `Agent::operator()` method. + * + */ + ///@{ + double * agents_data = nullptr; + size_t agents_data_ncols = 0u; + ///@} + + bool directed = false; + + std::vector< VirusPtr > viruses = {}; + std::vector< epiworld_double > prevalence_virus = {}; ///< Initial prevalence_virus of each virus + std::vector< bool > prevalence_virus_as_proportion = {}; + std::vector< VirusToAgentFun > viruses_dist_funs = {}; + + std::vector< ToolPtr > tools = {}; + std::vector< epiworld_double > prevalence_tool = {}; + std::vector< bool > prevalence_tool_as_proportion = {}; + std::vector< ToolToAgentFun > tools_dist_funs = {}; + + std::vector< Entity > entities = {}; + std::vector< Entity > entities_backup = {}; + + std::mt19937 engine; + + std::uniform_real_distribution<> runifd = + std::uniform_real_distribution<> (0.0, 1.0); + std::normal_distribution<> rnormd = + std::normal_distribution<>(0.0); + std::gamma_distribution<> rgammad = + std::gamma_distribution<>(); + std::lognormal_distribution<> rlognormald = + std::lognormal_distribution<>(); + std::exponential_distribution<> rexpd = + std::exponential_distribution<>(); + std::binomial_distribution<> rbinomd = + std::binomial_distribution<>(); + + std::function>*,Model*,epiworld_double)> rewire_fun; + epiworld_double rewire_prop = 0.0; + + std::map parameters; + epiworld_fast_uint ndays = 0; + Progress pb; + + std::vector< UpdateFun > state_fun = {}; + std::vector< std::string > states_labels = {}; + epiworld_fast_uint nstates = 0u; + + bool verbose = true; + int current_date = 0; + + void dist_tools(); + void dist_virus(); + // void dist_entities(); + + std::chrono::time_point time_start; + std::chrono::time_point time_end; + + // std::chrono::milliseconds + std::chrono::duration time_elapsed = + std::chrono::duration::zero(); + epiworld_fast_uint n_replicates = 0u; + void chrono_start(); + void chrono_end(); + + std::vector> global_actions; + + Queue queue; + bool use_queuing = true; + + /** + * @brief Variables used to keep track of the actions + * to be made regarding viruses. + */ + std::vector< Action > actions = {}; + epiworld_fast_uint nactions = 0u; + + /** + * @brief Construct a new Action object + * + * @param agent_ Agent over which the action will be called + * @param virus_ Virus pointer included in the action + * @param tool_ Tool pointer included in the action + * @param entity_ Entity pointer included in the action + * @param new_state_ New state of the agent + * @param call_ Function the action will call + * @param queue_ Change in the queue + * @param idx_agent_ Location of agent in object. + * @param idx_object_ Location of object in agent. + */ + void actions_add( + Agent * agent_, + VirusPtr virus_, + ToolPtr tool_, + Entity * entity_, + epiworld_fast_uint new_state_, + epiworld_fast_int queue_, + ActionFun call_, + int idx_agent_, + int idx_object_ + ); + + /** + * @brief Executes the stored action + * + * @param model_ Model over which it will be executed. + */ + void actions_run(); + + /** + * @name Tool Mixers + * + * These functions combine the effects tools have to deliver + * a single effect. For example, wearing a mask, been vaccinated, + * and the immune system combine together to jointly reduce + * the susceptibility for a given virus. + * + */ + MixerFun susceptibility_reduction_mixer = susceptibility_reduction_mixer_default; + MixerFun transmission_reduction_mixer = transmission_reduction_mixer_default; + MixerFun recovery_enhancer_mixer = recovery_enhancer_mixer_default; + MixerFun death_reduction_mixer = death_reduction_mixer_default; + + /** + * @brief Advanced usage: Makes a copy of data and returns it as undeleted pointer + * + * @param copy + */ + virtual Model * clone_ptr(); + +public: + + std::vector array_double_tmp; + std::vector * > array_virus_tmp; + + Model(); + Model(const Model & m); + Model(Model & m) = delete; + Model(Model && m); + Model & operator=(const Model & m); + + virtual ~Model() {}; + + void clone_population( + std::vector< Agent > & other_population, + std::vector< Entity > & other_entities, + Model * other_model, + bool & other_directed + ) const ; + + void clone_population( + const Model & other_model + ); + + /** + * @name Set the backup object + * @details `backup` can be used to restore the entire object + * after a run. This can be useful if the user wishes to have + * individuals start with the same network from the beginning. + * + */ + ///@{ + void set_backup(); + // void restore_backup(); + ///@} + + DataBase & get_db(); + epiworld_double & operator()(std::string pname); + + size_t size() const; + + /** + * @name Random number generation + * + * @param eng Random number generator + * @param s Seed + */ + ///@{ + void set_rand_engine(std::mt19937 & eng); + std::mt19937 & get_rand_endgine(); + void seed(size_t s); + void set_rand_norm(epiworld_double mean, epiworld_double sd); + void set_rand_unif(epiworld_double a, epiworld_double b); + void set_rand_exp(epiworld_double lambda); + void set_rand_gamma(epiworld_double alpha, epiworld_double beta); + void set_rand_lognormal(epiworld_double mean, epiworld_double shape); + void set_rand_binom(int n, epiworld_double p); + epiworld_double runif(); + epiworld_double runif(epiworld_double a, epiworld_double b); + epiworld_double rnorm(); + epiworld_double rnorm(epiworld_double mean, epiworld_double sd); + epiworld_double rgamma(); + epiworld_double rgamma(epiworld_double alpha, epiworld_double beta); + epiworld_double rexp(); + epiworld_double rexp(epiworld_double lambda); + epiworld_double rlognormal(); + epiworld_double rlognormal(epiworld_double mean, epiworld_double shape); + int rbinom(); + int rbinom(int n, epiworld_double p); + ///@} + + /** + * @name Add Virus/Tool to the model + * + * This is done before the model has been initialized. + * + * @param v Virus to be added + * @param t Tool to be added + * @param preval Initial prevalence (initial state.) It can be + * specified as a proportion (between zero and one,) or an integer + * indicating number of individuals. + */ + ///@{ + void add_virus(Virus & v, epiworld_double preval); + void add_virus_n(Virus & v, epiworld_fast_uint preval); + void add_virus_fun(Virus & v, VirusToAgentFun fun); + void add_tool(Tool & t, epiworld_double preval); + void add_tool_n(Tool & t, epiworld_fast_uint preval); + void add_tool_fun(Tool & t, ToolToAgentFun fun); + void add_entity(Entity e); + void rm_virus(size_t virus_pos); + void rm_tool(size_t tool_pos); + void rm_entity(size_t entity_pos); + ///@} + + /** + * @brief Associate agents-entities from a file + * + * The structure of the file should be two columns separated by + * space. The first column indexing between 0 and nagents-1, and the + * second column between 0 and nentities - 1. + * + * @param fn Path to the file. + * @param skip How many rows to skip. + */ + void load_agents_entities_ties(std::string fn, int skip); + + /** + * @name Accessing population of the model + * + * @param fn std::string Filename of the edgelist file. + * @param skip int Number of lines to skip in `fn`. + * @param directed bool Whether the graph is directed or not. + * @param size Size of the network. + * @param al AdjList to read into the model. + */ + ///@{ + void agents_from_adjlist( + std::string fn, + int size, + int skip = 0, + bool directed = false + ); + + void agents_from_edgelist( + const std::vector< int > & source, + const std::vector< int > & target, + int size, + bool directed + ); + + void agents_from_adjlist(AdjList al); + + bool is_directed() const; + + std::vector< Agent > & get_agents(); ///< Returns a reference to the vector of agents. + + std::vector< epiworld_fast_uint > get_agents_states() const; ///< Returns a vector with the states of the agents. + + std::vector< Viruses_const > get_agents_viruses() const; ///< Returns a const vector with the viruses of the agents. + + std::vector< Viruses > get_agents_viruses(); ///< Returns a vector with the viruses of the agents. + + std::vector< Entity > & get_entities(); + + void agents_smallworld( + epiworld_fast_uint n = 1000, + epiworld_fast_uint k = 5, + bool d = false, + epiworld_double p = .01 + ); + void agents_empty_graph(epiworld_fast_uint n = 1000); + ///@} + + /** + * @name Functions to run the model + * + * @param seed Seed to be used for Pseudo-RNG. + * @param ndays Number of days (steps) of the simulation. + * @param fun In the case of `run_multiple`, a function that is called + * after each experiment. + * + */ + ///@{ + void update_state(); + void mutate_virus(); + void next(); + virtual void run( + epiworld_fast_uint ndays, + int seed = -1 + ); ///< Runs the simulation (after initialization) + void run_multiple( ///< Multiple runs of the simulation + epiworld_fast_uint ndays, + epiworld_fast_uint nexperiments, + int seed_ = -1, + std::function*)> fun = make_save_run(), + bool reset = true, + bool verbose = true, + int nthreads = 1 + ); + ///@} + + size_t get_n_viruses() const; + size_t get_n_tools() const; + epiworld_fast_uint get_ndays() const; + epiworld_fast_uint get_n_replicates() const; + void set_ndays(epiworld_fast_uint ndays); + bool get_verbose() const; + void verbose_off(); + void verbose_on(); + int today() const; ///< The current time of the model + + /** + * @name Rewire the network preserving the degree sequence. + * + * @details This implementation assumes an undirected network, + * thus if {(i,j), (k,l)} -> {(i,l), (k,j)}, the reciprocal + * is also true, i.e., {(j,i), (l,k)} -> {(j,k), (l,i)}. + * + * @param proportion Proportion of ties to be rewired. + * + * @result A rewired version of the network. + */ + ///@{ + void set_rewire_fun(std::function>*,Model*,epiworld_double)> fun); + void set_rewire_prop(epiworld_double prop); + epiworld_double get_rewire_prop() const; + void rewire(); + ///@} + + /** + * @brief Wrapper of `DataBase::write_data` + * + * @param fn_virus_info Filename. Information about the virus. + * @param fn_virus_hist Filename. History of the virus. + * @param fn_tool_info Filename. Information about the tool. + * @param fn_tool_hist Filename. History of the tool. + * @param fn_total_hist Filename. Aggregated history (state) + * @param fn_transmission Filename. Transmission history. + * @param fn_transition Filename. Markov transition history. + * @param fn_reproductive_number Filename. Case by case reproductive number + */ + void write_data( + std::string fn_virus_info, + std::string fn_virus_hist, + std::string fn_tool_info, + std::string fn_tool_hist, + std::string fn_total_hist, + std::string fn_transmission, + std::string fn_transition, + std::string fn_reproductive_number, + std::string fn_generation_time + ) const; + + /** + * @name Export the network data in edgelist form + * + * @param fn std::string. File name. + * @param source Integer vector + * @param target Integer vector + * + * @details When passing the source and target, the function will + * write the edgelist on those. + */ + ///@{ + void write_edgelist( + std::string fn + ) const; + + void write_edgelist( + std::vector< int > & source, + std::vector< int > & target + ) const; + ///@} + + std::map & params(); + + /** + * @brief Reset the model + * + * @details Resetting the model will: + * - clear the database + * - restore the population (if `set_backup()` was called before) + * - re-distribute tools + * - re-distribute viruses + * - set the date to 0 + * + */ + virtual void reset(); + void print(bool lite = false) const; + + Model && clone() const; + + /** + * @name Manage state (states) in the model + * + * @details + * + * The functions `get_state` return the current values for the + * states included in the model. + * + * @param lab `std::string` Name of the state. + * + * @return `add_state*` returns nothing. + * @return `get_state_*` returns a vector of pairs with the + * states and their labels. + */ + ///@{ + void add_state(std::string lab, UpdateFun fun = nullptr); + const std::vector< std::string > & get_states() const; + const std::vector< UpdateFun > & get_state_fun() const; + void print_state_codes() const; + ///@} + + /** + * @name Setting and accessing parameters from the model + * + * @details Tools can incorporate parameters included in the model. + * Internally, parameters in the tool are stored as pointers to + * an std::map<> of parameters in the model. Using the `epiworld_fast_uint` + * method directly fetches the parameters in the order these were + * added to the tool. Accessing parameters via the `std::string` method + * involves searching the parameter directly in the std::map<> member + * of the model (so it is not recommended.) + * + * The `par()` function members are aliases for `get_param()`. + * + * In the case of the function `read_params`, users can pass a file + * listing parameters to be included in the model. Each line in the + * file should have the following structure: + * + * ``` + * [name of parameter 1]: [value in double] + * [name of parameter 2]: [value in double] + * ... + * ``` + * + * The only condition for parameter names is that these do not include + * a colon. + * + * + * @param initial_val + * @param pname Name of the parameter to add or to fetch + * @param fn Path to the file containing parameters + * @return The current value of the parameter + * in the model. + * + */ + ///@{ + epiworld_double add_param(epiworld_double initial_val, std::string pname); + void read_params(std::string fn); + epiworld_double get_param(epiworld_fast_uint k); + epiworld_double get_param(std::string pname); + // void set_param(size_t k, epiworld_double val); + void set_param(std::string pname, epiworld_double val); + // epiworld_double par(epiworld_fast_uint k); + epiworld_double par(std::string pname); + ///@} + + void get_elapsed( + std::string unit = "auto", + epiworld_double * last_elapsed = nullptr, + epiworld_double * total_elapsed = nullptr, + std::string * unit_abbr = nullptr, + bool print = true + ) const; + + /** + * @name Set the user data object + * + * @param names string vector with the names of the variables. + */ + ///[@ + void set_user_data(std::vector< std::string > names); + void add_user_data(epiworld_fast_uint j, epiworld_double x); + void add_user_data(std::vector< epiworld_double > x); + UserData & get_user_data(); + ///@} + + /** + * @brief Set a global action + * + * @param fun A function to be called on the prescribed date + * @param name Name of the action. + * @param date Integer indicating when the function is called (see details) + * + * @details When date is less than zero, then the function is called + * at the end of every day. Otherwise, the function will be called only + * at the end of the indicated date. + */ + void add_global_action( + std::function*)> fun, + std::string name = "A global action", + int date = -99 + ); + + void add_global_action( + GlobalAction action + ); + + GlobalAction & get_global_action(std::string name); ///< Retrieve a global action by name + GlobalAction & get_global_action(size_t i); ///< Retrieve a global action by index + + void rm_global_action(std::string name); ///< Remove a global action by name + void rm_global_action(size_t i); ///< Remove a global action by index + + void run_global_actions(); + + void clear_state_set(); + + /** + * @name Queuing system + * @details When queueing is on, the model will keep track of which agents + * are either in risk of exposure or exposed. This then is used at each + * step to act only on the aforementioned agents. + * + */ + ////@{ + void queuing_on(); ///< Activates the queuing system (default.) + void queuing_off(); ///< Deactivates the queuing system. + bool is_queuing_on() const; ///< Query if the queuing system is on. + Queue & get_queue(); ///< Retrieve the `Queue` object. + ///@} + + /** + * @name Get the susceptibility reduction object + * + * @param v + * @return epiworld_double + */ + ///@{ + void set_susceptibility_reduction_mixer(MixerFun fun); + void set_transmission_reduction_mixer(MixerFun fun); + void set_recovery_enhancer_mixer(MixerFun fun); + void set_death_reduction_mixer(MixerFun fun); + ///@} + + const std::vector< VirusPtr > & get_viruses() const; + const std::vector< ToolPtr > & get_tools() const; + Virus & get_virus(size_t id); + Tool & get_tool(size_t id); + + /** + * @brief Set the agents data object + * + * @details The data should be an array with the data stored in a + * column major order, i.e., by column. + * + * @param data_ Pointer to the first element of an array of size + * `size() * ncols_`. + * @param ncols_ Number of features included in the data. + * + */ + void set_agents_data(double * data_, size_t ncols_); + double * get_agents_data(); + size_t get_agents_data_ncols() const; + + /** + * @brief Set the name object + * + * @param name + */ + void set_name(std::string name); + std::string get_name() const; + + bool operator==(const Model & other) const; + bool operator!=(const Model & other) const {return !operator==(other);}; + +}; + +#endif diff --git a/inst/include/epiworld/model-meat-print.hpp b/inst/include/epiworld/model-meat-print.hpp new file mode 100644 index 00000000..673f5189 --- /dev/null +++ b/inst/include/epiworld/model-meat-print.hpp @@ -0,0 +1,331 @@ +#ifndef EPIWORLD_MODEL_MEAT_PRINT_HPP +#define EPIWORLD_MODEL_MEAT_PRINT_HPP + +template +inline void Model::print(bool lite) const +{ + + // Horizontal line + std::string line = ""; + for (epiworld_fast_uint i = 0u; i < 80u; ++i) + line += "_"; + + // Prints a message if debugging is on + EPI_DEBUG_NOTIFY_ACTIVE() + + printf_epiworld("%s\n",line.c_str()); + + if (lite) + { + // Printing the name of the model + printf_epiworld("%s", name.c_str()); + + // Printing the number of agents, viruses, and tools + printf_epiworld( + "\nIt features %i agents, %i virus(es), and %i tool(s).\n", + static_cast(size()), + static_cast(get_n_viruses()), + static_cast(get_n_tools()) + ); + + printf_epiworld( + "The model has %i states.", + static_cast(nstates) + ); + + if (today() != 0) + { + printf_epiworld( + "\nThe final distribution is: " + ); + + int nstate_int = static_cast(nstates); + + for (int i = 0u; i < nstate_int; ++i) + { + printf_epiworld( + "%i %s%s", + static_cast(db.today_total[ i ]), + states_labels[i].c_str(), + ( + i == (nstate_int - 2) + ) ? ", and " : ( + (i == (nstate_int - 1)) ? ".\n" : ", " + ) + ); + } + } else { + printf_epiworld(" The model hasn't been run yet.\n"); + } + + return; + } + + printf_epiworld("%s\n%s\n\n",line.c_str(), "SIMULATION STUDY"); + + printf_epiworld("Name of the model : %s\n", (this->name == "") ? std::string("(none)").c_str() : name.c_str()); + printf_epiworld("Population size : %i\n", static_cast(size())); + + auto ncols = get_agents_data_ncols(); + + if (ncols > 0) + { + printf_epiworld("Agents' data loaded : yes (%i columns/features)\n", static_cast(ncols)); + } + else + { + printf_epiworld("Agents' data : (none)\n"); + } + + printf_epiworld("Number of entities : %i\n", static_cast(entities.size())); + printf_epiworld("Days (duration) : %i (of %i)\n", today(), static_cast(ndays)); + printf_epiworld("Number of viruses : %i\n", static_cast(db.get_n_viruses())); + if (n_replicates > 0u) + { + std::string abbr; + epiworld_double elapsed; + epiworld_double total; + get_elapsed("auto", &elapsed, &total, &abbr, false); + printf_epiworld("Last run elapsed t : %.2f%s\n", elapsed, abbr.c_str()); + if (n_replicates > 1u) + { + printf_epiworld("Total elapsed t : %.2f%s (%i runs)\n", total, abbr.c_str(), static_cast(n_replicates)); + } + + // Elapsed time in speed + get_elapsed("microseconds", &elapsed, &total, &abbr, false); + printf_epiworld("Last run speed : %.2f million agents x day / second\n", + static_cast(this->size()) * + static_cast(this->get_ndays()) / + static_cast(elapsed) + ); + if (n_replicates > 1u) + { + printf_epiworld("Average run speed : %.2f million agents x day / second\n", + static_cast(this->size()) * + static_cast(this->get_ndays()) * + static_cast(n_replicates) / + static_cast(total) + ); + } + + } else { + printf_epiworld("Last run elapsed t : -\n"); + } + + + if (rewire_fun) + { + printf_epiworld("Rewiring : on (%.2f)\n\n", rewire_prop); + } else { + printf_epiworld("Rewiring : off\n\n"); + } + + // Printing global actions + printf_epiworld("Global actions:\n"); + for (auto & a : global_actions) + { + if (a.get_day() < 0) + { + printf_epiworld(" - %s (runs daily)\n", a.get_name().c_str()); + } else { + printf_epiworld(" - %s (day %i)\n", a.get_name().c_str(), a.get_day()); + } + } + + if (global_actions.size() == 0u) + { + printf_epiworld(" (none)\n"); + } + + printf_epiworld("\nVirus(es):\n"); + size_t n_viruses_model = viruses.size(); + for (size_t i = 0u; i < n_viruses_model; ++i) + { + + if ((n_viruses_model > 10) && (i >= 10)) + { + printf_epiworld(" ...and %li more viruses...\n", n_viruses_model - i); + break; + } + + if (i < n_viruses_model) + { + + if (prevalence_virus_as_proportion[i]) + { + + printf_epiworld( + " - %s (baseline prevalence: %.2f%%)\n", + viruses[i]->get_name().c_str(), + prevalence_virus[i] * 100.00 + ); + + } + else + { + + printf_epiworld( + " - %s (baseline prevalence: %i seeds)\n", + viruses[i]->get_name().c_str(), + static_cast(prevalence_virus[i]) + ); + + } + + } else { + + printf_epiworld( + " - %s (originated in the model...)\n", + viruses[i]->get_name().c_str() + ); + + } + + } + + if (viruses.size() == 0u) + { + printf_epiworld(" (none)\n"); + } + + printf_epiworld("\nTool(s):\n"); + size_t n_tools_model = tools.size(); + for (size_t i = 0u; i < tools.size(); ++i) + { + + if ((n_tools_model > 10) && (i >= 10)) + { + printf_epiworld(" ...and %li more tools...\n", n_tools_model - i); + break; + } + + if (i < n_tools_model) + { + if (prevalence_tool_as_proportion[i]) + { + + printf_epiworld( + " - %s (baseline prevalence: %.2f%%)\n", + tools[i]->get_name().c_str(), + prevalence_tool[i] * 100.0 + ); + + } + else + { + + printf_epiworld( + " - %s (baseline prevalence: %i seeds)\n", + tools[i]->get_name().c_str(), + static_cast(prevalence_tool[i]) + ); + + } + + } else { + + printf_epiworld( + " - %s (originated in the model...)\n", + tools[i]->get_name().c_str() + ); + + } + + + } + + if (tools.size() == 0u) + { + printf_epiworld(" (none)\n"); + } + + // Information about the parameters included + printf_epiworld("\nModel parameters:\n"); + epiworld_fast_uint nchar = 0u; + for (auto & p : parameters) + if (p.first.length() > nchar) + nchar = p.first.length(); + + std::string fmt = " - %-" + std::to_string(nchar + 1) + "s: "; + for (auto & p : parameters) + { + std::string fmt_tmp = fmt; + if (std::fabs(p.second) < 0.0001) + fmt_tmp += "%.1e\n"; + else + fmt_tmp += "%.4f\n"; + + printf_epiworld( + fmt_tmp.c_str(), + p.first.c_str(), + p.second + ); + + } + + if (parameters.size() == 0u) + { + printf_epiworld(" (none)\n"); + } + + nchar = 0u; + for (auto & p : states_labels) + if (p.length() > nchar) + nchar = p.length(); + + + + if (today() != 0) + { + fmt = + std::string(" - (%") + + std::to_string(std::to_string(nstates).length()) + + std::string("d) %-") + std::to_string(nchar) + + std::string("s : %") + + std::to_string(std::to_string(size()).length()) + + std::string("i -> %i\n"); + } else { + fmt = + std::string(" - (%") + + std::to_string(std::to_string(nstates).length()) + + std::string("d) %-") + std::to_string(nchar) + + std::string("s : %i\n"); + } + + if (today() != 0) + { + printf_epiworld("\nDistribution of the population at time %i:\n", today()); + for (size_t s = 0u; s < nstates; ++s) + { + + printf_epiworld( + fmt.c_str(), + s, + states_labels[s].c_str(), + db.hist_total_counts[s], + db.today_total[ s ] + ); + + } + // else + // { + + // printf_epiworld( + // fmt.c_str(), + // s, + // states_labels[s].c_str(), + // db.today_total[ s ] + // ); + + // } + } + + if (today() != 0) + (void) db.transition_probability(true); + + return; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/model-meat.hpp b/inst/include/epiworld/model-meat.hpp new file mode 100644 index 00000000..431a58c2 --- /dev/null +++ b/inst/include/epiworld/model-meat.hpp @@ -0,0 +1,2814 @@ +#ifndef EPIWORLD_MODEL_MEAT_HPP +#define EPIWORLD_MODEL_MEAT_HPP + + + +/** + * @brief Function factory for saving model runs + * + * @details This function is the default behavior of the `run_multiple` + * member of `Model`. By default only the total history ( + * case counts by unit of time.) + * + * @tparam TSeq + * @param fmt + * @param total_hist + * @param virus_info + * @param virus_hist + * @param tool_info + * @param tool_hist + * @param transmission + * @param transition + * @return std::function*)> + */ +template +inline std::function*)> make_save_run( + std::string fmt, + bool total_hist, + bool virus_info, + bool virus_hist, + bool tool_info, + bool tool_hist, + bool transmission, + bool transition, + bool reproductive, + bool generation + ) +{ + + // Counting number of % + int n_fmt = 0; + for (auto & f : fmt) + if (f == '%') + n_fmt++; + + if (n_fmt != 1) + throw std::logic_error("The -fmt- argument must have only one \"%\" symbol."); + + // Listting things to save + std::vector< bool > what_to_save = { + virus_info, + virus_hist, + tool_info, + tool_hist, + total_hist, + transmission, + transition, + reproductive, + generation + }; + + std::function*)> saver = [fmt,what_to_save]( + size_t niter, Model * m + ) -> void { + + std::string virus_info = ""; + std::string virus_hist = ""; + std::string tool_info = ""; + std::string tool_hist = ""; + std::string total_hist = ""; + std::string transmission = ""; + std::string transition = ""; + std::string reproductive = ""; + std::string generation = ""; + + char buff[1024u]; + if (what_to_save[0u]) + { + virus_info = fmt + std::string("_virus_info.csv"); + snprintf(buff, sizeof(buff), virus_info.c_str(), niter); + virus_info = buff; + } + if (what_to_save[1u]) + { + virus_hist = fmt + std::string("_virus_hist.csv"); + snprintf(buff, sizeof(buff), virus_hist.c_str(), niter); + virus_hist = buff; + } + if (what_to_save[2u]) + { + tool_info = fmt + std::string("_tool_info.csv"); + snprintf(buff, sizeof(buff), tool_info.c_str(), niter); + tool_info = buff; + } + if (what_to_save[3u]) + { + tool_hist = fmt + std::string("_tool_hist.csv"); + snprintf(buff, sizeof(buff), tool_hist.c_str(), niter); + tool_hist = buff; + } + if (what_to_save[4u]) + { + total_hist = fmt + std::string("_total_hist.csv"); + snprintf(buff, sizeof(buff), total_hist.c_str(), niter); + total_hist = buff; + } + if (what_to_save[5u]) + { + transmission = fmt + std::string("_transmission.csv"); + snprintf(buff, sizeof(buff), transmission.c_str(), niter); + transmission = buff; + } + if (what_to_save[6u]) + { + transition = fmt + std::string("_transition.csv"); + snprintf(buff, sizeof(buff), transition.c_str(), niter); + transition = buff; + } + if (what_to_save[7u]) + { + + reproductive = fmt + std::string("_reproductive.csv"); + snprintf(buff, sizeof(buff), reproductive.c_str(), niter); + reproductive = buff; + + } + if (what_to_save[8u]) + { + + generation = fmt + std::string("_generation.csv"); + snprintf(buff, sizeof(buff), generation.c_str(), niter); + generation = buff; + + } + + + m->write_data( + virus_info, + virus_hist, + tool_info, + tool_hist, + total_hist, + transmission, + transition, + reproductive, + generation + ); + + }; + + return saver; +} + +template +inline void Model::actions_add( + Agent * agent_, + VirusPtr virus_, + ToolPtr tool_, + Entity * entity_, + epiworld_fast_uint new_state_, + epiworld_fast_int queue_, + ActionFun call_, + int idx_agent_, + int idx_object_ +) { + + ++nactions; + + #ifdef EPI_DEBUG + if (nactions == 0) + throw std::logic_error("Actions cannot be zero!!"); + + if ((virus_ != nullptr) && idx_agent_ >= 0) + { + if (idx_agent_ >= static_cast(virus_->get_agent()->get_n_viruses())) + throw std::logic_error( + "The virus to add is out of range in the host agent." + ); + } + #endif + + if (nactions > actions.size()) + { + + actions.push_back( + Action( + agent_, virus_, tool_, entity_, new_state_, queue_, call_, + idx_agent_, idx_object_ + )); + + } + else + { + + Action & A = actions.at(nactions - 1u); + + A.agent = agent_; + A.virus = virus_; + A.tool = tool_; + A.entity = entity_; + A.new_state = new_state_; + A.queue = queue_; + A.call = call_; + A.idx_agent = idx_agent_; + A.idx_object = idx_object_; + + } + + return; + +} + +template +inline void Model::actions_run() +{ + // Making the call + while (nactions != 0u) + { + + Action a = actions[--nactions]; + Agent * p = a.agent; + + // Applying function + if (a.call) + { + a.call(a, this); + } + + // Updating state + if (static_cast(p->state) != a.new_state) + { + + if (a.new_state >= static_cast(nstates)) + throw std::range_error( + "The proposed state " + std::to_string(a.new_state) + " is out of range. " + + "The model currently has " + std::to_string(nstates - 1) + " states."); + + // Figuring out if we need to undo a change + // If the agent has made a change in the state recently, then we + // need to undo the accounting, e.g., if A->B was made, we need to + // undo it and set B->A so that the daily accounting is right. + if (p->state_last_changed == today()) + { + + // Updating accounting + db.update_state(p->state_prev, p->state, true); // Undoing + db.update_state(p->state_prev, a.new_state); + + for (size_t v = 0u; v < p->n_viruses; ++v) + { + db.update_virus(p->viruses[v]->id, p->state, p->state_prev); // Undoing + db.update_virus(p->viruses[v]->id, p->state_prev, a.new_state); + } + + for (size_t t = 0u; t < p->n_tools; ++t) + { + db.update_tool(p->tools[t]->id, p->state, p->state_prev); // Undoing + db.update_tool(p->tools[t]->id, p->state_prev, a.new_state); + } + + // Changing to the new state, we won't update the + // previous state in case we need to undo the change + p->state = a.new_state; + + } else { + + // Updating accounting + db.update_state(p->state, a.new_state); + + for (size_t v = 0u; v < p->n_viruses; ++v) + db.update_virus(p->viruses[v]->id, p->state, a.new_state); + + for (size_t t = 0u; t < p->n_tools; ++t) + db.update_tool(p->tools[t]->id, p->state, a.new_state); + + // Saving the last state and setting the new one + p->state_prev = p->state; + p->state = a.new_state; + + // It used to be a day before, but we still + p->state_last_changed = today(); + + } + + } + + #ifdef EPI_DEBUG + if (static_cast(p->state) >= static_cast(nstates)) + throw std::range_error( + "The new state " + std::to_string(p->state) + " is out of range. " + + "The model currently has " + std::to_string(nstates - 1) + " states."); + #endif + + // Updating queue + if (use_queuing) + { + + if (a.queue == Queue::Everyone) + queue += p; + else if (a.queue == -Queue::Everyone) + queue -= p; + else if (a.queue == Queue::OnlySelf) + queue[p->get_id()]++; + else if (a.queue == -Queue::OnlySelf) + queue[p->get_id()]--; + else if (a.queue != Queue::NoOne) + throw std::logic_error( + "The proposed queue change is not valid. Queue values can be {-2, -1, 0, 1, 2}." + ); + + } + + } + + return; + +} + +/** + * @name Default function for combining susceptibility_reduction levels + * + * @tparam TSeq + * @param pt + * @return epiworld_double + */ +///@{ +template +inline epiworld_double susceptibility_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model * m +) +{ + epiworld_double total = 1.0; + for (auto & tool : p->get_tools()) + total *= (1.0 - tool->get_susceptibility_reduction(v, m)); + + return 1.0 - total; + +} + +template +inline epiworld_double transmission_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model* m +) +{ + epiworld_double total = 1.0; + for (auto & tool : p->get_tools()) + total *= (1.0 - tool->get_transmission_reduction(v, m)); + + return (1.0 - total); + +} + +template +inline epiworld_double recovery_enhancer_mixer_default( + Agent* p, + VirusPtr v, + Model* m +) +{ + epiworld_double total = 1.0; + for (auto & tool : p->get_tools()) + total *= (1.0 - tool->get_recovery_enhancer(v, m)); + + return 1.0 - total; + +} + +template +inline epiworld_double death_reduction_mixer_default( + Agent* p, + VirusPtr v, + Model* m +) { + + epiworld_double total = 1.0; + for (auto & tool : p->get_tools()) + { + total *= (1.0 - tool->get_death_reduction(v, m)); + } + + return 1.0 - total; + +} +///@} + +template +inline Model * Model::clone_ptr() +{ + Model * ptr = new Model(*dynamic_cast*>(this)); + + #ifdef EPI_DEBUG + if (*this != *ptr) + throw std::logic_error("Model::clone_ptr The copies of the model don't match."); + #endif + + return ptr; +} + +template +inline Model::Model() +{ + db.model = this; + db.user_data = this; + if (use_queuing) + queue.model = this; +} + +template +inline Model::Model(const Model & model) : + name(model.name), + db(model.db), + population(model.population), + population_backup(model.population_backup), + directed(model.directed), + viruses(model.viruses), + prevalence_virus(model.prevalence_virus), + prevalence_virus_as_proportion(model.prevalence_virus_as_proportion), + viruses_dist_funs(model.viruses_dist_funs), + tools(model.tools), + prevalence_tool(model.prevalence_tool), + prevalence_tool_as_proportion(model.prevalence_tool_as_proportion), + tools_dist_funs(model.tools_dist_funs), + entities(model.entities), + entities_backup(model.entities_backup), + // prevalence_entity(model.prevalence_entity), + // prevalence_entity_as_proportion(model.prevalence_entity_as_proportion), + // entities_dist_funs(model.entities_dist_funs), + rewire_fun(model.rewire_fun), + rewire_prop(model.rewire_prop), + parameters(model.parameters), + ndays(model.ndays), + pb(model.pb), + state_fun(model.state_fun), + states_labels(model.states_labels), + nstates(model.nstates), + verbose(model.verbose), + current_date(model.current_date), + global_actions(model.global_actions), + queue(model.queue), + use_queuing(model.use_queuing), + array_double_tmp(model.array_double_tmp.size()), + array_virus_tmp(model.array_virus_tmp.size()) +{ + + + // Removing old neighbors + for (auto & p : population) + p.model = this; + + if (population_backup.size() != 0u) + for (auto & p : population_backup) + p.model = this; + + for (auto & e : entities) + e.model = this; + + if (entities_backup.size() != 0u) + for (auto & e : entities_backup) + e.model = this; + + // Pointing to the right place. This needs + // to be done afterwards since the state zero is set as a function + // of the population. + db.model = this; + db.user_data.model = this; + + if (use_queuing) + queue.model = this; + + agents_data = model.agents_data; + agents_data_ncols = model.agents_data_ncols; + +} + +template +inline Model::Model(Model && model) : + name(std::move(model.name)), + db(std::move(model.db)), + population(std::move(model.population)), + agents_data(std::move(model.agents_data)), + agents_data_ncols(std::move(model.agents_data_ncols)), + directed(std::move(model.directed)), + // Virus + viruses(std::move(model.viruses)), + prevalence_virus(std::move(model.prevalence_virus)), + prevalence_virus_as_proportion(std::move(model.prevalence_virus_as_proportion)), + viruses_dist_funs(std::move(model.viruses_dist_funs)), + // Tools + tools(std::move(model.tools)), + prevalence_tool(std::move(model.prevalence_tool)), + prevalence_tool_as_proportion(std::move(model.prevalence_tool_as_proportion)), + tools_dist_funs(std::move(model.tools_dist_funs)), + // Entities + entities(std::move(model.entities)), + entities_backup(std::move(model.entities_backup)), + // prevalence_entity(std::move(model.prevalence_entity)), + // prevalence_entity_as_proportion(std::move(model.prevalence_entity_as_proportion)), + // entities_dist_funs(std::move(model.entities_dist_funs)), + // Pseudo-RNG + engine(std::move(model.engine)), + runifd(std::move(model.runifd)), + rnormd(std::move(model.rnormd)), + rgammad(std::move(model.rgammad)), + rlognormald(std::move(model.rlognormald)), + rexpd(std::move(model.rexpd)), + // Rewiring + rewire_fun(std::move(model.rewire_fun)), + rewire_prop(std::move(model.rewire_prop)), + parameters(std::move(model.parameters)), + // Others + ndays(model.ndays), + pb(std::move(model.pb)), + state_fun(std::move(model.state_fun)), + states_labels(std::move(model.states_labels)), + nstates(model.nstates), + verbose(model.verbose), + current_date(std::move(model.current_date)), + global_actions(std::move(model.global_actions)), + queue(std::move(model.queue)), + use_queuing(model.use_queuing), + array_double_tmp(model.array_double_tmp.size()), + array_virus_tmp(model.array_virus_tmp.size()) +{ + + db.model = this; + db.user_data.model = this; + + if (use_queuing) + queue.model = this; + +} + +template +inline Model & Model::operator=(const Model & m) +{ + name = m.name; + + population = m.population; + population_backup = m.population_backup; + + for (auto & p : population) + p.model = this; + + if (population_backup.size() != 0) + for (auto & p : population_backup) + p.model = this; + + for (auto & e : entities) + e.model = this; + + if (entities_backup.size() != 0) + for (auto & e : entities_backup) + e.model = this; + + db = m.db; + db.model = this; + db.user_data.model = this; + + directed = m.directed; + + viruses = m.viruses; + prevalence_virus = m.prevalence_virus; + prevalence_virus_as_proportion = m.prevalence_virus_as_proportion; + viruses_dist_funs = m.viruses_dist_funs; + + tools = m.tools; + prevalence_tool = m.prevalence_tool; + prevalence_tool_as_proportion = m.prevalence_tool_as_proportion; + tools_dist_funs = m.tools_dist_funs; + + entities = m.entities; + entities_backup = m.entities_backup; + // prevalence_entity = m.prevalence_entity; + // prevalence_entity_as_proportion = m.prevalence_entity_as_proportion; + // entities_dist_funs = m.entities_dist_funs; + + rewire_fun = m.rewire_fun; + rewire_prop = m.rewire_prop; + + parameters = m.parameters; + ndays = m.ndays; + pb = m.pb; + + state_fun = m.state_fun; + states_labels = m.states_labels; + nstates = m.nstates; + + verbose = m.verbose; + + current_date = m.current_date; + + global_actions = m.global_actions; + + queue = m.queue; + use_queuing = m.use_queuing; + + // Making sure population is passed correctly + // Pointing to the right place + db.model = this; + db.user_data.model = this; + + agents_data = m.agents_data; + agents_data_ncols = m.agents_data_ncols; + + // Figure out the queuing + if (use_queuing) + queue.model = this; + + array_double_tmp.resize(std::max( + size(), + static_cast(1024 * 1024) + )); + + array_virus_tmp.resize(1024u); + + return *this; + +} + +template +inline DataBase & Model::get_db() +{ + return db; +} + +template +inline std::vector> & Model::get_agents() +{ + return population; +} + +template +inline std::vector< epiworld_fast_uint > Model::get_agents_states() const +{ + std::vector< epiworld_fast_uint > states(population.size()); + for (size_t i = 0u; i < population.size(); ++i) + states[i] = population[i].get_state(); + + return states; +} + +template +inline std::vector< Viruses_const > Model::get_agents_viruses() const +{ + + std::vector< Viruses_const > viruses(population.size()); + for (size_t i = 0u; i < population.size(); ++i) + viruses[i] = population[i].get_viruses(); + + return viruses; + +} + +// Same as before, but the non const version +template +inline std::vector< Viruses > Model::get_agents_viruses() +{ + + std::vector< Viruses > viruses(population.size()); + for (size_t i = 0u; i < population.size(); ++i) + viruses[i] = population[i].get_viruses(); + + return viruses; + +} + +template +inline std::vector> & Model::get_entities() +{ + return entities; +} + +template +inline void Model::agents_smallworld( + epiworld_fast_uint n, + epiworld_fast_uint k, + bool d, + epiworld_double p +) +{ + agents_from_adjlist( + rgraph_smallworld(n, k, p, d, *this) + ); +} + +template +inline void Model::agents_empty_graph( + epiworld_fast_uint n +) +{ + + // Resizing the people + population.clear(); + population.resize(n, Agent()); + + // Filling the model and ids + size_t i = 0u; + for (auto & p : population) + { + p.id = i++; + p.model = this; + } + + +} + +// template +// inline void Model::set_rand_engine(std::mt19937 & eng) +// { +// engine = std::make_shared< std::mt19937 >(eng); +// } + +template +inline void Model::set_rand_gamma(epiworld_double alpha, epiworld_double beta) +{ + rgammad = std::gamma_distribution<>(alpha,beta); +} + +template +inline void Model::set_rand_norm(epiworld_double mean, epiworld_double sd) +{ + rnormd = std::normal_distribution<>(mean, sd); +} + +template +inline void Model::set_rand_unif(epiworld_double a, epiworld_double b) +{ + runifd = std::uniform_real_distribution<>(a, b); +} + +template +inline void Model::set_rand_lognormal(epiworld_double mean, epiworld_double shape) +{ + rlognormald = std::lognormal_distribution<>(mean, shape); +} + +template +inline void Model::set_rand_exp(epiworld_double lambda) +{ + rexpd = std::exponential_distribution<>(lambda); +} + +template +inline void Model::set_rand_binom(int n, epiworld_double p) +{ + rbinomd = std::binomial_distribution<>(n, p); +} + +template +inline epiworld_double & Model::operator()(std::string pname) { + + if (parameters.find(pname) == parameters.end()) + throw std::range_error("The parameter "+ pname + "is not in the model."); + + return parameters[pname]; + +} + +template +inline size_t Model::size() const { + return population.size(); +} + +template +inline void Model::dist_virus() +{ + + // Starting first infection + int n = size(); + std::vector< size_t > idx(n); + + int n_left = n; + std::iota(idx.begin(), idx.end(), 0); + + for (size_t v = 0u; v < viruses.size(); ++v) + { + + if (viruses_dist_funs[v]) + { + + viruses_dist_funs[v](*viruses[v], this); + + } else { + + // Picking how many + int nsampled; + if (prevalence_virus_as_proportion[v]) + { + nsampled = static_cast(std::floor(prevalence_virus[v] * size())); + } + else + { + nsampled = static_cast(prevalence_virus[v]); + } + + if (nsampled > static_cast(size())) + throw std::range_error("There are only " + std::to_string(size()) + + " individuals in the population. Cannot add the virus to " + std::to_string(nsampled)); + + + VirusPtr virus = viruses[v]; + + while (nsampled > 0) + { + + int loc = static_cast(floor(runif() * (n_left--))); + + Agent & agent = population[idx[loc]]; + + // Adding action + agent.add_virus( + virus, + const_cast * >(this), + virus->state_init, + virus->queue_init + ); + + // Adjusting sample + nsampled--; + std::swap(idx[loc], idx[n_left]); + + } + + } + + // Apply the actions + actions_run(); + } + +} + +template +inline void Model::dist_tools() +{ + + // Starting first infection + int n = size(); + std::vector< size_t > idx(n); + for (epiworld_fast_uint t = 0; t < tools.size(); ++t) + { + + if (tools_dist_funs[t]) + { + + tools_dist_funs[t](*tools[t], this); + + } else { + + // Picking how many + int nsampled; + if (prevalence_tool_as_proportion[t]) + { + nsampled = static_cast(std::floor(prevalence_tool[t] * size())); + } + else + { + nsampled = static_cast(prevalence_tool[t]); + } + + if (nsampled > static_cast(size())) + throw std::range_error("There are only " + std::to_string(size()) + + " individuals in the population. Cannot add the tool to " + std::to_string(nsampled)); + + ToolPtr tool = tools[t]; + + int n_left = n; + std::iota(idx.begin(), idx.end(), 0); + while (nsampled > 0) + { + int loc = static_cast(floor(runif() * n_left--)); + + population[idx[loc]].add_tool( + tool, + const_cast< Model * >(this), + tool->state_init, tool->queue_init + ); + + nsampled--; + + std::swap(idx[loc], idx[n_left]); + + } + + } + + // Apply the actions + actions_run(); + + } + +} + +// template +// inline void Model::dist_entities() +// { + +// // Starting first infection +// int n = size(); +// std::vector< size_t > idx(n); +// for (epiworld_fast_uint e = 0; e < entities.size(); ++e) +// { + +// if (entities_dist_funs[e]) +// { + +// entities_dist_funs[e](entities[e], this); + +// } else { + +// // Picking how many +// int nsampled; +// if (prevalence_entity_as_proportion[e]) +// { +// nsampled = static_cast(std::floor(prevalence_entity[e] * size())); +// } +// else +// { +// nsampled = static_cast(prevalence_entity[e]); +// } + +// if (nsampled > static_cast(size())) +// throw std::range_error("There are only " + std::to_string(size()) + +// " individuals in the population. Cannot add the entity to " + std::to_string(nsampled)); + +// Entity & entity = entities[e]; + +// int n_left = n; +// std::iota(idx.begin(), idx.end(), 0); +// while (nsampled > 0) +// { +// int loc = static_cast(floor(runif() * n_left--)); + +// population[idx[loc]].add_entity(entity, this, entity.state_init, entity.queue_init); + +// nsampled--; + +// std::swap(idx[loc], idx[n_left]); + +// } + +// } + +// // Apply the actions +// actions_run(); + +// } + +// } + +template +inline void Model::chrono_start() { + time_start = std::chrono::steady_clock::now(); +} + +template +inline void Model::chrono_end() { + time_end = std::chrono::steady_clock::now(); + time_elapsed += (time_end - time_start); + n_replicates++; +} + +template +inline void Model::set_backup() +{ + + if (population_backup.size() == 0u) + population_backup = population; + + if (entities_backup.size() == 0u) + entities_backup = entities; + +} + +// template +// inline void Model::restore_backup() +// { + +// // Restoring the data +// population = *population_backup; +// entities = *entities_backup; + +// // And correcting the pointer +// for (auto & p : population) +// p.model = this; + +// for (auto & e : entities) +// e.model = this; + +// } + +template +inline std::mt19937 & Model::get_rand_endgine() +{ + return engine; +} + +template +inline epiworld_double Model::runif() { + // CHECK_INIT() + return runifd(engine); +} + +template +inline epiworld_double Model::runif(epiworld_double a, epiworld_double b) { + // CHECK_INIT() + return runifd(engine) * (b - a) + a; +} + +template +inline epiworld_double Model::rnorm() { + // CHECK_INIT() + return rnormd(engine); +} + +template +inline epiworld_double Model::rnorm(epiworld_double mean, epiworld_double sd) { + // CHECK_INIT() + return rnormd(engine) * sd + mean; +} + +template +inline epiworld_double Model::rgamma() { + return rgammad(engine); +} + +template +inline epiworld_double Model::rgamma(epiworld_double alpha, epiworld_double beta) { + auto old_param = rgammad.param(); + rgammad.param(std::gamma_distribution<>::param_type(alpha, beta)); + epiworld_double ans = rgammad(engine); + rgammad.param(old_param); + return ans; +} + +template +inline epiworld_double Model::rexp() { + return rexpd(engine); +} + +template +inline epiworld_double Model::rexp(epiworld_double lambda) { + auto old_param = rexpd.param(); + rexpd.param(std::exponential_distribution<>::param_type(lambda)); + epiworld_double ans = rexpd(engine); + rexpd.param(old_param); + return ans; +} + +template +inline epiworld_double Model::rlognormal() { + return rlognormald(engine); +} + +template +inline epiworld_double Model::rlognormal(epiworld_double mean, epiworld_double shape) { + auto old_param = rlognormald.param(); + rlognormald.param(std::lognormal_distribution<>::param_type(mean, shape)); + epiworld_double ans = rlognormald(engine); + rlognormald.param(old_param); + return ans; +} + +template +inline int Model::rbinom() { + return rbinomd(engine); +} + +template +inline int Model::rbinom(int n, epiworld_double p) { + auto old_param = rbinomd.param(); + rbinomd.param(std::binomial_distribution<>::param_type(n, p)); + epiworld_double ans = rbinomd(engine); + rbinomd.param(old_param); + return ans; +} + +template +inline void Model::seed(size_t s) { + this->engine.seed(s); +} + +template +inline void Model::add_virus(Virus & v, epiworld_double preval) +{ + + if (preval > 1.0) + throw std::range_error("Prevalence of virus cannot be above 1.0"); + + if (preval < 0.0) + throw std::range_error("Prevalence of virus cannot be negative"); + + // Checking the state + epiworld_fast_int init_, post_, rm_; + v.get_state(&init_, &post_, &rm_); + + if (init_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -init- state." + ); + else if (post_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -post- state." + ); + + // Recording the variant + db.record_virus(v); + + // Adding new virus + viruses.push_back(std::make_shared< Virus >(v)); + prevalence_virus.push_back(preval); + prevalence_virus_as_proportion.push_back(true); + viruses_dist_funs.push_back(nullptr); + +} + +template +inline void Model::add_virus_n(Virus & v, epiworld_fast_uint preval) +{ + + // Checking the ids + epiworld_fast_int init_, post_, rm_; + v.get_state(&init_, &post_, &rm_); + + if (init_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -init- state." + ); + else if (post_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -post- state." + ); + + // Setting the id + db.record_virus(v); + + // Adding new virus + viruses.push_back(std::make_shared< Virus >(v)); + prevalence_virus.push_back(preval); + prevalence_virus_as_proportion.push_back(false); + viruses_dist_funs.push_back(nullptr); + +} + +template +inline void Model::add_virus_fun(Virus & v, VirusToAgentFun fun) +{ + + // Checking the ids + epiworld_fast_int init_, post_, rm_; + v.get_state(&init_, &post_, &rm_); + + if (init_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -init- state." + ); + else if (post_ == -99) + throw std::logic_error( + "The virus \"" + v.get_name() + "\" has no -post- state." + ); + + // Setting the id + db.record_virus(v); + // v.set_id(viruses.size()); + + // Adding new virus + viruses.push_back(std::make_shared< Virus >(v)); + prevalence_virus.push_back(0.0); + prevalence_virus_as_proportion.push_back(false); + viruses_dist_funs.push_back(fun); + +} + +template +inline void Model::add_tool(Tool & t, epiworld_double preval) +{ + + if (preval > 1.0) + throw std::range_error("Prevalence of tool cannot be above 1.0"); + + if (preval < 0.0) + throw std::range_error("Prevalence of tool cannot be negative"); + + db.record_tool(t); + + // Adding the tool to the model (and database.) + tools.push_back(std::make_shared< Tool >(t)); + prevalence_tool.push_back(preval); + prevalence_tool_as_proportion.push_back(true); + tools_dist_funs.push_back(nullptr); + +} + +template +inline void Model::add_tool_n(Tool & t, epiworld_fast_uint preval) +{ + + db.record_tool(t); + + tools.push_back(std::make_shared >(t)); + prevalence_tool.push_back(preval); + prevalence_tool_as_proportion.push_back(false); + tools_dist_funs.push_back(nullptr); + +} + +template +inline void Model::add_tool_fun(Tool & t, ToolToAgentFun fun) +{ + + db.record_tool(t); + + tools.push_back(std::make_shared >(t)); + prevalence_tool.push_back(0.0); + prevalence_tool_as_proportion.push_back(false); + tools_dist_funs.push_back(fun); +} + + +template +inline void Model::add_entity(Entity e) +{ + + e.model = this; + e.id = entities.size(); + entities.push_back(e); + +} + +template +inline void Model::rm_virus(size_t virus_pos) +{ + + if (viruses.size() <= virus_pos) + throw std::range_error( + std::string("The specified virus (") + + std::to_string(virus_pos) + + std::string(") is out of range. ") + + std::string("There are only ") + + std::to_string(viruses.size()) + + std::string(" viruses.") + ); + + // Flipping with the last one + std::swap(viruses[virus_pos], viruses[viruses.size() - 1]); + std::swap(viruses_dist_funs[virus_pos], viruses_dist_funs[viruses.size() - 1]); + std::swap(prevalence_virus[virus_pos], prevalence_virus[viruses.size() - 1]); + std::vector::swap( + prevalence_virus_as_proportion[virus_pos], + prevalence_virus_as_proportion[viruses.size() - 1] + ); + + viruses.pop_back(); + viruses_dist_funs.pop_back(); + prevalence_virus.pop_back(); + prevalence_virus_as_proportion.pop_back(); + + return; + +} + +template +inline void Model::rm_tool(size_t tool_pos) +{ + + if (tools.size() <= tool_pos) + throw std::range_error( + std::string("The specified tool (") + + std::to_string(tool_pos) + + std::string(") is out of range. ") + + std::string("There are only ") + + std::to_string(tools.size()) + + std::string(" tools.") + ); + + // Flipping with the last one + std::swap(tools[tool_pos], tools[tools.size() - 1]); + std::swap(tools_dist_funs[tool_pos], tools_dist_funs[tools.size() - 1]); + std::swap(prevalence_tool[tool_pos], prevalence_tool[tools.size() - 1]); + + /* There's an error on windows: + https://github.com/UofUEpiBio/epiworldR/actions/runs/4801482395/jobs/8543744180#step:6:84 + + More clear here: + https://stackoverflow.com/questions/58660207/why-doesnt-stdswap-work-on-vectorbool-elements-under-clang-win + */ + std::vector::swap( + prevalence_tool_as_proportion[tool_pos], + prevalence_tool_as_proportion[tools.size() - 1] + ); + + // auto old = prevalence_tool_as_proportion[tool_pos]; + // prevalence_tool_as_proportion[tool_pos] = prevalence_tool_as_proportion[tools.size() - 1]; + // prevalence_tool_as_proportion[tools.size() - 1] = old; + + + tools.pop_back(); + tools_dist_funs.pop_back(); + prevalence_tool.pop_back(); + prevalence_tool_as_proportion.pop_back(); + + return; + +} + +template +inline void Model::load_agents_entities_ties( + std::string fn, + int skip + ) +{ + + int i,j; + std::ifstream filei(fn); + + if (!filei) + throw std::logic_error("The file " + fn + " was not found."); + + int linenum = 0; + std::vector< epiworld_fast_uint > source_; + std::vector< std::vector< epiworld_fast_uint > > target_(entities.size()); + + target_.reserve(1e5); + + while (!filei.eof()) + { + + if (linenum++ < skip) + continue; + + filei >> i >> j; + + // Looking for exceptions + if (filei.bad()) + throw std::logic_error( + "I/O error while reading the file " + + fn + ); + + if (filei.fail()) + break; + + if (i >= static_cast(this->size())) + throw std::range_error( + "The agent["+std::to_string(linenum)+"] = " + std::to_string(i) + + " is above the max id " + std::to_string(this->size() - 1) + ); + + if (j >= static_cast(this->entities.size())) + throw std::range_error( + "The entity["+std::to_string(linenum)+"] = " + std::to_string(j) + + " is above the max id " + std::to_string(this->entities.size() - 1) + ); + + target_[j].push_back(i); + + population[i].add_entity(entities[j], nullptr); + + } + + // // Iterating over entities + // for (size_t e = 0u; e < entities.size(); ++e) + // { + + // // This entity will have individuals assigned to it, so we add it + // if (target_[e].size() > 0u) + // { + + // // Filling in the gaps + // prevalence_entity[e] = static_cast(target_[e].size()); + // prevalence_entity_as_proportion[e] = false; + + // // Generating the assignment function + // auto who = target_[e]; + // entities_dist_funs[e] = + // [who](Entity & e, Model* m) -> void { + + // for (auto w : who) + // m->population[w].add_entity(e, m, e.state_init, e.queue_init); + + // return; + + // }; + + // } + + // } + + return; + +} + +template +inline void Model::agents_from_adjlist( + std::string fn, + int size, + int skip, + bool directed + ) { + + AdjList al; + al.read_edgelist(fn, size, skip, directed); + this->agents_from_adjlist(al); + +} + +template +inline void Model::agents_from_edgelist( + const std::vector< int > & source, + const std::vector< int > & target, + int size, + bool directed +) { + + AdjList al(source, target, size, directed); + agents_from_adjlist(al); + +} + +template +inline void Model::agents_from_adjlist(AdjList al) { + + // Resizing the people + agents_empty_graph(al.vcount()); + + const auto & tmpdat = al.get_dat(); + + for (size_t i = 0u; i < tmpdat.size(); ++i) + { + + // population[i].id = i; + population[i].model = this; + + for (const auto & link: tmpdat[i]) + { + + population[i].add_neighbor( + population[link.first], + true, true + ); + + } + + } + + #ifdef EPI_DEBUG + for (auto & p: population) + { + if (p.id >= static_cast(al.vcount())) + throw std::logic_error( + "Agent's id cannot be negative above or equal to the number of agents!"); + } + #endif + +} + +template +inline bool Model::is_directed() const +{ + if (population.size() == 0u) + throw std::logic_error("The population hasn't been initialized."); + + return directed; +} + +template +inline int Model::today() const { + + if (ndays == 0) + return 0; + + return this->current_date; +} + +template +inline void Model::next() { + + db.record(); + ++this->current_date; + + // Advancing the progress bar + if ((this->current_date >= 1) && verbose) + pb.next(); + + return ; +} + +template +inline void Model::run( + epiworld_fast_uint ndays, + int seed +) +{ + + if (size() == 0u) + throw std::logic_error("There's no agents in this model!"); + + if (nstates == 0u) + throw std::logic_error( + std::string("No states registered in this model. ") + + std::string("At least one state should be included. See the function -Model::add_state()-") + ); + + // Setting up the number of steps + this->ndays = ndays; + + if (seed >= 0) + engine.seed(seed); + + array_double_tmp.resize(std::max( + size(), + static_cast(1024 * 1024) + )); + + + array_virus_tmp.resize(1024); + + // Checking whether the proposed state in/out/removed + // are valid + epiworld_fast_int _init, _end, _removed; + int nstate_int = static_cast(nstates); + for (auto & v : viruses) + { + v->get_state(&_init, &_end, &_removed); + + // Negative unspecified state + if (((_init != -99) && (_init < 0)) || (_init >= nstate_int)) + throw std::range_error("States must be between 0 and " + + std::to_string(nstates - 1)); + + // Negative unspecified state + if (((_end != -99) && (_end < 0)) || (_end >= nstate_int)) + throw std::range_error("States must be between 0 and " + + std::to_string(nstates - 1)); + + if (((_removed != -99) && (_removed < 0)) || (_removed >= nstate_int)) + throw std::range_error("States must be between 0 and " + + std::to_string(nstates - 1)); + + } + + for (auto & t : tools) + { + t->get_state(&_init, &_end); + + // Negative unspecified state + if (((_init != -99) && (_init < 0)) || (_init >= nstate_int)) + throw std::range_error("States must be between 0 and " + + std::to_string(nstates - 1)); + + // Negative unspecified state + if (((_end != -99) && (_end < 0)) || (_end >= nstate_int)) + throw std::range_error("States must be between 0 and " + + std::to_string(nstates - 1)); + + } + + // Starting first infection and tools + reset(); + + // Initializing the simulation + chrono_start(); + EPIWORLD_RUN((*this)) + { + + #ifdef EPI_DEBUG + db.n_transmissions_potential = 0; + db.n_transmissions_today = 0; + #endif + + // We can execute these components in whatever order the + // user needs. + this->update_state(); + + // We start with the global actions + this->run_global_actions(); + + // In this case we are applying degree sequence rewiring + // to change the network just a bit. + this->rewire(); + + // This locks all the changes + this->next(); + + // Mutation must happen at the very end of all + this->mutate_virus(); + + } + + // The last reaches the end... + this->current_date--; + + chrono_end(); + +} + +template +inline void Model::run_multiple( + epiworld_fast_uint ndays, + epiworld_fast_uint nexperiments, + int seed_, + std::function*)> fun, + bool reset, + bool verbose, + int nthreads +) +{ + + if (seed_ >= 0) + this->seed(seed_); + + // Seeds will be reproducible by default + std::vector< int > seeds_n(nexperiments); + // #ifdef EPI_DEBUG + // std::fill( + // seeds_n.begin(), + // seeds_n.end(), + // std::floor( + // runif() * static_cast(std::numeric_limits::max()) + // ) + // ); + // #else + for (auto & s : seeds_n) + { + s = static_cast( + std::floor( + runif() * static_cast(std::numeric_limits::max()) + ) + ); + } + // #endif + + EPI_DEBUG_NOTIFY_ACTIVE() + + bool old_verb = this->verbose; + verbose_off(); + + // Setting up backup + if (reset) + set_backup(); + + #ifdef _OPENMP + + omp_set_num_threads(nthreads); + + // Generating copies of the model + std::vector< Model * > these; + for (size_t i = 0; i < static_cast(std::max(nthreads - 1, 0)); ++i) + these.push_back(clone_ptr()); + + // Figuring out how many replicates + std::vector< size_t > nreplicates(nthreads, 0); + std::vector< size_t > nreplicates_csum(nthreads, 0); + size_t sums = 0u; + for (int i = 0; i < nthreads; ++i) + { + nreplicates[i] = static_cast( + std::floor(nexperiments/nthreads) + ); + + // This takes the cumsum + nreplicates_csum[i] = sums; + + sums += nreplicates[i]; + + } + + if (sums < nexperiments) + nreplicates[nthreads - 1] += (nexperiments - sums); + + Progress pb_multiple( + nreplicates[0u], + EPIWORLD_PROGRESS_BAR_WIDTH + ); + + if (verbose) + { + + printf_epiworld( + "Starting multiple runs (%i) using %i thread(s)\n", + static_cast(nexperiments), + static_cast(nthreads) + ); + + pb_multiple.start(); + + } + + #ifdef EPI_DEBUG + // Checking the initial state of all the models. Throw an + // exception if they are not the same. + for (size_t i = 1; i < static_cast(std::max(nthreads - 1, 0)); ++i) + { + + if (db != these[i]->db) + { + throw std::runtime_error( + "The initial state of the models is not the same" + ); + } + } + #endif + + #pragma omp parallel shared(these, nreplicates, nreplicates_csum, seeds_n) \ + firstprivate(nexperiments, nthreads, fun, reset, verbose, pb_multiple, ndays) \ + default(shared) + { + + auto iam = omp_get_thread_num(); + + for (size_t n = 0u; n < nreplicates[iam]; ++n) + { + size_t sim_id = nreplicates_csum[iam] + n; + if (iam == 0) + { + + // Initializing the seed + run(ndays, seeds_n[sim_id]); + + if (fun) + fun(n, this); + + // Only the first one prints + if (verbose) + pb_multiple.next(); + + } else { + + // Initializing the seed + these[iam - 1]->run(ndays, seeds_n[sim_id]); + + if (fun) + fun(sim_id, these[iam - 1]); + + } + + } + + } + + // Adjusting the number of replicates + n_replicates += (nexperiments - nreplicates[0u]); + + for (auto & ptr : these) + delete ptr; + + #else + // if (reset) + // set_backup(); + + Progress pb_multiple( + nexperiments, + EPIWORLD_PROGRESS_BAR_WIDTH + ) + ; + if (verbose) + { + + printf_epiworld( + "Starting multiple runs (%i)\n", + static_cast(nexperiments) + ); + + pb_multiple.start(); + + } + + for (size_t n = 0u; n < nexperiments; ++n) + { + + run(ndays, seeds_n[n]); + + if (fun) + fun(n, this); + + if (verbose) + pb_multiple.next(); + + } + #endif + + if (verbose) + pb_multiple.end(); + + if (old_verb) + verbose_on(); + + return; + +} + +template +inline void Model::update_state() { + + // Next state + if (use_queuing) + { + int i = -1; + for (auto & p: population) + if (queue[++i] > 0) + { + if (state_fun[p.state]) + state_fun[p.state](&p, this); + } + + } + else + { + + for (auto & p: population) + if (state_fun[p.state]) + state_fun[p.state](&p, this); + + } + + actions_run(); + +} + + + +template +inline void Model::mutate_virus() { + + if (use_queuing) + { + + int i = -1; + for (auto & p: population) + { + + if (queue[++i] == 0) + continue; + + if (p.n_viruses > 0u) + for (auto & v : p.get_viruses()) + v->mutate(this); + + } + + } + else + { + + for (auto & p: population) + { + + if (p.n_viruses > 0u) + for (auto & v : p.get_viruses()) + v->mutate(this); + + } + + } + + +} + +template +inline size_t Model::get_n_viruses() const { + return db.size(); +} + +template +inline size_t Model::get_n_tools() const { + return tools.size(); +} + +template +inline epiworld_fast_uint Model::get_ndays() const { + return ndays; +} + +template +inline epiworld_fast_uint Model::get_n_replicates() const +{ + return n_replicates; +} + +template +inline void Model::set_ndays(epiworld_fast_uint ndays) { + this->ndays = ndays; +} + +template +inline bool Model::get_verbose() const { + return verbose; +} + +template +inline void Model::verbose_on() { + verbose = true; +} + +template +inline void Model::verbose_off() { + verbose = false; +} + +template +inline void Model::set_rewire_fun( + std::function>*,Model*,epiworld_double)> fun + ) { + rewire_fun = fun; +} + +template +inline void Model::set_rewire_prop(epiworld_double prop) +{ + + if (prop < 0.0) + throw std::range_error("Proportions cannot be negative."); + + if (prop > 1.0) + throw std::range_error("Proportions cannot be above 1.0."); + + rewire_prop = prop; +} + +template +inline epiworld_double Model::get_rewire_prop() const { + return rewire_prop; +} + +template +inline void Model::rewire() { + + if (rewire_fun) + rewire_fun(&population, this, rewire_prop); +} + + +template +inline void Model::write_data( + std::string fn_virus_info, + std::string fn_virus_hist, + std::string fn_tool_info, + std::string fn_tool_hist, + std::string fn_total_hist, + std::string fn_transmission, + std::string fn_transition, + std::string fn_reproductive_number, + std::string fn_generation_time + ) const +{ + + db.write_data( + fn_virus_info, fn_virus_hist, + fn_tool_info, fn_tool_hist, + fn_total_hist, fn_transmission, fn_transition, + fn_reproductive_number, fn_generation_time + ); + +} + +template +inline void Model::write_edgelist( + std::string fn + ) const +{ + + // Figuring out the writing sequence + std::vector< const Agent * > wseq(size()); + for (const auto & p: population) + wseq[p.id] = &p; + + std::ofstream efile(fn, std::ios_base::out); + efile << "source target\n"; + if (this->is_directed()) + { + + for (const auto & p : wseq) + { + for (auto & n : p->neighbors) + efile << p->id << " " << n << "\n"; + } + + } else { + + for (const auto & p : wseq) + { + for (auto & n : p->neighbors) + if (static_cast(p->id) <= static_cast(n)) + efile << p->id << " " << n << "\n"; + } + + } + +} + +template +inline void Model::write_edgelist( +std::vector< int > & source, +std::vector< int > & target +) const { + + // Figuring out the writing sequence + std::vector< const Agent * > wseq(size()); + for (const auto & p: population) + wseq[p.id] = &p; + + if (this->is_directed()) + { + + for (const auto & p : wseq) + { + for (auto & n : p->neighbors) + { + source.push_back(static_cast(p->id)); + target.push_back(static_cast(n)); + } + } + + } else { + + for (const auto & p : wseq) + { + for (auto & n : p->neighbors) { + if (static_cast(p->id) <= static_cast(n)) { + source.push_back(static_cast(p->id)); + target.push_back(static_cast(n)); + } + } + } + + } + + +} + +template +inline std::map & Model::params() +{ + return parameters; +} + +template +inline void Model::reset() { + + // Restablishing people + pb = Progress(ndays, 80); + + if (population_backup.size() != 0u) + { + population = population_backup; + + #ifdef EPI_DEBUG + for (size_t i = 0; i < population.size(); ++i) + { + + if (population[i] != population_backup[i]) + throw std::logic_error("Model::reset population doesn't match."); + + } + #endif + + } + else + { + for (auto & p : population) + p.reset(); + + #ifdef EPI_DEBUG + for (auto & a: population) + { + if (a.get_state() != 0u) + throw std::logic_error("Model::reset population doesn't match." + "Some agents are not in the baseline state."); + } + #endif + } + + if (entities_backup.size() != 0) + { + entities = entities_backup; + + #ifdef EPI_DEBUG + for (size_t i = 0; i < entities.size(); ++i) + { + + if (entities[i] != entities_backup[i]) + throw std::logic_error("Model::reset entities don't match."); + + } + #endif + + } + else + { + for (auto & e: entities) + e.reset(); + } + + current_date = 0; + + db.reset(); + + // This also clears the queue + if (use_queuing) + queue.reset(); + + // Re distributing tools and virus + dist_virus(); + dist_tools(); + + // Recording the original state (at time 0) and advancing + // to time 1 + next(); + + +} + +// Too big to keep here +#include "model-meat-print.hpp" + +template +inline Model && Model::clone() const { + + // Step 1: Regen the individuals and make sure that: + // - Neighbors point to the right place + // - DB is pointing to the right place + Model res(*this); + + // Removing old neighbors + for (auto & p: res.population) + p.neighbors.clear(); + + // Rechecking individuals + for (epiworld_fast_uint p = 0u; p < size(); ++p) + { + // Making room + const Agent & agent_this = population[p]; + Agent & agent_res = res.population[p]; + + // Agent pointing to the right model and agent + agent_res.model = &res; + agent_res.viruses.agent = &agent_res; + agent_res.tools.agent = &agent_res; + + // Readding + std::vector< Agent * > neigh = agent_this.neighbors; + for (epiworld_fast_uint n = 0u; n < neigh.size(); ++n) + { + // Point to the right neighbors + int loc = res.population_ids[neigh[n]->get_id()]; + agent_res.add_neighbor(res.population[loc], true, true); + + } + + } + + return res; + +} + + + +template +inline void Model::add_state( + std::string lab, + UpdateFun fun +) +{ + + // Checking it doesn't match + for (auto & s : states_labels) + if (s == lab) + throw std::logic_error("state \"" + s + "\" already registered."); + + states_labels.push_back(lab); + state_fun.push_back(fun); + nstates++; + +} + + +template +inline const std::vector< std::string > & +Model::get_states() const +{ + return states_labels; +} + +template +inline const std::vector< UpdateFun > & +Model::get_state_fun() const +{ + return state_fun; +} + +template +inline void Model::print_state_codes() const +{ + + // Horizontal line + std::string line = ""; + for (epiworld_fast_uint i = 0u; i < 80u; ++i) + line += "_"; + + printf_epiworld("\n%s\nstates CODES\n\n", line.c_str()); + + epiworld_fast_uint nchar = 0u; + for (auto & p : states_labels) + if (p.length() > nchar) + nchar = p.length(); + + std::string fmt = " %2i = %-" + std::to_string(nchar + 1 + 4) + "s\n"; + for (epiworld_fast_uint i = 0u; i < nstates; ++i) + { + + printf_epiworld( + fmt.c_str(), + i, + (states_labels[i] + " (S)").c_str() + ); + + } + +} + + + +template +inline epiworld_double Model::add_param( + epiworld_double initial_value, + std::string pname + ) { + + if (parameters.find(pname) == parameters.end()) + parameters[pname] = initial_value; + + return initial_value; + +} + +template +inline void Model::read_params(std::string fn) +{ + + std::ifstream paramsfile(fn); + + if (!paramsfile) + throw std::logic_error("The file " + fn + " was not found."); + + std::regex pattern("^([^:]+)\\s*[:]\\s*([0-9]+|[0-9]*\\.[0-9]+)?\\s*$"); + + std::string line; + std::smatch match; + auto empty = std::sregex_iterator(); + + while (std::getline(paramsfile, line)) + { + + // Is it a comment or an empty line? + if (std::regex_match(line, std::regex("^([*].+|//.+|#.+|\\s*)$"))) + continue; + + // Finding the patter, if it doesn't match, then error + std::regex_match(line, match, pattern); + + if (match.empty()) + throw std::logic_error("The line does not match parameters:\n" + line); + + // Capturing the number + std::string anumber = match[2u].str() + match[3u].str(); + epiworld_double tmp_num = static_cast( + std::strtod(anumber.c_str(), nullptr) + ); + + add_param( + tmp_num, + std::regex_replace( + match[1u].str(), + std::regex("^\\s+|\\s+$"), + "") + ); + + } + +} + +template +inline epiworld_double Model::get_param(std::string pname) +{ + if (parameters.find(pname) == parameters.end()) + throw std::logic_error("The parameter " + pname + " does not exists."); + + return parameters[pname]; +} + +template +inline void Model::set_param(std::string pname, epiworld_double value) +{ + if (parameters.find(pname) == parameters.end()) + throw std::logic_error("The parameter " + pname + " does not exists."); + + parameters[pname] = value; + + return; + +} + +// // Same as before but using the size_t method +// template +// inline void Model::set_param(size_t k, epiworld_double value) +// { +// if (k >= parameters.size()) +// throw std::logic_error("The parameter index " + std::to_string(k) + " does not exists."); + +// // Access the k-th element of the std::unordered_map parameters + + +// *(parameters.begin() + k) = value; + +// return; +// } + +template +inline epiworld_double Model::par(std::string pname) +{ + return parameters[pname]; +} + +#define DURCAST(tunit,txtunit) {\ + elapsed = std::chrono::duration_cast(\ + time_end - time_start).count(); \ + elapsed_total = std::chrono::duration_cast(time_elapsed).count(); \ + abbr_unit = txtunit;} + +template +inline void Model::get_elapsed( + std::string unit, + epiworld_double * last_elapsed, + epiworld_double * total_elapsed, + std::string * unit_abbr, + bool print +) const { + + // Preparing the result + epiworld_double elapsed, elapsed_total; + std::string abbr_unit; + + // Figuring out the length + if (unit == "auto") + { + + size_t tlength = std::to_string( + static_cast(floor(time_elapsed.count())) + ).length(); + + if (tlength <= 1) + unit = "nanoseconds"; + else if (tlength <= 3) + unit = "microseconds"; + else if (tlength <= 6) + unit = "milliseconds"; + else if (tlength <= 8) + unit = "seconds"; + else if (tlength <= 9) + unit = "minutes"; + else + unit = "hours"; + + } + + if (unit == "nanoseconds") DURCAST(nanoseconds,"ns") + else if (unit == "microseconds") DURCAST(microseconds,"\xC2\xB5s") + else if (unit == "milliseconds") DURCAST(milliseconds,"ms") + else if (unit == "seconds") DURCAST(seconds,"s") + else if (unit == "minutes") DURCAST(minutes,"m") + else if (unit == "hours") DURCAST(hours,"h") + else + throw std::range_error("The time unit " + unit + " is not supported."); + + + if (last_elapsed != nullptr) + *last_elapsed = elapsed; + if (total_elapsed != nullptr) + *total_elapsed = elapsed_total; + if (unit_abbr != nullptr) + *unit_abbr = abbr_unit; + + if (!print) + return; + + if (n_replicates > 1u) + { + printf_epiworld("last run elapsed time : %.2f%s\n", + elapsed, abbr_unit.c_str()); + printf_epiworld("total elapsed time : %.2f%s\n", + elapsed_total, abbr_unit.c_str()); + printf_epiworld("total runs : %i\n", + static_cast(n_replicates)); + printf_epiworld("mean run elapsed time : %.2f%s\n", + elapsed_total/static_cast(n_replicates), abbr_unit.c_str()); + + } else { + printf_epiworld("last run elapsed time : %.2f%s.\n", elapsed, abbr_unit.c_str()); + } +} + +template +inline void Model::set_user_data(std::vector< std::string > names) +{ + db.set_user_data(names); +} + +template +inline void Model::add_user_data(epiworld_fast_uint j, epiworld_double x) +{ + db.add_user_data(j, x); +} + +template +inline void Model::add_user_data(std::vector x) +{ + db.add_user_data(x); +} + +template +inline UserData & Model::get_user_data() +{ + return db.get_user_data(); +} + +template +inline void Model::add_global_action( + std::function*)> fun, + std::string name, + int date +) +{ + + global_actions.push_back( + GlobalAction( + fun, + name, + date + ) + ); + +} + +template +inline void Model::add_global_action( + GlobalAction action +) +{ + global_actions.push_back(action); +} + +template +GlobalAction & Model::get_global_action( + std::string name +) +{ + + for (auto & a : global_actions) + if (a.name == name) + return a; + + throw std::logic_error("The global action " + name + " was not found."); + +} + +template +GlobalAction & Model::get_global_action( + size_t index +) +{ + + if (index >= global_actions.size()) + throw std::range_error("The index " + std::to_string(index) + " is out of range."); + + return global_actions[index]; + +} + +// Remove implementation +template +inline void Model::rm_global_action( + std::string name +) +{ + + for (auto it = global_actions.begin(); it != global_actions.end(); ++it) + { + if (it->get_name() == name) + { + global_actions.erase(it); + return; + } + } + + throw std::logic_error("The global action " + name + " was not found."); + +} + +// Same as above, but the index implementation +template +inline void Model::rm_global_action( + size_t index +) +{ + + if (index >= global_actions.size()) + throw std::range_error("The index " + std::to_string(index) + " is out of range."); + + global_actions.erase(global_actions.begin() + index); + +} + +template +inline void Model::run_global_actions() +{ + + for (auto & action: global_actions) + { + action(this, today()); + actions_run(); + } + +} + +template +inline void Model::queuing_on() +{ + use_queuing = true; +} + +template +inline void Model::queuing_off() +{ + use_queuing = false; +} + +template +inline bool Model::is_queuing_on() const +{ + return use_queuing; +} + +template +inline Queue & Model::get_queue() +{ + return queue; +} + +template +inline const std::vector< VirusPtr > & Model::get_viruses() const +{ + return viruses; +} + +template +const std::vector< ToolPtr > & Model::get_tools() const +{ + return tools; +} + +template +inline Virus & Model::get_virus(size_t id) +{ + + if (viruses.size() <= id) + throw std::length_error("The specified id for the virus is out of range"); + + return *viruses[id]; + +} + +template +inline Tool & Model::get_tool(size_t id) +{ + + if (tools.size() <= id) + throw std::length_error("The specified id for the tools is out of range"); + + return *tools[id]; + +} + + +template +inline void Model::set_agents_data(double * data_, size_t ncols_) +{ + agents_data = data_; + agents_data_ncols = ncols_; +} + +template +inline double * Model::get_agents_data() { + return this->agents_data; +} + +template +inline size_t Model::get_agents_data_ncols() const { + return this->agents_data_ncols; +} + + +template +inline void Model::set_name(std::string name) +{ + this->name = name; +} + +template +inline std::string Model::get_name() const +{ + return this->name; +} + +#define VECT_MATCH(a, b, c) \ + EPI_DEBUG_FAIL_AT_TRUE(a.size() != b.size(), c) \ + for (size_t __i = 0u; __i < a.size(); ++__i) \ + {\ + EPI_DEBUG_FAIL_AT_TRUE(a[__i] != b[__i], c) \ + } + +template +inline bool Model::operator==(const Model & other) const +{ + EPI_DEBUG_FAIL_AT_TRUE(name != other.name, "names don't match") + EPI_DEBUG_FAIL_AT_TRUE(db != other.db, "database don't match") + + VECT_MATCH(population, other.population, "population doesn't match") + + EPI_DEBUG_FAIL_AT_TRUE( + using_backup != other.using_backup, + "Model:: using_backup don't match" + ) + + if ((population_backup.size() != 0) & (other.population_backup.size() != 0)) + { + + // False is population_backup.size() != other.population_backup.size() + if (population_backup.size() != other.population_backup.size()) + return false; + + for (size_t i = 0u; i < population_backup.size(); ++i) + { + if (population_backup[i] != other.population_backup[i]) + return false; + } + + } else if ((population_backup.size() == 0) & (other.population_backup.size() != 0)) { + return false; + } else if ((population_backup.size() != 0) & (other.population_backup.size() == 0)) + { + return false; + } + + EPI_DEBUG_FAIL_AT_TRUE( + agents_data != other.agents_data, + "Model:: agents_data don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + agents_data_ncols != other.agents_data_ncols, + "Model:: agents_data_ncols don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + directed != other.directed, + "Model:: directed don't match" + ) + + // Viruses ----------------------------------------------------------------- + EPI_DEBUG_FAIL_AT_TRUE( + viruses.size() != other.viruses.size(), + "Model:: viruses.size() don't match" + ) + + for (size_t i = 0u; i < viruses.size(); ++i) + { + EPI_DEBUG_FAIL_AT_TRUE( + *viruses[i] != *other.viruses[i], + "Model:: *viruses[i] don't match" + ) + + } + + VECT_MATCH( + prevalence_virus, + other.prevalence_virus, + "virus prevalence don't match" + ) + + VECT_MATCH( + prevalence_virus_as_proportion, + other.prevalence_virus_as_proportion, + "virus prevalence as prop don't match" + ) + + // Tools ------------------------------------------------------------------- + EPI_DEBUG_FAIL_AT_TRUE( + tools.size() != other.tools.size(), + "Model:: tools.size() don't match" + ) + + for (size_t i = 0u; i < tools.size(); ++i) + { + EPI_DEBUG_FAIL_AT_TRUE( + *tools[i] != *other.tools[i], + "Model:: *tools[i] don't match" + ) + + } + + VECT_MATCH( + prevalence_tool, + other.prevalence_tool, + "tools prevalence don't match" + ) + + VECT_MATCH( + prevalence_tool_as_proportion, + other.prevalence_tool_as_proportion, + "tools as prop don't match" + ) + + VECT_MATCH( + entities, + other.entities, + "entities don't match" + ) + + if ((entities_backup.size() != 0) & (other.entities_backup.size() != 0)) + { + + for (size_t i = 0u; i < entities_backup.size(); ++i) + { + + EPI_DEBUG_FAIL_AT_TRUE( + entities_backup[i] != other.entities_backup[i], + "Model:: entities_backup[i] don't match" + ) + + } + + } else if ((entities_backup.size() == 0) & (other.entities_backup.size() != 0)) { + EPI_DEBUG_FAIL_AT_TRUE(true, "entities_backup don't match") + } else if ((entities_backup.size() != 0) & (other.entities_backup.size() == 0)) + { + EPI_DEBUG_FAIL_AT_TRUE(true, "entities_backup don't match") + } + + EPI_DEBUG_FAIL_AT_TRUE( + rewire_prop != other.rewire_prop, + "Model:: rewire_prop don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + parameters.size() != other.parameters.size(), + "Model:: () don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + parameters != other.parameters, + "Model:: parameters don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + ndays != other.ndays, + "Model:: ndays don't match" + ) + + VECT_MATCH( + states_labels, + other.states_labels, + "state labels don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + nstates != other.nstates, + "Model:: nstates don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + verbose != other.verbose, + "Model:: verbose don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + current_date != other.current_date, + "Model:: current_date don't match" + ) + + VECT_MATCH(global_actions, other.global_actions, "global action don't match"); + + EPI_DEBUG_FAIL_AT_TRUE( + queue != other.queue, + "Model:: queue don't match" + ) + + + EPI_DEBUG_FAIL_AT_TRUE( + use_queuing != other.use_queuing, + "Model:: use_queuing don't match" + ) + + return true; + +} + +#undef VECT_MATCH +#undef DURCAST +#undef CASES_PAR +#undef CASE_PAR +#undef CHECK_INIT +#endif diff --git a/inst/include/epiworld/models/README.md b/inst/include/epiworld/models/README.md new file mode 100644 index 00000000..dd17d23b --- /dev/null +++ b/inst/include/epiworld/models/README.md @@ -0,0 +1,21 @@ +# Models included in epiworld + +epiworld features multiple predefined models. The overall structure of the `models` +namespace is: + +- It contains factory functions. + +- The functions return `epiworld::Model` type objects. + +The following models are included: + +1. `models::ModelSIS` SIS + +2. `models::ModelSIR` SIR + +3. `models::ModelSIRCONN` SIR connected graph + +4. `models::ModelSEIRCONN` SEIR connected graph + +5. `models::ModelSURV` Surveillance + diff --git a/inst/include/epiworld/models/diffnet.hpp b/inst/include/epiworld/models/diffnet.hpp new file mode 100644 index 00000000..ee9a9967 --- /dev/null +++ b/inst/include/epiworld/models/diffnet.hpp @@ -0,0 +1,209 @@ +#ifndef EPIWORLD_DIFFNET_H +#define EPIWORLD_DIFFNET_H + +/** + * @brief Template for a Network Diffusion Model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param initial_prevalence epiworld_double Initial prevalence + * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system + * @param initial_recovery epiworld_double Initial recovery rate of the immune system + */ +template +class ModelDiffNet : public epiworld::Model +{ +private: +public: + + ModelDiffNet() {}; + + ModelDiffNet( + ModelDiffNet & model, + std::string innovation_name, + epiworld_double prevalence, + epiworld_double prob_adopt, + bool normalize_exposure = true, + double * agents_data = nullptr, + size_t data_ncols = 0u, + std::vector< size_t > data_cols = {}, + std::vector< double > params = {} + ); + + ModelDiffNet( + std::string innovation_name, + epiworld_double prevalence, + epiworld_double prob_adopt, + bool normalize_exposure = true, + double * agents_data = nullptr, + size_t data_ncols = 0u, + std::vector< size_t > data_cols = {}, + std::vector< double > params = {} + ); + + static const int NONADOPTER = 0; + static const int ADOPTER = 1; + + bool normalize_exposure = true; + std::vector< size_t > data_cols; + std::vector< double > params; +}; + +template +inline ModelDiffNet::ModelDiffNet( + ModelDiffNet & model, + std::string innovation_name, + epiworld_double prevalence, + epiworld_double prob_adopt, + bool normalize_exposure, + double * agents_data, + size_t data_ncols, + std::vector< size_t > data_cols, + std::vector< double > params + ) +{ + + // Adding additional parameters + this->normalize_exposure = normalize_exposure; + this->data_cols = data_cols; + this->params = params; + + epiworld::UpdateFun update_non_adopters = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + // Measuring exposure + // If the neighbor is infected, then proceed + size_t nviruses = m->get_n_viruses(); + std::vector< Virus* > innovations(nviruses, {}); + std::vector< bool > stored(nviruses, false); + std::vector< double > exposure(nviruses, 0.0); + + ModelDiffNet * diffmodel = dynamic_cast*>(m); + + Agent & agent = *p; + + // For each one of the possible innovations, we have to compute + // the adoption probability, which is a function of exposure + for (auto & neighbor: agent.get_neighbors()) + { + + if (neighbor->get_state() == ModelDiffNet::ADOPTER) + { + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + /* And it is a function of susceptibility_reduction as well */ + double p_i = + (1.0 - agent.get_susceptibility_reduction(v, m)) * + (1.0 - agent.get_transmission_reduction(v, m)) + ; + + size_t vid = v->get_id(); + if (!stored[vid]) + { + stored[vid] = true; + innovations[vid] = &(*v); + } + exposure[vid] += p_i; + + } + + } + + } + + // Computing probability of adoption + for (size_t i = 0u; i < nviruses; ++i) + { + + if (diffmodel->normalize_exposure) + exposure.at(i) /= agent.get_n_neighbors(); + + for (auto & j: diffmodel->data_cols) + exposure.at(i) += agent(j) * diffmodel->params.at(j); + + // Baseline probability of adoption + double p = m->get_viruses()[i]->get_prob_infecting(m); + exposure.at(i) += std::log(p) - std::log(1.0 - p); + + // Computing as log + exposure.at(i) = 1.0/(1.0 + std::exp(-exposure.at(i))); + + } + + // Running the roulette to see is an innovation is adopted + int which = roulette(exposure, m); + + // No innovation was adopted + if (which < 0) + return; + + // Otherwise, it is adopted from any of the neighbors + agent.add_virus( + *innovations.at(which), + m, + ModelDiffNet::ADOPTER + ); + + return; + + }; + + // Adding agents data + model.set_agents_data(agents_data, data_ncols); + + // Adding statuses + model.add_state("Non adopters", update_non_adopters); + model.add_state("Adopters"); + + // Adding parameters + std::string parname = std::string("Prob. Adopting ") + innovation_name; + model.add_param(prob_adopt, parname); + + // Preparing the virus ------------------------------------------- + epiworld::Virus innovation(innovation_name); + innovation.set_state(1,1,1); + + innovation.set_prob_infecting(&model(parname)); + + model.add_virus(innovation, prevalence); + + model.set_name( + std::string("Diffusion of Innovations - ") + innovation_name); + + return; + +} + +template +inline ModelDiffNet::ModelDiffNet( + std::string innovation_name, + epiworld_double prevalence, + epiworld_double prob_adopt, + bool normalize_exposure, + double * agents_data, + size_t data_ncols, + std::vector< size_t > data_cols, + std::vector< double > params + ) +{ + + ModelDiffNet( + *this, + innovation_name, + prevalence, + prob_adopt, + normalize_exposure, + agents_data, + data_ncols, + data_cols, + params + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/globalactions.hpp b/inst/include/epiworld/models/globalactions.hpp new file mode 100644 index 00000000..f0e20fe1 --- /dev/null +++ b/inst/include/epiworld/models/globalactions.hpp @@ -0,0 +1,139 @@ +#ifndef EPIWORLD_GLOBALACTIONS_HPP +#define EPIWORLD_GLOBALACTIONS_HPP + +// This function creates a global action that distributes a tool +// to agents with probability p. +/** + * @brief Global action that distributes a tool to agents with probability p. + * + * @tparam TSeq Sequence type (should match `TSeq` across the model) + * @param p Probability of distributing the tool. + * @param tool Tool function. + * @return std::function*)> + */ +template +inline std::function*)> globalaction_tool( + Tool & tool, + double p +) { + + std::function*)> fun = [p,&tool]( + Model * model + ) -> void { + + for (auto & agent : model->get_agents()) + { + + // Check if the agent has the tool + if (agent.has_tool(tool)) + continue; + + // Adding the tool + if (model->runif() < p) + agent.add_tool(tool, model); + + + } + + #ifdef EPIWORLD_DEBUG + tool.print(); + #endif + + return; + + + }; + + return fun; + +} + +// Same function as above, but p is now a function of a vector of coefficients +// and a vector of variables. +/** + * @brief Global action that distributes a tool to agents with probability + * p = 1 / (1 + exp(-\sum_i coef_i * agent(vars_i))). + * + * @tparam TSeq Sequence type (should match `TSeq` across the model) + * @param coefs Vector of coefficients. + * @param vars Vector of variables. + * @param tool_fun Tool function. + * @return std::function*)> + */ +template +inline std::function*)> globalaction_tool_logit( + Tool & tool, + std::vector< size_t > vars, + std::vector< double > coefs +) { + + std::function*)> fun = [coefs,vars,&tool]( + Model * model + ) -> void { + + for (auto & agent : model->get_agents()) + { + + // Check if the agent has the tool + if (agent.has_tool(tool)) + continue; + + // Computing the probability using a logit. Uses OpenMP reduction + // to sum the coefficients. + double p = 0.0; + #pragma omp parallel for reduction(+:p) + for (size_t i = 0u; i < coefs.size(); ++i) + p += coefs.at(i) * agent(vars[i]); + + p = 1.0 / (1.0 + std::exp(-p)); + + // Adding the tool + if (model->runif() < p) + agent.add_tool(tool, model); + + + } + + #ifdef EPIWORLD_DEBUG + tool.print(); + #endif + + return; + + + }; + + return fun; + +} + +// A global action that updates a parameter in the model. +/** + * @brief Global action that updates a parameter in the model. + * + * @tparam TSeq Sequence type (should match `TSeq` across the model) + * @param param Parameter to update. + * @param value Value to update the parameter to. + * @return std::function*)> + */ +template +inline std::function*)> globalaction_set_param( + std::string param, + double value +) { + + std::function*)> fun = [value,param]( + Model * model + ) -> void { + + model->set_param(param, value); + + return; + + + }; + + return fun; + +} +#endif \ No newline at end of file diff --git a/inst/include/epiworld/models/models.hpp b/inst/include/epiworld/models/models.hpp new file mode 100644 index 00000000..2e27bfef --- /dev/null +++ b/inst/include/epiworld/models/models.hpp @@ -0,0 +1,24 @@ +#ifndef EPIWORLD_MODELS_HPP +#define EPIWORLD_MODELS_HPP + +namespace epimodels { + + #include "globalactions.hpp" + #include "sis.hpp" + #include "sir.hpp" + #include "seir.hpp" + #include "surveillance.hpp" + #include "sirconnected.hpp" + #include "seirconnected.hpp" + #include "sird.hpp" + #include "sisd.hpp" + #include "seird.hpp" + #include "sirdconnected.hpp" + #include "seirdconnected.hpp" + #include "sirlogit.hpp" + #include "diffnet.hpp" + + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/models/seir.hpp b/inst/include/epiworld/models/seir.hpp new file mode 100644 index 00000000..25f24085 --- /dev/null +++ b/inst/include/epiworld/models/seir.hpp @@ -0,0 +1,137 @@ +#ifndef EPIWORLD_MODELS_SEIR_HPP +#define EPIWORLD_MODELS_SEIR_HPP + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence epiworld_double Initial prevalence the immune system + * @param transmission_rate epiworld_double Transmission rate of the virus + * @param avg_incubation_days epiworld_double Average incubation days of the virus. + * @param recovery_rate epiworld_double Recovery rate of the virus. + */ +template +class ModelSEIR : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int REMOVED = 3; + +public: + + ModelSEIR() {}; + + ModelSEIR( + ModelSEIR & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ); + + ModelSEIR( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ); + + epiworld::UpdateFun update_exposed_seir = []( + epiworld::Agent * p, + epiworld::Model * m + ) -> void { + + // Getting the virus + auto v = p->get_virus(0); + + // Does the agent become infected? + if (m->runif() < 1.0/(v->get_incubation(m))) + p->change_state(m, ModelSEIR::INFECTED); + + return; + }; + + + epiworld::UpdateFun update_infected_seir = []( + epiworld::Agent * p, + epiworld::Model * m + ) -> void { + // Does the agent recover? + if (m->runif() < (m->par("Recovery rate"))) + p->rm_virus(0, m); + + return; + }; + +}; + + +template +inline ModelSEIR::ModelSEIR( + ModelSEIR & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ) +{ + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Exposed", model.update_exposed_seir); + model.add_state("Infected", model.update_infected_seir); + model.add_state("Removed"); + + // Setting up parameters + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(avg_incubation_days, "Incubation days"); + model.add_param(recovery_rate, "Recovery rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(ModelSEIR::EXPOSED, ModelSEIR::REMOVED, ModelSEIR::REMOVED); + + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_incubation(&model("Incubation days")); + + // Adding the tool and the virus + model.add_virus(virus, prevalence); + + model.set_name("Susceptible-Exposed-Infected-Removed (SEIR)"); + + return; + +} + +template +inline ModelSEIR::ModelSEIR( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ) +{ + + ModelSEIR( + *this, + vname, + prevalence, + transmission_rate, + avg_incubation_days, + recovery_rate + ); + + return; + +} + + + +#endif diff --git a/inst/include/epiworld/models/seirconnected.hpp b/inst/include/epiworld/models/seirconnected.hpp new file mode 100644 index 00000000..4a8ed8e4 --- /dev/null +++ b/inst/include/epiworld/models/seirconnected.hpp @@ -0,0 +1,330 @@ +#ifndef EPIWORLD_MODELS_SEIRCONNECTED_HPP +#define EPIWORLD_MODELS_SEIRCONNECTED_HPP + +template +class ModelSEIRCONN : public epiworld::Model +{ +public: + + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int RECOVERED = 3; + + + ModelSEIRCONN() {}; + + ModelSEIRCONN( + ModelSEIRCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ); + + ModelSEIRCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ); + + void run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + +}; + +template +inline void ModelSEIRCONN::run( + epiworld_fast_uint ndays, + int seed +) +{ + + Model::run(ndays, seed); + +} + +template +inline void ModelSEIRCONN::reset() +{ + + Model::reset(); + + Model::set_rand_binom( + Model::size(), + static_cast( + Model::par("Contact rate"))/ + static_cast(Model::size()) + ); + + return; + +} + +template +inline Model * ModelSEIRCONN::clone_ptr() +{ + + ModelSEIRCONN * ptr = new ModelSEIRCONN( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + */ +template +inline ModelSEIRCONN::ModelSEIRCONN( + ModelSEIRCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + // epiworld_double prob_reinfection + ) +{ + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Sampling how many individuals + int ndraw = m->rbinom(); + + if (ndraw == 0) + return; + + // Drawing from the set + int nviruses_tmp = 0; + for (int i = 0; i < ndraw; ++i) + { + // Now selecting who is transmitting the disease + int which = static_cast( + std::floor(m->size() * m->runif()) + ); + + /* There is a bug in which runif() returns 1.0. It is rare, but + * we saw it here. See the Notes section in the C++ manual + * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 + * And the reported bug in GCC: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 + * + */ + if (which == static_cast(m->size())) + --which; + + // Can't sample itself + if (which == static_cast(p->get_id())) + continue; + + // If the neighbor is infected, then proceed + auto & neighbor = m->get_agents()[which]; + if (neighbor.get_state() == ModelSEIRCONN::INFECTED) + { + + for (const VirusPtr & v : neighbor.get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor.get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus( + *m->array_virus_tmp[which], + m, + ModelSEIRCONN::EXPOSED + ); + + return; + + }; + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSEIRCONN::EXPOSED) + { + + // Getting the virus + auto & v = p->get_virus(0u); + + // Does the agent become infected? + if (m->runif() < 1.0/(v->get_incubation(m))) + { + + p->change_state(m, ModelSEIRCONN::INFECTED); + return; + + } + + + } else if (state == ModelSEIRCONN::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + return ; + + } else + throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIR)") ; + + return; + + }; + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Prob. Transmission"); + model.add_param(recovery_rate, "Prob. Recovery"); + model.add_param(avg_incubation_days, "Avg. Incubation days"); + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Exposed", update_infected); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state( + ModelSEIRCONN::EXPOSED, + ModelSEIRCONN::RECOVERED, + ModelSEIRCONN::RECOVERED + ); + + virus.set_prob_infecting(&model("Prob. Transmission")); + virus.set_prob_recovery(&model("Prob. Recovery")); + virus.set_incubation(&model("Avg. Incubation days")); + + model.add_virus(virus, prevalence); + + model.queuing_off(); // No queuing need + + // Adding the empty population + model.agents_empty_graph(n); + + model.set_name("Susceptible-Exposed-Infected-Removed (SEIR) (connected)"); + + return; + +} + +template +inline ModelSEIRCONN::ModelSEIRCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + ) +{ + + ModelSEIRCONN( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + avg_incubation_days, + recovery_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/seirconnected_logit.hpp b/inst/include/epiworld/models/seirconnected_logit.hpp new file mode 100644 index 00000000..6718f678 --- /dev/null +++ b/inst/include/epiworld/models/seirconnected_logit.hpp @@ -0,0 +1,333 @@ +#ifndef EPIWORLD_MODELS_SEIRCONNECTEDLOGIT_HPP +#define EPIWORLD_MODELS_SEIRCONNECTEDLOGIT_HPP + +template +class ModelSEIRCONNLogit : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int RECOVERE = 3; + +public: + + ModelSEIRCONNLogit() { + tracked_agents_infected.reserve(1e4); + tracked_agents_infected_next.reserve(1e4); + }; + + ModelSEIRCONNLogit( + ModelSEIRCONNLogit & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + double * covars, + std::vector< double > logit_params + ); + + ModelSEIRCONNLogit( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate + double * covars, + std::vector< double > logit_params + ); + + // Tracking who is infected and who is not + std::vector< epiworld::Agent<>* > tracked_agents_infected = {}; + std::vector< epiworld::Agent<>* > tracked_agents_infected_next = {}; + + bool tracked_started = false; + int tracked_ninfected = 0; + int tracked_ninfected_next = 0; + +}; + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Reproductive number (beta) + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + */ +template +inline ModelSEIRCONNLogit::ModelSEIRCONNLogit( + ModelSEIRCONNLogit & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + double * covars, + std::vector< double > logit_params + // epiworld_double prob_reinfection + ) +{ + + auto * _tracked_agents_infected = &model.tracked_agents_infected; + auto * _tracked_agents_infected_next = &model.tracked_agents_infected_next; + auto * _tracked_started = &model.tracked_started; + auto * _tracked_ninfected = &model.tracked_ninfected; + auto * _tracked_ninfected_next = &model.tracked_ninfected_next; + + std::function *)> tracked_agents_check_init = + [ + _tracked_started, + _tracked_agents_infected, + _tracked_ninfected + ](epiworld::Model * m) + { + + if (*_tracked_started) + return; + + /* Checking first if it hasn't */ + if (!*_tracked_started) + { + + /* Listing who is infected */ + for (auto & p : m->get_agents()) + { + if (p.get_state() == ModelSEIRCONNLogit::INFECTED) + { + + _tracked_agents_infected->push_back(&p); + *_tracked_ninfected += 1; + + } + } + + for (auto & p: *_tracked_agents_infected) + { + if (p->get_n_viruses() == 0) + throw std::logic_error("Cannot be infected and have no viruses."); + } + + *_tracked_started = true; + + } + + }; + + epiworld::UpdateFun update_susceptible = + [ + tracked_agents_check_init, + _tracked_ninfected, + _tracked_agents_infected + ](epiworld::Agent * p, epiworld::Model * m) -> void + { + + tracked_agents_check_init(m); + + // No infected individual? + if (*_tracked_ninfected == 0) + return; + + // Computing probability of contagion + // P(infected) = 1 - (1 - beta/Pop * ptransmit) ^ ninfected + epiworld_double prob_infect = 1.0 - std::pow( + 1.0 - (m->par("Contact rate")) * (m->par("Transmission rate")) / m->size(), + *_tracked_ninfected + ); + + if (m->runif() < prob_infect) + { + + // Now selecting who is transmitting the disease + epiworld_fast_uint which = static_cast( + std::floor(*_tracked_ninfected * m->runif()) + ); + + // Infecting the individual + #ifdef EPI_DEBUG + if (_tracked_agents_infected->operator[](which)->get_n_viruses() == 0) + { + + printf_epiworld("[epi-debug] date: %i\n", m->today()); + printf_epiworld("[epi-debug] sim#: %i\n", m->get_n_replicates()); + + throw std::logic_error( + "[epi-debug] The agent " + std::to_string(which) + " has no "+ + "virus to share. The agent's state is: " + + std::to_string(_tracked_agents_infected->operator[](which)->get_state()) + ); + } + #endif + p->add_virus( + _tracked_agents_infected->operator[](which)->get_virus(0u), + ModelSEIRCONNLogit::EXPOSED + ); + + return; + + } + + return; + + }; + + epiworld::UpdateFun update_infected = + [ + tracked_agents_check_init, + _tracked_agents_infected_next, + _tracked_ninfected_next + + ](epiworld::Agent * p, epiworld::Model * m) -> void + { + + tracked_agents_check_init(m); + auto state = p->get_state(); + + if (state == ModelSEIRCONNLogit::EXPOSED) + { + + // Does the agent become infected? + if (m->runif() < 1.0/(m->par("Avg. Incubation days"))) + { + // Adding the individual to the queue + _tracked_agents_infected_next->push_back(p); + *_tracked_ninfected_next += 1; + + p->change_state(m, ModelSEIRCONNLogit::INFECTED); + + return; + + } + + + } else if (state == ModelSEIRCONNLogit::INFECTED) + { + + if (m->runif() < (m->par("Recovery rate"))) + { + + *_tracked_ninfected_next -= 1; + p->rm_virus(0, m); + return; + + } + + _tracked_agents_infected_next->push_back(p); + + } + + return; + + }; + + epiworld::GlobalFun global_accounting = + [ + _tracked_started, + _tracked_agents_infected, + _tracked_agents_infected_next, + _tracked_ninfected, + _tracked_ninfected_next + ](epiworld::Model* m) -> void + { + + // On the last day, also reset tracked agents and + // set the initialized value to false + if (static_cast(m->today()) == (m->get_ndays() - 1)) + { + + *_tracked_started = false; + _tracked_agents_infected->clear(); + _tracked_agents_infected_next->clear(); + *_tracked_ninfected = 0; + *_tracked_ninfected_next = 0; + + return; + } + + std::swap(*_tracked_agents_infected, *_tracked_agents_infected_next); + _tracked_agents_infected_next->clear(); + + *_tracked_ninfected += *_tracked_ninfected_next; + *_tracked_ninfected_next = 0; + + }; + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(avg_incubation_days, "Avg. Incubation days"); + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Exposed", update_infected); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + // Adding agent's parameters + model.set_agents_data(covars, logit_params.size()); + for (size_t i = 0u; i < logit_params.size(); ++i) + model.add_param( + std::string("x" + std::to_string(i)), + logit_params[i] + ); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1,3,3); + model.add_virus(virus, prevalence); + + // Adding updating function + model.add_global_action(global_accounting, "Accounting", -1); + + model.queuing_off(); // No queuing need + + // Adding the empty population + model.agents_empty_graph(n); + + model.set_name("Susceptible-Exposed-Infected-Removed (SEIR) (connected)"); + + return; + +} + +template +inline ModelSEIRCONNLogit::ModelSEIRCONNLogit( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + double * covars, + std::vector< double > logit_params + ) +{ + + ModelSEIRCONNLogit( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + avg_incubation_days, + covars, + logit_params + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/seird.hpp b/inst/include/epiworld/models/seird.hpp new file mode 100644 index 00000000..adf121e2 --- /dev/null +++ b/inst/include/epiworld/models/seird.hpp @@ -0,0 +1,205 @@ +#ifndef EPIWORLD_MODELS_SEIRD_HPP +#define EPIWORLD_MODELS_SEIRD_HPP + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed-Deceased (SEIRD) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence epiworld_double Initial prevalence the immune system + * @param transmission_rate epiworld_double Transmission rate of the virus + * @param avg_incubation_days epiworld_double Average incubation days of the virus + * @param recovery_rate epiworld_double Recovery rate of the virus. + * @param death_rate epiworld_double Death rate of the virus. + */ +template +class ModelSEIRD : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int REMOVED = 3; + static const int DECEASED = 4; + +public: + + ModelSEIRD() {}; + + ModelSEIRD( + ModelSEIRD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + ModelSEIRD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + epiworld::UpdateFun update_exposed_seir = []( + epiworld::Agent * p, + epiworld::Model * m + ) -> void { + + // Getting the virus + auto v = p->get_virus(0); + + // Does the agent become infected? + if (m->runif() < 1.0/(v->get_incubation(m))) + p->change_state(m, ModelSEIRD::INFECTED); + + return; + }; + + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSEIRD::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Die + m->array_double_tmp[n_events++] = + v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + +#ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } +#else + if (n_events == 0u) + return; +#endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + if ((which % 2) == 0) // If odd + { + + size_t which_v = std::ceil(which / 2); + p->rm_agent_by_virus(which_v, m); + + } else { + + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + } + + return ; + + } else + throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIRD)") ; + + return; + + }; +}; + + + +template +inline ModelSEIRD::ModelSEIRD( + ModelSEIRD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate +) +{ + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Exposed", model.update_exposed_seir); + model.add_state("Infected", model.update_infected); + model.add_state("Removed"); + model.add_state("Deceased"); + + // Setting up parameters + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(avg_incubation_days, "Incubation days"); + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(death_rate, "Death rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(ModelSEIRD::EXPOSED, ModelSEIRD::REMOVED, ModelSEIRD::DECEASED); + + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_incubation(&model("Incubation days")); + virus.set_prob_death(&model("Death rate")); + + // Adding the tool and the virus + model.add_virus(virus, prevalence); + + model.set_name("Susceptible-Exposed-Infected-Removed-Deceased (SEIRD)"); + + return; + +} + +template +inline ModelSEIRD::ModelSEIRD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate +) +{ + + ModelSEIRD( + *this, + vname, + prevalence, + transmission_rate, + avg_incubation_days, + recovery_rate, + death_rate + ); + + return; + +} + + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/models/seirdconnected.hpp b/inst/include/epiworld/models/seirdconnected.hpp new file mode 100644 index 00000000..bffdd0e3 --- /dev/null +++ b/inst/include/epiworld/models/seirdconnected.hpp @@ -0,0 +1,353 @@ +#ifndef EPIWORLD_MODELS_SEIRDCONNECTED_HPP +#define EPIWORLD_MODELS_SEIRDCONNECTED_HPP + +template +class ModelSEIRDCONN : public epiworld::Model +{ +public: + + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int REMOVED = 3; + static const int DECEASED = 4; + + + ModelSEIRDCONN() {}; + + ModelSEIRDCONN( + ModelSEIRDCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + ModelSEIRDCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + void run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + +}; + +template +inline void ModelSEIRDCONN::run( + epiworld_fast_uint ndays, + int seed +) +{ + + Model::run(ndays, seed); + +} + +template +inline void ModelSEIRDCONN::reset() +{ + + Model::reset(); + + Model::set_rand_binom( + Model::size(), + static_cast( + Model::par("Contact rate"))/ + static_cast(Model::size()) + ); + + return; + +} + +template +inline Model * ModelSEIRDCONN::clone_ptr() +{ + + ModelSEIRDCONN * ptr = new ModelSEIRDCONN( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + * @param death_rate Probability of death + */ +template +inline ModelSEIRDCONN::ModelSEIRDCONN( + ModelSEIRDCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + // epiworld_double prob_reinfection + ) +{ + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Sampling how many individuals + int ndraw = m->rbinom(); + + if (ndraw == 0) + return; + + // Drawing from the set + int nviruses_tmp = 0; + for (int i = 0; i < ndraw; ++i) + { + // Now selecting who is transmitting the disease + int which = static_cast( + std::floor(m->size() * m->runif()) + ); + + /* There is a bug in which runif() returns 1.0. It is rare, but + * we saw it here. See the Notes section in the C++ manual + * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 + * And the reported bug in GCC: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 + * + */ + if (which == static_cast(m->size())) + --which; + + // Can't sample itself + if (which == static_cast(p->get_id())) + continue; + + // If the neighbor is infected, then proceed + auto & neighbor = m->get_agents()[which]; + if (neighbor.get_state() == ModelSEIRDCONN::INFECTED) + { + + for (const VirusPtr & v : neighbor.get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor.get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus( + *m->array_virus_tmp[which], + m, + ModelSEIRDCONN::EXPOSED + ); + + return; + + }; + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSEIRDCONN::EXPOSED) + { + + // Getting the virus + auto & v = p->get_virus(0u); + + // Does the agent become infected? + if (m->runif() < 1.0/(v->get_incubation(m))) + { + + p->change_state(m, ModelSEIRDCONN::INFECTED); + return; + + } + + + } else if (state == ModelSEIRDCONN::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Die + m->array_double_tmp[n_events++] = + v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + if ((which % 2) == 0) // If odd + { + + size_t which_v = std::ceil(which / 2); + p->rm_agent_by_virus(which_v, m); + + } else { + + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + } + + return ; + + } else + throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIRD)") ; + + return; + + }; + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Prob. Transmission"); + model.add_param(recovery_rate, "Prob. Recovery"); + model.add_param(avg_incubation_days, "Avg. Incubation days"); + model.add_param(death_rate, "Death rate"); + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Exposed", update_infected); + model.add_state("Infected", update_infected); + model.add_state("Removed"); + model.add_state("Deceased"); + + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state( + ModelSEIRDCONN::EXPOSED, + ModelSEIRDCONN::REMOVED, + ModelSEIRDCONN::DECEASED + ); + + virus.set_prob_infecting(&model("Prob. Transmission")); + virus.set_prob_recovery(&model("Prob. Recovery")); + virus.set_incubation(&model("Avg. Incubation days")); + virus.set_prob_death(&model("Death rate")); + model.add_virus(virus, prevalence); + + model.queuing_off(); // No queuing need + + // Adding the empty population + model.agents_empty_graph(n); + + model.set_name("Susceptible-Exposed-Infected-Removed-Deceased (SEIRD) (connected)"); + + return; + +} + +template +inline ModelSEIRDCONN::ModelSEIRDCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + ModelSEIRDCONN( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + avg_incubation_days, + recovery_rate, + death_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/sir.hpp b/inst/include/epiworld/models/sir.hpp new file mode 100644 index 00000000..25bce99a --- /dev/null +++ b/inst/include/epiworld/models/sir.hpp @@ -0,0 +1,92 @@ +#ifndef EPIWORLD_SIR_H +#define EPIWORLD_SIR_H + +/** + * @brief Template for a Susceptible-Infected-Removed (SIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param initial_prevalence epiworld_double Initial prevalence + * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system + * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system + */ +template +class ModelSIR : public epiworld::Model +{ +public: + + ModelSIR() {}; + + ModelSIR( + ModelSIR & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + + ModelSIR( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + +}; + +template +inline ModelSIR::ModelSIR( + ModelSIR & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ) +{ + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Infected", epiworld::default_update_exposed); + model.add_state("Recovered"); + + // Setting up parameters + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(transmission_rate, "Transmission rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1,2,2); + + virus.set_prob_recovery(&model("Recovery rate")); + virus.set_prob_infecting(&model("Transmission rate")); + + model.add_virus(virus, prevalence); + + model.set_name("Susceptible-Infected-Recovered (SIR)"); + + return; + +} + +template +inline ModelSIR::ModelSIR( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ) +{ + + ModelSIR( + *this, + vname, + prevalence, + transmission_rate, + recovery_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/sirconnected.hpp b/inst/include/epiworld/models/sirconnected.hpp new file mode 100644 index 00000000..84cf844d --- /dev/null +++ b/inst/include/epiworld/models/sirconnected.hpp @@ -0,0 +1,329 @@ +#ifndef EPIWORLD_MODELS_SIRCONNECTED_HPP +#define EPIWORLD_MODELS_SIRCONNECTED_HPP + +template +class ModelSIRCONN : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int INFECTED = 1; + static const int RECOVERED = 2; + +public: + + ModelSIRCONN() { + + // tracked_agents_infected.reserve(1e4); + // tracked_agents_infected_next.reserve(1e4); + + }; + + ModelSIRCONN( + ModelSIRCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + + ModelSIRCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + + // Tracking who is infected and who is not + // std::vector< epiworld::Agent* > tracked_agents_infected = {}; + // std::vector< epiworld::Agent* > tracked_agents_infected_next = {}; + // std::vector< epiworld_double > tracked_agents_weight = {}; + // std::vector< epiworld_double > tracked_agents_weight_next = {}; + + // int tracked_ninfected = 0; + // int tracked_ninfected_next = 0; + // epiworld_double tracked_current_infect_prob = 0.0; + + void run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + + +}; + +template +inline void ModelSIRCONN::run( + epiworld_fast_uint ndays, + int seed +) +{ + + // tracked_agents_infected.clear(); + // tracked_agents_infected_next.clear(); + + // tracked_ninfected = 0; + // tracked_ninfected_next = 0; + // tracked_current_infect_prob = 0.0; + + Model::run(ndays, seed); + +} + +template +inline void ModelSIRCONN::reset() +{ + + Model::reset(); + + // Model::set_rand_binom( + // Model::size(), + // static_cast( + // Model::par("Contact rate"))/ + // static_cast(Model::size()) + // ); + + return; + +} + +template +inline Model * ModelSIRCONN::clone_ptr() +{ + + ModelSIRCONN * ptr = new ModelSIRCONN( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + +/** + * @brief Template for a Susceptible-Infected-Removed (SIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + */ +template +inline ModelSIRCONN::ModelSIRCONN( + ModelSIRCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate + // epiworld_double prob_reinfection + ) +{ + + + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Sampling how many individuals + m->set_rand_binom( + m->size(), + static_cast( + m->par("Contact rate"))/ + static_cast(m->size()) + ); + + int ndraw = m->rbinom(); + + if (ndraw == 0) + return; + + // Drawing from the set + int nviruses_tmp = 0; + for (int i = 0; i < ndraw; ++i) + { + // Now selecting who is transmitting the disease + int which = static_cast( + std::floor(m->size() * m->runif()) + ); + + /* There is a bug in which runif() returns 1.0. It is rare, but + * we saw it here. See the Notes section in the C++ manual + * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 + * And the reported bug in GCC: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 + * + */ + if (which == static_cast(m->size())) + --which; + + // Can't sample itself + if (which == static_cast(p->get_id())) + continue; + + // If the neighbor is infected, then proceed + auto & neighbor = m->get_agents()[which]; + if (neighbor.get_state() == ModelSIRCONN::INFECTED) + { + + for (const VirusPtr & v : neighbor.get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor.get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + + return; + + }; + + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSIRCONN::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + return ; + + } else + throw std::logic_error("This function can only be applied to infected individuals. (SIR)") ; + + return; + + }; + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + // model.add_param(prob_reinfection, "Prob. Reinfection"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1, 2, 2); + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_recovery(&model("Recovery rate")); + + model.add_virus(virus, prevalence); + + model.queuing_off(); // No queuing need + + model.agents_empty_graph(n); + + model.set_name("Susceptible-Infected-Removed (SIR) (connected)"); + + return; + +} + +template +inline ModelSIRCONN::ModelSIRCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ) +{ + + ModelSIRCONN( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + recovery_rate + ); + + return; + +} + + +#endif diff --git a/inst/include/epiworld/models/sird.hpp b/inst/include/epiworld/models/sird.hpp new file mode 100644 index 00000000..6109e94a --- /dev/null +++ b/inst/include/epiworld/models/sird.hpp @@ -0,0 +1,101 @@ +#ifndef EPIWORLD_SIRD_H +#define EPIWORLD_SIRD_H + +/** + * @brief Template for a Susceptible-Infected-Removed-Deceased (SIRD) model + * + * @param model A Model object where to set up the SIRD. + * @param vname std::string Name of the virus + * @param initial_prevalence epiworld_double Initial prevalence + * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system + * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system + * @param initial_death epiworld_double Initial death_rate of the immune system + */ +template +class ModelSIRD : public epiworld::Model +{ +public: + + ModelSIRD() {}; + + ModelSIRD( + ModelSIRD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + ModelSIRD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + +}; + +template +inline ModelSIRD::ModelSIRD( + ModelSIRD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Infected", epiworld::default_update_exposed); + model.add_state("Recovered"), + model.add_state("Deceased") + ; + + // Setting up parameters + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(transmission_rate, "Transmission rate"), + model.add_param(death_rate, "Death rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1,2,3); + virus.set_prob_recovery(&model("Recovery rate")); + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_death(&model("Death rate")); + + model.add_virus(virus, prevalence); + + model.set_name("Susceptible-Infected-Recovered-Deceased (SIRD)"); + + return; + +} + +template +inline ModelSIRD::ModelSIRD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + ModelSIRD( + *this, + vname, + prevalence, + transmission_rate, + recovery_rate, + death_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/sirdconnected.hpp b/inst/include/epiworld/models/sirdconnected.hpp new file mode 100644 index 00000000..d111d647 --- /dev/null +++ b/inst/include/epiworld/models/sirdconnected.hpp @@ -0,0 +1,354 @@ +#ifndef EPIWORLD_MODELS_SIRDCONNECTED_HPP +#define EPIWORLD_MODELS_SIRDCONNECTED_HPP + +template +class ModelSIRDCONN : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int INFECTED = 1; + static const int RECOVERED = 2; + static const int DECEASED = 3; + +public: + + ModelSIRDCONN() { + + // tracked_agents_infected.reserve(1e4); + // tracked_agents_infected_next.reserve(1e4); + + }; + + ModelSIRDCONN( + ModelSIRDCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + ModelSIRDCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + // Tracking who is infected and who is not + // std::vector< epiworld::Agent* > tracked_agents_infected = {}; + // std::vector< epiworld::Agent* > tracked_agents_infected_next = {}; + // std::vector< epiworld_double > tracked_agents_weight = {}; + // std::vector< epiworld_double > tracked_agents_weight_next = {}; + + // int tracked_ninfected = 0; + // int tracked_ninfected_next = 0; + // epiworld_double tracked_current_infect_prob = 0.0; + + void run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + + +}; + +template +inline void ModelSIRDCONN::run( + epiworld_fast_uint ndays, + int seed +) +{ + + // tracked_agents_infected.clear(); + // tracked_agents_infected_next.clear(); + + // tracked_ninfected = 0; + // tracked_ninfected_next = 0; + // tracked_current_infect_prob = 0.0; + + Model::run(ndays, seed); + +} + +template +inline void ModelSIRDCONN::reset() +{ + + Model::reset(); + + // Model::set_rand_binom( + // Model::size(), + // static_cast( + // Model::par("Contact rate"))/ + // static_cast(Model::size()) + // ); + + return; + +} + +template +inline Model * ModelSIRDCONN::clone_ptr() +{ + + ModelSIRDCONN * ptr = new ModelSIRDCONN( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + +/** + * @brief Template for a Susceptible-Infected-Removed (SIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + * @param death_rate Probability of death + */ +template +inline ModelSIRDCONN::ModelSIRDCONN( + ModelSIRDCONN & model, + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + // epiworld_double prob_reinfection + ) +{ + + + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Sampling how many individuals + m->set_rand_binom( + m->size(), + static_cast( + m->par("Contact rate"))/ + static_cast(m->size()) + ); + + int ndraw = m->rbinom(); + + if (ndraw == 0) + return; + + // Drawing from the set + int nviruses_tmp = 0; + for (int i = 0; i < ndraw; ++i) + { + // Now selecting who is transmitting the disease + int which = static_cast( + std::floor(m->size() * m->runif()) + ); + + /* There is a bug in which runif() returns 1.0. It is rare, but + * we saw it here. See the Notes section in the C++ manual + * https://en.cppreference.com/mwiki/index.php?title=cpp/numeric/random/uniform_real_distribution&oldid=133329 + * And the reported bug in GCC: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176 + * + */ + if (which == static_cast(m->size())) + --which; + + // Can't sample itself + if (which == static_cast(p->get_id())) + continue; + + // If the neighbor is infected, then proceed + auto & neighbor = m->get_agents()[which]; + if (neighbor.get_state() == ModelSIRDCONN::INFECTED) + { + + for (const VirusPtr & v : neighbor.get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor.get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + } + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + + return; + + }; + + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSIRDCONN::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + for (const auto & v : p->get_viruses()) + { + + // Die + m->array_double_tmp[n_events++] = + v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + } + +#ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } +#else + if (n_events == 0u) + return; +#endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + if ((which % 2) == 0) // If odd + { + + size_t which_v = std::ceil(which / 2); + p->rm_agent_by_virus(which_v, m); + + } else { + + size_t which_v = std::floor(which / 2); + p->rm_virus(which_v, m); + + } + + return ; + + } else + throw std::logic_error("This function can only be applied to infected individuals. (SIR)") ; + + return; + + }; + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + model.add_state("Deceased"); + + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(death_rate, "Death rate"); + // model.add_param(prob_reinfection, "Prob. Reinfection"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1, 2, 3); + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_recovery(&model("Recovery rate")); + virus.set_prob_death(&model("Death rate")); + + model.add_virus(virus, prevalence); + + model.queuing_off(); // No queuing need + + model.agents_empty_graph(n); + + model.set_name("Susceptible-Infected-Removed-Deceased (SIRD) (connected)"); + + return; + +} + +template +inline ModelSIRDCONN::ModelSIRDCONN( + std::string vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + ModelSIRDCONN( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + recovery_rate, + death_rate + ); + + return; + +} + + +#endif diff --git a/inst/include/epiworld/models/sirlogit.hpp b/inst/include/epiworld/models/sirlogit.hpp new file mode 100644 index 00000000..00eff4e7 --- /dev/null +++ b/inst/include/epiworld/models/sirlogit.hpp @@ -0,0 +1,325 @@ +// #include "../epiworld.hpp" + +#ifndef EPIWORLD_MODELS_SIRLOGIT_HPP +#define EPIWORLD_MODELS_SIRLOGIT_HPP + +template +class ModelSIRLogit : public epiworld::Model +{ +private: + static const int SUSCEPTIBLE = 0; + static const int INFECTED = 1; + static const int RECOVERED = 2; + +public: + + ModelSIRLogit() {}; + + /** + * @param vname Name of the virus. + * @param coefs_infect Double ptr. Infection coefficients. + * @param coefs_recover Double ptr. Recovery coefficients. + * @param ncoef_infect Unsigned int. Number of infection coefficients. + * @param ncoef_recover Unsigned int. Number of recovery coefficients. + * @param coef_infect_cols Vector. Ids of infection vars. + * @param coef_recover_cols Vector. Ids of recover vars. + */ + ModelSIRLogit( + ModelSIRLogit & model, + std::string vname, + double * data, + size_t ncols, + std::vector< double > coefs_infect, + std::vector< double > coefs_recover, + std::vector< size_t > coef_infect_cols, + std::vector< size_t > coef_recover_cols, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double prevalence + ); + + ModelSIRLogit( + std::string vname, + double * data, + size_t ncols, + std::vector< double > coefs_infect, + std::vector< double > coefs_recover, + std::vector< size_t > coef_infect_cols, + std::vector< size_t > coef_recover_cols, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double prevalence + ); + + void run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + Model * clone_ptr(); + + void reset(); + + std::vector< double > coefs_infect; + std::vector< double > coefs_recover; + std::vector< size_t > coef_infect_cols; + std::vector< size_t > coef_recover_cols; + +}; + + + +template +inline void ModelSIRLogit::run( + epiworld_fast_uint ndays, + int seed +) +{ + + Model::run(ndays, seed); + +} + +template +inline Model * ModelSIRLogit::clone_ptr() +{ + + ModelSIRLogit * ptr = new ModelSIRLogit( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + +template +inline void ModelSIRLogit::reset() +{ + + /* Checking specified columns in the model */ + for (const auto & c : coef_infect_cols) + { + if (c >= Model::agents_data_ncols) + throw std::range_error("Columns specified in coef_infect_cols out of range."); + } + + for (const auto & c : coef_recover_cols) + { + if (c >= Model::agents_data_ncols) + throw std::range_error("Columns specified in coef_recover_cols out of range."); + } + + /* Checking attributes */ + if (coefs_infect.size() != (coef_infect_cols.size() + 1u)) + throw std::logic_error( + "The number of coefficients (infection) doesn't match the number of features. It must be as many features of the agents plus 1 (exposure.)" + ); + + if (coefs_recover.size() != coef_recover_cols.size()) + throw std::logic_error( + "The number of coefficients (recovery) doesn't match the number of features. It must be as many features of the agents." + ); + + Model::reset(); + + return; + +} + +/** + * @brief Template for a Susceptible-Infected-Removed (SIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param prob_transmission Probability of transmission + * @param prob_recovery Probability of recovery + */ +template +inline ModelSIRLogit::ModelSIRLogit( + ModelSIRLogit & model, + std::string vname, + double * data, + size_t ncols, + std::vector< double > coefs_infect, + std::vector< double > coefs_recover, + std::vector< size_t > coef_infect_cols, + std::vector< size_t > coef_recover_cols, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double prevalence + ) +{ + + if (coef_infect_cols.size() == 0u) + throw std::logic_error("No columns specified for coef_infect_cols."); + + if (coef_recover_cols.size() == 0u) + throw std::logic_error("No columns specified for coef_recover_cols."); + + // Saving the variables + model.set_agents_data( + data, ncols + ); + + model.coefs_infect = coefs_infect; + model.coefs_recover = coefs_recover; + model.coef_infect_cols = coef_infect_cols; + model.coef_recover_cols = coef_recover_cols; + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Getting the right type + ModelSIRLogit * _m = dynamic_cast*>(m); + + // Exposure coefficient + const double coef_exposure = _m->coefs_infect[0u]; + + // This computes the prob of getting any neighbor variant + size_t nviruses_tmp = 0u; + + double baseline = 0.0; + for (size_t k = 0u; k < _m->coef_infect_cols.size(); ++k) + baseline += p->operator[](k) * _m->coefs_infect[k + 1u]; + + for (auto & neighbor: p->get_neighbors()) + { + + for (const VirusPtr & v : neighbor->get_viruses()) + { + + #ifdef EPI_DEBUG + if (nviruses_tmp >= m->array_virus_tmp.size()) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + baseline + + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) * + coef_exposure + ; + + // Applying the plogis function + m->array_double_tmp[nviruses_tmp] = 1.0/ + (1.0 + std::exp(-m->array_double_tmp[nviruses_tmp])); + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + } + + // No virus to compute + if (nviruses_tmp == 0u) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + + return; + + }; + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + // Getting the right type + ModelSIRLogit * _m = dynamic_cast*>(m); + + // Computing recovery probability once + double prob = 0.0; + #pragma omp simd reduction(+:prob) + for (size_t i = 0u; i < _m->coefs_recover.size(); ++i) + prob += p->operator[](i) * _m->coefs_recover[i]; + + // Computing logis + prob = 1.0/(1.0 + std::exp(-prob)); + + if (prob > m->runif()) + p->rm_virus(0, m); + + return; + + }; + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + // Setting up parameters + // model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + // model.add_param(prob_reinfection, "Prob. Reinfection"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state( + ModelSIRLogit::INFECTED, + ModelSIRLogit::RECOVERED, + ModelSIRLogit::RECOVERED + ); + + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_recovery(&model("Recovery rate")); + + // virus.set_prob + + model.add_virus(virus, prevalence); + + model.set_name("Susceptible-Infected-Removed (SIR) (logit)"); + + return; + +} + +template +inline ModelSIRLogit::ModelSIRLogit( + std::string vname, + double * data, + size_t ncols, + std::vector< double > coefs_infect, + std::vector< double > coefs_recover, + std::vector< size_t > coef_infect_cols, + std::vector< size_t > coef_recover_cols, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double prevalence + ) +{ + + ModelSIRLogit( + *this, + vname, + data, + ncols, + coefs_infect, + coefs_recover, + coef_infect_cols, + coef_recover_cols, + transmission_rate, + recovery_rate, + prevalence + ); + + return; + +} + + +#endif diff --git a/inst/include/epiworld/models/sis.hpp b/inst/include/epiworld/models/sis.hpp new file mode 100644 index 00000000..edbae17c --- /dev/null +++ b/inst/include/epiworld/models/sis.hpp @@ -0,0 +1,92 @@ +#ifndef EPIWORLD_MODELS_SIS_HPP +#define EPIWORLD_MODELS_SIS_HPP + +/** + * @brief Template for a Susceptible-Infected-Susceptible (SIS) model + * + * @param vname std::string Name of the virus + * @param initial_prevalence epiworld_double Initial prevalence + * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system + * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system + */ +template +class ModelSIS : public epiworld::Model +{ + +public: + + ModelSIS() {}; + + ModelSIS( + ModelSIS & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + + ModelSIS( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ); + +}; + +template +inline ModelSIS::ModelSIS( + ModelSIS & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ) +{ + + model.set_name("Susceptible-Infected-Susceptible (SIS)"); + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Infected", epiworld::default_update_exposed); + + // Setting up parameters + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1,0,0); + + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_recovery(&model("Recovery rate")); + virus.set_prob_death(0.0); + + model.add_virus(virus, prevalence); + + return; + +} + +template +inline ModelSIS::ModelSIS( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate + ) +{ + + ModelSIS( + *this, + vname, + prevalence, + transmission_rate, + recovery_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/sisd.hpp b/inst/include/epiworld/models/sisd.hpp new file mode 100644 index 00000000..1d3a424b --- /dev/null +++ b/inst/include/epiworld/models/sisd.hpp @@ -0,0 +1,100 @@ +#ifndef EPIWORLD_MODELS_SISD_HPP +#define EPIWORLD_MODELS_SISD_HPP + +/** + * @brief Template for a Susceptible-Infected-Susceptible-Deceased (SISD) model + * + * @param vname std::string Name of the virus + * @param initial_prevalence epiworld_double Initial prevalence + * @param initial_efficacy epiworld_double Initial susceptibility_reduction of the immune system + * @param initial_recovery epiworld_double Initial recovery_rate rate of the immune system + * @param inital_death epiworld_double Initial death_rate of the immune system + */ +template +class ModelSISD : public epiworld::Model +{ + +public: + + ModelSISD() {}; + + ModelSISD( + ModelSISD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + + ModelSISD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ); + +}; + +template +inline ModelSISD::ModelSISD( + ModelSISD & model, + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + model.set_name("Susceptible-Infected-Susceptible-Deceased (SISD)"); + + // Adding statuses + model.add_state("Susceptible", epiworld::default_update_susceptible); + model.add_state("Infected", epiworld::default_update_exposed); + model.add_state("Deceased"); + + // Setting up parameters + model.add_param(transmission_rate, "Transmission rate"); + model.add_param(recovery_rate, "Recovery rate"); + model.add_param(death_rate, "Death rate"); + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname); + virus.set_state(1,0,2); + + virus.set_prob_infecting(&model("Transmission rate")); + virus.set_prob_recovery(&model("Recovery rate")); + virus.set_prob_death(0.01); + + model.add_virus(virus, prevalence); + + return; + +} + +template +inline ModelSISD::ModelSISD( + std::string vname, + epiworld_double prevalence, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + epiworld_double death_rate + ) +{ + + ModelSISD( + *this, + vname, + prevalence, + transmission_rate, + recovery_rate, + death_rate + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/models/surveillance.hpp b/inst/include/epiworld/models/surveillance.hpp new file mode 100644 index 00000000..695f0a7f --- /dev/null +++ b/inst/include/epiworld/models/surveillance.hpp @@ -0,0 +1,392 @@ +#ifndef EPIWORLD_MODELS_SURVEILLANCE_HPP +#define EPIWORLD_MODELS_SURVEILLANCE_HPP + +template +class ModelSURV : public epiworld::Model { + +private: + // state + static const int SUSCEPTIBLE = 0; + static const int LATENT = 1; + static const int SYMPTOMATIC = 2; + static const int SYMPTOMATIC_ISOLATED = 3; // sampled and discovered + static const int ASYMPTOMATIC = 4; + static const int ASYMPTOMATIC_ISOLATED = 5; + static const int RECOVERED = 6; + static const int REMOVED = 7; + +public: + + /** + * @name Construct a new ModelSURV object + * + * The ModelSURV class simulates a survaillence model where agents can be + * isolated, even if asyptomatic. + * + * @param vname String. Name of the virus + * @param prevalence Integer. Number of initial cases of the virus. + * @param efficacy_vax Double. Efficacy of the vaccine (1 - P(acquire the disease)). + * @param latent_period Double. Shape parameter of a `Gamma(latent_period, 1)` + * distribution. This coincides with the expected number of latent days. + * @param infect_period Double. Shape parameter of a `Gamma(infected_period, 1)` + * distribution. This coincides with the expected number of infectious days. + * @param prob_symptoms Double. Probability of generating symptoms. + * @param prop_vaccinated Double. Probability of vaccination. Coincides with + * the initial prevalence of vaccinated individuals. + * @param prop_vax_redux_transm Double. Factor by which the vaccine reduces + * transmissibility. + * @param prop_vax_redux_infect Double. Factor by which the vaccine reduces + * the chances of becoming infected. + * @param surveillance_prob Double. Probability of testing an agent. + * @param prob_transmission Double. Raw transmission probability. + * @param prob_death Double. Raw probability of death for symptomatic individuals. + * @param prob_noreinfect Double. Probability of no re-infection. + * + * @details + * This model features the following states: + * + * - Susceptible + * - Latent + * - Symptomatic + * - Symptomatic isolated + * - Asymptomatic + * - Asymptomatic isolated + * - Recovered + * - Removed + * + * @returns An object of class `epiworld_surv` + * + */ + ///@{ + ModelSURV() {}; + + ModelSURV( + ModelSURV & model, + std::string vname, + epiworld_fast_uint prevalence = 50, + epiworld_double efficacy_vax = 0.9, + epiworld_double latent_period = 3u, + epiworld_double infect_period = 6u, + epiworld_double prob_symptoms = 0.6, + epiworld_double prop_vaccinated = 0.25, + epiworld_double prop_vax_redux_transm = 0.5, + epiworld_double prop_vax_redux_infect = 0.5, + epiworld_double surveillance_prob = 0.001, + epiworld_double prob_transmission = 1.0, + epiworld_double prob_death = 0.001, + epiworld_double prob_noreinfect = 0.9 + ); + + ModelSURV( + std::string vname, + epiworld_fast_uint prevalence = 50, + epiworld_double efficacy_vax = 0.9, + epiworld_double latent_period = 3u, + epiworld_double infect_period = 6u, + epiworld_double prob_symptoms = 0.6, + epiworld_double prop_vaccinated = 0.25, + epiworld_double prop_vax_redux_transm = 0.5, + epiworld_double prop_vax_redux_infect = 0.5, + epiworld_double surveillance_prob = 0.001, + epiworld_double prob_transmission = 1.0, + epiworld_double prob_death = 0.001, + epiworld_double prob_noreinfect = 0.9 + ); + ///@} + +}; + +template +inline ModelSURV::ModelSURV( + ModelSURV & model, + std::string vname, + epiworld_fast_uint prevalence, + epiworld_double efficacy_vax, + epiworld_double latent_period, + epiworld_double infect_period, + epiworld_double prob_symptoms, + epiworld_double prop_vaccinated, + epiworld_double prop_vax_redux_transm, + epiworld_double prop_vax_redux_infect, + epiworld_double surveillance_prob, + epiworld_double prob_transmission, + epiworld_double prob_death, + epiworld_double prob_noreinfect + ) +{ + + EPI_NEW_UPDATEFUN_LAMBDA(surveillance_update_susceptible, TSeq) { + + // This computes the prob of getting any neighbor variant + epiworld_fast_uint nviruses_tmp = 0u; + for (auto & neighbor: p->get_neighbors()) + { + + for (size_t i = 0u; i < neighbor->get_n_viruses(); ++i) + { + + auto & v = neighbor->get_virus(i); + + /* And it is a function of susceptibility_reduction as well */ + epiworld_double tmp_transmission = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_double_tmp[nviruses_tmp] = tmp_transmission; + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + } + + // No virus to compute on + if (nviruses_tmp == 0) + return; + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->add_virus(*m->array_virus_tmp[which], m); + return; + + }; + + + epiworld::UpdateFun surveillance_update_exposed = + [](epiworld::Agent * p, epiworld::Model * m) -> void + { + + epiworld::VirusPtr & v = p->get_virus(0u); + epiworld_double p_die = v->get_prob_death(m) * (1.0 - p->get_death_reduction(v, m)); + + epiworld_fast_uint days_since_exposed = m->today() - v->get_date(); + epiworld_fast_uint state = p->get_state(); + + // Figuring out latent period + if (v->get_data().size() == 0u) + { + epiworld_double latent_days = m->rgamma(m->par("Latent period"), 1.0); + v->get_data().push_back(latent_days); + + v->get_data().push_back( + m->rgamma(m->par("Infect period"), 1.0) + latent_days + ); + } + + // If still latent, nothing happens + if (days_since_exposed <= v->get_data()[0u]) + return; + + // If past days infected + latent, then bye. + if (days_since_exposed >= v->get_data()[1u]) + { + p->rm_virus(0, m); + return; + } + + // If it is infected, then it can be asymptomatic or symptomatic + if (state == ModelSURV::LATENT) + { + + // Will be symptomatic? + if (EPI_RUNIF() < m->par("Prob of symptoms")) + p->change_state(m, ModelSURV::SYMPTOMATIC); + else + p->change_state(m, ModelSURV::ASYMPTOMATIC); + + return; + + } + + // Otherwise, it can be removed + if (EPI_RUNIF() < p_die) + { + p->change_state(m, ModelSURV::REMOVED, -1); + return; + } + + return; + + }; + + std::vector< epiworld_fast_uint > exposed_state = { + SYMPTOMATIC, + SYMPTOMATIC_ISOLATED, + ASYMPTOMATIC, + ASYMPTOMATIC_ISOLATED, + LATENT + }; + + epiworld::GlobalFun surveillance_program = + [exposed_state]( + epiworld::Model* m + ) -> void + { + + // How many will we find + std::binomial_distribution<> bdist(m->size(), m->par("Surveilance prob.")); + int nsampled = bdist(m->get_rand_endgine()); + + int to_go = nsampled + 1; + + epiworld_double ndetected = 0.0; + epiworld_double ndetected_asympt = 0.0; + + auto & pop = m->get_agents(); + std::vector< bool > sampled(m->size(), false); + + while (to_go-- > 0) + { + + // Who is the lucky one + epiworld_fast_uint i = static_cast(std::floor(EPI_RUNIF() * m->size())); + + if (sampled[i]) + continue; + + sampled[i] = true; + epiworld::Agent * p = &pop[i]; + + // If still exposed for the next term + if (epiworld::IN(p->get_state(), exposed_state )) + { + + ndetected += 1.0; + if (p->get_state() == ModelSURV::ASYMPTOMATIC) + { + ndetected_asympt += 1.0; + p->change_state(m, ModelSURV::ASYMPTOMATIC_ISOLATED); + } + else + { + p->change_state(m, ModelSURV::SYMPTOMATIC_ISOLATED); + } + + } + + } + + // Writing the user data + std::vector< int > totals; + m->get_db().get_today_total(nullptr,&totals); + m->add_user_data( + { + static_cast(nsampled), + ndetected, + ndetected_asympt, + static_cast(totals[ModelSURV::ASYMPTOMATIC]) + } + ); + + + }; + + model.add_state("Susceptible", surveillance_update_susceptible); + model.add_state("Latent", surveillance_update_exposed); + model.add_state("Symptomatic", surveillance_update_exposed); + model.add_state("Symptomatic isolated", surveillance_update_exposed); + model.add_state("Asymptomatic", surveillance_update_exposed); + model.add_state("Asymptomatic isolated", surveillance_update_exposed); + model.add_state("Recovered"); + model.add_state("Removed"); + + // General model parameters + model.add_param(latent_period, "Latent period"); + model.add_param(infect_period, "Infect period"); + model.add_param(prob_symptoms, "Prob of symptoms"); + model.add_param(surveillance_prob, "Surveilance prob."); + model.add_param(efficacy_vax, "Vax efficacy"); + model.add_param(prop_vax_redux_transm, "Vax redux transmission"); + model.add_param(prob_transmission, "Prob of transmission"); + model.add_param(prob_death, "Prob. death"); + model.add_param(prob_noreinfect, "Prob. no reinfect"); + + // Virus ------------------------------------------------------------------ + epiworld::Virus covid("Covid19"); + covid.set_state(LATENT, RECOVERED, REMOVED); + covid.set_post_immunity(&model("Prob. no reinfect")); + covid.set_prob_death(&model("Prob. death")); + + epiworld::VirusFun ptransmitfun = []( + epiworld::Agent * p, + epiworld::Virus & v, + epiworld::Model * m + ) -> epiworld_double + { + // No chance of infecting + epiworld_fast_uint s = p->get_state(); + if (s == ModelSURV::LATENT) + return static_cast(0.0); + else if (s == ModelSURV::SYMPTOMATIC_ISOLATED) + return static_cast(0.0); + else if (s == ModelSURV::ASYMPTOMATIC_ISOLATED) + return static_cast(0.0); + + // Otherwise + return m->par("Prob of transmission"); + }; + + covid.set_prob_infecting_fun(ptransmitfun); + + model.add_virus_n(covid, prevalence); + + model.set_user_data({"nsampled", "ndetected", "ndetected_asympt", "nasymptomatic"}); + model.add_global_action(surveillance_program, "Surveilance program", -1); + + // Vaccine tool ----------------------------------------------------------- + epiworld::Tool vax("Vaccine"); + vax.set_susceptibility_reduction(&model("Vax efficacy")); + vax.set_transmission_reduction(&model("Vax redux transmission")); + + model.add_tool(vax, prop_vaccinated); + + model.set_name("Surveillance"); + + return; + +} + +template +inline ModelSURV::ModelSURV( + std::string vname, + epiworld_fast_uint prevalence, + epiworld_double efficacy_vax, + epiworld_double latent_period, + epiworld_double infect_period, + epiworld_double prob_symptoms, + epiworld_double prop_vaccinated, + epiworld_double prop_vax_redux_transm, + epiworld_double prop_vax_redux_infect, + epiworld_double surveillance_prob, + epiworld_double prob_transmission, + epiworld_double prob_death, + epiworld_double prob_noreinfect + ) +{ + + ModelSURV( + *this, + vname, + prevalence, + efficacy_vax, + latent_period, + infect_period, + prob_symptoms, + prop_vaccinated, + prop_vax_redux_transm, + prop_vax_redux_infect, + surveillance_prob, + prob_transmission, + prob_death, + prob_noreinfect + ); + + return; + +} + +#endif diff --git a/inst/include/epiworld/network-bones.hpp b/inst/include/epiworld/network-bones.hpp new file mode 100644 index 00000000..b6496573 --- /dev/null +++ b/inst/include/epiworld/network-bones.hpp @@ -0,0 +1,25 @@ +#ifndef EPIWORLD_NETWORK_BONES_HPP +#define EPIWORLD_NETWORK_BONES_HPP +template +class Network { + +private: + NType data; + +public: + + NType(); + + Edgetype operator()(int i, int j); + + bool is_directed() const; + size_t vcount() const; + size_t ecount() const; + + void add_edge(int i, int j); + void rm_edge(int i, int j); + + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/progress.hpp b/inst/include/epiworld/progress.hpp new file mode 100644 index 00000000..a0cd001c --- /dev/null +++ b/inst/include/epiworld/progress.hpp @@ -0,0 +1,84 @@ +#ifndef EPIWORLD_PROGRESS_HPP +#define EPIWORLD_PROGRESS_HPP + +#ifndef EPIWORLD_PROGRESS_BAR_WIDTH +#define EPIWORLD_PROGRESS_BAR_WIDTH 80 +#endif + +/** + * @brief A simple progress bar + */ +class Progress { +private: + int width; ///< Total width size (number of bars) + int n; ///< Total number of iterations + epiworld_double step_size; ///< Size of the step + int last_loc; ///< Last location of the bar + int cur_loc; ///< Last location of the bar + int i; ///< Current iteration step + +public: + Progress() {}; + Progress(int n_, int width_); + ~Progress() {}; + + void start(); + void next(); + void end(); + +}; + +inline Progress::Progress(int n_, int width_) { + + + width = std::max(7, width_ - 7); + n = n_; + step_size = static_cast(width)/static_cast(n); + last_loc = 0; + i = 0; + +} + +inline void Progress::start() +{ + + #ifndef EPI_DEBUG + for (int j = 0; j < (width); ++j) + { + printf_epiworld("_"); + } + printf_epiworld("\n"); + #endif +} + +inline void Progress::next() { + + if (i == 0) + start(); + + cur_loc = std::floor((++i) * step_size); + + + #ifndef EPI_DEBUG + for (int j = 0; j < (cur_loc - last_loc); ++j) + { + printf_epiworld("|"); + } + #endif + + if (i >= n) + end(); + + last_loc = cur_loc; + +} + +inline void Progress::end() { + + #ifndef EPI_DEBUG + printf_epiworld(" done.\n"); + #endif + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/queue-bones.hpp b/inst/include/epiworld/queue-bones.hpp new file mode 100644 index 00000000..c8644199 --- /dev/null +++ b/inst/include/epiworld/queue-bones.hpp @@ -0,0 +1,122 @@ +#ifndef EPIWORLD_QUEUE_BONES_HPP +#define EPIWORLD_QUEUE_BONES_HPP + +/** + * @brief Controls which agents are verified at each step + * + * @details The idea is that only agents who are either in + * an infected state or have an infected neighbor should be + * checked. Otherwise it makes no sense (no chance to recover + * or capture the disease). + * + * @tparam TSeq + */ +template +class Queue +{ + friend class Model; + +private: + + /** + * @brief Count of ego's neighbors in queue (including ego) + */ + std::vector< epiworld_fast_int > active; + Model * model = nullptr; + int n_in_queue = 0; + + // Auxiliary variable that checks how many steps + // left are there + // int n_steps_left; + // bool queuing_started = false; + +public: + + void operator+=(Agent * p); + void operator-=(Agent * p); + epiworld_fast_int & operator[](epiworld_fast_uint i); + + // void initialize(Model * m, Agent * p); + void reset(); + + bool operator==(const Queue & other) const; + bool operator!=(const Queue & other) const {return !operator==(other);}; + + static const int NoOne = 0; + static const int OnlySelf = 1; + static const int Everyone = 2; + +}; + +template +inline void Queue::operator+=(Agent * p) +{ + + if (++active[p->id] == 1) + n_in_queue++; + + for (auto n : p->neighbors) + { + + if (++active[n] == 1) + n_in_queue++; + + } + +} + +template +inline void Queue::operator-=(Agent * p) +{ + + if (--active[p->id] == 0) + n_in_queue--; + + for (auto n : p->neighbors) + { + if (--active[n] == 0) + n_in_queue--; + } + +} + +template +inline epiworld_fast_int & Queue::operator[](epiworld_fast_uint i) +{ + return active[i]; +} + +template +inline void Queue::reset() +{ + + if (n_in_queue) + { + + for (auto & q : this->active) + q = 0; + + n_in_queue = 0; + + } + + active.resize(model->size(), 0); + +} + +template +inline bool Queue::operator==(const Queue & other) const +{ + if (active.size() != other.active.size()) + return false; + + for (size_t i = 0u; i < active.size(); ++i) + { + if (active[i] != other.active[i]) + return false; + } + + return true; +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/randgraph.hpp b/inst/include/epiworld/randgraph.hpp new file mode 100644 index 00000000..cdf82728 --- /dev/null +++ b/inst/include/epiworld/randgraph.hpp @@ -0,0 +1,529 @@ +#ifndef EPIWORLD_RANDGRA +#define EPIWORLD_RANDGRA + +template +class Model; + +template +class Agent; + +class AdjList; + + +template +inline void rewire_degseq( + TDat * agents, + Model * model, + epiworld_double proportion + ); + +template +inline void rewire_degseq( + std::vector< Agent > * agents, + Model * model, + epiworld_double proportion + ) +{ + + #ifdef EPI_DEBUG + std::vector< int > _degree0(agents->size(), 0); + for (size_t i = 0u; i < _degree0.size(); ++i) + _degree0[i] = model->get_agents()[i].get_neighbors().size(); + #endif + + // Identifying individuals with degree > 0 + std::vector< epiworld_fast_uint > non_isolates; + std::vector< epiworld_double > weights; + epiworld_double nedges = 0.0; + + for (epiworld_fast_uint i = 0u; i < agents->size(); ++i) + { + if (agents->operator[](i).get_neighbors().size() > 0u) + { + non_isolates.push_back(i); + epiworld_double wtemp = static_cast( + agents->operator[](i).get_neighbors().size() + ); + weights.push_back(wtemp); + nedges += wtemp; + } + } + + if (non_isolates.size() == 0u) + throw std::logic_error("The graph is completely disconnected."); + + // Cumulative probs + weights[0u] /= nedges; + for (epiworld_fast_uint i = 1u; i < non_isolates.size(); ++i) + { + weights[i] /= nedges; + weights[i] += weights[i - 1u]; + } + + // Only swap if needed + epiworld_fast_uint N = non_isolates.size(); + epiworld_double prob; + int nrewires = floor(proportion * nedges); + while (nrewires-- > 0) + { + + // Picking egos + prob = model->runif(); + int id0 = N - 1; + for (epiworld_fast_uint i = 0u; i < N; ++i) + if (prob <= weights[i]) + { + id0 = i; + break; + } + + prob = model->runif(); + int id1 = N - 1; + for (epiworld_fast_uint i = 0u; i < N; ++i) + if (prob <= weights[i]) + { + id1 = i; + break; + } + + // Correcting for under or overflow. + if (id1 == id0) + id1++; + + if (id1 >= static_cast(N)) + id1 = 0; + + Agent & p0 = agents->operator[](non_isolates[id0]); + Agent & p1 = agents->operator[](non_isolates[id1]); + + // Picking alters (relative location in their lists) + // In this case, these are uniformly distributed within the list + int id01 = std::floor(p0.get_n_neighbors() * model->runif()); + int id11 = std::floor(p1.get_n_neighbors() * model->runif()); + + // When rewiring, we need to flip the individuals from the other + // end as well, since we are dealing withi an undirected graph + + // Finding what neighbour is id0 + model->get_agents()[id0].swap_neighbors( + model->get_agents()[id1], + id01, + id11 + ); + + + } + + #ifdef EPI_DEBUG + for (size_t _i = 0u; _i < _degree0.size(); ++_i) + { + if (_degree0[_i] != static_cast(model->get_agents()[_i].get_n_neighbors())) + throw std::logic_error("[epi-debug] Degree does not match afted rewire_degseq."); + } + #endif + + return; + +} + +template +inline void rewire_degseq( + AdjList * agents, + Model * model, + epiworld_double proportion + ) +{ + + // Identifying individuals with degree > 0 + std::vector< epiworld_fast_int > nties(agents->vcount(), 0); + + #ifdef EPI_DEBUG + std::vector< int > _degree0(agents->vcount(), 0); + for (size_t i = 0u; i < _degree0.size(); ++i) + _degree0[i] = agents->get_dat()[i].size(); + #endif + + std::vector< epiworld_fast_uint > non_isolates; + non_isolates.reserve(nties.size()); + + std::vector< epiworld_double > weights; + weights.reserve(nties.size()); + + epiworld_double nedges = 0.0; + auto & dat = agents->get_dat(); + + for (size_t i = 0u; i < dat.size(); ++i) + nties[i] += dat[i].size(); + + bool directed = agents->is_directed(); + for (size_t i = 0u; i < dat.size(); ++i) + { + if (nties[i] > 0) + { + non_isolates.push_back(i); + if (directed) + { + weights.push_back( + static_cast(nties[i]) + ); + nedges += static_cast(nties[i]); + } + else { + weights.push_back( + static_cast(nties[i])/2.0 + ); + nedges += static_cast(nties[i]) / 2.0; + } + } + } + + if (non_isolates.size() == 0u) + throw std::logic_error("The graph is completely disconnected."); + + // Cumulative probs + weights[0u] /= nedges; + for (epiworld_fast_uint i = 1u; i < non_isolates.size(); ++i) + { + weights[i] /= nedges; + weights[i] += weights[i - 1u]; + } + + // Only swap if needed + epiworld_fast_uint N = non_isolates.size(); + epiworld_double prob; + int nrewires = floor(proportion * nedges / ( + agents->is_directed() ? 1.0 : 2.0 + )); + + while (nrewires-- > 0) + { + + // Picking egos + prob = model->runif(); + int id0 = N - 1; + for (epiworld_fast_uint i = 0u; i < N; ++i) + if (prob <= weights[i]) + { + id0 = i; + break; + } + + prob = model->runif(); + int id1 = N - 1; + for (epiworld_fast_uint i = 0u; i < N; ++i) + if (prob <= weights[i]) + { + id1 = i; + break; + } + + // Correcting for under or overflow. + if (id1 == id0) + id1++; + + if (id1 >= static_cast(N)) + id1 = 0; + + std::map & p0 = agents->get_dat()[non_isolates[id0]]; + std::map & p1 = agents->get_dat()[non_isolates[id1]]; + + // Picking alters (relative location in their lists) + // In this case, these are uniformly distributed within the list + int id01 = std::floor(p0.size() * model->runif()); + int id11 = std::floor(p1.size() * model->runif()); + + // Since it is a map, we need to find the actual ids (positions) + // are not good enough. + int count = 0; + for (auto & n : p0) + if (count++ == id01) + id01 = n.first; + + count = 0; + for (auto & n : p1) + if (count++ == id11) + id11 = n.first; + + // When rewiring, we need to flip the individuals from the other + // end as well, since we are dealing withi an undirected graph + + // Finding what neighbour is id0 + if (!agents->is_directed()) + { + + std::map & p01 = agents->get_dat()[id01]; + std::map & p11 = agents->get_dat()[id11]; + + std::swap(p01[id0], p11[id1]); + + } + + // Moving alter first + std::swap(p0[id01], p1[id11]); + + } + + #ifdef EPI_DEBUG + for (size_t _i = 0u; _i < _degree0.size(); ++_i) + { + if (_degree0[_i] != static_cast(agents->get_dat()[_i].size())) + throw std::logic_error( + "[epi-debug] Degree does not match afted rewire_degseq. " + + std::string("Expected: ") + + std::to_string(_degree0[_i]) + + std::string(", observed: ") + + std::to_string(agents->get_dat()[_i].size()) + ); + } + #endif + + + return; + +} + +template +inline AdjList rgraph_bernoulli( + epiworld_fast_uint n, + epiworld_double p, + bool directed, + Model & model +) { + + std::vector< int > source; + std::vector< int > target; + + // Checking the density (how many) + std::binomial_distribution<> d( + n * (n - 1.0) / (directed ? 1.0 : 2.0), + p + ); + + epiworld_fast_uint m = d(model.get_rand_endgine()); + + source.resize(m); + target.resize(m); + + epiworld_fast_uint a,b; + for (epiworld_fast_uint i = 0u; i < m; ++i) + { + a = floor(model.runif() * n); + + if (!directed) + b = floor(model.runif() * a); + else + { + b = floor(model.runif() * n); + if (b == a) + b++; + + if (b >= n) + b = 0u; + } + + source[i] = static_cast(a); + target[i] = static_cast(b); + + } + + AdjList al(source, target, static_cast(n), directed); + + return al; + +} + +template +inline AdjList rgraph_bernoulli2( + epiworld_fast_uint n, + epiworld_double p, + bool directed, + Model & model +) { + + std::vector< int > source; + std::vector< int > target; + + // Checking the density (how many) + std::binomial_distribution<> d( + n * (n - 1.0) / (directed ? 1.0 : 2.0), + p + ); + + // Need to compensate for the possible number of diagonal + // elements sampled. If n * n, then each diag element has + // 1/(n^2) chance of sampling + + epiworld_fast_uint m = d(model.get_rand_endgine()); + + source.resize(m); + target.resize(m); + + double n2 = static_cast(n * n); + + int loc,row,col; + for (epiworld_fast_uint i = 0u; i < m; ++i) + { + loc = floor(model.runif() * n2); + col = floor(static_cast(loc)/static_cast(n)); + row = loc - row * n; + + // Undirected needs to swap + if (!directed && (col > row)) + std::swap(col, row); + + source[i] = row; + target[i] = col; + + } + + AdjList al(source, target, static_cast(n), directed); + + return al; + +} + +inline AdjList rgraph_ring_lattice( + epiworld_fast_uint n, + epiworld_fast_uint k, + bool directed = false +) { + + if ((n - 1u) < k) + throw std::logic_error("k can be at most n - 1."); + + std::vector< int > source; + std::vector< int > target; + + if (!directed) + if (k > 1u) k = static_cast< size_t >(floor(k / 2.0)); + + for (size_t i = 0; i < n; ++i) + { + + for (size_t j = 1u; j <= k; ++j) + { + + // Next neighbor + size_t l = i + j; + if (l >= n) l = l - n; + + source.push_back(i); + target.push_back(l); + + } + + } + + return AdjList(source, target, n, directed); + +} + +/** + * @brief Smallworld network (Watts-Strogatz) + * + * @tparam TSeq + * @param n + * @param k + * @param p + * @param directed + * @param model + * @return AdjList + */ +template +inline AdjList rgraph_smallworld( + epiworld_fast_uint n, + epiworld_fast_uint k, + epiworld_double p, + bool directed, + Model & model +) { + + // Creating the ring lattice + AdjList ring = rgraph_ring_lattice(n,k,directed); + + // Rewiring and returning + if (k > 0u) + rewire_degseq(&ring, &model, p); + + return ring; + +} + +/** + * @brief Generates a blocked network + * + * Since block sizes and number of connections between blocks are fixed, + * this routine is fully deterministic. + * + * @tparam TSeq + * @param n Size of the network + * @param blocksize Size of the block. + * @param ncons Number of connections between blocks + * @param model A model + * @return AdjList + */ +template +inline AdjList rgraph_blocked( + epiworld_fast_uint n, + epiworld_fast_uint blocksize, + epiworld_fast_uint ncons, + Model& +) { + + std::vector< int > source_; + std::vector< int > target_; + + size_t i = 0u; + size_t cum_node_count = 0u; + while (i < n) + { + + for (size_t j = 0; j < blocksize; ++j) + { + + for (size_t k = 0; k < j; ++k) + { + // No loops + if (k == j) + continue; + + // Exists the loop in case there are no more + // nodes available + if ((i + k) >= n) + break; + + source_.push_back(static_cast(j + i)); + target_.push_back(static_cast(k + i)); + } + + // No more nodes left to build connections + if (++cum_node_count >= n) + break; + + } + + // Connections between this and the previou sone + if (i != 0) + { + + size_t max_cons = std::min(ncons, n - cum_node_count); + + // Generating the connections + for (size_t j = 0u; j < max_cons; ++j) + { + + source_.push_back(static_cast(i + j - blocksize)); + target_.push_back(static_cast(i + j)); + + } + } + + i += blocksize; + + } + + return AdjList(source_, target_, n, false); + +} + +#endif diff --git a/inst/include/epiworld/random_graph.hpp b/inst/include/epiworld/random_graph.hpp new file mode 100644 index 00000000..dfe6883d --- /dev/null +++ b/inst/include/epiworld/random_graph.hpp @@ -0,0 +1,54 @@ +#ifndef EPIWORLD_RANDOM_GRAPH_HPP +#define EPIWORLD_RANDOM_GRAPH_HPP + +class RandGraph { + +private: + std::shared_ptr< std::mt19937 > engine; + std::shared_ptr< std::uniform_real_distribution<> > unifd; + int N = 0; + bool initialized = false; + +public: + + RandGraph(int N_) : N(N_) {}; + + void init(int s); + void set_rand_engine(std::mt19937 & e); + epiworld_double runif(); + +}; + + +inline void RandGraph::init(int s) { + + if (!engine) + engine = std::make_shared< std::mt19937 >(); + + engine->seed(s); + + if (!unifd) + unifd = std::make_shared< std::uniform_real_distribution<> >(0, 1); + + initialized = true; + + +} + +inline void RandGraph::set_rand_engine(std::mt19937 & e) +{ + + engine = std::make_shared< std::mt19937 >(e); + +} + +inline epiworld_double RandGraph::runif() { + + if (!initialized) + throw std::logic_error("The object has not been initialized"); + + return (*unifd)(engine); + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/seq_processing.hpp b/inst/include/epiworld/seq_processing.hpp new file mode 100644 index 00000000..7ccef699 --- /dev/null +++ b/inst/include/epiworld/seq_processing.hpp @@ -0,0 +1,94 @@ +#ifndef EPIWORLD_SEQ_PROCESSING_HPP +#define EPIWORLD_SEQ_PROCESSING_HPP + +/** + * @brief Hasher function to turn the sequence into an integer vector + * + * @tparam TSeq + * @param x + * @return std::vector + */ +template +inline std::vector default_seq_hasher(const TSeq & x); + +template<> +inline std::vector default_seq_hasher>(const std::vector & x) { + return x; +} + +template<> +inline std::vector default_seq_hasher>(const std::vector & x) { + std::vector ans(x.size()); + size_t j = 0; + for (const auto & i : x) + ans[j++] = i? 1 : 0; + return ans; +} + +template<> +inline std::vector default_seq_hasher(const int & x) { + return {x}; +} + +template<> +inline std::vector default_seq_hasher(const bool & x) { + return {x ? 1 : 0}; +} + +/** + * @brief Default way to write sequences + * + * @tparam TSeq + * @param seq + * @return std::string + */ +template +inline std::string default_seq_writer(const TSeq & seq); + +template<> +inline std::string default_seq_writer>( + const std::vector & seq +) { + + std::string out = ""; + for (const auto & s : seq) + out = out + std::to_string(s); + + return out; + +} + +template<> +inline std::string default_seq_writer>( + const std::vector & seq +) { + + std::string out = ""; + for (const auto & s : seq) + out = out + (s ? "1" : "0"); + + return out; + +} + +template<> +inline std::string default_seq_writer( + const bool & seq +) { + + return seq ? "1" : "0"; + +} + +template<> +inline std::string default_seq_writer( + const int & seq +) { + + return std::to_string(seq); + +} + + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/templates/viruses.hpp b/inst/include/epiworld/templates/viruses.hpp new file mode 100644 index 00000000..2ae18873 --- /dev/null +++ b/inst/include/epiworld/templates/viruses.hpp @@ -0,0 +1,34 @@ +#include "../epiworld.hpp" +using namespace epiworld; +#ifndef EPIWORLD_TEMPLATES_VIRUSES_HPP +#define EPIWORLD_TEMPLATES_VIRUSES_HPP + +template +inline VirusFun virus_fun_logit( + std::string name, + std::vector< size_t > vars, + std::vector< epiworld_double > coefs +) { + + VirusFun fun_infect = [coefs,vars]( + Agent * agent, + Virus & virus, + Model * model + ) -> VirusFun { + + size_t K = coefs.size(); + epiworld_double res = 0.0; + + #pragma omp simd reduction(+:res) + for (size_t i = 0u; i < K; ++i) + res += agent->operator[i] * coefs.at(i); + + return 1.0/(1.0 + std::exp(-res)); + + }; + + return fun_infect; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/tool-bones.hpp b/inst/include/epiworld/tool-bones.hpp new file mode 100644 index 00000000..9cd56b27 --- /dev/null +++ b/inst/include/epiworld/tool-bones.hpp @@ -0,0 +1,112 @@ + +#ifndef EPIWORLD_TOOL_BONES_HPP +#define EPIWORLD_TOOL_BONES_HPP + +template +class Virus; + +template +class Agent; + +template +class Model; + +template +class Tool; + +/** + * @brief Tools for defending the agent against the virus + * + * @tparam TSeq Type of sequence + */ +template +class Tool { + friend class Agent; + friend class Model; + friend void default_add_tool(Action & a, Model * m); + friend void default_rm_tool(Action & a, Model * m); +private: + + Agent * agent = nullptr; + int pos_in_agent = -99; ///< Location in the agent + + int date = -99; + int id = -99; + std::shared_ptr tool_name = nullptr; + std::shared_ptr sequence = nullptr; + ToolFun susceptibility_reduction_fun = nullptr; + ToolFun transmission_reduction_fun = nullptr; + ToolFun recovery_enhancer_fun = nullptr; + ToolFun death_reduction_fun = nullptr; + + // Setup parameters + std::vector< epiworld_double * > params; + + epiworld_fast_int state_init = -99; + epiworld_fast_int state_post = -99; + + epiworld_fast_int queue_init = Queue::NoOne; ///< Change of state when added to agent. + epiworld_fast_int queue_post = Queue::NoOne; ///< Change of state when removed from agent. + + void set_agent(Agent * p, size_t idx); + +public: + Tool(std::string name = "unknown tool"); + // Tool(TSeq d, std::string name = "unknown tool"); + + void set_sequence(TSeq d); + void set_sequence(std::shared_ptr d); + std::shared_ptr get_sequence(); + + /** + * @name Get and set the tool functions + * + * @param v The virus over which to operate + * @param fun the function to be used + * + * @return epiworld_double + */ + ///@{ + epiworld_double get_susceptibility_reduction(VirusPtr v, Model * model); + epiworld_double get_transmission_reduction(VirusPtr v, Model * model); + epiworld_double get_recovery_enhancer(VirusPtr v, Model * model); + epiworld_double get_death_reduction(VirusPtr v, Model * model); + + void set_susceptibility_reduction_fun(ToolFun fun); + void set_transmission_reduction_fun(ToolFun fun); + void set_recovery_enhancer_fun(ToolFun fun); + void set_death_reduction_fun(ToolFun fun); + + void set_susceptibility_reduction(epiworld_double * prob); + void set_transmission_reduction(epiworld_double * prob); + void set_recovery_enhancer(epiworld_double * prob); + void set_death_reduction(epiworld_double * prob); + + void set_susceptibility_reduction(epiworld_double prob); + void set_transmission_reduction(epiworld_double prob); + void set_recovery_enhancer(epiworld_double prob); + void set_death_reduction(epiworld_double prob); + ///@} + + void set_name(std::string name); + std::string get_name() const; + + Agent * get_agent(); + int get_id() const; + void set_id(int id); + void set_date(int d); + int get_date() const; + + void set_state(epiworld_fast_int init, epiworld_fast_int post); + void set_queue(epiworld_fast_int init, epiworld_fast_int post); + void get_state(epiworld_fast_int * init, epiworld_fast_int * post); + void get_queue(epiworld_fast_int * init, epiworld_fast_int * post); + + bool operator==(const Tool & other) const; + bool operator!=(const Tool & other) const {return !operator==(other);}; + + void print() const; + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/tool-meat.hpp b/inst/include/epiworld/tool-meat.hpp new file mode 100644 index 00000000..e70d2f60 --- /dev/null +++ b/inst/include/epiworld/tool-meat.hpp @@ -0,0 +1,498 @@ + +#ifndef EPIWORLD_TOOLS_MEAT_HPP +#define EPIWORLD_TOOLS_MEAT_HPP + +/** + * @brief Factory function of ToolFun base on logit + * + * @tparam TSeq + * @param vars Vector indicating the position of the variables to use. + * @param coefs Vector of coefficients. + * @return ToolFun + */ +template +inline ToolFun tool_fun_logit( + std::vector< int > vars, + std::vector< double > coefs, + Model * model +) { + + // Checking that there are features + if (coefs.size() == 0u) + throw std::logic_error( + "The -coefs- argument should feature at least one element." + ); + + if (coefs.size() != vars.size()) + throw std::length_error( + std::string("The length of -coef- (") + + std::to_string(coefs.size()) + + std::string(") and -vars- (") + + std::to_string(vars.size()) + + std::string(") should match. ") + ); + + // Checking that there are variables in the model + if (model != nullptr) + { + + size_t K = model->get_agents_data_ncols(); + for (const auto & var: vars) + { + if ((var >= static_cast(K)) | (var < 0)) + throw std::range_error( + std::string("The variable ") + + std::to_string(var) + + std::string(" is out of range.") + + std::string(" The agents only feature ") + + std::to_string(K) + + std::string("variables (features).") + ); + } + + } + + std::vector< epiworld_double > coefs_f; + for (auto c: coefs) + coefs_f.push_back(static_cast(c)); + + ToolFun fun_ = [coefs_f,vars]( + Tool& tool, + Agent * agent, + VirusPtr virus, + Model * model + ) -> epiworld_double { + + size_t K = coefs_f.size(); + epiworld_double res = 0.0; + + #pragma omp simd reduction(+:res) + for (size_t i = 0u; i < K; ++i) + res += agent->operator[](vars.at(i)) * coefs_f.at(i); + + return 1.0/(1.0 + std::exp(-res)); + + }; + + return fun_; + +} + +template +inline Tool::Tool(std::string name) +{ + set_name(name); +} + +// template +// inline Tool::Tool(TSeq d, std::string name) { +// sequence = std::make_shared(d); +// tool_name = std::make_shared(name); +// } + +template +inline void Tool::set_sequence(TSeq d) { + sequence = std::make_shared(d); +} + +template +inline void Tool::set_sequence(std::shared_ptr d) { + sequence = d; +} + +template +inline std::shared_ptr Tool::get_sequence() { + return sequence; +} + +template +inline epiworld_double Tool::get_susceptibility_reduction( + VirusPtr v, + Model * model +) +{ + + if (susceptibility_reduction_fun) + return susceptibility_reduction_fun(*this, this->agent, v, model); + + return DEFAULT_TOOL_CONTAGION_REDUCTION; + +} + +template +inline epiworld_double Tool::get_transmission_reduction( + VirusPtr v, + Model * model +) +{ + + if (transmission_reduction_fun) + return transmission_reduction_fun(*this, this->agent, v, model); + + return DEFAULT_TOOL_TRANSMISSION_REDUCTION; + +} + +template +inline epiworld_double Tool::get_recovery_enhancer( + VirusPtr v, + Model * model +) +{ + + if (recovery_enhancer_fun) + return recovery_enhancer_fun(*this, this->agent, v, model); + + return DEFAULT_TOOL_RECOVERY_ENHANCER; + +} + +template +inline epiworld_double Tool::get_death_reduction( + VirusPtr v, + Model * model +) +{ + + if (death_reduction_fun) + return death_reduction_fun(*this, this->agent, v, model); + + return DEFAULT_TOOL_DEATH_REDUCTION; + +} + +template +inline void Tool::set_susceptibility_reduction_fun( + ToolFun fun +) +{ + susceptibility_reduction_fun = fun; +} + +template +inline void Tool::set_transmission_reduction_fun( + ToolFun fun +) +{ + transmission_reduction_fun = fun; +} + +template +inline void Tool::set_recovery_enhancer_fun( + ToolFun fun +) +{ + recovery_enhancer_fun = fun; +} + +template +inline void Tool::set_death_reduction_fun( + ToolFun fun +) +{ + death_reduction_fun = fun; +} + +template +inline void Tool::set_susceptibility_reduction(epiworld_double * prob) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return *prob; + }; + + susceptibility_reduction_fun = tmpfun; + +} + +// EPIWORLD_SET_LAMBDA(susceptibility_reduction) +template +inline void Tool::set_transmission_reduction(epiworld_double * prob) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return *prob; + }; + + transmission_reduction_fun = tmpfun; + +} + +// EPIWORLD_SET_LAMBDA(transmission_reduction) +template +inline void Tool::set_recovery_enhancer(epiworld_double * prob) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return *prob; + }; + + recovery_enhancer_fun = tmpfun; + +} + +// EPIWORLD_SET_LAMBDA(recovery_enhancer) +template +inline void Tool::set_death_reduction(epiworld_double * prob) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return *prob; + }; + + death_reduction_fun = tmpfun; + +} + +// EPIWORLD_SET_LAMBDA(death_reduction) + +// #undef EPIWORLD_SET_LAMBDA +template +inline void Tool::set_susceptibility_reduction( + epiworld_double prob +) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return prob; + }; + + susceptibility_reduction_fun = tmpfun; + +} + +template +inline void Tool::set_transmission_reduction( + epiworld_double prob +) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return prob; + }; + + transmission_reduction_fun = tmpfun; + +} + +template +inline void Tool::set_recovery_enhancer( + epiworld_double prob +) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return prob; + }; + + recovery_enhancer_fun = tmpfun; + +} + +template +inline void Tool::set_death_reduction( + epiworld_double prob +) +{ + + ToolFun tmpfun = + [prob](Tool &, Agent *, VirusPtr, Model *) + { + return prob; + }; + + death_reduction_fun = tmpfun; + +} + +template +inline void Tool::set_name(std::string name) +{ + if (name != "") + tool_name = std::make_shared(name); +} + +template +inline std::string Tool::get_name() const { + + if (tool_name) + return *tool_name; + + return "unknown tool"; + +} + +template +inline Agent * Tool::get_agent() +{ + return this->agent; +} + +template +inline void Tool::set_agent(Agent * p, size_t idx) +{ + agent = p; + pos_in_agent = static_cast(idx); +} + +template +inline int Tool::get_id() const { + return id; +} + + +template +inline void Tool::set_id(int id) +{ + this->id = id; +} + +template +inline void Tool::set_date(int d) +{ + this->date = d; +} + +template +inline int Tool::get_date() const +{ + return date; +} + +template +inline void Tool::set_state( + epiworld_fast_int init, + epiworld_fast_int end +) +{ + state_init = init; + state_post = end; +} + +template +inline void Tool::set_queue( + epiworld_fast_int init, + epiworld_fast_int end +) +{ + queue_init = init; + queue_post = end; +} + +template +inline void Tool::get_state( + epiworld_fast_int * init, + epiworld_fast_int * post +) +{ + if (init != nullptr) + *init = state_init; + + if (post != nullptr) + *post = state_post; + +} + +template +inline void Tool::get_queue( + epiworld_fast_int * init, + epiworld_fast_int * post +) +{ + if (init != nullptr) + *init = queue_init; + + if (post != nullptr) + *post = queue_post; + +} + +template<> +inline bool Tool>::operator==( + const Tool> & other + ) const +{ + + if (sequence->size() != other.sequence->size()) + return false; + + for (size_t i = 0u; i < sequence->size(); ++i) + { + if (sequence->operator[](i) != other.sequence->operator[](i)) + return false; + } + + if (tool_name != other.tool_name) + return false; + + if (state_init != other.state_init) + return false; + + if (state_post != other.state_post) + return false; + + if (queue_init != other.queue_init) + return false; + + if (queue_post != other.queue_post) + return false; + + + return true; + +} + +template +inline bool Tool::operator==(const Tool & other) const +{ + if (*sequence != *other.sequence) + return false; + + if (tool_name != other.tool_name) + return false; + + if (state_init != other.state_init) + return false; + + if (state_post != other.state_post) + return false; + + if (queue_init != other.queue_init) + return false; + + if (queue_post != other.queue_post) + return false; + + return true; + +} + + +template +inline void Tool::print() const +{ + + printf_epiworld("Tool : %s\n", tool_name->c_str()); + printf_epiworld("Id : %s\n", (id < 0)? std::string("(empty)").c_str() : std::to_string(id).c_str()); + printf_epiworld("state_init : %i\n", state_init); + printf_epiworld("state_post : %i\n", state_post); + printf_epiworld("queue_init : %i\n", queue_init); + printf_epiworld("queue_post : %i\n", queue_post); + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/tools-bones.hpp b/inst/include/epiworld/tools-bones.hpp new file mode 100644 index 00000000..c3ee9f2f --- /dev/null +++ b/inst/include/epiworld/tools-bones.hpp @@ -0,0 +1,210 @@ +#ifndef EPIWORLD_TOOLS_BONES_HPP +#define EPIWORLD_TOOLS_BONES_HPP + +template +class Tool; + +template +class Agent; + +// #define ToolPtr std::shared_ptr< Tool > + +/** + * @brief Set of tools (useful for building iterators) + * + * @tparam TSeq + */ +template +class Tools { + friend class Tool; + friend class Agent; +private: + std::vector< ToolPtr > * dat; + const epiworld_fast_uint * n_tools; + +public: + + Tools() = delete; + Tools(Agent & p) : dat(&p.tools), n_tools(&p.n_tools) {}; + + typename std::vector< ToolPtr >::iterator begin(); + typename std::vector< ToolPtr >::iterator end(); + + ToolPtr & operator()(size_t i); + ToolPtr & operator[](size_t i); + + size_t size() const noexcept; + + void print() const noexcept; + +}; + +template +inline typename std::vector< ToolPtr >::iterator Tools::begin() +{ + + if (*n_tools == 0u) + return dat->end(); + + return dat->begin(); +} + +template +inline typename std::vector< ToolPtr >::iterator Tools::end() +{ + + return begin() + *n_tools; +} + +template +inline ToolPtr & Tools::operator()(size_t i) +{ + + if (i >= *n_tools) + throw std::range_error("Tool index out of range."); + + return dat->operator[](i); + +} + +template +inline ToolPtr & Tools::operator[](size_t i) +{ + + return dat->operator[](i); + +} + +template +inline size_t Tools::size() const noexcept +{ + return *n_tools; +} + +template +inline void Tools::print() const noexcept +{ + if (*n_tools == 0u) + { + printf_epiworld("List of tools (none)\n"); + return; + } + + printf_epiworld("List of tools (%i): ", *n_tools); + + // Printing the name of each virus separated by a comma + for (size_t i = 0u; i < *n_tools; ++i) + { + if (i == *n_tools - 1u) + { + printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); + } else + { + printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); + } + } + + printf_epiworld("\n"); + +} + +/** + * @brief Set of Tools (const) (useful for iterators) + * + * @tparam TSeq + */ +template +class Tools_const { + friend class Tool; + friend class Agent; +private: + const std::vector< ToolPtr > * dat; + const epiworld_fast_uint * n_tools; + +public: + + Tools_const() = delete; + Tools_const(const Agent & p) : dat(&p.tools), n_tools(&p.n_tools) {}; + + typename std::vector< ToolPtr >::const_iterator begin() const; + typename std::vector< ToolPtr >::const_iterator end() const; + + const ToolPtr & operator()(size_t i); + const ToolPtr & operator[](size_t i); + + size_t size() const noexcept; + + void print() const noexcept; + +}; + +template +inline typename std::vector< ToolPtr >::const_iterator Tools_const::begin() const { + + if (*n_tools == 0u) + return dat->end(); + + return dat->begin(); +} + +template +inline typename std::vector< ToolPtr >::const_iterator Tools_const::end() const { + + return begin() + *n_tools; +} + +template +inline const ToolPtr & Tools_const::operator()(size_t i) +{ + + if (i >= *n_tools) + throw std::range_error("Tool index out of range."); + + return dat->operator[](i); + +} + +template +inline const ToolPtr & Tools_const::operator[](size_t i) +{ + + return dat->operator[](i); + +} + +template +inline size_t Tools_const::size() const noexcept +{ + return *n_tools; +} + +template +inline void Tools_const::print() const noexcept +{ + if (*n_tools == 0u) + { + printf_epiworld("List of tools (none)\n"); + return; + } + + printf_epiworld("List of tools (%i): ", *n_tools); + + // Printing the name of each virus separated by a comma + for (size_t i = 0u; i < *n_tools; ++i) + { + if (i == *n_tools - 1u) + { + printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); + } else + { + printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); + } + } + + printf_epiworld("\n"); + +} + + + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/userdata-bones.hpp b/inst/include/epiworld/userdata-bones.hpp new file mode 100644 index 00000000..bdda6381 --- /dev/null +++ b/inst/include/epiworld/userdata-bones.hpp @@ -0,0 +1,100 @@ +#ifndef EPIWORLD_USERDATA_BONES_HPP +#define EPIWORLD_USERDATA_BONES_HPP + +template +class Model; + +template +class DataBase; + +/** + * @brief Personalized data by the user + * + * @tparam TSeq + */ +template +class UserData +{ + friend class Model; + friend class DataBase; + +private: + Model * model; + + std::vector< std::string > data_names; + std::vector< int > data_dates; + std::vector< epiworld_double > data_data; + + epiworld_fast_uint k = 0u; + epiworld_fast_uint n = 0u; + + int last_day = -1; + +public: + + UserData() = delete; + UserData(Model & m) : model(&m) {}; + UserData(Model * m) : model(m) {}; + + /** + * @brief Construct a new User Data object + * + * @param names A vector of names. The length of the vector sets + * the number of columns to record. + */ + UserData(std::vector< std::string > names); + + /** + * @name Append data + * + * @param x A vector of length `ncol()` (if vector), otherwise a `epiworld_double`. + * @param j Index of the data point, from 0 to `ncol() - 1`. + */ + ///@{ + void add(std::vector x); + void add( + epiworld_fast_uint j, + epiworld_double x + ); + ///@} + + /** + * @name Access data + * + * @param i Row (0 through ndays - 1.) + * @param j Column (0 through `ncols()`). + * @return epiworld_double& + */ + ///@{ + epiworld_double & operator()( + epiworld_fast_uint i, + epiworld_fast_uint j + ); + + epiworld_double & operator()( + epiworld_fast_uint i, + std::string name + ); + ///@} + + std::vector< std::string > & get_names(); + + std::vector< int > & get_dates(); + + std::vector< epiworld_double > & get_data(); + + void get_all( + std::vector< std::string > * names = nullptr, + std::vector< int > * date = nullptr, + std::vector< epiworld_double > * data = nullptr + ); + + epiworld_fast_uint nrow() const; + epiworld_fast_uint ncol() const; + + void write(std::string fn); + void print() const; + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/userdata-meat.hpp b/inst/include/epiworld/userdata-meat.hpp new file mode 100644 index 00000000..66a5081c --- /dev/null +++ b/inst/include/epiworld/userdata-meat.hpp @@ -0,0 +1,217 @@ +#ifndef EPIWORLD_USERDATA_MEAT_HPP +#define EPIWORLD_USERDATA_MEAT_HPP + +template +class UserData; + +template +inline UserData::UserData(std::vector< std::string > names) +{ + + k = names.size(); + data_names = names; + +} + +template +inline void UserData::add(std::vector x) +{ + + if (x.size() != k) + throw std::out_of_range( + "The size of -x-, " + std::to_string(x.size()) + ", does not match " + + "the number of elements registered (" + std::to_string(k)); + + for (auto & i : x) + data_data.push_back(i); + + data_dates.push_back(model->today()); + + n++; + last_day = model->today(); + +} + +template +inline void UserData::add(epiworld_fast_uint j, epiworld_double x) +{ + + // Starting with a new day? + if (static_cast(model->today()) != last_day) + { + + std::vector< epiworld_double > tmp(k, 0.0); + + tmp[j] = x; + + add(tmp); + + } + else + { + + this->operator()(n - 1, j) = x; + + } + +} + +template +inline std::vector< std::string > & UserData::get_names() +{ + return data_names; +} + +template +inline std::vector< int > & UserData::get_dates() +{ + return data_dates; +} + +template +inline std::vector< epiworld_double > & UserData::get_data() +{ + return data_data; +} + +template +inline void UserData::get_all( + std::vector< std::string > * names, + std::vector< int > * date, + std::vector< epiworld_double > * data +) +{ + + if (names != nullptr) + names = &this->data_names; + + if (date != nullptr) + date = &this->data_dates; + + if (data != nullptr) + data = &this->data_data; + +} + +template +inline epiworld_double & UserData::operator()( + epiworld_fast_uint i, + epiworld_fast_uint j +) +{ + + if (j >= k) + throw std::out_of_range("j cannot be greater than k - 1."); + + if (i >= n) + throw std::out_of_range("j cannot be greater than n - 1."); + + return data_data[k * i + j]; + +} + +template +inline epiworld_double & UserData::operator()( + epiworld_fast_uint i, + std::string name +) +{ + int loc = -1; + for (epiworld_fast_uint l = 0u; l < k; ++l) + { + + if (name == data_names[l]) + { + + loc = l; + break; + + } + + } + + if (loc < 0) + throw std::range_error( + "The variable \"" + name + "\" is not present " + + "in the user UserData database." + ); + + return operator()(i, static_cast(loc)); + +} + +template +inline epiworld_fast_uint UserData::nrow() const +{ + return n; +} + +template +inline epiworld_fast_uint UserData::ncol() const +{ + return k; +} + +template +inline void UserData::write(std::string fn) +{ + std::ofstream file_ud(fn, std::ios_base::out); + + // File header + file_ud << "\"date\""; + for (auto & cn : data_names) + file_ud << " \"" + cn + "\""; + file_ud << "\n"; + + epiworld_fast_uint ndata = 0u; + for (epiworld_fast_uint i = 0u; i < n; ++i) + { + file_ud << data_dates[i]; + + for (epiworld_fast_uint j = 0u; j < k; ++j) + file_ud << " " << data_data[ndata++]; + + file_ud << "\n"; + } + + return; +} + +template +inline void UserData::print() const +{ + // File header + printf_epiworld("Total records: %llu\n", n); + printf_epiworld("date"); + + for (auto & cn : data_names) + { + + printf_epiworld(" %s", cn.c_str()); + + } + + printf_epiworld("\n"); + + epiworld_fast_uint ndata = 0u; + + for (epiworld_fast_uint i = 0u; i < n; ++i) + { + + printf_epiworld("%i", data_dates[i]); + + for (epiworld_fast_uint j = 0u; j < k; ++j) + { + + printf_epiworld(" %.2f", data_data[ndata++]); + + } + + printf_epiworld("\n"); + + } + + return; +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/virus-bones.hpp b/inst/include/epiworld/virus-bones.hpp new file mode 100644 index 00000000..1a34f8e0 --- /dev/null +++ b/inst/include/epiworld/virus-bones.hpp @@ -0,0 +1,164 @@ +#ifndef EPIWORLD_VIRUS_HPP +#define EPIWORLD_VIRUS_HPP + +template +class Agent; + +template +class Virus; + +template +class Model; + +/** + * @brief Virus + * + * @tparam TSeq + * @details + * Raw transmisibility of a virus should be a function of its genetic + * sequence. Nonetheless, transmisibility can be reduced as a result of + * having one or more tools to fight the virus. Because of this, transmisibility + * should be a function of the agent. + */ +template +class Virus { + friend class Agent; + friend class Model; + friend class DataBase; + friend void default_add_virus(Action & a, Model * m); + friend void default_rm_virus(Action & a, Model * m); +private: + + Agent * agent = nullptr; + int pos_in_agent = -99; ///< Location in the agent + int agent_exposure_number = -99; + + std::shared_ptr baseline_sequence = nullptr; + std::shared_ptr virus_name = nullptr; + int date = -99; + int id = -99; + bool active = true; + MutFun mutation_fun = nullptr; + PostRecoveryFun post_recovery_fun = nullptr; + VirusFun probability_of_infecting_fun = nullptr; + VirusFun probability_of_recovery_fun = nullptr; + VirusFun probability_of_death_fun = nullptr; + VirusFun incubation_fun = nullptr; + + // Setup parameters + std::vector< epiworld_double * > params = {}; + std::vector< epiworld_double > data = {}; + + epiworld_fast_int state_init = -99; ///< Change of state when added to agent. + epiworld_fast_int state_post = -99; ///< Change of state when removed from agent. + epiworld_fast_int state_removed = -99; ///< Change of state when agent is removed + + epiworld_fast_int queue_init = Queue::Everyone; ///< Change of state when added to agent. + epiworld_fast_int queue_post = -Queue::Everyone; ///< Change of state when removed from agent. + epiworld_fast_int queue_removed = -99; ///< Change of state when agent is removed + +public: + Virus(std::string name = "unknown virus"); + + void mutate(Model * model); + void set_mutation(MutFun fun); + + std::shared_ptr get_sequence(); + void set_sequence(TSeq sequence); + + Agent * get_agent(); + void set_agent(Agent * p, epiworld_fast_uint idx); + + void set_date(int d); + int get_date() const; + + void set_id(int idx); + int get_id() const; + + /** + * @name Get and set the tool functions + * + * @param v The virus over which to operate + * @param fun the function to be used + * + * @return epiworld_double + */ + ///@{ + epiworld_double get_prob_infecting(Model * model); + epiworld_double get_prob_recovery(Model * model); + epiworld_double get_prob_death(Model * model); + epiworld_double get_incubation(Model * model); + + void post_recovery(Model * model); + void set_post_recovery(PostRecoveryFun fun); + void set_post_immunity(epiworld_double prob); + void set_post_immunity(epiworld_double * prob); + + void set_prob_infecting_fun(VirusFun fun); + void set_prob_recovery_fun(VirusFun fun); + void set_prob_death_fun(VirusFun fun); + void set_incubation_fun(VirusFun fun); + + void set_prob_infecting(const epiworld_double * prob); + void set_prob_recovery(const epiworld_double * prob); + void set_prob_death(const epiworld_double * prob); + void set_incubation(const epiworld_double * prob); + + void set_prob_infecting(epiworld_double prob); + void set_prob_recovery(epiworld_double prob); + void set_prob_death(epiworld_double prob); + void set_incubation(epiworld_double prob); + ///@} + + + void set_name(std::string name); + std::string get_name() const; + + std::vector< epiworld_double > & get_data(); + + /** + * @name Get and set the state and queue + * + * After applied, viruses can change the state and affect + * the queue of agents. These function sets the default values, + * which are retrieved when adding or removing a virus does not + * specify a change in state or in queue. + * + * @param init After the virus/tool is added to the agent. + * @param end After the virus/tool is removed. + * @param removed After the agent (Agent) is removed. + */ + ///@{ + void set_state( + epiworld_fast_int init, + epiworld_fast_int end, + epiworld_fast_int removed = -99 + ); + + void set_queue( + epiworld_fast_int init, + epiworld_fast_int end, + epiworld_fast_int removed = -99 + ); + + void get_state( + epiworld_fast_int * init, + epiworld_fast_int * end, + epiworld_fast_int * removed = nullptr + ); + + void get_queue( + epiworld_fast_int * init, + epiworld_fast_int * end, + epiworld_fast_int * removed = nullptr + ); + ///@} + + bool operator==(const Virus & other) const; + bool operator!=(const Virus & other) const {return !operator==(other);}; + + void print() const; + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/virus-meat.hpp b/inst/include/epiworld/virus-meat.hpp new file mode 100644 index 00000000..d5a10ae4 --- /dev/null +++ b/inst/include/epiworld/virus-meat.hpp @@ -0,0 +1,699 @@ +#ifndef EPIWORLD_VIRUS_MEAT_HPP +#define EPIWORLD_VIRUS_MEAT_HPP + +/** + * @brief Factory function of VirusFun base on logit + * + * @tparam TSeq + * @param vars Vector indicating the position of the variables to use. + * @param coefs Vector of coefficients. + * @return VirusFun + */ +template +inline VirusFun virus_fun_logit( + std::vector< int > vars, + std::vector< double > coefs, + Model * model, + bool logit = true +) { + + // Checking that there are features + if (coefs.size() == 0u) + throw std::logic_error( + "The -coefs- argument should feature at least one element." + ); + + if (coefs.size() != vars.size()) + throw std::length_error( + std::string("The length of -coef- (") + + std::to_string(coefs.size()) + + std::string(") and -vars- (") + + std::to_string(vars.size()) + + std::string(") should match. ") + ); + + // Checking that there are variables in the model + if (model != nullptr) + { + + size_t K = model->get_agents_data_ncols(); + for (const auto & var: vars) + { + if ((var >= static_cast(K)) | (var < 0)) + throw std::range_error( + std::string("The variable ") + + std::to_string(var) + + std::string(" is out of range.") + + std::string(" The agents only feature ") + + std::to_string(K) + + std::string("variables (features).") + ); + } + + } + + std::vector< epiworld_double > coefs_f; + for (auto c: coefs) + coefs_f.push_back(static_cast(c)); + + VirusFun fun_infect = [coefs_f,vars,logit]( + Agent * agent, + Virus & virus, + Model * model + ) -> epiworld_double { + + size_t K = coefs_f.size(); + epiworld_double res = 0.0; + + #pragma omp simd reduction(+:res) + for (size_t i = 0u; i < K; ++i) + res += agent->operator[](vars.at(i)) * coefs_f.at(i); + + return 1.0/(1.0 + std::exp(-res)); + + }; + + return fun_infect; + +} + +template +inline Virus::Virus(std::string name) { + set_name(name); +} + +template +inline void Virus::mutate( + Model * model +) { + + if (mutation_fun) + if (mutation_fun(agent, *this, model)) + model->get_db().record_virus(*this); + + return; + +} + +template +inline void Virus::set_mutation( + MutFun fun +) { + mutation_fun = MutFun(fun); +} + +template +inline std::shared_ptr Virus::get_sequence() +{ + + return baseline_sequence; + +} + +template +inline void Virus::set_sequence(TSeq sequence) +{ + + baseline_sequence = std::make_shared(sequence); + return; + +} + +template +inline Agent * Virus::get_agent() +{ + + return agent; + +} + +template +inline void Virus::set_agent(Agent * p, epiworld_fast_uint idx) +{ + + #ifdef EPI_DEBUG + if (idx >= p->viruses.size()) + { + printf_epiworld( + "[epi-debug]Virus::set_agent id to set up is outside of range." + ); + } + #endif + + agent = p; + pos_in_agent = static_cast(idx); + +} + +template +inline void Virus::set_id(int idx) +{ + + id = idx; + return; + +} + +template +inline int Virus::get_id() const +{ + + return id; + +} + +template +inline void Virus::set_date(int d) +{ + + date = d; + return; + +} + +template +inline int Virus::get_date() const +{ + + return date; + +} + +template +inline epiworld_double Virus::get_prob_infecting( + Model * model +) +{ + + if (probability_of_infecting_fun) + return probability_of_infecting_fun(agent, *this, model); + + return EPI_DEFAULT_VIRUS_PROB_INFECTION; + +} + + + +template +inline epiworld_double Virus::get_prob_recovery( + Model * model +) +{ + + if (probability_of_recovery_fun) + return probability_of_recovery_fun(agent, *this, model); + + return EPI_DEFAULT_VIRUS_PROB_RECOVERY; + +} + + + +template +inline epiworld_double Virus::get_prob_death( + Model * model +) +{ + + if (probability_of_death_fun) + return probability_of_death_fun(agent, *this, model); + + return EPI_DEFAULT_VIRUS_PROB_DEATH; + +} + +template +inline epiworld_double Virus::get_incubation( + Model * model +) +{ + + if (incubation_fun) + return incubation_fun(agent, *this, model); + + return EPI_DEFAULT_INCUBATION_DAYS; + +} + +template +inline void Virus::set_prob_infecting_fun(VirusFun fun) +{ + probability_of_infecting_fun = fun; +} + +template +inline void Virus::set_prob_recovery_fun(VirusFun fun) +{ + probability_of_recovery_fun = fun; +} + +template +inline void Virus::set_prob_death_fun(VirusFun fun) +{ + probability_of_death_fun = fun; +} + +template +inline void Virus::set_incubation_fun(VirusFun fun) +{ + incubation_fun = fun; +} + +template +inline void Virus::set_prob_infecting(const epiworld_double * prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return *prob; + }; + + probability_of_infecting_fun = tmpfun; +} + +template +inline void Virus::set_prob_recovery(const epiworld_double * prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return *prob; + }; + + probability_of_recovery_fun = tmpfun; +} + +template +inline void Virus::set_prob_death(const epiworld_double * prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return *prob; + }; + + probability_of_death_fun = tmpfun; +} + +template +inline void Virus::set_incubation(const epiworld_double * prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return *prob; + }; + + incubation_fun = tmpfun; +} + +template +inline void Virus::set_prob_infecting(epiworld_double prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return prob; + }; + + probability_of_infecting_fun = tmpfun; +} + +template +inline void Virus::set_prob_recovery(epiworld_double prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return prob; + }; + + probability_of_recovery_fun = tmpfun; +} + +template +inline void Virus::set_prob_death(epiworld_double prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return prob; + }; + + probability_of_death_fun = tmpfun; +} + +template +inline void Virus::set_incubation(epiworld_double prob) +{ + VirusFun tmpfun = + [prob](Agent *, Virus &, Model *) + { + return prob; + }; + + incubation_fun = tmpfun; +} + +template +inline void Virus::set_post_recovery(PostRecoveryFun fun) +{ + if (post_recovery_fun) + { + printf_epiworld( + "Warning: a PostRecoveryFun is alreay in place (overwriting)." + ); + } + + post_recovery_fun = fun; +} + +template +inline void Virus::post_recovery( + Model * model +) +{ + + if (post_recovery_fun) + post_recovery_fun(agent, *this, model); + + return; + +} + +template +inline void Virus::set_post_immunity( + epiworld_double prob +) +{ + + if (post_recovery_fun) + { + + std::string msg = + std::string( + "You cannot set post immunity when a post_recovery " + ) + + std::string( + "function is already in place. Redesign the post_recovery function." + ); + + throw std::logic_error(msg); + + } + + // To make sure that we keep registering the virus + ToolPtr __no_reinfect = std::make_shared>( + "Immunity (" + *virus_name + ")" + ); + + __no_reinfect->set_susceptibility_reduction(prob); + __no_reinfect->set_death_reduction(0.0); + __no_reinfect->set_transmission_reduction(0.0); + __no_reinfect->set_recovery_enhancer(0.0); + + PostRecoveryFun tmpfun = + [__no_reinfect]( + Agent * p, Virus &, Model * m + ) + { + + // Have we registered the tool? + if (__no_reinfect->get_id() == -99) + m->get_db().record_tool(*__no_reinfect); + + p->add_tool(__no_reinfect, m); + + return; + + }; + + post_recovery_fun = tmpfun; + +} + +template +inline void Virus::set_post_immunity( + epiworld_double * prob +) +{ + + if (post_recovery_fun) + { + + std::string msg = + std::string( + "You cannot set post immunity when a post_recovery " + ) + + std::string( + "function is already in place. Redesign the post_recovery function." + ); + + throw std::logic_error(msg); + + } + + // To make sure that we keep registering the virus + ToolPtr __no_reinfect = std::make_shared>( + "Immunity (" + *virus_name + ")" + ); + + __no_reinfect->set_susceptibility_reduction(prob); + __no_reinfect->set_death_reduction(0.0); + __no_reinfect->set_transmission_reduction(0.0); + __no_reinfect->set_recovery_enhancer(0.0); + + PostRecoveryFun tmpfun = + [__no_reinfect](Agent * p, Virus &, Model * m) + { + + // Have we registered the tool? + if (__no_reinfect->get_id() == -99) + m->get_db().record_tool(*__no_reinfect); + + p->add_tool(__no_reinfect, m); + + return; + + }; + + post_recovery_fun = tmpfun; + +} + +template +inline void Virus::set_name(std::string name) +{ + + if (name == "") + virus_name = nullptr; + else + virus_name = std::make_shared(name); + +} + +template +inline std::string Virus::get_name() const +{ + + if (virus_name) + return *virus_name; + + return "unknown virus"; + +} + +template +inline std::vector< epiworld_double > & Virus::get_data() { + return data; +} + +template +inline void Virus::set_state( + epiworld_fast_int init, + epiworld_fast_int end, + epiworld_fast_int removed +) +{ + state_init = init; + state_post = end; + state_removed = removed; +} + +template +inline void Virus::set_queue( + epiworld_fast_int init, + epiworld_fast_int end, + epiworld_fast_int removed +) +{ + + queue_init = init; + queue_post = end; + queue_removed = removed; + +} + +template +inline void Virus::get_state( + epiworld_fast_int * init, + epiworld_fast_int * end, + epiworld_fast_int * removed +) +{ + + if (init != nullptr) + *init = state_init; + + if (end != nullptr) + *end = state_post; + + if (removed != nullptr) + *removed = state_removed; + +} + +template +inline void Virus::get_queue( + epiworld_fast_int * init, + epiworld_fast_int * end, + epiworld_fast_int * removed +) +{ + + if (init != nullptr) + *init = queue_init; + + if (end != nullptr) + *end = queue_post; + + if (removed != nullptr) + *removed = queue_removed; + +} + +template<> +inline bool Virus>::operator==( + const Virus> & other + ) const +{ + + + EPI_DEBUG_FAIL_AT_TRUE( + baseline_sequence->size() != other.baseline_sequence->size(), + "Virus:: baseline_sequence don't match" + ) + + for (size_t i = 0u; i < baseline_sequence->size(); ++i) + { + + EPI_DEBUG_FAIL_AT_TRUE( + baseline_sequence->operator[](i) != other.baseline_sequence->operator[](i), + "Virus:: baseline_sequence[i] don't match" + ) + + } + + EPI_DEBUG_FAIL_AT_TRUE( + virus_name != other.virus_name, + "Virus:: virus_name don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_init != other.state_init, + "Virus:: state_init don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_post != other.state_post, + "Virus:: state_post don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_removed != other.state_removed, + "Virus:: state_removed don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_init != other.queue_init, + "Virus:: queue_init don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_post != other.queue_post, + "Virus:: queue_post don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_removed != other.queue_removed, + "Virus:: queue_removed don't match" + ) + + return true; + +} + +template +inline bool Virus::operator==(const Virus & other) const +{ + + EPI_DEBUG_FAIL_AT_TRUE( + *baseline_sequence != *other.baseline_sequence, + "Virus:: baseline_sequence don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + virus_name != other.virus_name, + "Virus:: virus_name don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_init != other.state_init, + "Virus:: state_init don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_post != other.state_post, + "Virus:: state_post don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + state_removed != other.state_removed, + "Virus:: state_removed don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_init != other.queue_init, + "Virus:: queue_init don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_post != other.queue_post, + "Virus:: queue_post don't match" + ) + + EPI_DEBUG_FAIL_AT_TRUE( + queue_removed != other.queue_removed, + "Virus:: queue_removed don't match" + ) + + return true; + +} + +template +inline void Virus::print() const +{ + + printf_epiworld("Virus : %s\n", virus_name->c_str()); + printf_epiworld("Id : %s\n", (id < 0)? std::string("(empty)").c_str() : std::to_string(id).c_str()); + printf_epiworld("state_init : %i\n", state_init); + printf_epiworld("state_post : %i\n", state_post); + printf_epiworld("state_removed : %i\n", state_removed); + printf_epiworld("queue_init : %i\n", queue_init); + printf_epiworld("queue_post : %i\n", queue_post); + printf_epiworld("queue_removed : %i\n", queue_removed); + +} + +#endif diff --git a/inst/include/epiworld/viruses-bones.hpp b/inst/include/epiworld/viruses-bones.hpp new file mode 100644 index 00000000..247df3be --- /dev/null +++ b/inst/include/epiworld/viruses-bones.hpp @@ -0,0 +1,218 @@ +#ifndef EPIWORLD_VIRUSES_BONES_HPP +#define EPIWORLD_VIRUSES_BONES_HPP + +template +class Virus; + +template +class Agent; + +/** + * @brief Set of viruses (useful for building iterators) + * + * @tparam TSeq + */ +template +class Viruses { + friend class Virus; + friend class Agent; +private: + std::vector< VirusPtr > * dat; + const epiworld_fast_uint * n_viruses; + +public: + + Viruses() = delete; + Viruses(Agent & p) : dat(&p.viruses), n_viruses(&p.n_viruses) {}; + + typename std::vector< VirusPtr >::iterator begin(); + typename std::vector< VirusPtr >::iterator end(); + + VirusPtr & operator()(size_t i); + VirusPtr & operator[](size_t i); + + size_t size() const noexcept; + + void print() const noexcept; + +}; + +template +inline typename std::vector< VirusPtr >::iterator Viruses::begin() +{ + + if (*n_viruses == 0u) + return dat->end(); + + return dat->begin(); +} + +template +inline typename std::vector< VirusPtr >::iterator Viruses::end() +{ + + #ifdef EPI_DEBUG + if (dat->size() < *n_viruses) + throw EPI_DEBUG_ERROR(std::logic_error, "Viruses:: The end of the virus is out of range"); + #endif + + return begin() + *n_viruses; +} + +template +inline VirusPtr & Viruses::operator()(size_t i) +{ + + if (i >= *n_viruses) + throw std::range_error("Virus index out of range."); + + return dat->operator[](i); + +} + +template +inline VirusPtr & Viruses::operator[](size_t i) +{ + + return dat->operator[](i); + +} + +template +inline size_t Viruses::size() const noexcept +{ + return *n_viruses; +} + +template +inline void Viruses::print() const noexcept +{ + + if (*n_viruses == 0u) + { + printf_epiworld("List of viruses (none)\n"); + return; + } + + printf_epiworld("List of viruses (%i): ", *n_viruses); + + // Printing the name of each virus separated by a comma + for (size_t i = 0u; i < *n_viruses; ++i) + { + if (i == *n_viruses - 1u) + { + printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); + } else + { + printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); + } + } + + printf_epiworld("\n"); + +} + +/** + * @brief Set of Viruses (const) (useful for iterators) + * + * @tparam TSeq + */ +template +class Viruses_const { + friend class Virus; + friend class Agent; +private: + const std::vector< VirusPtr > * dat; + const epiworld_fast_uint * n_viruses; + +public: + + Viruses_const() = delete; + Viruses_const(const Agent & p) : dat(&p.viruses), n_viruses(&p.n_viruses) {}; + + typename std::vector< VirusPtr >::const_iterator begin() const; + typename std::vector< VirusPtr >::const_iterator end() const; + + const VirusPtr & operator()(size_t i); + const VirusPtr & operator[](size_t i); + + size_t size() const noexcept; + + void print() const noexcept; + +}; + +template +inline typename std::vector< VirusPtr >::const_iterator Viruses_const::begin() const { + + if (*n_viruses == 0u) + return dat->end(); + + return dat->begin(); +} + +template +inline typename std::vector< VirusPtr >::const_iterator Viruses_const::end() const { + + #ifdef EPI_DEBUG + if (dat->size() < *n_viruses) + throw EPI_DEBUG_ERROR(std::logic_error, "Viruses_const:: The end of the virus is out of range"); + #endif + return begin() + *n_viruses; +} + +template +inline const VirusPtr & Viruses_const::operator()(size_t i) +{ + + if (i >= *n_viruses) + throw std::range_error("Virus index out of range."); + + return dat->operator[](i); + +} + +template +inline const VirusPtr & Viruses_const::operator[](size_t i) +{ + + return dat->operator[](i); + +} + +template +inline size_t Viruses_const::size() const noexcept +{ + return *n_viruses; +} + +template +inline void Viruses_const::print() const noexcept +{ + + if (*n_viruses == 0u) + { + printf_epiworld("List of viruses (none)\n"); + return; + } + + printf_epiworld("List of viruses (%i): ", *n_viruses); + + // Printing the name of each virus separated by a comma + for (size_t i = 0u; i < *n_viruses; ++i) + { + if (i == *n_viruses - 1u) + { + printf_epiworld("%s", dat->operator[](i)->get_name().c_str()); + } else + { + printf_epiworld("%s, ", dat->operator[](i)->get_name().c_str()); + } + } + + printf_epiworld("\n"); + +} + + +#endif \ No newline at end of file diff --git a/man/epiworldR-package.Rd b/man/epiworldR-package.Rd index 9932a1eb..1d594f7a 100644 --- a/man/epiworldR-package.Rd +++ b/man/epiworldR-package.Rd @@ -19,7 +19,7 @@ Useful links: } \author{ -\strong{Maintainer}: Derek Meyer \email{derekmeyer37@gmail.com} +\strong{Maintainer}: Derek Meyer \email{derekmeyer37@gmail.com} (\href{https://orcid.org/0009-0005-1350-6988}{ORCID}) Authors: \itemize{ @@ -28,6 +28,9 @@ Authors: Other contributors: \itemize{ + \item Susan Holmes (\href{https://orcid.org/0000-0002-2208-8168}{ORCID}) (JOSS reviewer) [reviewer] + \item Abinash Satapathy (\href{https://orcid.org/0000-0002-2955-2744}{ORCID}) (JOSS reviewer) [reviewer] + \item Carinogurjao [reviewer] \item Centers for Disease Control and Prevention (Award number 1U01CK000585; 75D30121F00003) [funder] } diff --git a/src/epiworld-common.h b/src/epiworld-common.h index e0dfc203..6c5892ac 100644 --- a/src/epiworld-common.h +++ b/src/epiworld-common.h @@ -1,6 +1,6 @@ #ifndef EPIWORLD_COMMON_HPP #define EPIWORLD_COMMON_HPP -#include "epiworld.hpp" +#include "epiworld/epiworld.hpp" #endif \ No newline at end of file