YCP Logo Assignment 3: Forest Fire Simulation

Due: Thursday, Feb 24th by 11:59 PM

Updated 2/15 - Added Simulation output section, filled in Deliverables and Grading sections.

Updated 2/24 - You can wait until after winter break to do the experiment to measure the speedup.

Getting Started

Download CS365_Assign3.zip. Extract the contents of the archive into a directory.

You will modify the source file ForestFire.c. You may also add additional header files and source files of your own.

Your Task

Implement a cellular automaton forest fire simulation using MPI.

figures/ffanim.gif

The animation above is based on a run of the simulation described below with the parameters w=128, h=128, numGen=24, pGrowth=.05, pLightning=.005, pBurn=.3. Yellow dots are lighting strikes, and red dots are burning trees.

Simulation parameters

The simulation has the following parameters:

  • size - the total number of parallel processes
  • w - the global number of columns of cells
  • h - the global number of rows of cells
  • M - the number of columns of processes
  • N - the number of rows of processes
  • numGen - the number of time steps to simulate
  • pGrowth - the probability that an empty cell with become a tree
  • pLightning - the probability that a tree will spontaneously become a burning tree
  • pBurn - the probability that a tree with a burning neighbor will become a burning tree

These parameters are specified via command line parameters to the runpar script. For example, the command

./runpar 20 258 90 5 4 24 .05 .005 .3

would run the simulation using a 258x90 grid, 20 processes (arranged as 5x4 grid), for 24 time steps, with pGrowth=.05, pLightning=.005, and pBurn=.3.

Each process can access these parameters through its command line arguments (as argv[1] through argv[9]). You may wish to use the CmdLine data type described in Lecture 5 to help process the command line arguments.

Simulation output

The output of the simulation should be a text file called simout.txt. It should be a picture of the state of the global simulation grid at the end of the simulation. Each row of characters in the file corresponds to one row of the global grid. Use the following characters to represent the grid cells:

. an empty cell
t a tree
* a burning tree

Rules

The cellular automaton is a w x h grid of cells.

At any given time, each cell is in one of the following states: empty, tree, or burning tree.

Initially (at time step 0), every cell of the grid is empty.

Dynamic behavior: at each time step of the simulation, a cell's next state is computed according to the simulation rules:

  • A burning tree becomes an empty cell
  • A tree with a burning neighbor becomes a burning tree with probability pBurn; note that this probability is checked once for each burning neighbor, so a tree with multiple burning neighbors is more likely to catch fire than a tree with a single burning neighbor
  • An empty cell becomes a tree (growth) with probability pGrowth
  • A tree with no burning neighbors becomes a burning tree (lightning strike) with probability pLightning

Implementation, Hints

Since many parts of the simulation will need access to the simulation parameters, it would be a good idea to define a struct type for them. E.g.:

// Simulation parameters
typedef struct {
        int w;             // number of columns of cells
        int h;             // number of rows of cells
        ...etc...
} Params;

Using the Params type, you can use a single object to store all of the simulation parameters, passing a pointer to the object to each function that will access them.

Each parallel process should use its rank to determine which rectangular region of the overall simulation grid it will simulate locally. It will also need to know the ranks of the processes responsible for the 8 neighboring regions.

You will probably want to develop a data type to represent a local grid of cells. As discussed in Lecture 4, the local grid should have a one row/column border on each side representing cells from the 8 neighboring regions.

At each time step, each parallel process should compute a new value for each cell. Note that you cannot directly update the cell values using a single array. The reason is that the rule where a tree may catch on fire if one of its neighbors is one fire. However, the decision about whether a cell is on fire at the next time step should be based exclusively on cells that are one fire in the current time step.

You may use the Grid datatype from Lab 2 as the means of storing data and communicating with neighboring processes.

Generating random numbers

You should use different seeds for the random number generators used by each parallel process. (If you don't, they will all generate the same sequence of values.)

Suggestion: each parallel process can add its rank to some arbitrary seed base value:

srandom(0x1234abcd + rank);

Several of the simulation rules are events that occur according to a random probability. For example, an empty cell turns into a tree with a probability determined by the simulation parameter pGrowth. You can determine such events in the program using code like

if (rand_prob() < params->pGrowth) {

where rand_prob() is a function that returns a floating-point value somewhere in the range 0..1. One possible implementation:

float rand_prob(void)
{
        long r = random();
        return (float) r / (float)RAND_MAX;
}

Deliverables

Update 2/24 - You can wait until after winter break to do the speedup experiment.

There are three deliverables:

  • The code
  • A spreadsheet containing data showing the speedup when running a simulation of a fixed size and number of time steps on increasing numbers of processors
  • Text document containing an interpretation of the speedup data

Speedup is defined as

sequential time / parallel time

where parallel time is the amount of time needed for the parallel program to complete execution on multiple processors, and sequential time is the amount of time needed for the program to complete execution on a single processor. For example: if the sequential time is 12 seconds, and the parallel time is 4 seconds, then the speedup is 3.

Speedup should be no greater than the number of processors. In the ideal case that the speedup is equal to the number of processors, then the computation is "perfectly" parallelized.

You should collect data for increasing numbers of processors: for example, you could collect execution times for 1, 2, 3, ..., n processors. Enter the data in a spreadsheet, and plot a graph showing the speedup as the number of processors increases. (The gnumeric spreadsheet application is available on the cluster head node.)

You should submit a short text document in which you interpret your speedup data. Does the speedup increase linearly with the number of processors, or is there a plateau? Is the observed speedup equal to or less than the number of processors? If the speedup is less than the number of processors, what do you think the reason is?

Grading

Your grade will be determined as follows:

  • Program

    • Division of work: 25%
    • Local computation: 20%
    • Communication: 25%
    • Output: 10%
  • Speedup data / graph: 10%

  • Speedup data interpretation: 10%

Submitting

From the directory containing your source files, spreadsheet, and text document, run the command

make submit

When prompted, enter your Marmoset username and password. You should see a message indicating that the submission was successfully uploaded to the server.

Important: You should log into the server and download your submitted files. Check to make sure that the files you submitted were the ones you intended. The server URL is

https://camel.ycp.edu:8443