1 1 Методика визуализации данных

Одним из важных моментов при проведении численных экспериментов является визуализация полученных данных. Когда промежуточных результатов мало, например важно отобразить лишь факт окончания очередной итерации, можно ограничиться консольным (текстовым) выводом. В этом случае никаких особенных инструментов не требуется. Вывод даных осуществляется стандартными средствами языка. Так же в качестве отдельной задачи можно рассматривать вопрос обработки конечных результатов счета, сохраненных в файле. Для операционной ситемы Linux cуществует достаточно широкий спектр приложений, которые можно использовать для визуальной обработки данных, полученных в результате выполнения расчетной задачи. В качестве таких приложений можно упомянуть как различные электронные таблицы типа OpenOffice.org Calc, Kspread и др., так и широкий список специализированных пакетов a la всем известный Grapher.

Однако вопрос использования подобных средств визуализации выходит за рамки темы настоящей книги. Здесь мы рассмотрим методики оперативного визуального отображения данных непосредственно в процессе счета задачи.

Зачастую возникает необходимость визуализировать промежуточные результаты, полученные по окончании каждой отдельной итерации. Например при проведении численных экспериментов, связанных с решением задач электо-, газо- или термодинамики, может появиться желание посмотреть эволюцию исследуемого процесса во времени. Иногда полученная картинка может повлиять на решение о целесообразности дальнейшего счета. К примеру, если увиденная динамика процесса далека от ожидаемой, то это может свидетельствовать о возможных ошибках в численном алгоритме или неверных начальных и граничых условиях задачи.

Поскольку визуализация должна осуществляться непосредственно во время счета, использование отдельных специализированных пакетов графической обработки данных отпадает. Средства визуализации должны быть встроены в программу как часть численного алгоритма. В Linux существует огромное количество различных библиотек работы с графикой как в режиме консоли (SVGA), так и в среде X-Windows.

В операционной системе UNIX (в частности в Linux) существует одна особенность, которую следует учитывать при разработке программ, имеющих средства визуализации. Дело в том, что консоль кластера (главная машина кластера) не является персональным компьютером в полном смысле этого слова. Скорее всего пользователь, проводящий численное моделирование на кластере, не будет подключен к системе в течение всего времени счета задачи. Закрытие пользователем сеанса работы (logout) без снятия задачи со счета или приостановки задачи возможно только в том случае, когда задача выполняется в режиме демона. То есть у задачи отсутствует как консольный, так и графический вывод. Другими словами, задача выполняется в системе в фоновом режиме. В случае с PVM это означает, во-первых, отсуствие постоянно действующего графического интерфейса задачи, и, во-вторых, программа запущена командой spawn без перенаправления вывода в STDOUT на консоль. То есть использовать "spawn -> myprog" нельзя.

Когда программа запускается командой spawn без перенаправления, весь консольный вывод програмы записывается в log-файл системы PVM (например в /tmp/pvml.0). Таким образом пользователь, запустив программу, может безболезнено закончить сеанс работы с консолью, программа при этом будет продолжать работу. Мониторинг получаемых данных можно в любой момент осуществить с помощью стандартной команды операционной системы tail.

tail -f /tmp/pvml.0

Запустив эту команду, вы можете в реальном времени отслеживать изменение log-файла. Текст, добавляемый программой в конец log-файла, будет тут же отображаться у вас на текстовой консоли.

Несколько сложнее обстоит дело с процессом визуального отображения данных. Закрытие пользовательского сеанса в худшем случае приведет к аварийному останову программы, в лучшем к закрытию графического окна и к невозможности восстановить это окно при следующих сеансах работы.

Решить эту проблему можно двумя разными способами. Первый способ заключается в разнесении процессов счета и визуализации на две отдельные задачи. Первая запускается на исполнение средствами параллельной виртуальной машины, вторая является обычной пользовательской задачей, которая будет работать только тогда, когда это необходимо. В этом варианте графическая программа должна как-то получать от основной программы данные, подлежащие визуализации. Наиболее простым способом общения этих двух программ является их связь через файл, в который параллельная программа будет периодически (например после каждой итерации) записывать массив данных. Графическая программа, читая этот файл будет в цикле прорисовывать картинку, в соответствии с полученными данными.

Однако, использовать для подобного процесса обычные файлы нельзя, поскольку могут возникнуть непреодолимые сложности, связанные с синхронизацией операций чтения и записи в файл. Может произойти ситуация, когда графическая программа начала читать очередной блок данных, а программа счета в это время еще не закончила писать данные в файл. В этом случае весьма вероятен сбой. Вторая сложность заключается в объеме передаваемых через файл данных. Расчетная программа на каждой итерации может генерить достаточно большое количество данных, которые требуется визуализировать. При ограниченном объеме жестких дисков в короткое время файловая система может переполнится, что не приведет ни к чему хорошему.

В операционных системах UNIX имеется такое понятие, как "именованные каналы" или FIFO. Термин "FIFO" - это аббревиатура от английской фразы "first in, fisrt out". Другими словами именованный канал является буфером. Данные, которые в него попадают при записи, могут быть прочитаны из него в той же последовательности, причем прочитанные данные автоматически удаляются из FIFO. Поскольку в UNIX все, начиная с тектовых файлов и кончая сокетами и процессором, для пользователя являетя файлами, к которым можно обратится обычными средствами файлового доступа, определенными в используемом языке программирования, то и именованные каналы FIFO тоже суть файлы. Используя FIFO для обмена данных, мы автоматически решаем проблему синхронизации чтения/записи данных для наших двух программ и, кроме того, прочитанные и обработанные данные будут автоматически удалены из файловой системы, предотвращая ее переполнение.

Как уже было сказано, с точки зрения программиста, работа с файлами FIFO ничем не отличается от работы с обыкновенными файлами. Отличие заключается в методе создания такого файла. Для создания FIFO следует воспользоваться командой операционной системы mkfifo. Для этого в каталоге, где будет располагаться именованный канал, необходимо выполнить следующую команду:

mkfifo my_fifo_data

Параметром команды является имя создаваемого файла FIFO.

После этого в расчетной части программы, точнее в том ее процессе, который будет исполняться на консоли кластера, в локальной файловой системе которой мы создали наш файл, програмист должен организовать периодический сброс в этот файл необходимых для визуализации процесса блоков данных. Открытие файл FIFO в режиме write only и его закрытие может выполняться соответственно в самом начале и в самом конце программы. Оператор записи в файл будет находиться в теле цикла. В программе визуализации именованный канал также открывается и закрывается вне тела цикла. Открытие файла происходит в режиме read only.

Особенностью чтения данных из FIFO является отсутствие для программиста необходимости каким-либо образом организовывать процесс ожидания поступления новой порции данных. Если в программе управление передано оператору чтения до фактического появления в FIFO новой порции данных, выполнение программы автоматически приостанавливается до того момента, когда эти данные появятся в FIFO.

Следует однако отметить, что длительное отсутствие пользователя в системе и, соответственно, отсутствие в системе работающей программы визуализации, которая читает данные из FIFO, приводит к постепенному росту размера файла FIFO и, в конце концов, переполнению, файловой системы, как это происходило бы с обычным файлом.

Рассмотрим второй способ организации процесса визуализации данных. Поскольку с точки зрения операционной системы процесс, запущенный на узле параллельной виртуальной машины, является полноправной пользовательской задачей, то такая задача естественно может иметь графический нитерфейс. Другими словами программист имеет возможность заставить материнский процесс параллельной задачи открыть графическое окно и что-либо в этом окне нарисовать. В случае, если выбран такой способ визуализации, на нет необходимости использовать ресурсы файловой системы и исчезают проблемы, связаные с файловым доступом, о которых мы говорили выше.

Однако задача, имеющая графический интерфейс, не работает в режиме демона (фоновом режиме) и, соответственно, нет возможности безболезненно для этой задачи закрыть пользовательскую сессия (logout). Проблема эта решаемая. Поскольку закрытие графического окна у задачи переводит ее в фоновый режим, можно заставить нашу программу визуализировать данные только тогда, когда мы этого хотим. Вопрос в том, как сообщить программе, что ей надо открыть графический интерфейс и показать нам, что она нам такое насчитала. Самый простой способ сделать это - использовать файл-триггер, наличие которого будет сигнализировать программе, что графический интерфейс требуется, отсутствие же которого - есть сигнал программе закрыть графическое окно, если оно открыто. В FORTRANе вероятно будет проще использовать файл-триггер несколько по-другому: файл присутствует всегда, а сигналом к открытию/закрытию графического окна служит его содержимое. Наиболее просто управлять таким файл-триггером можно с помощью следующего простого скрипта:

#!/bin/sh
#
if [ ! -e ~/trigger.dat ]; then
     touch ~/trigger.dat
fi

SHOW_FLAG=`cat ~/trigger.dat`

if [ "${SHOW_FLAG}" = "0" ];  then
    echo -n 1 > ~/trigger.dat
else
    echo -n 0 > ~/trigger.dat
fi

Этот скрипт проверяет содержимое файла trigger.dat, находящегося в домашнем каталоге пользователя (при необходимости создавая этот файл), и меняет его содержимое в циклическом порядке: "0" меняет на "1" и наоборот. Программа должна периодически читать этот файл и в зависимости от прочитанных данных открывать или закрывать графическое окно.

Перейдем непосредственно к способам создания в программе графического окна и прорисовки в нем изображения. Как уже говорилось ранее, инструментов для работы с графикой существует огромное количество. Но, поскольку цель этой книги - показать наиболее простые способы воспользоваться всеми прелестями кластерной архитектуры, то позволю себе дать одну рекомендацию.

Существует одна очень простая в установке и использовании библиотека: G2. Эта библиотека имеет еще то преимущество, что может использоваться как в программах, написанных на C, так и в программах на FORTRANе. Получить эту библиотеку можно на сайте разработчиков http://g2.sourceforge.net или в разделе Download этого сайта. На момент написания книги текущая версия библиотеки была 0.49a. Библиотека распространяется в исходных кодах. Забыл упомянуть еще одно замечательное свойство библиотеки G2. Библиотека может с одинаковой легкостью использоваться как для прорисовки графики в окне X-Windows, так и для создания аналогичной графики в виде .png или .jpg - файлов. Последнее представляет интерес для создания последовательности слайдов из которых при желании можно сделать презентацию или фильм, сконфертировав их в avi или DivX. Так же с помощью этой библиотеки аналогично созданию графических файлов или прорисовки графики в окне создавать файлы в формате Enchanced PostScript, подготовленные для печати на любом принтере, понимающем PostScrip или, если печать ведется в Linux, вообще на любом принтере.

Для установки библиотеки G2 в вашей системе необходимо для начала распаковать архив с исходными кодами в каком-либо каталоге.

После этого, для установки библиотеки, вы должны воспользоваться сакраментальной последовательностью команд:

./configure
make depend
make
make install

Теперь рассмотрим практические вопросы программирования с использованием библиотеки G2 на языках C и FORTRAN. Подробно рассматривать ВСЕ функции, которыми вы можете пользоваться при создании графических изображений мы не будем, описания функций вы всегда можете посмотреть в прилагаемой к библиотеке документации. Вместо этого приведем два примера программ, которые в окне X-Windows размером 640x640 пикселей рисуют распределение некоей функции и кратко опишем использованные в программе функции.

Для конкретики предположим, что мы считаем некоторую двумерную газодинамическую задачу. Разностная сетка у нас имеет размер 640x640. По окончании каждой итерации счета мы получаем распределение плотности по пространству. Для отображения на картинке величины плотности в каждой ячейки нашей разностной сетки мы будем использовать 255 градаций серого цвета. Минимальная плотность будет рисоваться черным цветом, максимальная - белым. Конечно саму программу расчета дифференциальных уравнений мы писать не будем, вместо этого в том месте программы, где мы должны получить массив плотностей, запишем простой цикл, в котором этот массив будет заполнен неким осмысленным, но произвольным образом.

