Extending with Models
AI-generated content
This chapter was automatically generated by an AI agent and has not been reviewed by a human. Read with caution.
Although composition can be used to extend and create new models, a common practice in epiworld is to create new models via inheritance. As the reader will see in later sections, all the included models in epiworld have been built by inheriting from the main Model<> class.
This section highlights some important considerations when using the inheritance approach.
Execution workflow: Events¶
The epiworld library is a discrete-time ABM that updates agents' states daily. As such, the framework implements what we call an "events" system that is triggered multiple times at the end of every step. Events essentially accumulate all changes that agents experience during the day, including adding tools, setting or removing viruses, and adding new connections or entities. The events system is implemented using a LIFO (last-in, first-out) schedule, so the last update is the one that takes effect.
Under the LIFO system, if an agent receives multiple updates in the same step - moving from state A to B, then to C, and finally to D - the last change takes effect and is reflected in the model's accounting. That is, instead of registering transitions A->B, B->C, and C->D, we record A->D.
Execution workflow: main calls¶
The Model<>::run() method shows how events are accumulated in the model. The following modified code snippet illustrates this clearly:
// Resets the entire system, distributing tools, viruses,
// and entities accordingly
reset();
// The next function records the current state
// of the model in the database and advances
// the day (step)
next();
for (int niter = 0; niter < get_ndays(); ++niter)
{
// We can execute these components in whatever
// order the user needs.
this->update_state(); /// <<< run_events() >>>
// We start with the Global events
this->run_globalevents(); /// <<< run_events() >>>
// In this case we are applying degree sequence
// rewiring to change the network just a bit.
this->rewire();
// Writes the database and advances the day (step)
this->next();
// Mutation must happen at the very end of all
this->mutate_virus();
}
The key aspect to notice is that both Model<TSeq>::update_state() and Model<TSeq>::run_globalevents() call Model<TSeq>::run_events(). This means that, after each function is called, the events that change agents' tools, viruses, and entities are dispatched.
In practice, this provides users with a way to modify the state of the model instance on the fly. For example, starting with version 0.15.0.0 of epiworld, the ModelMeaslesSchool model includes the class InterventionMeaslesPEP (which derives from GlobalEvent) that implements an intervention to move quarantined agents out of quarantine if the post-exposure prophylaxis tool is distributed and effective.
Example: The ModelSIS class¶
The SIS (Susceptible-Infected-Susceptible) model is one of the simplest models included in epiworld. With only two states, the class implementation of the SIS model takes less than 100 lines of code. Here is the main component:
template<typename TSeq = EPI_DEFAULT_TSEQ>
class ModelSIS : public Model<TSeq>
{
public:
static const int SUSCEPTIBLE = 0;
static const int INFECTED = 1;
ModelSIS() {};
ModelSIS(
const std::string & vname,
epiworld_double prevalence,
epiworld_double transmission_rate,
epiworld_double recovery_rate
);
};
template<typename TSeq>
inline ModelSIS<TSeq>::ModelSIS(
const std::string & vname,
epiworld_double prevalence,
epiworld_double transmission_rate,
epiworld_double recovery_rate
)
{
this->set_name("Susceptible-Infected-Susceptible (SIS)");
// Adding statuses
this->add_state("Susceptible", default_update_susceptible<TSeq>);
this->add_state("Infected", default_update_exposed<TSeq>);
// Setting up parameters
this->add_param(transmission_rate, "Transmission rate");
this->add_param(recovery_rate, "Recovery rate");
// Preparing the virus
Virus<TSeq> virus(vname, prevalence, true);
virus.set_state(INFECTED, SUSCEPTIBLE, SUSCEPTIBLE);
virus.set_prob_infecting("Transmission rate");
virus.set_prob_recovery("Recovery rate");
virus.set_prob_death(0.0);
this->add_virus(virus);
}
The benefit of this approach is that users can directly use ModelSIS<> to create a simple system where a virus has already been added: