/* heat.c Use PVM to solve a simple heat diffusion differential equation, using 1 master program and 5 slaves. The master program sets up the data, communicates it to the slaves and waits for the results to be sent from the slaves. Produces xgraph ready files of the results. */ #include "pvm3.h" #include <stdio.h> #include <math.h> #include <time.h> #define SLAVENAME "heatslv" #define NPROC 5 #define TIMESTEP 100 #define PLOTINC 10 #define SIZE 1000 int num_data = SIZE/NPROC; main() { int mytid, task_ids[NPROC], i, j; int left, right, k, l; int step = TIMESTEP; int info; double init[SIZE], solution[TIMESTEP][SIZE]; double result[TIMESTEP*SIZE/NPROC], deltax2; FILE *filenum; char *filename[4][7]; double deltat[4]; time_t t0; int etime[4]; filename[0][0] = "graph1"; filename[1][0] = "graph2"; filename[2][0] = "graph3"; filename[3][0] = "graph4"; deltat[0] = 5.0e-1; deltat[1] = 5.0e-3; deltat[2] = 5.0e-6; deltat[3] = 5.0e-9; /* enroll in pvm */ mytid = pvm_mytid(); /* spawn the slave tasks */ info = pvm_spawn(SLAVENAME,(char **)0,PvmTaskDefault,"", NPROC,task_ids); /* create the initial data set */ for (i = 0; i < SIZE; i++) init[i] = sin(M_PI * ( (double)i / (double)(SIZE-1) )); init[0] = 0.0; init[SIZE-1] = 0.0; /* run the problem 4 times for different values of delta t */ for (l = 0; l < 4; l++) { deltax2 = (deltat[l]/pow(1.0/(double)SIZE,2.0)); /* start timing for this run */ time(&t0); etime[l] = t0; /* send the initial data to the slaves. */ /* include neighbor info for exchanging boundary data */ for (i = 0; i < NPROC; i++) { pvm_initsend(PvmDataDefault); left = (i == 0) ? 0 : task_ids[i-1]; pvm_pkint(&left, 1, 1); right = (i == (NPROC-1)) ? 0 : task_ids[i+1]; pvm_pkint(&right, 1, 1); pvm_pkint(&step, 1, 1); pvm_pkdouble(&deltax2, 1, 1); pvm_pkint(&num_data, 1, 1); pvm_pkdouble(&init[num_data*i], num_data, 1); pvm_send(task_ids[i], 4); } /* wait for the results */ for (i = 0; i < NPROC; i++) { pvm_recv(task_ids[i], 7); pvm_upkdouble(&result[0], num_data*TIMESTEP, 1); /* update the solution */ for (j = 0; j < TIMESTEP; j++) for (k = 0; k < num_data; k++) solution[j][num_data*i+k] = result[wh(j,k)]; } /* stop timing */ time(&t0); etime[l] = t0 - etime[l]; /* produce the output */ filenum = fopen(filename[l][0], "w"); fprintf(filenum,"TitleText: Wire Heat over Delta Time: %e\n", deltat[l]); fprintf(filenum,"XUnitText: Distance\nYUnitText: Heat\n"); for (i = 0; i < TIMESTEP; i = i + PLOTINC) { fprintf(filenum,"\"Time index: %d\n",i); for (j = 0; j < SIZE; j++) fprintf(filenum,"%d %e\n",j, solution[i][j]); fprintf(filenum,"\n"); } fclose (filenum); } /* print the timing information */ printf("Problem size: %d\n",SIZE); for (i = 0; i < 4; i++) printf("Time for run %d: %d sec\n",i,etime[i]); /* kill the slave processes */ for (i = 0; i < NPROC; i++) pvm_kill(task_ids[i]); pvm_exit(); } int wh(x, y) int x, y; { return(x*num_data+y); }
The heatslv programs do the actual computation of the heat diffusion through the wire. The slave program consists of an infinite loop that receives an initial data set, iteratively computes a solution based on this data set (exchanging boundary information with neighbors on each iteration), and sends the resulting partial solution back to the master process.
Rather than using an infinite loop in the slave tasks, we could send a special message to the slave ordering it to exit. To avoid complicating the message passing, however, we simply use the infinite loop in the slave tasks and kill them off from the master program. A third option would be to have the slaves execute only once, exiting after processing a single data set from the master. This would require placing the master's spawn call inside the main for loop of heat.c. While this option would work, it would needlessly add overhead to the overall computation.
For each time step and before each compute phase, the boundary values of the temperature matrix are exchanged. The left-hand boundary elements are first sent to the left neighbor task and received from the right neighbor task. Symmetrically, the right-hand boundary elements are sent to the right neighbor and then received from the left neighbor. The task ids for the neighbors are checked to make sure no attempt is made to send or receive messages to nonexistent tasks.