Итак, программа на языке С:

  1. #include <g2.h>
  2. #include <g2_X11.h>
  3.  
  4. main()
  5. {
  6. int d,i,x,y;
  7. //массив плотностей
  8. double q[640][640];
  9. //массив соответствующих цветов
  10. int colors[640][640];
  11. //специальный массив, в котором хранятся оттенки серого цвета
  12. int grays[255];
  13.  
  14. //откроем графическое окно размером 640x640
  15. d=g2_open_X11(640,640);
  16.  
  17. //подготавливаем 255 возможных оттенков серого
  18. for(i=0;i<255;i++)
  19. grays[i]=g2_ink(d,(double) (i/254.0),(double) (i/254.0),(double) (i/254.0));
  20.  
  21. ////////////////////////////////////////////////////////////
  22. //подготавливаем массив плотности (произвольным образом)
  23. //вместо чтения из fifo-файла или из pvm-сообщения заполняем массив вручную
  24. //в реальной задаче вместо этого куска кода будет
  25. //стоять один оператор чтения данных из файла или
  26. //операторы приема сообщения и распаковки его в массив
  27. for(x=0;x<640;x++) {
  28. for(y=0;y<640;y++) {
  29. q[x][y]=sqrt(abs((300*300)-(x*y)));
  30. }
  31. }
  32. ////////////////////////////////////////////////////////////
  33.  
  34. //вычисляем максимум и минимум плотности
  35. double min=q[0][0]; double max=q[0][0];
  36. for(x=0;x<640;x++) {
  37. for(y=0;y<640;y++) {
  38. if(min>q[x][y]) min=q[x][y];
  39. if(max<q[x][y]) max=q[x][y];
  40. }
  41. }
  42.  
  43. //вычисляем цвета пикселей
  44. for(x=0;x<640;x++) {
  45. for(y=0;y<640;y++) {
  46. //конформно отражаем распределение плотностей
  47. //к диапазону 0-254 и в качестве цвета пикселя
  48. //берем целую часть от полученной величины
  49. int c=(int) ((254*(q[x][y]-min))/(max-min));
  50. colors[x][y]=grays[c];
  51. }
  52. }
  53.  
  54. //рисуем распределение плотности в графическом окне X-Windows
  55. g2_image(d,0.0,0.0,640,640,&colors[0][0]);
  56.  
  57. //рисуем белый прямоугольник в котором расположим текст
  58. int color = g2_ink(d,1.0,1.0,1.0);
  59. g2_pen(d,color);
  60. g2_filled_rectangle(d,15.0,15.0,80.0,35.0);
  61.  
  62. //рисуем черным цветом подпись к картинке в
  63. //нарисованном белом прямоугольнике
  64. color = g2_ink(d,0.0,0.0,0.0);
  65. g2_pen(d,color);
  66. g2_string(d,20.0,20.0,"Test field");
  67.  
  68. //немножко поспим чтобы можно было полюбоваться
  69. //полученной картинкой
  70. sleep(10);
  71.  
  72. //закроем графическое окно
  73. g2_close(d);
  74. }
  75.  

Теперь сделаем тоже самое, но на языке FORTRAN:

  1. program draw
  2. integer i,x,y
  3. real*8 q(0:639,0:639)
  4. real*8 qmin,qmax
  5. real colors(0:639,0:639)
  6. real grays(0:255)
  7. real c
  8. real color
  9.  
  10. d=g2_open_vd()
  11. c откроем графическое окно размером 640x640
  12. d1=g2_open_X11(640.0, 640.0)
  13. call g2_attach(d, d1)
  14.  
  15. c подготавливаем 255 возможных оттенков серого
  16. do i=0,254
  17. grays(i)=g2_ink(d1,i/254.0,i/254.0,i/254.0)
  18. enddo
  19.  
  20. c подготавливаем массив плотности (произвольным образом)
  21. c вместо чтения из fifo-файла или pvm-сообщения заполняем массив вручную
  22. c в реальной задаче вместо этого куска кода будет
  23. c стоять один оператор чтения данных из файла или
  24. c операторы приема сообщения и распаковки его в массив
  25. do x=0,639
  26. do y=0,639
  27. q(x,y)=sqrt(abs((300.0*300.0)-(x*y*1.0)))
  28. enddo
  29. enddo
  30. c ////////////////////////////////////////////////////////////
  31.  
  32.  
  33. c вычисляем максимум и минимум плотности
  34. qmin=q(0,0)
  35. qmax=q(0,0)
  36. do x=0,639
  37. do y=0,639
  38. if(qmin.gt.q(x,y)) then
  39. qmin=q(x,y)
  40. endif
  41. if(qmax.lt.q(x,y)) then
  42. qmax=q(x,y)
  43. endif
  44. enddo
  45. enddo
  46.  
  47.  
  48. c вычисляем цвета пикселей
  49. do x=0,639
  50. do y=0,639
  51. c //конформно отражаем распределение плотностей
  52. c //к диапазону 0-254 и в качестве цвета пикселя
  53. c //берем целую часть от полученной величины
  54. c=((254*(q(x,y)-qmin))/(qmax-qmin))
  55. colors(x,y)=grays(c)
  56. enddo
  57. enddo
  58.  
  59. c рисуем распределение плотности в графическом окне X-Windows
  60. call g2_image(d1,0.0,0.0,640.0,640.0,colors)
  61. c call g2_line(d1,15.0,15.0,20.0,20.0)
  62.  
  63. c рисуем белый прямоугольник в котором расположим текст
  64. color = g2_ink(d1,1.0,1.0,1.0)
  65. call g2_pen(d1,color)
  66. call g2_filled_rectangle(d1,15.0,15.0,80.0,35.0)
  67.  
  68. c рисуем черным цветом подпись к картинке в
  69. c нарисованном белом прямоугольнике
  70. color = g2_ink(d1,0.0,0.0,0.0);
  71. call g2_pen(d1,color);
  72. call g2_string(d1,20.0,20.0,"Test field")
  73.  
  74.  
  75. call g2_flush(d1)
  76.  
  77. c немножко поспим чтобы можно было полюбоваться
  78. с полученной картинкой
  79. read (*,*) a
  80.  
  81. call g2_close(d1)
  82.  
  83. stop
  84. end
  85.  

