The Pop2net basics
After the installation of Pop2net, the first thing we have to do is to import the package in order to use it:
[1]:
import pop2net as p2n
Environment
Almost everthing you can do in Pop2net starts with creating an instance of p2n.Environment
. You can imagine the Environment
as the world where all the entities we create live.
[3]:
env = p2n.Environment()
The Environment
has three very important attributes:
Environment.g
is a network-graph which stores all actors, locations and their relations to each other as a bipartite network:
[4]:
env.g
[4]:
<networkx.classes.graph.Graph at 0x73b738198170>
You never need to access Environment.g
yourself unless you are a very experienced Pop2net user. In most cases, anything you want to do with the network can be done using the convenient methods provided by Pop2net.
Environment.actors
returns a list of all actors stored inEnvironment.g
:
[5]:
env.actors
[5]:
[]
Environment.locations
returns a list of all locations stored inEnvironment.g
:
[6]:
env.locations
[6]:
[]
The agents: actors and locations.
In Pop2net, there are two primary entity types: actors and locations. If you’re here to build an agent-based model (as we hope), you might be wondering, “Where are the agents?” Typically in agent-based modelling, an agent is an autonomous, acting entity. In Pop2net, these active entities are called actors. A location, on the other hand, is a place an actor can visit, where they encounter other actors and establish connections. However, it’s not merely that locations exhibit
agency in Pop2net; from a software perspective, locations are agents in Pop2net as well. (This becomes clear when you integrate Pop2net with frameworks like Mesa or AgentPy: locations must explicitly inherit from mesa.Agent
or agentpy.Agent
, just like actors.) To clarify that both entity types are types of agents, we use the terms actors and locations.
Adding actors and locations to the environment
To bring the environment to life, we have to add actors and locations to the environment.
[7]:
# create an actor
actor = p2n.Actor()
# add the actor to the environment
env.add_actor(actor)
[8]:
# create a location
location = p2n.Location()
# add the location to the environment
env.add_location(location)
Let’s ensure that the entities have been added to the environment:
[9]:
env.actors
[9]:
[<pop2net.actor.Actor at 0x73b7381980e0>]
[10]:
env.locations
[10]:
[<pop2net.location.Location at 0x73b7381981d0>]
A quick look: Plotting a network graph
Often the best thing you can do to understand things is to look at them. In Pop2net, the NetworkInspector
helps you to understand the network you are building. For instance, using the NetworkInsepctor
, you can visualize the bipartite network of actors and locations:
[11]:
inspector = p2n.NetworkInspector(env)
inspector.plot_bipartite_network()
In the NetworkInspector’s network visualizations, actors are always depicted as circles, while locations are represented as squares. You can also hover over the nodes to view additional information about them. (By the way: the network visualization is just a wrapper around the great little network visualization tool BokehGraph.)
To be honest, the above is not really a network because there are only two nodes without any edge. However, the graph gives an intuitive overview of the entities in your environment.
There is also a method to plot the graph at the actor level:
[12]:
inspector.plot_actor_network()
And there is also a method to plot both graphs!
[13]:
inspector.plot_networks()
Creating edges between actors and locations
Our environment has one actor and one location. Let’s create an edge between those two entities by adding the actor to the location:
[14]:
location.add_actor(actor)
By the way: we could also let the actor
do the same action by running actor.add_location(location)
. And we could also let the environment do the work using Environment.add_actor_to_location(location=location, actor=actor)
.
Anyway, let’s have a look at the network graph:
[15]:
fig = inspector.plot_bipartite_network()
The attribute location.actors
shows all actors that are connected to this location:
[16]:
location.actors
[16]:
[<pop2net.actor.Actor at 0x73b7381980e0>]
The attribute actor.locations
shows all locations that are connected to the actor:
[17]:
actor.locations
[17]:
[<pop2net.location.Location at 0x73b7381981d0>]
Let’s add 3 more actors to the environment. This time, we’ll use env.add_locations()
to add multiple actors at once.
[18]:
env.add_actors([p2n.Actor() for _ in range(3)])
Now we have 4 actors in our environment:
[19]:
env.actors
[19]:
[<pop2net.actor.Actor at 0x73b7381980e0>,
<pop2net.actor.Actor at 0x73b66ffdcaa0>,
<pop2net.actor.Actor at 0x73b66fc1ef00>,
<pop2net.actor.Actor at 0x73b66ffabf20>]
Let’s add an additional location. But now we create our own location class School
which inherits from p2n.Location
. You will understand the purpose of this later.
[20]:
class School(p2n.Location):
pass
env.add_location(School())
Now, the environment has two locations:
[21]:
env.locations
[21]:
[<pop2net.location.Location at 0x73b7381981d0>,
<__main__.School at 0x73b66faf52e0>]
Let’s have a look at the bipartite network graph:
[22]:
inspector.plot_bipartite_network()
Because there is still only one edge, in the next step, we assign each actor to one location and make sure that each location has not more than 2 actors:
[23]:
for location in env.locations:
for actor in env.actors:
if len(location.actors) < 2 and len(actor.locations) == 0:
location.add_actor(actor)
Let’s look at the graph again:
[24]:
inspector.plot_bipartite_network()
Just because we can, let’s create some more edges by adding a single actor to all locations:
[25]:
actor = env.actors[0]
actor.add_locations(env.locations)
[26]:
inspector.plot_bipartite_network()
Here is the corresponding actor-level network graph:
[27]:
inspector.plot_actor_network()
Removing entities from the graph
So far, we only added actors to the environment, locations to the environment, actors to locations and locations to actors. Of course we can also remove things.
To remove the edge between an actor and a location, we can use Environment.remove_actor_from_location()
, Actor.remove_location()
, Actor.remove_locations()
, Location.remove_actor()
or Location.remove_actors()
. In the following example, we remove the edge between actor
and location
using Location.remove_actor()
:
[28]:
location.remove_actor(actor)
[29]:
inspector.plot_networks()
To remove actors or locations from the graph, we can use Environment.remove_actor()
and Environment.remove_location()
. In the following example, we remove actor
from the graph:
[30]:
env.remove_actor(actor)
[31]:
inspector.plot_networks()
Let’s put actor
back in the graph:
[32]:
env.add_actor(actor)
[33]:
inspector.plot_networks()
Connecting actors quickly
In Pop2net, actors are considered connected if they share a location. Thus, in order to connect actors, we first have to create a location first and then assign the actors to this location.
The methods Actor.connect()
and Environment.connect_actors()
simplify that process by creating an instance from a given location class and directly assigning the given actors to this location instance. If no location class is set as the argument location_cls
, p2n.Location
is used as a default location class. In the following, we connect actor
with the second actor of the environment env.actors[1]
using actor.connect()
. Under the hood, Pop2net creates one new location
instance and assigns both actors to this location.
[34]:
actor.connect(actor=env.actors[1])
[35]:
inspector.plot_networks()
Let’s connect those two actors also via the location type School
:
[36]:
actor.connect(actor=env.actors[1], location_cls=School)
[37]:
inspector.plot_networks()
Using Environment.connect_actors()
, we can connect more than two actors simultaneously. By setting actors = env.actors
and location_cls = World
, all actors in the environment are assigned to a new instance of World
.
[38]:
class World(p2n.Location):
pass
[39]:
env.connect_actors(actors=env.actors, location_cls=World)
[40]:
inspector.plot_networks()