MyNixOS website logo
Description

A Framework for Designing and Running Agent Based Models.

This is a package for creating and running Agent Based Models (ABM). It provides a set of base classes with core functionality to allow bootstrapped models. For more intensive modeling, the supplied classes can be extended to fit researcher needs.

villager

R build status Codecov test coverage

villager is a framework for creating and running agent based models in R. It's purpose is to provide an extensible framework where modeling can be done in native R.

Features

  • Extensible data output system (csv, excel sheets, sqlite)
  • Built in support for agents and resources
  • Easy to use time management

Installing

villager should be installed with devtools.

devtools::install_github("zizroc/villager")

Takeaways

When reading though the Readme and vignettes, it's important to take note of a few concepts

  • Villages are the highest aggregate; they contain villages which in turn contain agents (winiks)
  • Agents and resources can be subclassed to support additional properties
  • The data_writer class can be subclassed when writing to data sources other than csv
  • Models are functions that are added to villages; each village can exhibit different behavior

Using villager

villager is about modeling populations with (optional) associated resources. It supports a community level aggregation of agents, referred to as villages or an individual village. Agents, which are referred to as gender-neutral winiks, are members of community level aggregations.

villager compliant models must conform to the function template below. The winik_mgr and resource_mgr are responsible for interacting with the individual agents and resources.

test_model <- function(current_state, previous_state, model_data, winik_mgr, resource_mgr) {
  ...
  ...
}

Creating & Managing Agents

Agents are created by instantiating the winik class. There are a number of winik properties that can be passed to the constructor.

test_model <- function(current_state, previous_state, model_data, winik_mgr, resource_mgr) {
  mother <- winik$new(first_name="Kirsten", last_name="Taylor", age=9125)
  father <- winik$new(first_name="Joshua", last_name="Thompson", age=7300)
  daughter <- winik$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
}

To add winiks to the simulation, use the provided winik_mgr object to call add_winik. Because the classes are R6, the object can be modified after being added to the manager and the changes will be persisted without needing to re-add the villager. For example, setting a daughter's mother and her father below. Note that the standard way is to modify the properties beforehand, although not strictly necessary.

test_model <- function(current_state, previous_state, model_data, winik_mgr, resource_mgr) {
  winik_mgr <- winik_manager$new()
  winik_mgr$add_winik(mother)
  winik_mgr$add_winik(father)
  winik_mgr$add_winik(daughter)
  daughter$mother_id <- mother$identifier
  daughter$father_id <- father$identifier
}

The winik manager can also be used to pair winiks, representitive of a relationship or social bond.

winik_mgr$winik_mgr$connect_winiks(mother, father)

Creating & Managing Resources

Resources are similar to winiks in that they're both R6 classes, are instantiated similarly, and are also managed by an object passed into the model. An example of creating resources and adding them to the simualtion is given below.

test_model <- function(current_state, previous_state, model_data, winik_mgr, resource_mgr) {
  corn_resource <- resource$new(name="corn", quantity = 10)
  fish_resource <- resource$new(name="fish", quantity = 15)
  corn_resource$quantity=5
  
  resource_mgr <- resource_manager$new()
  resource_mgr$add_resource(corn_resource)
  resource_mgr$add_resource(fish_resource)
  fish_resource$quantity=5
}

State

Objects of type village, winik, and resourcehave particular states at a particular time. As the simulation progresses, the state of these change based on model logic. At the end of each time step, the state of each object is saved, giving a complete record of the system's evolution. The essence of any agent based model is changing the state at each time step. villager provides a mechanim for defining the initial state and for changing the state throughout the simulation.

Managing the Initial State

Creating the initial state is done by creating a function that resembles model functions from above. The manager classes are used to populate the village with an initial population of agents and resources.

