Skip to content

Commit

Permalink
Merge pull request #93 from zizroc/develop
Browse files Browse the repository at this point in the history
JOSS Feedback
  • Loading branch information
ThomasThelen authored Aug 7, 2022
2 parents e5ef26f + 8c9861d commit 76d5a66
Show file tree
Hide file tree
Showing 30 changed files with 1,200 additions and 979 deletions.
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
YEAR: 2020
COPYRIGHT HOLDER: MIT License
YEAR: 2022
COPYRIGHT HOLDER: Thomas Thelen
4 changes: 2 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Generated by roxygen2: do not edit by hand

export(agent)
export(agent_manager)
export(data_writer)
export(resource)
export(resource_manager)
export(simulation)
export(village)
export(village_state)
export(winik)
export(winik_manager)
80 changes: 40 additions & 40 deletions R/winik.R → R/agent.R
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
#' @export
#' @title Winik
#' @title agent
#' @docType class
#' @description This is an object that represents a villager (winik).
#' @description This is an object that represents a villager (agent).
#' @details This class acts as an abstraction for handling villager-level logic. It can take a
#' number of functions that run at each timestep. It also has an associated
#' @field identifier A unique identifier that can be used to identify and find the winik
#' @field first_name The winik's first name
#' @field last_name The winik's last name
#' @field age The winik's age
#' @field mother_id The identifier of the winik's mother
#' @field father_id The identifier of the winik's father
#' @field profession The winik's profession
#' @field partner The identifier of the winik's partner
#' @field gender The winik's gender
#' @field identifier A unique identifier that can be used to identify and find the agent
#' @field first_name The agent's first name
#' @field last_name The agent's last name
#' @field age The agent's age
#' @field mother_id The identifier of the agent's mother
#' @field father_id The identifier of the agent's father
#' @field profession The agent's profession
#' @field partner The identifier of the agent's partner
#' @field gender The agent's gender
#' @field alive A boolean flag that represents whether the villager is alive or dead
#' @field children A list of children identifiers
#' @field health A percentage value of the winik's current health
#' @field health A percentage value of the agent's current health
#' @section Methods:
#' \describe{
#' \item{\code{as_table()}}{Represents the current state of the winik as a tibble}
#' \item{\code{as_table()}}{Represents the current state of the agent as a tibble}
#' \item{\code{get_age()}}{Returns age in terms of years}
#' \item{\code{get_gender()}}{}
#' \item{\code{get_days_sincelast_birth()}}{Get the number of days since the winik last gave birth}
#' \item{\code{initialize()}}{Create a new winik}
#' \item{\code{get_days_sincelast_birth()}}{Get the number of days since the agent last gave birth}
#' \item{\code{initialize()}}{Create a new agent}
#' \item{\code{propagate()}}{Runs every day}
#' }
winik <- R6::R6Class("winik",
agent <- R6::R6Class("agent",
public = list(
age = NULL,
alive = NULL,
Expand All @@ -40,24 +40,24 @@ winik <- R6::R6Class("winik",
partner = NULL,
profession = NULL,

#' Create a new winik
#' Create a new agent
#'
#' @description Used to created new winik objects.
#' @description Used to created new agent objects.
#'
#' @export
#' @param age The age of the winik
#' @param alive Boolean whether the winik is alive or not
#' @param children An ordered list of of the children from this winik
#' @param gender The gender of the winik
#' @param identifier The winik's identifier
#' @param first_name The winik's first name
#' @param last_name The winik's last naem
#' @param mother_id The identifier of the winik's monther
#' @param father_id The identifier of the winik' father
#' @param partner The identifier of the winik's partner
#' @param profession The winik's profession
#' @param health A percentage value of the winik's current health
#' @return A new winik object
#' @param age The age of the agent
#' @param alive Boolean whether the agent is alive or not
#' @param children An ordered list of of the children from this agent
#' @param gender The gender of the agent
#' @param identifier The agent's identifier
#' @param first_name The agent's first name
#' @param last_name The agent's last naem
#' @param mother_id The identifier of the agent's monther
#' @param father_id The identifier of the agent' father
#' @param partner The identifier of the agent's partner
#' @param profession The agent's profession
#' @param health A percentage value of the agent's current health
#' @return A new agent object
initialize = function(identifier = NA,
first_name = NA,
last_name = NA,
Expand Down Expand Up @@ -90,14 +90,14 @@ winik <- R6::R6Class("winik",
#' A function that returns true or false whether the villager dies
#' This is run each day
#'
#' @return A boolean whether the winik is alive (true for yes)
#' @return A boolean whether the agent is alive (true for yes)
is_alive = function() {
# The villager survived the day
return(self$alive)
},

#' Gets the number of days from the last birth. This is also
#' the age of the most recently born winik
#' the age of the most recently born agent
#'
#' @return The number of days since last birth
get_days_since_last_birth = function() {
Expand All @@ -108,10 +108,10 @@ winik <- R6::R6Class("winik",
return(0)
},

#' Connects a child to the winik. This method ensures that the
#' Connects a child to the agent. This method ensures that the
#' 'children' vector is ordered.
#'
#' @param child The Winik object representing the child
#' @param child The agent object representing the child
#' @return None
add_child = function(child) {
sort_children <- function() {
Expand Down Expand Up @@ -139,16 +139,16 @@ winik <- R6::R6Class("winik",
}
},

#' Returns a data.frame representation of the winik
#' Returns a data.frame representation of the agent
#'
#' @description I hope there's a more scalable way to do this in R; Adding every new attribute to this
#' function isn't practical
#' @details The village_state holds a copy of all of the villagers at each timestep; this method is used to turn
#' the winik properties into the object inserted in the village_state.
#' the agent properties into the object inserted in the village_state.
#' @export
#' @return A data.frame representation of the winik
#' @return A data.frame representation of the agent
as_table = function() {
winik_table <- data.frame(
agent_table <- data.frame(
age = self$age,
alive = self$alive,
father_id = self$father_id,
Expand All @@ -161,7 +161,7 @@ winik <- R6::R6Class("winik",
partner = self$partner,
profession = self$profession
)
return(winik_table)
return(agent_table)
}
)
)
201 changes: 201 additions & 0 deletions R/agent_manager.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#' @export
#' @title agent Manager
#' @docType class
#' @description A class that abstracts the management of aggregations of agent classes. Each village should have
#' an instance of a agent_manager to interface the agents inside.
#' @field agents A list of agents objects that the agent manager manages.
#' @field agent_class A class describing agents. This is usually the default villager supplied 'agent' class
#' @section Methods:
#' \describe{
#' \item{\code{add_agent()}}{Adds a single agent to the manager.}
#' \item{\code{get_average_age()}}{Returns the average age, in years, of all the agents.}
#' \item{\code{get_living_agents()}}{Gets a list of all the agents that are currently alive.}
#' \item{\code{get_states()}}{Returns a data.frame consisting of all of the managed agents.}
#' \item{\code{get_agent()}}{Retrieves a particular agent from the manager.}
#' \item{\code{get_agent_index()}}{Retrieves the index of a agent.}
#' \item{\code{initialize()}}{Creates a new manager instance.}
#' \item{\code{load()}}{Loads a csv file defining a population of agents and places them in the manager.}
#' \item{\code{remove_agent()}}{Removes a agent from the manager}
#' }
agent_manager <- R6::R6Class("agent_manager",
public = list(
agents = NULL,
agent_class = NULL,

#' Creates a new agent manager instance.
#'
#' @param agent_class The class that's being used to represent agents being managed
initialize = function(agent_class=villager::agent) {
self$agents <- vector()
self$agent_class <- agent_class
},

#' Given the identifier of a agent, sort through all of the managed agents and return it
#' if it exists.
#'
#' @description Return the R6 instance of a agent with identiifier 'agent_identifier'.
#' @param agent_identifier The identifier of the requested agent.
#' @return An R6 agent object
get_agent = function(agent_identifier) {
for (agent in self$agents) {
if (agent$identifier == agent_identifier) {
return(agent)
}
}
},

#' Returns a list of all the agents that are currently alive.
#'
#' @return A list of living agents
get_living_agents = function() {
living_agents <- list()
for (agent in self$agents) {
if (agent$alive) {
living_agents <- append(living_agents, agent)
}
}
return(living_agents)
},

#' Adds a agent to the manager.
#'
#' @param new_agent The agent to add to the manager
#' @return None
add_agent = function(new_agent) {
# Create an identifier if it's null
if (is.null(new_agent$identifier)) {
new_agent$identifier <- uuid::UUIDgenerate()
}
self$agents <- append(self$agents, new_agent)
},

#' Removes a agent from the manager
#'
#' @param agent_identifier The identifier of the agent being removed
#' @return None
remove_agent = function(agent_identifier) {
agent_index <- self$get_agent_index(agent_identifier)
self$agents <- self$agents[-agent_index]
},

#' Returns a data.frame of agents
#'
#' @details Each row of the data.frame represents a agent object
#' @return A single data.frame of all agents
get_states = function() {
# Allocate the appropriate sized table so that the row can be emplaced instead of appended
agent_count <- length(self$agents)
agent_fields <- names(self$agent_class$public_fields)
column_names <- agent_fields[!agent_fields %in% c("children")]
state_table <- data.frame(matrix(nrow = agent_count, ncol = length(column_names)))

if (agent_count > 0) {
# Since we know that a agent exists and we need to match the columns here with the
# column names in agent::as_table, get the first agent and use its column names
colnames(state_table) <- column_names
for (i in 1:agent_count) {
state_table[i, ] <- self$agents[[i]]$as_table()
}
}
return(state_table)
},

#' Returns the index of a agent in the internal agent list
#'
#' @param agent_identifier The identifier of the agent being located
#' @return The index in the list, or R's default return value
get_agent_index = function(agent_identifier) {
for (i in seq_len(length(self$agents))) {
if (self$agents[[i]]$identifier == agent_identifier) {
return(i)
}
}
return(NA)
},

#' Connects two agents together as mates
#'
#' @param agent_a A agent that will be connected to agent_b
#' @param agent_b A agent that will be connected to agent_a
connect_agents = function(agent_a, agent_b) {
agent_a$partner <- agent_b$identifier
agent_b$partner <- agent_a$identifier
},

#' Returns the total number of agents that are alive
#' @return The numnber of living agents
get_living_population = function() {
total_living_population <- 0
for (agent in self$agents)
if (agent$alive == TRUE) {
total_living_population <- total_living_population + 1
}
return(total_living_population)
},

#' Returns the averag age, in years, of all of the agents
#'
#' @details This is an *example* of the kind of logic that the manager might handle. In this case,
#' the manager is performing calculations about its aggregation (agents). Note that the 364 days needs to
#' work better
#'
#' @return The average age in years
get_average_age = function() {
total_age <- 0
for (agent in self$agents)
total_age <- total_age + agent$age
average_age_days <- total_age / length(self$agents)
return(average_age_days / 364)
},

#' Takes all of the agents in the manager and reconstructs the children
#'
#' @details This is typically called when loading agents from disk for the first time.
#' When children are created during the simulation, the family connections are made
#' through the agent class and added to the manager via add_agent.
#' @return None
add_children = function() {
for (agent in self$agents) {
if (!is.na(agent$mother_id)) {
if (!is.na(self$get_agent_index(agent$mother_id))) {
mother <- self$get_agent(agent$mother_id)
mother$add_child(agent)
}
}
if (!is.na(agent$father_id)) {
if (!is.na(self$get_agent_index(agent$father_id))) {
father <- self$get_agent(agent$father_id)
father$add_child(agent)
}
}
}
},

#' Loads agents from disk.
#'
#' @details Populates the agent manager with a set of agents defined in a csv file.
#' @param file_name The location of the file holding the agents.
#' @return None
load = function(file_name) {
agents <- read.csv(file_name, row.names = NULL)
for (i in seq_len(nrow(agents))) {
agents_row <- agents[i, ]
new_agent <- agent$new(
identifier = agents_row$identifier,
first_name = agents_row$first_name,
last_name = agents_row$last_name,
age = agents_row$age,
mother_id = agents_row$mother_id,
father_id = agents_row$father_id,
partner = agents_row$partner,
gender = agents_row$gender,
profession = agents_row$profession,
alive = agents_row$alive,
health = agents_row$health
)
self$add_agent(new_agent)
}
self$add_children()
}
)
)
Loading

0 comments on commit 76d5a66

Please sign in to comment.