From 90954eb7e6486c4750743310bb12652fcd1293db Mon Sep 17 00:00:00 2001 From: Joe Dasenbrock Date: Sat, 17 Aug 2019 12:27:12 -0500 Subject: [PATCH 1/3] Refactored Boltzmann example docs and scripts. Made substantial edits to the Boltzmann wealth distribution example. The general train of thought wasn't changed in the .md file, but many paragraph were restructured significantly and a few paragraphs were added. Also, the corresponding files in `examples/` were edited as well. Since the docs page gives two examples of the Boltzmann model (with a grid and without), two separate `.jl` files where created so that either variation can be easily executed by the user. The code referenced in docs page is exactly the same code in the two `.jl` files. --- docs/src/boltzmann_example01.md | 194 +++++++++++++----- examples/boltzmann_wealth_distribution.jl | 142 +++++++------ ...boltzmann_wealth_distribution_with_grid.jl | 128 ++++++++++++ 3 files changed, 340 insertions(+), 124 deletions(-) create mode 100644 examples/boltzmann_wealth_distribution_with_grid.jl diff --git a/docs/src/boltzmann_example01.md b/docs/src/boltzmann_example01.md index f5cb883a87..fd3a046cc5 100644 --- a/docs/src/boltzmann_example01.md +++ b/docs/src/boltzmann_example01.md @@ -1,159 +1,222 @@ # Boltzmann wealth distribution -This is a simple agent-based model in economics. Despite its simplicity, it shows striking emergent wealth distribution. The first model that we will does not have spatial structure. +The Boltzmann wealth distribution is a simple agent-based model (ABM) in economics. Despite its simplicity, the model shows striking emergent wealth distribution. This model is the first in this set of examples that does not necessarily have a spatial structure. At first, we will build a model that does not have a spatial structure, and then we will modify the model to include a spatial structure. -* We start with a number of agents all of which have one unit of wealth. +The Boltzmann model is conceptually very simple: + +* We start with a number of agents, each of which have one unit of wealth. * At every step, agents give one unit of their money (if they have any) to a random agent. * We will see how wealth will be distributed after a few steps. -The code of this tutorial is in the `examples/boltzmann_wealth_distribution.jl` file on the Github repository. +The code referenced in this tutorial is available in the [`examples/boltzmann_wealth_distribution.jl`](https://github.com/kavir1698/Agents.jl/blob/master/examples/boltzmann_wealth_distribution.jl) and [`examples/boltzmann_wealth_distribution_with_grid.jl`](https://github.com/kavir1698/Agents.jl/blob/master/examples/boltzmann_wealth_distribution_with_grid.jl) files on the Github repository. ## Building the model -Agents.jl structures simulations in three components. A _model_ component that keeps all model-level variables and data, an _agent_ component that keeps all agent-level variables and data, and _space_ component that keeps space-level data. - -At the beginning of each building any model, define your types for each of these components. After that, you will have to initialize your model, and write one or two functions to change angets and/or the model at each step. This will be all before you can run your model and analyze its results. +Recall that Agents.jl structures simulations in three components: a _model_ component that keeps all model-level variables and data, an _agent_ component that keeps all agent-level variables and data, and a _space_ component that keeps space-level data. These components are provided in Agents.jl as abstract types, and this typing allows for tools from Agents.jl manage the rest of the path to producing data and visualizations. -Now let's build our three types of this model: - -These types should be subtypes of the following abstract types: +Subtyping the model components in the following way will allow all the built-in functions to work on your defined types: * The agent type should be a subtype of `AbstractAgent`. * The model type should be a subtype of `AbstractModel`. * The space type should be a subtype of `AbstractSpace`. -This subtyping will allow all the built-in functions to work on your define types. +At first in this example, we will not be using a spatial structure: ```julia using Agents -# 1. define agent type +""" +Defines the agent type. + +The agent type must be a subtype of AbstractAgent. + +Commonly, an agent type will require a field for location value in the form +`pos::Tuple{T, T}`. In the first part of this example we will not be using a spatial +structure, therefore we will not define a field for position. + +""" mutable struct MyAgent{T<:Integer} <: AbstractAgent + "The identifier number of the agent." id::T - pos::Tuple{T, T} # x,y coords + "The agent's wealth." wealth::T end ``` -The agent type has to have the `id` and the `pos` (for position) fields, but it can have any other fields that you desire. Here we add a `wealth` field that accepts integers. If your space is a grid, the position should accept a `Tuple{Integer, Integer}` representing x, y coordinates. + +The agent type has to have the `id` field and a `pos` field for position (if a spatial structure is used in the model). However, the agent can have any other fields that you desire, here we add a `wealth` field that accepts integers. ```julia -# 2. define a model type +"Define the model type." mutable struct MyModel{T<:AbstractVector} <: AbstractModel - agents::T # an array of agents + "An array of agents." + agents::T + "A field for the scheduler function." scheduler::Function end ``` -The model type has to have the `space`, `agents`, and `scheduler` fields. `space` will keep our space type, `agents` will be an array of all the agents, and `scheduler` will hold a function that specifies the order at which agents activate at each generation. See [Scheduler functions](@ref) for available scheduler functions, and [Space functions](@ref) for available space structures. +The model type has to have the `agents`, and `scheduler` fields. `agents` will be an array of all the agents, and `scheduler` will hold a function that specifies the order at which agents activate at each generation. See [Scheduler functions](@ref) for available scheduler functions. -Since for this first step, we do not need a space structure, we will not define a space type and not create field for it in the model `struct`. +Since we do not need a space structure for this first step, we will not define a space type and not create field for it in the model `struct`. Now we write a function to instantiate the model: ```julia -# 3. instantiate the model -function instantiate_model(;numagents) - agents = [MyAgent(i, (1,1), 1) for i in 1:numagents] # create a list of agents - model = MyModel(agents, random_activation) # instantiate the model +"Function to instantiate the model." +function instantiate_model(; numagents) + # Create a list of agents, each with position (1,1) and one unit of + # wealth. + agents = [MyAgent(i, 1) for i in 1:numagents] + + # Instantiate and return the model. + model = MyModel(agents, random_activation) return model end ``` -We can start our model by running the function: +We can start our model by running the function and specifying `numagents`: ```julia model = instantiate_model(numagents=100) ``` -Now we have to write a step function for agents. An step function should always take two positional arguments: first an agent object, and second your model object. Every agent will perform actions within this function at each step. Here, we say if an agent activate (defined by the scheduler), and has any wealth, it should choose a random agent and give one unit of its wealth to it. +Now, we need to write a step function for agents. An step function should always take two positional arguments: first an agent object, and second a model object. Every agent will perform actions within this function at each step. Here, we say if an agent is activated (when defined by the scheduler) and has any wealth, then it should choose a random agent and give one unit of its wealth to said random agent. ```julia -# Agent step function: define what the agent does at each step +""" +Define the agent step function. + +Defines what the agent should do at each step. +""" function agent_step!(agent::AbstractAgent, model::AbstractModel) + # If the agent's wealth is zero, then do nothing. if agent.wealth == 0 return + # Otherwise, choose a random agent, subtract one unit of own wealth + # and add one unit of wealth to the randomly chosen agent. else - agent2 = model.agents[rand(1:nagents(model))] + random_agent = model.agents[rand(1:nagents(model))] agent.wealth -= 1 - agent2.wealth += 1 + random_agent.wealth += 1 end end ``` -That's it. We can run the model. The `step!` function (see [Model functions](@ref)) runs the model. We can run it without collecting data for one step: +Now we can run the model using the `step!` function (see [Model functions](@ref)). We can use the `step!` function from Agents.jl to run the model one step, without collecting data: ```julia +# Step the model once. step!(agent_step!, model) ``` -or for multiple steps: +...or, similarly, to run the model multiple steps: ```julia +# Step the model 10 times. step!(agent_step!, model, 10) ``` -or we can run it for multiple steps and collect data: +To run the model multiple steps and collect data we need to specify which properties to collect (`agent_properties` below) and on which steps to collect the data (`steps_to_collect_data` below): ```julia +# An array of Symbols for the agent fields that are to be collected, in +# this case wealth is the only variable to be collected. agent_properties = [:wealth] +# Specifies at which steps data should be collected. steps_to_collect_data = collect(1:10) +# Use the step function to run the model 10 times and collect data at +# each step. data = step!(agent_step!, model, 10, agent_properties, steps_to_collect_data) ``` -This code collects all agents' wealth at each step and stores them in a `DataFrame`. -We can then interactively plot the data in DataVoyager and see the distribution of wealth at each step +This code collects all agents' wealth at each step and stores them in a `DataFrame`. We can then interactively plot the data in DataVoyager and see the distribution of wealth at each step. This can be accomplished by simply using the `visualize_data` function: ```julia visualize_data(data) ``` -Often, in ABM we want to run a model many times and observe the average behavior of the system. We can do this easily with the `batchrunner` function. It accepts the same arguments and in the same order as the `step!` function: +Often, in ABM we want to run a model many times and observe the average behavior of the system. We can do this easily with the `batchrunner` function. The first arguments to `batchrunner` are the same as the arguments passed to `step!`, and for `batchrunner` we also specify the number of replicates (15 in the below example): ```julia model = instantiate_model(numagents=100) -data = batchrunner(agent_step!, model, 10, agent_properties, steps_to_collect_data, 10) +# Run the model through 10 steps 15 separate times. +data = batchrunner(agent_step!, model, 10, agent_properties, steps_to_collect_data, 15) ``` -We can include a grid in our model and let the agents interact only with those in the same node. To that end, we will have to modify the model type and write a space type: +In the model that we built so far, agents choose to give part of their wealth to any random agent. In the real world, transfers of wealth between people or groups are not likely to be completely random. Transfers are more likely to happen between people in the same network. + +For example, if a person hires a contractor to build a house, the contractor would be chosen from a network of some kind or another, and the contractor would not be chosen at random from all existing contractors. Perhaps the person would have a recommendation from a friend, or perhaps the contractor would be chosen from a pool of potential contractors in the same geographical area. + +We can add a network effect similar to this by including a grid in our model and letting the agents interact only with those in the same node. + +To that end, we will have to modify the model and agent types as well as write a space type: ```julia -# Add grid field to the model type -mutable struct MyModel2{T<:AbstractSpace, Y<:AbstractVector} <: AbstractModel - space::T - agents::Y # an array of agents +"Define the model type." +mutable struct MyModel{T<:AbstractVector} <: AbstractModel + "An array of agents." + agents::T + "A field for the scheduler function." scheduler::Function + "The space field." + space::S end -# define a space type +"The space type that serves as the grid for the model." mutable struct MyGrid{T<:Integer, Y<:AbstractVector} <: AbstractSpace + "The dimensions of the grid." dimensions::Tuple{T, T} + "A field for the space type." space::SimpleGraph - agent_positions::Y # an array of arrays for each grid node + "An array of arrays for each grid node." + agent_positions::Y end ``` -The space type has to have the `dimensions`, `space`, and `agent_positions` fields. `dimensions` should be there only if you are using a grid space. The `space` field keeps the actual graph of the space. The `agent_positions` is always an array of arrays. An array for each node of the space. It will be used to keep the `agent.id`s of agents in each node. +A few notes on the space type and the updated model: + +* The space type requires the `dimensions`, `space`, and `agent_positions` fields. +* The field `dimensions` should be there only if you are using a grid space. +* The `space` field keeps the actual graph of the space. +* The `agent_positions` is always an array of arrays, where each array is for for each node of the space. +* The value of `agent.id` for each agent that is in the node will be stored in each node-array. We also have to modify the model instantiation function: ```julia -function instantiate_model(;numagents, griddims) - agent_positions = [Int64[] for i in 1:gridsize(griddims)] # an array of arrays for each node of the space - mygrid = MyGrid(griddims, grid(griddims), agent_positions) # instantiate the grid structure - model = MyModel2(mygrid, MyAgent[], random_activation) # instantiate the model - agents = [MyAgent(i, (1,1), 1) for i in 1:numagents] # create a list of agents - for ag in agents - add_agent!(ag, model) +"Function to instantiate the model." +function instantiate_model(; numagents, griddims) + # An array of arrays for each node of the space. + agent_positions = [Int64[] for i in 1:gridsize(griddims)] + # Instantiate the grid structure. + mygrid = MyGrid(griddims, grid(griddims), agent_positions) + # Create a list of agents, each with position (1,1) and one unit of + # wealth. + agents = [MyAgent(i, (1,1), 1) for i in 1:numagents] + + # Instantiate and return the model. + model = MyModel(mygrid, MyAgent[], random_activation) + + # Use the `add_agent!` function to add agents to the model. + for agent in agents + add_agent!(agent, model) end + return model end +``` + +Now we can use our redefined instantiation function to instantiate a new model: +```julia model = instantiate_model(numagents=100, griddims=(5,5)) ``` -We should now add agents to random positions on the grid. The `move_agent!` function updates the `agent_positions` field of `model.space` and the `pos` field of each agent. It is possible to add agents to specific nodes by specifying a node number of x,y,z coordinates (see [Space functions](@ref) for more details). +We should now add agents to random positions on the grid. The `move_agent!` function updates the `agent_positions` field of `model.space` and the `pos` field of each agent. It is possible to add agents to specific nodes by specifying a node number of x,y,z coordinates (see [Space functions](@ref) for more details), however in this case the agent is placed on a random position on the grid. ```julia +# For each agent, move the agent to a random location on the grid by using the +# `move_agent!` function. for agent in model.agents move_agent!(agent, model) end @@ -162,15 +225,42 @@ end We need a new step function that allows agents to give money only to other agents in the same cell: ```julia +""" +Define the agent step function. + +Defines what the agent should do at each step. + +TODO - this needs to be modified so that it actually only gives money +to agents in the same cell, as described in the latter part of the +example. As of this writing, the below is the same function. + +""" function agent_step!(agent::AbstractAgent, model::AbstractModel) + # If the agent's wealth is zero, then do nothing. if agent.wealth == 0 return + # Otherwise, choose a random agent, subtract one unit of own wealth + # and add one unit of wealth to the randomly chosen agent. else - agent2 = model.agents[rand(1:nagents(model))] + random_agent = model.agents[rand(1:nagents(model))] agent.wealth -= 1 - agent2.wealth += 1 + random_agent.wealth += 1 end end ``` -The model can be run as we did previously. \ No newline at end of file +The model can be run as we did previously: + +```julia +# Run the model multiple steps and collect data. +# An array of Symbols for the agent fields that are to be collected, in +# this case wealth is the only variable to be collected. +agent_properties = [:wealth] +# Specifies at which steps data should be collected. +steps_to_collect_data = collect(1:10) +# Use the step function to run the model 10 times and collect data at +# each step. +data = step!(agent_step!, model, 10, agent_properties, steps_to_collect_data) +``` + +...and the `visualize_data` function can be used to visualized the outcome of the experiment. diff --git a/examples/boltzmann_wealth_distribution.jl b/examples/boltzmann_wealth_distribution.jl index e5dea1bc10..20fef215d7 100644 --- a/examples/boltzmann_wealth_distribution.jl +++ b/examples/boltzmann_wealth_distribution.jl @@ -1,98 +1,96 @@ -using StatsBase +""" +The first part of the Boltzmann Wealth Distribution example. + +In the first part of the Boltzmann example, the experiment is ran without +a spatial structure. In the second part, a spatial structure is added, +and agents are required to only give money to agents who are on the +same node. + +This example can be ran by navigating to the examples/ folder, starting +a julia REPL session and running: + +``` +julia> include("boltzmann_wealth_distribution.jl") +``` + +This will instantiate the model and create a `DataFrame` `data` that +contains the result of running the model 10 steps. After running the +model, the results can be visualized in DataVoyager like this: + +``` +julia> visualize_data(data); +``` + +...which should result in a pop-up window that displays graphs +depicting the results of the experiment. + +""" using Agents -# 1. define agent type +""" +Defines the agent type. + +The agent type must be a subtype of AbstractAgent. + +Commonly, an agent type will require a field for location value in the form +`pos::Tuple{T, T}`. In the first part of this example we will not be using a spatial +structure, therefore we will not define a field for position. + +""" mutable struct MyAgent{T<:Integer} <: AbstractAgent + "The identifier number of the agent." id::T - pos::Tuple{T, T} # x,y coords + "The agent's wealth." wealth::T end -# 2. define a space type -mutable struct MyGrid{T<:Integer, Y<:AbstractVector} <: AbstractSpace - dimensions::Tuple{T, T} - space::SimpleGraph - agent_positions::Y # an array of arrays for each grid node -end - -# 3. define a model type -mutable struct MyModel{T<:AbstractSpace, Y<:AbstractVector} <: AbstractModel - space::T - agents::Y # an array of agents +"Define the model type." +mutable struct MyModel{T<:AbstractVector} <: AbstractModel + "An array of agents." + agents::T + "A field for the scheduler function." scheduler::Function end +"Function to instantiate the model." +function instantiate_model(; numagents) + # Create a list of agents, each with position (1,1) and one unit of + # wealth. + agents = [MyAgent(i, 1) for i in 1:numagents] -# 4. instantiate the model -function instantiate_model(;numagents, griddims) - agent_positions = [Int64[] for i in 1:gridsize(griddims)] # an array of arrays for each node of the space - mygrid = MyGrid(griddims, grid(griddims), agent_positions) # instantiate the grid structure - model = MyModel(mygrid, MyAgent[], random_activation) # instantiate the model - agents = [MyAgent(i, (1,1), 1) for i in 1:numagents] # create a list of agents - for ag in agents - add_agent!(ag, model) - end + # Instantiate and return the model. + model = MyModel(agents, random_activation) return model end -model = instantiate_model(numagents=100, griddims=(5,5)) - -# 5 Agent step function: define what the agent does at each step -function agent_step!(agent::AbstractAgent, model::AbstractModel) - if agent.wealth == 0 - return - else - agent2 = model.agents[rand(1:nagents(model))] - agent.wealth -= 1 - agent2.wealth += 1 - end -end - -# 4. Run the model 10 steps (only agent activations) -step!(agent_step!, model, 10) +""" +Define the agent step function. -# you may add one more function to the step!() function. This new function applies after the agent_step!(). Such functions can apply to change the model, e.g. change the number of individuals or change to the environment. Therefore, such models should only accept the model as their argument. -# step!(agent_step::Function, model_step::Function, model::AbstractModel, repeat::Integer) - - -# 7. You can move agents on the grid -# add agents to random positions. This update the `agent_positions` field of `model.space` and the `pos` for of each agent. It is possible to add agents to specific nodes by specifying a node number of x,y coordinates -for agent in model.agents - move_agent!(agent, model) -end - -# Now we need to add to the agents’ behaviors, letting them move around and only give money to other agents in the same cell. +Defines what the agent should do at each step. +""" function agent_step!(agent::AbstractAgent, model::AbstractModel) + # If the agent's wealth is zero, then do nothing. if agent.wealth == 0 return + # Otherwise, choose a random agent, subtract one unit of own wealth + # and add one unit of wealth to the randomly chosen agent. else - available_agents = get_node_contents(agent, model) - agent2id = rand(available_agents) + random_agent = model.agents[rand(1:nagents(model))] agent.wealth -= 1 - agent2 = [i for i in model.agents if i.id == agent2id][1] - agent2.wealth += 1 - # now move - neighboring_nodes = node_neighbors(agent, model) - move_agent!(agent, rand(neighboring_nodes), model) + random_agent.wealth += 1 end end -model = instantiate_model(numagents=100, griddims=(5,5)) -step!(agent_step!, model, 10) +# Instantiate the model. +model = instantiate_model(numagents=100) -# 8. collect data - -properties = [:wealth] -aggregators = [StatsBase.mean, StatsBase.median, StatsBase.std] +# Run the model multiple steps and collect data. +# An array of Symbols for the agent fields that are to be collected, in +# this case wealth is the only variable to be collected. +agent_properties = [:wealth] +# Specifies at which steps data should be collected. steps_to_collect_data = collect(1:10) -#data = step!(agent_step!, model, 10, properties, aggregators, steps_to_collect_data) -data = step!(agent_step!, model, 10, properties, steps_to_collect_data) - -# 9. explore data visually -visualize_data(data) - -# 10. Running batch -model = instantiate_model(numagents=100, griddims=(5,5)) -data = batchrunner(agent_step!, model, 10, properties, aggregators, steps_to_collect_data, 10) -visualize_data(data) +# Use the step function to run the model 10 times and collect data at +# each step. +data = step!(agent_step!, model, 10, agent_properties, steps_to_collect_data) diff --git a/examples/boltzmann_wealth_distribution_with_grid.jl b/examples/boltzmann_wealth_distribution_with_grid.jl new file mode 100644 index 0000000000..280faaabd3 --- /dev/null +++ b/examples/boltzmann_wealth_distribution_with_grid.jl @@ -0,0 +1,128 @@ +""" +The second part of the Boltzmann Wealth Distribution example. + +In the first part of the Boltzmann example, the experiment is ran without +a spatial structure. In the second part, a spatial structure is added, +and agents are required to only give money to agents who are on the +same node. + +This example can be ran by navigating to the examples/ folder, starting +a julia REPL session and running: + +``` +julia> include("boltzmann_wealth_distribution_with_grid.jl") +``` + +This will instantiate the model and create a `DataFrame` `data` that +contains the result of running the model 10 steps. After running the +model, the results can be visualized in DataVoyager like this: + +``` +julia> visualize_data(data); +``` + +...which should result in a pop-up window that displays graphs +depicting the results of the experiment. + +""" + +using Agents + +""" +Defines the agent type. + +The agent type must be a subtype of AbstractAgent. + +""" +mutable struct MyAgent{T<:Integer} <: AbstractAgent + "The identifier number of the agent." + id::T + "The agent's grid position." + pos::Tuple{T, T} + "The agent's wealth." + wealth::T +end + +"Define the model type." +mutable struct MyModel{A<:AbstractVector, S<:AbstractSpace} <: AbstractModel + "A space dimension." + space::S + "An array of agents." + agents::A + "A field for the scheduler function." + scheduler::Function +end + +"The space type that serves as the grid for the model." +mutable struct MyGrid{T<:Integer, Y<:AbstractVector} <: AbstractSpace + "The dimensions of the grid." + dimensions::Tuple{T, T} + "A field for the space type." + space::SimpleGraph + "An array of arrays for each grid node." + agent_positions::Y +end + +"Function to instantiate the model." +function instantiate_model(; numagents, griddims) + # An array of arrays for each node of the space. + agent_positions = [Int64[] for i in 1:gridsize(griddims)] + # Instantiate the grid structure. + mygrid = MyGrid(griddims, grid(griddims), agent_positions) + # Create a list of agents, each with position (1,1) and one unit of + # wealth. + agents = [MyAgent(i, (1,1), 1) for i in 1:numagents] + + # Instantiate and return the model. + model = MyModel(mygrid, MyAgent[], random_activation) + + # Use the `add_agent!` function to add agents to the model. + for agent in agents + add_agent!(agent, model) + end + + return model +end + + +""" +Define the agent step function. + +Defines what the agent should do at each step. + +TODO - this needs to be modified so that it actually only gives money +to agents in the same cell, as described in the latter part of the +example. As of this writing, the below is the same function. + +""" +function agent_step!(agent::AbstractAgent, model::AbstractModel) + # If the agent's wealth is zero, then do nothing. + if agent.wealth == 0 + return + # Otherwise, choose a random agent, subtract one unit of own wealth + # and add one unit of wealth to the randomly chosen agent. + else + random_agent = model.agents[rand(1:nagents(model))] + agent.wealth -= 1 + random_agent.wealth += 1 + end +end + +# Instantiate the model. +model = instantiate_model(numagents=100, griddims=(5,5)) + +# For each agent, move the agent to a random location on the grid by using the +# `move_agent!` function. +for agent in model.agents + move_agent!(agent, model) +end + +# Run the model multiple steps and collect data. +# An array of Symbols for the agent fields that are to be collected, in +# this case wealth is the only variable to be collected. +agent_properties = [:wealth] +# Specifies at which steps data should be collected. +steps_to_collect_data = collect(1:10) +# Use the step function to run the model 10 times and collect data at +# each step. +data = step!(agent_step!, model, 10, agent_properties, steps_to_collect_data) From e2674b96631b6aa4ce3d8dc7fc79fa2b50fffc76 Mon Sep 17 00:00:00 2001 From: Joe Dasenbrock Date: Tue, 20 Aug 2019 06:09:43 -0500 Subject: [PATCH 2/3] Updated agent_step! function in Boltzmann example. This is for issue #27. Added new agent_step! code provided by kavir1698, function forces agents to only give wealth to someone on their own node. --- docs/src/boltzmann_example01.md | 16 ++++++++-------- .../boltzmann_wealth_distribution_with_grid.jl | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/src/boltzmann_example01.md b/docs/src/boltzmann_example01.md index fd3a046cc5..9adad37f72 100644 --- a/docs/src/boltzmann_example01.md +++ b/docs/src/boltzmann_example01.md @@ -230,21 +230,21 @@ Define the agent step function. Defines what the agent should do at each step. -TODO - this needs to be modified so that it actually only gives money -to agents in the same cell, as described in the latter part of the -example. As of this writing, the below is the same function. - """ function agent_step!(agent::AbstractAgent, model::AbstractModel) # If the agent's wealth is zero, then do nothing. if agent.wealth == 0 return - # Otherwise, choose a random agent, subtract one unit of own wealth - # and add one unit of wealth to the randomly chosen agent. + # Otherwise.. else - random_agent = model.agents[rand(1:nagents(model))] + #...create a list of all agents on the same node and select a random agent. + available_agents = get_node_contents(agent, model) + random_neighbor_agent_id = rand(available_agents) + random_neighbor_agent = [i for i in model.agents + if i.id == random_neighbor_agent_id][1] + # Then decrement the current agent's wealth and increment the neighbor's wealth. agent.wealth -= 1 - random_agent.wealth += 1 + random_neighbor_agent.wealth += 1 end end ``` diff --git a/examples/boltzmann_wealth_distribution_with_grid.jl b/examples/boltzmann_wealth_distribution_with_grid.jl index 280faaabd3..e8aadd2a9a 100644 --- a/examples/boltzmann_wealth_distribution_with_grid.jl +++ b/examples/boltzmann_wealth_distribution_with_grid.jl @@ -90,21 +90,21 @@ Define the agent step function. Defines what the agent should do at each step. -TODO - this needs to be modified so that it actually only gives money -to agents in the same cell, as described in the latter part of the -example. As of this writing, the below is the same function. - """ function agent_step!(agent::AbstractAgent, model::AbstractModel) # If the agent's wealth is zero, then do nothing. if agent.wealth == 0 return - # Otherwise, choose a random agent, subtract one unit of own wealth - # and add one unit of wealth to the randomly chosen agent. + # Otherwise.. else - random_agent = model.agents[rand(1:nagents(model))] + #...create a list of all agents on the same node and select a random agent. + available_agents = get_node_contents(agent, model) + random_neighbor_agent_id = rand(available_agents) + random_neighbor_agent = [i for i in model.agents + if i.id == random_neighbor_agent_id][1] + # Then decrement the current agent's wealth and increment the neighbor's wealth. agent.wealth -= 1 - random_agent.wealth += 1 + random_neighbor_agent.wealth += 1 end end From 329578990ccad2e29d13facff29ebbfc08ff8d3b Mon Sep 17 00:00:00 2001 From: Joe Dasenbrock Date: Tue, 20 Aug 2019 07:06:02 -0500 Subject: [PATCH 3/3] Modified Boltzmann agent_step! to move agents. Agents who trade wealth with only agents on their own node must be moved at every step, otherwise they will just trade wealth amongst themselves. --- docs/src/boltzmann_example01.md | 9 ++++++++- examples/boltzmann_wealth_distribution_with_grid.jl | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/src/boltzmann_example01.md b/docs/src/boltzmann_example01.md index 9adad37f72..1ad19900a9 100644 --- a/docs/src/boltzmann_example01.md +++ b/docs/src/boltzmann_example01.md @@ -222,7 +222,7 @@ for agent in model.agents end ``` -We need a new step function that allows agents to give money only to other agents in the same cell: +We need a new step function that allows agents to give money only to other agents in the same cell. Also, the `agent_step!` function must move the agent to a different location in every step - if the agent were not moved on every step, the agents would just trade wealth amongst themselves. ```julia """ @@ -245,6 +245,13 @@ function agent_step!(agent::AbstractAgent, model::AbstractModel) # Then decrement the current agent's wealth and increment the neighbor's wealth. agent.wealth -= 1 random_neighbor_agent.wealth += 1 + + # Now move the agent to a random node. + # If the agent weren't moved, agents would merely trade wealth + # amongst themselves on the same node. + neighboring_nodes = node_neighbors(agent, model) + move_agent!(agent, rand(neighboring_nodes), model) + end end ``` diff --git a/examples/boltzmann_wealth_distribution_with_grid.jl b/examples/boltzmann_wealth_distribution_with_grid.jl index e8aadd2a9a..7d776a224d 100644 --- a/examples/boltzmann_wealth_distribution_with_grid.jl +++ b/examples/boltzmann_wealth_distribution_with_grid.jl @@ -105,6 +105,12 @@ function agent_step!(agent::AbstractAgent, model::AbstractModel) # Then decrement the current agent's wealth and increment the neighbor's wealth. agent.wealth -= 1 random_neighbor_agent.wealth += 1 + + # Now move the agent to a random node. + # If the agent weren't moved, agents would merely trade wealth + # amongst themselves on the same node. + neighboring_nodes = node_neighbors(agent, model) + move_agent!(agent, rand(neighboring_nodes), model) end end