initial_condition <- function(current_state, model_data, winik_mgr, resource_mgr) {
  # Create the initial villagers
  mother <- winik$new(first_name="Kirsten", last_name="Taylor", age=9125)
  father <- winik$new(first_name="Joshua", last_name="Thompson", age=7300)
  daughter <- winik$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
  daughter$mother_id <- mother$identifier
  daughter$father_id <- father$identifier
  
  # Add the winiks to the manager
  winik_mgr$connect_winiks(mother, father)
  winik_mgr$add_winik(mother)
  winik_mgr$add_winik(father)
  winik_mgr$add_winik(daughter)
  
  # Create the resources
  corn_resource <- resource$new(name="corn", quantity = 10)
  fish_resource <- resource$new(name="fish", quantity = 15)
  
  # Add the resources to the manager
  resource_mgr$add_resource(corn_resource)
  resource_mgr$add_resource(fish_resource)
}

Creating Villages and Running Models

Models are tied to particular village instances. This binding is done when villages are created, shown below. Models can have names and must always be paired with an initial condition function and a model function.

small_village <- village$new("Test Model 1", initial_condition, test_model)

The simulator class is responsible for running simulations. It encapsulates all of the villages and controls the duration of the simulation. The simulator below runs for 100 time steps: roughly 13 years. The simulator can be paired with any number of villages, in the case of the simulator below, there's only a single village.

simulator <- simulation$new(100, list(small_village))
simulator$run_model()

Example: A small village with a single family

We can combine the examples above into a full simulation that...

  • Starts with an initial population of three villagers
  • Increases the age of each villager at the start of each day
  • Runs for 4745 days
  • Sets the villager profession after age 12
library(villager)
initial_condition <- function(current_state, model_data, winik_mgr, resource_mgr) {
  # Create the initial villagers
  mother <- winik$new(first_name="Kirsten", last_name="Taylor", age=9125)
  father <- winik$new(first_name="Joshua", last_name="Thompson", age=7300)
  daughter <- winik$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
  daughter$mother_id <- mother$identifier
  daughter$father_id <- father$identifier
  
  # Add the winiks to the manager
  winik_mgr$connect_winiks(mother, father)
  winik_mgr$add_winik(mother)
  winik_mgr$add_winik(father)
  winik_mgr$add_winik(daughter)
  
  # Create the resources
  corn_resource <- resource$new(name="corn", quantity = 10)
  fish_resource <- resource$new(name="fish", quantity = 15)
  
  # Add the resources to the manager
  resource_mgr$add_resource(corn_resource)
  resource_mgr$add_resource(fish_resource)
}

test_model <- function(current_state, previous_state, model_data, winik_mgr, resource_mgr) {
print(paste("Step:", current_state$step))
  for (winik in winik_mgr$get_living_winiks()) {
    winik$age <- winik$age+1
    if (winik$age >= 4383) {
      winik$profession <- "Farmer"
    }
  }
}

small_village <- village$new("Test Model", initial_condition, test_model)
simulator <- simulation$new(4745, list(small_village))
simulator$run_model()

Full Example 2

To demonstrate programatically creating villagers, consider the model below that has the following logic.

  • Starts with 10 villagers
  • Every even day, two new villagers are created
  • Every odd day, one villager dies
library(villager)
    current_day <- current_state$step
    print(current_day)
    if((current_day%%2) == 0) {
      # Then it's an even day
      # Create two new winiks whose first names are random numbers
      for (i in 1:2) {
        name <- runif(1, 0.0, 100)
        new_winik <- winik$new(first_name <- name, last_name <- "Smith")
        winik_mgr$add_winik(new_winik)
      }
    } else {
      # It's an odd day
      living_winiks <- winik_mgr$get_living_winiks()
      # Kill the first one
      living_winiks[[1]]$alive <- FALSE
    }
  }
  coastal_village <- village$new("Test village", initial_condition, model)
  simulator <- simulation$new(4, villages = list(coastal_village))
  simulator$run_model()
  mgr <- simulator$villages[[1]]$winik_mgr

Advanced Usage

In the examples above, the default properties of agents and resources were used. It's possible that these won't cover all the needs for more diverse models. There are vignettes on extending the agent and resource classes to handle these situations.

Contributing

Code contributions are welcome as pull requests to the develop branch. Bugs, comments, and questions can be submitted as Github Issues.

Metadata

Version

1.1.1

License

Unknown

Platforms (75)

    Darwin
    FreeBSD 13
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd13
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd13
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows