Basic Usage

This will walk you through setting up your first robot and complete simulation.

Test using built in examples

The examples are in the examples directory of the source code. In the near future, I’ll set up a way to run the examples directly when you install the package.

Creating a simple robot

For more detailed information about developing custom robots, see Make your own Robot.

To start, we will only need to make a simple robot based on the GridRobot. This needs to implement three methods:

  • receive_msg(): Code that is run when a robot receives a message

  • init(): Code that is run once when the robot is created

  • loop(): Code that is run in every step of the simulation

Create a file for your robot class. Let’s call it random_robot.py. Below is a simple Robot that moves randomly and changes direction every 10 seconds. You can copy this or directly download random_robot.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import random

from gridsim.grid_robot import GridRobot
import gridsim as gs


class RandomRobot(GridRobot):
    # Change direction every 10 ticks
    DIR_DURATION = 10

    def init(self):
        self.set_color(255, 0, 0)
        self._msg_sent = False

        # Next tick when Robot will change direction
        self._next_dir_change = self.get_tick()

    def receive_msg(self, msg: gs.Message, dist: float):
        # This robot got a message from another robot
        self._msg_sent = True

    def loop(self):

        # Change direction every DIR_DURATION ticks
        tick = self.get_tick()
        if tick >= self._next_dir_change:
            new_dir = random.choice(GridRobot.DIRS)
            self.set_direction(new_dir)
            self._next_dir_change = tick + RandomRobot.DIR_DURATION

        # Broadcast a test message to any robots nearby
        msg = gs.Message(self.id, {'test': 'hello'})
        self.set_tx_message(msg)

        # Sample the environment at the current location
        c = self.sample()

        # Change color depending on whether messages have been sent or received
        # Robot will be white when it has successfully sent & received a message
        blue = 255 * self._msg_sent
        # self.set_color(255, green, 0)
        self.set_color(255-c[0], 255-c[1], blue)

A minimal simulation example

To run a simulation, you need to create a couple of robots, place them in a World. Then you call the step() method to execute you simulation step-by-step. step() will handle running all of the robots’ code, as well as communication and movement.

We also want give our Robots something to sense by adding en environment to the World. An environment here is represented with an image. (You’ll see what this looks like in the next step.) In each cell, the Robots can sense the color of the cell (i.e., the RGB pixel value) at that location with the sample() method. If you set up the environment with an image whose resolution doesn’t match the grid dimensions, it will be rescaled, possibly stretching the image. To avoid any surprises, you should use an image whose resolution matches your grid dimensions (e.g., for a 50 × 50 grid, use a 50px × 50px image).

Use the code below or download minimal_simulation.py and the example environment ex_env.png.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import gridsim as gs

from random_robot import RandomRobot


def main():
    grid_width = 50  # Number of cells for the width & height of the world
    num_robots = 5
    num_steps = 100  # simulation steps to run

    # Create a few robots to place in your world
    robots = []
    for n in range(num_robots):
        robots.append(RandomRobot(grid_width/2 - n*2,
                                  grid_width/2 - n*2))

    # Create a 50 x 50 World with the Robots
    world = gs.World(grid_width, grid_width,
                     robots=robots,
                     environment="ex_env.png")

    # Run the simulation
    for n in range(num_steps):
        # Execute a simulation step
        world.step()
        # To make sure it works, print the tick (world time)
        print('Time:', world.get_time())

    print('SIMULATION FINISHED')


if __name__ == '__main__':
    # Run the simulation if this program is called directly
    main()

With these files and random_robot.py in the same directory, and gridsim installed, you should be able to run the code with:

$ python3 minimal_simulation.py

Adding the Viewer

With that simple example, you have no way to see what the robots are doing. For that, we add a Viewer. This requires adding only two lines of code to our minimal simulation above.

Use the code below or download viewer_simulation.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import gridsim as gs

from random_robot import RandomRobot


def main():
    grid_width = 50  # Number of cells for the width & height of the world
    num_robots = 5
    num_steps = 100  # simulation steps to run

    # Create a few robots to place in your world
    robots = []
    for n in range(num_robots):
        robots.append(RandomRobot(grid_width/2 - n*2,
                                  grid_width/2 - n*2))

    # Create a 50 x 50 World with the Robots
    world = gs.World(grid_width, grid_width,
                     robots=robots,
                     environment="ex_env.png")

    # Create a Viewer to display the World
    viewer = gs.Viewer(world)

    # Run the simulation
    for n in range(num_steps):
        # Execute a simulation step
        world.step()

        # Draw the world
        viewer.draw()

        # To make sure it works, print the tick (world time)
        print('Time:', world.get_time())

    print('SIMULATION FINISHED')


if __name__ == '__main__':
    # Run the simulation if this program is called directly
    main()

