Note
You can download this demonstration as a Jupyter Notebook
here
Stochastic processes and reproducibility¶
Random numbers and stochastic processes are essential to many agent-based models.
In Python, we can use the pseudo-random number generator from the built-in library random
.
Pseudo-random means that this module generates numbers in a sequence that appears random but is actually deterministic, based on an initial seed value. In other words, the generator will produce the same pseudo-random sequence over multiple runs if it is given the same seed at the beginning. We can define this seed to receive reproducible results from a model with stochastic processes.
Generating random numbers¶
[1]:
import agentpy as ap
import random
To illustrate, let us define a model that generates a list of ten pseudo-random numbers:
[2]:
class RandomModel(ap.Model):
def setup(self):
self.random_numbers = [random.randint(0, 9) for _ in range(10)]
print(f"Model {self.p.n} generated the numbers {self.random_numbers}")
Now if we run this model multiple times, we will get a different series of numbers:
[3]:
for i in range(2):
parameters = {'steps':0, 'n':i}
model = RandomModel(parameters)
results = model.run(display=False)
Model 0 generated the numbers [9, 3, 3, 8, 8, 0, 1, 9, 4, 7]
Model 1 generated the numbers [0, 5, 9, 4, 6, 5, 3, 2, 2, 0]
If we want the results to be reproducible,
we can define a parameter seed
that will be used automatically at the beginning of Model.run()
.
Now, we get the same series of numbers:
[4]:
for i in range(2):
parameters = {'seed':1, 'steps':0, 'n':i}
model = RandomModel(parameters)
model.run(display=False)
Model 0 generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
Model 1 generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
Using multiple generators¶
The automatic use of the seed
parameter calls the method random.seed()
, which affects the default number generator that is created as a hidden instance by the random
module.
For more advanced applications, we can create seperate generators for each object, using random.Random
.
We can ensure that the seeds of each object follow a controlled pseudo-random sequence by using also using seperate generator in the main model.
Note that we use a different parameter name model_seed to avoid the automatic use of the parameter seed
in this case.
[5]:
class RandomAgent2(ap.Agent):
def setup(self):
seed = model.seed_generator.getrandbits(128) # Get seed from model
self.random = random.Random(seed) # Create generator for this agent
self.random_numbers = [self.random.randint(0, 9) for _ in range(10)]
print(f"{self} generated the numbers {self.random_numbers}")
class RandomModel2(ap.Model):
def setup(self):
self.seed_generator = random.Random(self.p.model_seed)
self.add_agents(2, RandomAgent2)
for i in range(2):
print(f"Model {i}:")
parameters = {'model_seed': 1, 'steps': 0}
model = RandomModel2(parameters)
results = model.run(display=False)
print()
Model 0:
RandomAgent2 (Obj 1) generated the numbers [8, 7, 0, 1, 2, 3, 9, 4, 5, 0]
RandomAgent2 (Obj 2) generated the numbers [8, 1, 4, 6, 6, 3, 4, 3, 5, 1]
Model 1:
RandomAgent2 (Obj 1) generated the numbers [8, 7, 0, 1, 2, 3, 9, 4, 5, 0]
RandomAgent2 (Obj 2) generated the numbers [8, 1, 4, 6, 6, 3, 4, 3, 5, 1]
Alternatively, we could also have each agent start from the same seed:
[6]:
class RandomAgent3(ap.Agent):
def setup(self):
self.random = random.Random(self.p.agent_seed)
self.random_numbers = [self.random.randint(0, 9) for _ in range(10)]
print(f"{self} generated the numbers {self.random_numbers}")
class RandomModel3(ap.Model):
def setup(self):
self.add_agents(2, RandomAgent3)
for i in range(2):
print(f"\nModel {i}:")
parameters = {'agent_seed': 1, 'steps':0, 'n':i}
model = RandomModel3(parameters)
results = model.run(display=False)
Model 0:
RandomAgent3 (Obj 1) generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
RandomAgent3 (Obj 2) generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
Model 1:
RandomAgent3 (Obj 1) generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
RandomAgent3 (Obj 2) generated the numbers [2, 9, 1, 4, 1, 7, 7, 7, 6, 3]
Modeling stochastic processes¶
This section presents some stochastic operations that are often used in agent-based models. To start, we prepare a generic model with ten agents:
[7]:
model = ap.Model()
agents = model.add_agents(10)
agents
[7]:
AgentList [10 agents]
If we look at the agent’s ids, we see that they have been created in order:
[8]:
agents.id
[8]:
AttrList of attribute 'id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
We can shuffle this list with AgentList.shuffle()
:
[9]:
agents.shuffle().id
[9]:
AttrList of attribute 'id': [5, 10, 3, 9, 6, 4, 7, 1, 8, 2]
Or create a random subset with AgentList.random()
:
[10]:
agents.random(5).id
[10]:
AttrList of attribute 'id': [6, 9, 10, 3, 5]
Both AgentList.shuffle()
and AgentList.random()
can take a custom generator as an argument:
[11]:
for _ in range(2):
custom_generator = random.Random(1)
print(agents.random(5, custom_generator).id)
AttrList of attribute 'id': [3, 10, 6, 5, 9]
AttrList of attribute 'id': [3, 10, 6, 5, 9]
Note that the above selection is without repetition, i.e. every agent can only be selected once.
Outside these built-in functions of agentpy, there are many other tools that can be used for stochastic processes.
For example, we can use the methods random.choices()
to make a selection with repetition and probability weights.
In the following example, agents with a higher id are more likely to be chosen:
[12]:
choices = random.choices(agents, k=5, weights=agents.id)
If needed, the resulting list from such selections can be converted back into an AgentList
:
[13]:
ap.AgentList(choices).id
[13]:
AttrList of attribute 'id': [5, 4, 5, 8, 7]
Further reading¶
Random number generation in Python: https://realpython.com/python-random/
Stochasticity in agent-based models: http://www2.econ.iastate.edu/tesfatsi/ace.htm#Stochasticity
Pseudo-random number generators: https://en.wikipedia.org/wiki/Pseudorandom_number_generator
What is random: https://www.youtube.com/watch?v=9rIy0xY99a0