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)

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):

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

[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]