В отличие от C-программы, в FORTRANе все массивы, использованные в параметрах G2-функций не целочисленные, а объявлены как REAL. Это главное отличие и его надо помнить. Второе отличие заключается в размерности массивов. В FORTRANе по-умолчанию индексы массивов начинаются с 1. Чтобы G2-функции правильно понимали данные, переданные из FORTRAN-программы, необходимо массивы объявлять с начальным индексом равным 0. Например "REAL A(0:99)" вместо "REAL A(100)".

Результатом работы обеих программ является картинка, показанная ниже.

Результат работы программы

В том случае, когда прорисовкой графики занимается материнский процесс параллельной задачи, алгоритм по которому постороен этот процесс может быть таким:

<инициализаруем параллельный процесс>
do <итерация>=1 to N
	<обмениваемся данными с дочерними процессами>
	if <флаг визуализации> = 1 then
		if <графическое окно не открыто> then
			<открываем графическое окно>
		endif
		<подготавливаем данные для прорисовки>
		<рисуем картинку>
	else
		if <графическое окно запущено> then
			<закрываем графическое окно>
		endif
	endif
enddo
<заканчиваем работу параллельного процесса>

Теперь опишем вкратце те функции, которые мы использовали в наших тестовых программах.

int dev = g2_open_X11(int width, int height) - C
dev=g2_open_X11(width,height) - FORTRAN

функция открывает X11 окно шириной width пикселей и высотой height пикселей, возвращает идентификатор устройства dev вывода, который используется в других функциях

void g2_close(int dev) - C
call g2_close() - FORTRAN

функция закрывает графическое устройство с идентификатором dev открытое предыдущей функцией

void g2_plot(int dev, double x, double y) - C
call g2_plot(dev, x, y) - FORTRAN

функция рисует точку (пиксель) цветм, установленным функцией g2_pen

void g2_filled_rectangle(int dev, double x1, double y1, double x2, double y2) - C
call g2_filled_rectangle(dev, x1, y1, x2, y2) - FORTRAN

функция рисует прямоугольник с координатами левого нижнего угла (x1,y1) и координатами правого верхнего угла (x2,y2), после чего заливает этот прямоугольник цветом, установленным функцией g2_pen

void g2_string(int dev, double x, double y, char *text) - C
call g2_string(dev, x, y, 'some text') - FORTRAN

функция рисует текст, заданный переменной text
позиция начала текста определяется координатами (x,y)
цвет текста устанавливается функцией g2_pen

void g2_image(int dev, double x, double y, int x_size, int y_size, int *pens) - C
call (dev, x, y, x_size, y_size, &pens[0][0]) - FORTRAN

функция заполняет прямоугольную область с координатами левого нижнего угла (x,y), шириной x_size и высотой y_size
цвета пикселей этой области задаются в двумерном массиве pens размерностью (x_size,y_size)

int color=g2_ink(int dev, double red, double green, double blue) - C
color=g2_ink(dev, red, green, blue) - FORTRAN

функция создает новый цвет определяемый RGB параметрами red,green,blue
значения этих параметров - действительные числа в диапазоне 0..1
функция возвращает индекс нового цвета color.

void g2_pen(int dev, int color) - C
call g2_pen(dev, color) - FORTRAN

функция устанавливает текущий цвет для рисования
цвет определяется индексом цвета color который получен с помощью функции g2_ink


Copyright © 1998-2011 Юрий Сбитнев