Notice that adding the Viewer slows down the time to complete the simulation, because the display rate of the Viewer limits the simulation rate. If you want to run lots of simulations, turn off your Viewer.

Using configuration files

Gridsim also provides the ConfigParser for using YAML configuration files. This simplifies loading parameters and (as described in the next section) saving parameters with simulation results data.

The ConfigParser is un-opinionated; it doesn’t place any restrictions on what your configuration files look like, as long as they’re valid YAML files.

Compared to our minimal_simulation.py, we only need one line to create our ConfigParser, from which we can retrieve any parameter values.

Use the code below or download config_simulation.py and YAML configuration file simple_config.yml.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import gridsim as gs

from random_robot import RandomRobot


def main():
    config = gs.ConfigParser('simple_config.yml')
    print(config.get('name'))
    grid_width = config.get('grid_width')
    num_robots = config.get('num_robots')
    # You can specify a default value in case a parameter isn't in the
    # configuration file
    num_steps = config.get('num_steps', default=100)

    # Create a few robots to place in your world
    robots = []
    # Configuration values can also be lists, not just single values.
    x_pos = config.get('robot_x_pos')
    for n in range(num_robots):
        robots.append(RandomRobot(x_pos[n],
                                  grid_width/2 - n*2))

    # Create a 50 x 50 World with the Robots
    world = gs.World(grid_width, grid_width, robots=robots)

    # Run the simulation
    for n in range(num_steps):
        # Execute a simulation step
        world.step()
        # To make sure it works, print the tick (world time)
        print('Time:', world.get_time())

    print('SIMULATION FINISHED')


if __name__ == '__main__':
    # Run the simulation if this program is called directly
    main()

Logging data

Gridsim has a built-in Logger, designed to easily save data from your simulations to HDF5 files. This allows you to store complex data and simulation configurations together in one place. HDF5 files are also easy to read and write in many different programming languages.

There are three main ways to save data to your log files:

  • Save the parameters in your configuration with log_config(). (Note that not all data types can be saved with log_config. See its documentation for more details.)

  • Save a single parameter (that’s not in your configuration file) with log_param()

  • Save the state of your simulation/robots with log_state(). (This requires some setup.)

In order to log the state of the World, you first need to tell the Logger what you want to save about the log_state, this function is called and the result is added to your dataset. You can add as many aggregators as you want, each with their own name.

We can extend our config_simulation.py to show the three types of logging described above. Use the code below or download logger_simulation.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import gridsim as gs
from typing import List
import numpy as np
from datetime import datetime

from random_robot import RandomRobot


def green_agg(robots: List[gs.Robot]) -> np.ndarray:
    """
    This is a dummy aggregator function (for demonstration) that just saves
    the value of each robot's green color channel
    """
    out_arr = np.zeros([len(robots)])
    for i, r in enumerate(robots):
        out_arr[i] = r._color[1]

    return out_arr


def main():
    config = gs.ConfigParser('simple_config.yml')
    print(config.get('name'))
    grid_width = config.get('grid_width')
    num_robots = config.get('num_robots')
    # You can specify a default value in case a parameter isn't in the
    # configuration file
    num_steps = config.get('num_steps', default=100)

    # Create a few robots to place in your world
    robots = []
    # Configuration values can also be lists, not just single values.
    x_pos = config.get('robot_x_pos')
    for n in range(num_robots):
        robots.append(RandomRobot(x_pos[n],
                                  grid_width/2 - n*2))

    # Create a 50 x 50 World with the Robots
    world = gs.World(grid_width, grid_width, robots=robots)

    # Logger
    trial_num = config.get('trial_num', default=1)
    # Create a logger for this world that saves to the `test.h5` file
    logger = gs.Logger(world, 'test.h5', trial_num=trial_num,
                       overwrite_trials=True)
    # Tell the logger to run the `green_agg` function every time that
    # `log_state` is called
    logger.add_aggregator('green', green_agg)
    # Save the contents of the configuration, but leave out the 'name' parameter
    logger.log_config(config, exclude='name')
    # Save the date/time that the simulation was run
    logger.log_param('date', str(datetime.now()))

    # Run the simulation
    for n in range(num_steps):
        # Execute a simulation step
        world.step()

        # Log the state every step
        logger.log_state()

        # To make sure it works, print the tick (world time)
        print('Time:', world.get_time())

    print('SIMULATION FINISHED')


if __name__ == '__main__':
    # Run the simulation if this program is called directly
    main()

Complete example

Most simulations will involve all of these components, and multiple trials. You can download a complete, detailed example here: complete_simulation.py, as well as a corresponding YAML configuration file: ex_config.yml

Here, the configuration file is used as a command line argument, so it’s easy to switch what configuration file you use. Run it like this:

$ python3 complete_simulation.py ex_config.yml