Advanced Human Behavior Simulation Framework¶
In progress
This document is a work in progress if you see any errors, or exclusions or have any problems, please get in touch with us.
In the dynamic realm of Socio-Ecological Systems (SES) modeling, simulating human behavior is paramount. Humans, as key actors, play a pivotal role in shaping the interactions and outcomes within SES. Recognizing the complexity and nuances of human actions, ABSESpy introduces an advanced framework to model intricate human behaviors effectively.
The beauty of ABSESpy lies in its framework that seamlessly integrates these user-defined functions, creating a smooth workflow that mirrors the intricacies of human behavior in the real world. When implementing this human behavior framework with ABSESpy, users are allowed to define key elements of decision-making step by step with the following concepts:
- Decision: First, define the types of decisions that an actor can make. A particular type of decision can be made by various types of actors. However, pre-defining what the decision types are helps users to better understand and organize their models.
- Perception: Actors make decisions based on their perception of the surrounding world, including both the natural and social environments.
- Evaluate: As the process of evaluating perceptions can vary, different actors may have different methods of evaluating decisions. Users can choose different evaluation methods for the same decision across various actors.
- Response: Finally, based on the decision-making process, actors respond and their actions impact the surrounding environment.
Let's dive into them.
Decisions¶
from abses import Actor
from abses.actor import perception
from abses.decision import Decision
from abses.main import MainModel
import numpy as np
class OverWorking(Decision):
"""内卷策略"""
__strategies__ = {
True: "Decide to work longer.",
False: "No longer work more...",
}
def setup(self):
"""Initial strategy of a new actor."""
return np.random.choice([True, False], p=[0.1, 0.9])
A custom decision needs to inherit the Decision
class, and users can customize the class attribute __strategies__
to set the choice of strategies and explanations for each strategy. The strategy does not need to be actively instantiated because a decision always requires a decision-making subject, which we will define next:
class InvolutingActor(Actor):
"""A poor guy who has to work harder..."""
__decisions__ = OverWorking
model = MainModel()
actor = InvolutingActor(model=model)
As you can see, it's very simple. When customizing the main class, all you need to do is set __decisions__
in the class attributes. This type of main actors will automatically have this decision when initialized. You can access it using decisions
or a shortcut d
:
actor.decisions.over_working
<over_working: False>
actor.d.over_working
<over_working: False>
Preception¶
Generally speaking, actors (i.e., agents) in socio-ecological systems (SES) need to base their decisions on their perception of the world.
Let's create a agent who is tired from work, initially only working 7 hours a day. He will perceive the intensity of his colleagues' work around him.
class InvolutingActor(Actor):
"""A poor guy who has to work harder..."""
__decisions__ = OverWorking
def __init__(self, *args, working_hrs: float = 7.0, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.working_hrs = working_hrs
self.overwork = False
@perception
def avg_working_hrs(self) -> float:
"""The average wealth of acquaintances."""
colleagues = self.link.get("colleague")
return colleagues.array("working_hrs").mean()
Now, let's create some agents and make them be colluague of our poor guy.
We use link_to
method to do so. The tutorial of this method can be found here.
agents = model.agents.new(InvolutingActor, 3)
poor_guy, colleague_1, colleague_2 = agents
poor_guy.working_hrs = 7
colleague_1.working_hrs = 8
colleague_2.working_hrs = 8
# link_to() method can build links between agents and/or cells
poor_guy.link.to(colleague_1, "colleague")
poor_guy.link.to(colleague_2, "colleague")
poor_guy.link.get("colleague")
<ActorsList: (2)InvolutingActor>
# report the average working hours of its colleagues.
poor_guy.avg_working_hrs()
8.0
After this entity perceives the average working hours of colleagues, it will conduct an evaluation. We can use the decorator class method making
provided in the previously created decision OverWork
to decorate the evaluation function. In this way, the output of this function will serve as the current strategy for that decision.
class InvolutingActor(Actor):
"""A poor guy who has to work harder..."""
# ... other methods ...
@OverWorking.making
def feel_peer_pressure(self):
"""Feel stressful when others work harder than self."""
return self.working_hrs <= self.avg_working_hrs()
Decision making (Responses)¶
Once a decision is made by the subject, a response (i.e., "action" in practical terms) will be generated. We have provided another decorator response
, and users can use this decorator to logically specify what kind of action is produced under which strategy for which decision. It determines how agents respond to their decisions. This can involve moving to a new location, altering their state, or interacting with other entities. Here, let our class add a simple response when it "feels peer pressure" -i.e., work one hour more...:
class InvolutingActor(Actor):
"""A poor guy who has to work harder..."""
# ... other methods ...
@response(decision="over_working", strategy=True)
def work_harder(self):
"""Work harder."""
self.working_hrs += 1
Integrating the methods of the above-mentioned methods, we will get such a custom actor (agent) class InvolutingActor
(in Chinese it is called "内卷的人", that is, a person who has to consume his own energy due to competitive pressure but does not make substantial progress.)
class InvolutingActor(Actor):
"""A poor guy who has to work harder..."""
__decisions__ = OverWorking
def __init__(self, *args, working_hrs: float = 7.0, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.working_hrs = working_hrs
self.overwork = False
@perception
def avg_working_hrs(self) -> float:
"""The average wealth of acquaintances."""
acquaintance = self.link.get("colleague")
return acquaintance.array("working_hrs").mean()
@OverWorking.response(strategy=True)
def work_harder(self):
"""Work harder."""
self.working_hrs += 1
@OverWorking.making
def feel_peer_pressure(self):
"""Feel stressful when others work harder than self."""
return self.working_hrs <= self.avg_working_hrs()
Let's see what's happening now? We can use the decision.making()
method of the actor, so that the subject will automatically complete the "perception->evaluation->response" framework. That is, this person involved in intense competition has automatically increased his working hours (from 7 to 8).
agents = model.agents.new(InvolutingActor, 3)
poor_guy, colleague_1, colleague_2 = agents
poor_guy.working_hrs = 7
colleague_1.working_hrs = 8
colleague_2.working_hrs = 7
# link_to() method can build links between agents and/or cells
poor_guy.link.to(colleague_1, "colleague")
poor_guy.link.to(colleague_2, "colleague")
# decision making...
poor_guy.decisions.making()
poor_guy.working_hrs
8
Using decisions.making
will automatically check all the decisions set by the subject and implement the "perception->evaluation->response" framework. If the decision result does not meet the conditions for generating a response, it will not respond. Look, when this poor person's working hours are no longer lower than the average of those around him, he no longer actively increases his own working hours from 8 to 9:
poor_guy.decisions.making()
poor_guy.working_hrs
8