Здесь представлена программа ПВМ, которая вычисляет температурную диффузию в некой среде - в данном случае это проводник. Уравнение, описывающие одномерную температурную диффузию тонкого проводника:
Дискретизация:
В результате получаем явную формулу:
Начальные и граничные условия:
- для всех ;
- для .
Псевдокодом для таких вычислений будет следующий:
t = t+dt;
a(i+1,1)=0;
a(i+1,n+2)=0;
for j = 2:n+1;
a(i+1,j)=a(i,j) + mu*(a(i,j+1)-2*a(i,j)+a(i,j-1));
end;
t;
a(i+1,1:n+2);
plot(a(i,:))
end
Присмотритесь к коду внимательнее. В программе heat.c массив solution будет удерживать результаты решений уравнения температурной диффузии на каждом шаге. Этот массив - в формате xgraph - будет "выходным" при завершении программы (xgraph - программа, предназначенная для вывода данных на графопостроитель). Сначала порождаются задачи heatslv. Далее, подготавливается исходный набор данных. Обратите внимание на то, что для конца проводника значением начальной температуры будет ноль.
Далее, четыре раза исполняется основная часть программы - каждый раз с новым значением . Для определения количества времени, прошедшего от начала вычислений данной фазы, используется таймер. Начальный набор данных рассылается задачам heatslv. При каждой посылке совместно с начальным набором данных передаются идентификаторы задач-соседей слева и справа. Задачи heatslv будут использовать эту информацию при граничных коммуникациях. (В противном случае, нужно использовать групповые вызовы ПВМ для отображения задач на сегменты проводника. При использовании групповых вызовов, можно избежать явных указаний идентификаторов задач ведомых процессов.)
После рассылки исходных данных, ведущий процесс просто ожидает результаты. Когда результаты поступают, они интегрируются в результирующую матрицу, вычисляется затраченное время и решение записывается в xgraph-файл.
Как только все четыре фазы завершены, а результаты сохранены, ведущая программа выводит на экран информацию о временных затратах и принудительно завершает ведомые процессы.
Программа-пример heat.c:
heat.c
Применение ПВМ для решения дифференциального уравнения, описывающего
простейшую температурную диффузию, с использованием 1 ведущей
программы и 5 ведомых
Ведущая программа формирует данные, посылает их ведомым и ожидает
результаты, возвращаемые ведомыми. Файлы с полученными результатами
готовы для использования программой xgraph
*/
#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;
/* регистрация в ПВМ */
mytid = pvm_mytid();
/* порождение ведомых задач */
info = pvm_spawn(SLAVENAME,(char **)0,PvmTaskDefault,"",
NPROC,task_ids);
/* создание исходного набора данных */
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;
/* четырехразовое выполнение с разными значениями дельты t */
for (l = 0; l < 4; l++) {
deltax2 = (deltat[l]/pow(1.0/(double)SIZE,2.0));
/*засекаем время*/
time(&t0);
etime[l] = t0;
/* передача исходных данных ведомым */
/* дополнение информацией о соседях - для реализации возможности
обмена граничными данными */
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);
}
/* ожидание результатов */
for (i = 0; i < NPROC; i++) {
pvm_recv(task_ids[i], 7);
pvm_upkdouble(&result[0], num_data*TIMESTEP, 1);
/* "обновление" решения */
for (j = 0; j < TIMESTEP; j++)
for (k = 0; k < num_data; k++)
solution[j][num_data*i+k] = result[wh(j,k)];
}
/* остановка формирования временных интервалов */
time(&t0);
etime[l] = t0 - etime[l];
/* получение выходных данных */
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);
}
/* вывод на экран информации о временных интервалах */
printf("Problem size: %d\n",SIZE);
for (i = 0; i < 4; i++)
printf("Time for run %d: %d sec\n",i,etime[i]);
/* принудительное завершение ведомых процессов */
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);
}
Вместо создания бесконечных циклов в ведомых задачах, можно передавать специальные сообщения с приказами о завершении. Однако, во избежание сложностей, связанных с обменом сообщениями, в ведомых задачах используются именно бесконечные циклы, а завершаются они - принудительно - из ведущей программы. Третьим вариантом может быть реализация с ведомыми, исполняющимися только один раз и самостоятельно завершающимися после обработки единственных наборов данных, полученных от ведущего. Если бы этот вариант был воплощен, то к полезной нагрузке добавилась бы бесполезная.
В течение каждого шага каждой фазы, происходит обмен граничными значениями температурных матриц. Сначала, левосторонние граничные элементы передаются задаче-соседу слева, и соответствующие граничные элементы принимаются от задачи-соседа справа. Затем, симметрично, правосторонние граничные элементы передаются правому соседу, а соответствующие - принимаются от левого. Идентификаторы задач-соседей проверяются с целью обеспечения гарантий того, что не возникнет попыток передать сообщения несуществующим задачам (или принять).
Программа-пример heatslv.c:
heatslv.c
Ведомые принимают исходные данные от хоста, обмениваются граничной
информацией с соседями и вычисляют изменение температуры проводника.
Это делается за несколько итераций - "под руководством" ведущего
*/
#include "pvm3.h"
#include <stdio.h>
int num_data;
main()
{
int mytid, left, right, i, j, master;
int timestep;
double *init, *A;
double leftdata, rightdata, delta, leftside, rightside;
/* регистрация в ПВМ */
mytid = pvm_mytid();
master = pvm_parent();
/* прием своих данных от ведущей программы */
while(1) {
pvm_recv(master, 4);
pvm_upkint(&lef, 1, 1);
pvm_upkint(&right, 1, 1);
pvm_upkint(×tep, 1, 1);
pvm_upkdouble(&delta, 1, 1);
pvm_upkint(&num_data, 1, 1);
init = (double *) malloc(num_data*sizeof(double));
pvm_upkdouble(init, num_data, 1);
/* копирование исходных данных в свой рабочий массив */
A = (double *) malloc(num_data * timestep * sizeof(double));
for (i = 0; i < num_data; i++) A[i] = init[i];
/* реализация вычисления */
for (i = 0; i < timestep-1; i++) {
/* обмен граничной информацией со своими соседями */
/* передача налево, прием справа */
if (left != 0) {
pvm_initsend(PvmDataDefault);
pvm_pkdouble(&A[wh(i,0)],1,1);
pvm_send(left, 5);
}
if (right != 0) {
pvm_recv(right, 5);
pvm_upkdouble(&rightdata, 1, 1);
/* передача направо, прием слева */
pvm_initsend(PvmDataDefault);
pvm_pkdouble(&A[wh(i,num_data-1)],1,1);
pvm_send(right, 6);
}
if (left != 0) {
pvm_recv(left, 6);
pvm_upkdouble(&leftdata, 1, 1);
}
/* выполнение вычислений данной итерации */
for (j = 0; j < num_data; j++) {
leftside = (j == 0) ? leftdata : A[wh(i,j-1)];
rightside = (j == (num_data-1)) ? rightdata : A[wh(i,j+1)];
if ((j == 0) && (left == 0))
A[wh(i+1,j)] = 0.0;
else if ((j == (num_data-1)) && (right == 0))
A[wh(i+1,j)] = 0.0;
else
A[wh(i+1,j)] =
A[wh(i,j)]+delta*(rightside-2*A[wh(i,j)]+leftside);
}
}
/* передача результатов назад ведущей программе */
pvm_initsend(PvmDataDefault);
pvm_pkdouble(&A[0],num_data*timestep,1);
pvm_send(master,7);
}
/* только при удачном исходе */
pvm_exit();
}
int wh(x, y);
int x, y;
{
return(x*num_data+y);
}