Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JOSS Feedback #93

Merged
merged 8 commits into from
Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rename 'winik' to 'agent'
  • Loading branch information
ThomasThelen committed Jul 23, 2022
commit 1862f00fa2c153e7bc5b507d4860964df35129a0
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