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:

  1. 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.

  1. Environment.actors returns a list of all actors stored in Environment.g:

[5]:
env.actors
[5]:
[]
  1. Environment.locations returns a list of all locations stored in Environment.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()

Finding neighbors and shared locations

From the perspective of an actor, it is very important to know with whom the actor is connected in the network. As Pop2net works with bipartite networks, technically actors are only connected with locations. As you might already know, actors can access all connected locations via actor.locations:

[41]:
actor.locations
[41]:
[<pop2net.location.Location at 0x73b66f9dd9d0>,
 <__main__.School at 0x73b66fe6bb60>,
 <__main__.World at 0x73b66fcfab10>]

As locations are mainly a helping tool to connect actors, we often want to know which other actors are connected to the same locations. Every actor can access those other actors who are at the same location via actor.neighbors():

[42]:
actor.neighbors()
[42]:
[<pop2net.actor.Actor at 0x73b66ffdcaa0>,
 <pop2net.actor.Actor at 0x73b66fc1ef00>,
 <pop2net.actor.Actor at 0x73b66ffabf20>]

If we want to find only those neighbors who share a specific type of location with the actor, we can use the argument location_labels to filter by types of locations. In the following we only want neighbors who share a location of the type School with the actor:

[43]:
actor.neighbors(location_labels=["School"])
[43]:
[<pop2net.actor.Actor at 0x73b66fc1ef00>]

By using different types of locations we can organize the relations of an actor to other actors and then access neighbors with a certain relation to the actor as we just did it above.

Sometimes we need to access all the locations an actor shares with another actor. This can be done using actor.shared_locations():

[44]:
actor2 = env.actors[1]

actor.shared_locations(actor2)
[44]:
[<pop2net.location.Location at 0x73b66f9dd9d0>,
 <__main__.School at 0x73b66fe6bb60>,
 <__main__.World at 0x73b66fcfab10>]

Actor.shared_locations() also has the argument location_labels to filter by the type of the location:

[45]:
actor.shared_locations(actor2, location_labels=["World"])
[45]:
[<__main__.World at 0x73b66fcfab10>]
[46]:
actor.shared_locations(actor2, location_labels=["School"])
[46]:
[<__main__.School at 0x73b66fe6bb60>]