Числовой инструментарий, часть III |
В первой и второй частях этой мини-серии о математических инструменах от GNU/Linux речь шла о таких довольно "сухих" предметах, как манипуляции матрицами и циклы for. Эта часть добавляет на экран многоцветье, т.к. мы переходим к описанию графических возможностей GNU/Octave, Scilab и Tela. Вынуждено отойдем от принятого в предыдущих частях стиля изложения: описывать все три программы вместе. Дело в том, что "графические движки", стоящие за каждой программой, рознятся столь существенно, что их работу нельзя описать "одним блоком", отделавшись несколькими замечаниями по поводу различий. Тем не менее, статья начинается с введения, имеющего равное отношение сразу ко всем прогрммам. Для максимизации удобства читателя, все три программы будут решать три одинаковые проблемы из реальной жизни, что облегчит сравнение несмотря на имеющиеся различия в реализации. В последующих разделах Octave, Scilab и Tela придется справляться с предложенными задачами. Введение
ЗадачиДля того, чтобы соревнование программ было интересным, я составил задачи, не слишком отличающихся от тех, которые ставит Жизнь(tm). Каждой из трех программ придется справится с построением графиков трех разных видов:
Все графики должны быть выведены на два устройства,
OctaveВы знакомы с Gnuplot? Тогда использование создание графиков в GNU/Octave
Вы освоите играючи. Все, что понадобится -- добавить " Octave: двухмерный график дискретных данныхДля облегчения обсуждения я разбил код на блоки. В каждом блоке сгруппированы связанные по смыслу команды, а сам блок помечен цифрой в квадратных скобках для того, чтобы в дальнейшем было легче на него ссылаться. ### [1] Считать данные из set_i в матрицу размерности N_i-times-2 set1 = load("l1.ascii"); set2 = load("l2.ascii"); set3 = load("l3.ascii"); ### [2] Переинициализирвать Gnuplot graw("reset;"); clearplot; ### [3] Определить оформление графика ### во избежания проблем с кириллицей при выполнении примеров ### английские надписи не переводятся, а поясняются кое-где в комментариях, ### если эти комментарии будут мешать -- уберите их ### попытка вывода кириллических подписей ### оставляется в качестве самостоятельного упражнения читателю:) ### само собой, типа примечание переводчика:) gset title "Comparison of sets L1, L2, and L3"; ### Сравнение наборов данных L1, L2 и L3 gset xlabel "Temperature / K"; ### Температура, Кельвины gset ylabel "Voltage / V"; ### Напряжение, Вольты gset key top left; gset xrange [0 : 100]; gset yrange [8e-8 : 2e-6]; ### [4] Отрисовка графика hold on; ### Набор L1 gplot set1 title "Set L1" with points; ### Набор L2 gplot set2 title "Set L2" with points; ### Набор L3 gplot set3 title "Set L3" with points; hold off; ### [5] Переключаемся на вывод графика в файл PostScript gset terminal push; gset terminal postscript; gset output "oct1.eps"; replot; gset terminal pop; gset output; Блок [1] должен быть понятен на основе материала предыдущих статей. Взаимодействие с Gnuplot в первый раз происходит в блоке [2], где Gnuplot приводится в заранее известное состояние и X11-окно очищается для отрисовки графика. Заранее известное состояние полезно при экспериментировании с настройками Gnuplot. В нашем случае переинициализация не требуется, но и вреда от нее никакого. Смысл блока [3] из команд gset key top left что означает "поместить легенду (или "ключи" [key]
внутрь области отрисовки графика, в ее северо-западный угол".
Аргументы Теперь данные могут быть изображены на графике [4]. Поскольку размеры
наборов данных различны, их нельзя объединить в одной матрице. Следовательно,
каждый набор приходится отображать самостоятельно, а Gnuplot должен
быть проинформирован -- командой gplot set1 Название кривых, которое должно появится в легенде, также передается
в вызове Блок [5] типичен для "механизма" работы Gnuplot. Для
выполнения каждой команды отрисовки он использует терминал:
параметр Octave: Трехмерный график функции### [1] Определение функции function z = f(u, v) ## ограниченная [truncated] функция Rosenbrock'а z = 100.0*(v - u.^2).^2 + (1.0 - u).^2; zv = z(:); zv(find(zv > 100.0)) = 100.0; z = reshape(zv, size(z)); endfunction ### [2] Простая функция f() x = linspace(-3, 3, 40); y = linspace(-2, 4, 40); [xx, yy] = meshgrid(x, y); z_splot = splice_mat(xx, yy, f(xx, yy)); ### [3] Переинициализация Gnuplot graw("reset;"); clearplot; ### [4] Определим на графике украшения и пояснения gset data style line; gset title "Rosenbrock Function"; gset xlabel "u"; gset ylabel "v"; gset view 30, 160; gset hidden; gset nokey gset parametric; ### [5] Отрисуем график gsplot z_splot; ### [6] Переключимся на вывод в файл PostScript gset terminal push; gset terminal postscript; gset output "oct2.eps"; replot; gset terminal pop; gset output; gset noparametric; system("gzip --best --force oct2.eps"); Блок [1], как всегда, несложен для понимания (после прочтения 1-ой
и 2-ой частей этой мини-серии) и содержит исключительно известные
функции. В противоположность ему, блок [2] вводит в обиход парочку
новых. С помощью нашего друга linspace() мы легко создаем
два вектора, задающих точки для вычисления функции f(u, v):
из векторов x и y создается "решетка"
[grid], после чего в матрицах xx и yy каждая пара
элементов (xx(i, j), yy(i, j)), 1 <= i,j <= 40
задает точку (решетки), в которой будте вычисляться функция z = f(u,
v). Матрица всех значений z на точках решетки определяется,
как Пользовательская функция splice_mat() делает как раз то, что нужно: составляет из описывающих решетку xx и yy и значений z матрицу z_plot. Эта матрица уже может быть передана gsplot без хлопот:) (исходя из предположения, что Gnuplot находится в параметрическом режиме [parametric mode]. (А мы, конечно, будем именно в параметрическом режиме, поскольку для подобных задач только параметрический режим и годится (а я не хочу добавлять еще уровень вложенности скобок (ну правда!)))) Блок [4] представляет из себя набор вызовов
После такой тщательной подготовки команда блока [5] Блок [6] отрисовки в Postscript похож на сходный блок [5] из секции Octave: двухмерный график дискретных данных. Единственно, что еще делает блок [6] -- сжатие eps-файла с помощью gzip. Вообще, вызов system("shell-commands") выполняет команды shell-commands в дочернем шелле. Совершенно очевидно, что это очень полезно для взаимодействия с такими внешними приложениями, как gzip(1). Octave: Контурный график функции### [1] Определение функций function z = g(u, v) z = exp(u) .* (4.0*u.^2 + 2.0*v.^2 + 4.0*u.*v + 2.0*v + 1.0); endfunction ### [2] Определение весовых функций для расстояния между изолиниями function y = pow_weight(x, n) ## Отображаем интервал X в себя, "взвешивая" возведением в степень N d = max(x) - min(x); y = d*((x - min(x))/d).^n + min(x); endfunction ### [3] Используемая функция g() x = linspace(-4.5, -0.5, 40); y = linspace(-1.0, 3.0, 40); [xx, yy] = meshgrid(x, y); zz = g(xx, yy); z_splot = splice_mat(xx, yy, zz); ### [4] Вычисление расстояние между изолиниями iso_levels = pow_weight(linspace(min(min(zz))*1.01, ... max(max(zz))*0.99, 12), 3.0); il_str = sprintf("%f,", iso_levels); il_str = il_str(1 : length(il_str)-1); # удаляем "," на конце ### [5] Переинициализация Gnuplot graw("reset;"); clearplot; ### [6] Определение оформления и пояснений gset data style line; gset title "Contour Plot of g(u, v)"; ### "Контурный график g(u, v)" gset xlabel "u"; gset ylabel "v"; gset contour base; gset nosurface; gset view 0, 0; eval(sprintf("gset cntrparam levels discrete %s", il_str)); ### "дискретные уровни cntparam в gset %s" gset parametric; ### [7] Отрисовка gsplot z_splot; ### [8] Переключение в режим PostScript и отрисовка в файл gset terminal push; gset terminal postscript; gset output "oct3.eps"; replot; gset terminal pop; gset output; gset noparametric; После проработки примеров с двухмерным графиком дискретных функций и графиком трехмерной функции, первые три блока [1-3] не должны вызвать много вопросов. В блоке [4], однако, я был вынужден прибегнуть к "фокусу", который покажет себя в блоке [6]. Хитрость в том, чтобы изобразить изолинии с помощью функции, определяемой пользователем. Значения этой функции и, следовательно, положение изолиний, не известны заранее. В Gnuplot предлагается несколько способов определения изолиний, например
автоматическое вычисление заданного числа (линейно распределенных)
контуров или задание минимального и максимального значения приращения
между двумя соседними контурами. Наша задача требует более общего
решения, поскольку изолинии неравномерно распределены по оси z.
Для изолиний с полностью произвольными значениями, Gnuplot предлагает
третий способ, с помощью команды gset cntrparam discrete z1, z2, ..., zN где z1, z2, ..., zN педставляют величины z-ой изолинии, заданные как числа с плавающей точкой. Таким образом, gset cntrparam discrete 0.2, 0.4, 0.8 является абсолютно правильным вызовом, в то время как z1 = 0.2 z2 = 0.4 z3 = 0.8 gset cntrparam discrete z1, z2, z3 не приведет ни к чему, кроме вывода сообщения о синтаксической ошибке gset cntrparam discrete iso_levels Запомните, Мы оказываемся в тупике, из которого нас может вывести лишь применение "малой магии". Если мы предложим Octave полную строку вызова gset, такую, в которую "интерполированы" значения вектора iso_levels (программисты на Perl дни на пролет заняты именно этим), то дело оказывается "в шляпе". Вот как делается это фокус: # Преобразуем массив iso_levels в строку разделенных запятыми значений. # Octave использует спецификатор формата повторно, если имеется больше # значений для вывода, чем спецификаторов преобразвания формата. # Не советую пробовать этот фокус с pritf из С :-) il_string = sprintf("%f,", iso_levels) # Удалим запятую после последнего значения в строке. il_string = il_string(1 : length(il_string)-1) # Применяем трюк с интерполяцией во второй раз gset_string = sprintf("gset cntrparam levels discrete %s", il_string); # Выполняем команду, сохраненную в переменной gset_string eval(gset_string); Для читетелей, не любящих абстрактные описания, привожу протокол выполнения (длинные строки были отредактированы для того, чтобы "вписаться" по ширине): octave:10> il_string = sprintf("%f,", iso_levels) il_string = 0.583444,0.592029,0.652120,0.815224,1.132847,1.656497, \ 2.437679,3.527900,4.978667,6.841486,9.167864,12.009307, octave:11> il_string = il_string(1 : length(il_string)-1) il_string = 0.583444,0.592029,0.652120,0.815224,1.132847,1.656497, \ 2.437679,3.527900,4.978667,6.841486,9.167864,12.009307 octave:12> gset_string = sprintf("gset cntrparam levels discrete %s", \ il_string) gset_string = gset cntrparam levels discrete 0.583444,0.592029, \ 0.652120,0.815224,1.132847,1.656497,2.437679,3.527900, \ 4.978667,6.841486,9.167864,12.009307 В этом скрипте не вводится временная переменная gset_string, вместо этого sprintf() посылает свой вывод напрямую eval(). Блок [6]: Gnuplot не блещет в отрисовке контуров. На самом деле
руководство по GNUPlot предлагает не использовать эту функцию напрямую.
Мы, тем не менее, продолжим, поскольу наш подход легче для понимания.
Три следующих вызова gset contour base # draw contours in the xy-plane gset nosurface # do not draw the surface's mesh gset view 0, 0 # view the xy-plane from above Блоки [7] и [8] очень похожи на те, которые мы уже видели в разделах Octave: двухмерный график дискретных данных и Octave: Трехмерный график функции. Дополнительные примеры графиков для Gnuplot можно найти на странице http://www.gnuplot.org/gnuplot/gpdocs/all2.htm ScilabА теперь поговорим о совершенно другом подходе... Преодолевая проблему сложности, которую мы обсуждали в
разделе о всеобъемлющей сложности, Scilab [читается Сай-лэб] идет
другим путем (не таким путем надо было идти -- прим. пер.:)))).
В противоположность Gnuplot, Scilab не проводит строгого разделения
между отрисовкой и конфигурированием [графической подсистемы], предлагая
вместо этого (запустите scilab, а потом попробуйте команду Для тех, кто хотел бы взглянуть онлайновую справку, не устанавливая Scilab, руководство можно скачать из Сети. Scilab: двухмерный график дискретных данных// Как всегда, для предотвращения проблем с кириллицей, // аргументы функций не переводятся, а поясняются в комментариях. // [1] Считать наборы данных set_i в N_i-Х-2 матрицы set1 = read("l1.ascii", -1, 2); set2 = read("l2.ascii", -1, 2); set3 = read("l3.ascii", -1, 2); // [2] Очистка графического окна xbasc(); // [3] Нарисовать график; 1-я команда рисования определяет внешний вид графика plot2d(set1(:, 1), set1(:, 2), -1, "011", ... rect = [0, 8e-8, 100, 2e-6]); plot2d(set2(:, 1), set2(:, 2), -2, "000"); plot2d(set3(:, 1), set3(:, 2), -3, "000"); // [4] Зададим оформление xtitle(["Comparison of sets", "L1, L2, and L3"], ... "Temperature / K", "Voltage / V"); // "Сравниваем наборы L1, L2 и L3", "Температура/K", "Напряжение/V" legends(["Set L1 ", "Set L2 ", "Set L3 "], [-1, -2, -3], 2); // "Набор L2 ", и т.д. Справедливости ради следует заметить, // что в моей версии Scilab'а (2.4.5.что-то) из ALTLinux Spring 2001 // функции legends не оказалось ни в программе, ни в справке:( // но она точно появилась в версии 2.6, так что следите и обновляйтесь // дело того стоит. -- прим. переводчика // [5] Сохраняем содержимое окна с графиком в файл, преобразовав его в PostScript xbasimp(0, "sci1.xps"); unix("scilab -save_p sci1.xps.0 Postscript"); Блок [1] считывает данные из файлов в матрицы. Блок [2] очищает графическое окно (если оно уже есть). Важнее то, что xbasc() стирает все записанные в в окно графические команды. xbasc() расшифровывается, как [x-bas-c]: функции для работы с x11, базовый уровень, очистка [clear]. Намек: развлекаясь с функциями рисования графиков Scilab, очистка окна перед вызовом функций отрисовки часто экономит нажатие на клавиши: команда xbasc(); plot2d(...); написанная в одну строку может быть легко "возвращена" в для редактирования и вновь вызвана одним нажатием на клавишу return. А вот в блоке [3] тайное письмо появляется во всей своей красе! Что, в самом деле, означает следующая конструкция? plot2d(set1(:, 1), // вектор абсцисс set1(:, 2), // вектор ординат -1, // индеск стиля "011", // управление оформлением rect = [0, 8e-8, 100, 2e-6]); // размер графика Я добавил комментарии для каждого аргумента, но к чему относится "индекс стиля" со значением -1? К скверно одетому хакеру? И что означают строка "управления оформлением"?
Уф, немалый кусок отхватили! Блок [4] дает нам возможность отдохнуть. xtitle([title_line1; title_line2; ...], x_label, y_label) Добавляет к существующему графику заголовок (возможно из нескольких строк) и при необходимости украшает оси x и y текстовыми обозначениями. legends([legend_1, legend_2, ...], [style_1, style_2, ...], position) Помещает на график легенду в соответствии с указаниями, получаемыми в параметре position. Значение position = 1 указывает на "северо-восточный" угол. Остальные углы нумеруются по часовой стрелке, так что position = 2 поместит легенду в "северо-западный" угол. Параметры style_i имеют тот же смысл. что и числа, которые мы использовали в индексе стиля при вызове plot2d(). Они определяют то, какой тип маркетов или линий рисуется справа от текста легенды. Сам текст указывается в legend_i. И наконец блок [5] преобразует данные графического окна в Postscript-файл. Команда xbasimp(0, "sci1.xps") заново проигрывает все графические команды для окна с номером 0 (а это как раз наше единственное графическое окно) в файл sci1.xps. Название функции не имеет ничего общего с демоном X11 по имени "bas"', а происходит от x11 и basic, как в случае xbasc(). Окончание "imp" происходит от французского "imprimer", что означает "печать". Из какого языка к нам пришло название "fsck"? Думаю, из языка N vcls. Файл sci1.xps содержит почти правильный Postscript, но не совсем. Его содержимое должно быть обработано командой Scilab scilab -save_p sci1.xps.0 Postscript которая добавляет правильный заголовок Postscript и перемещает график на странице. "Внешний" Scilab вызывается функцией unix("комманды шелла"). Scilab: трехмерный график функции// [1] Определим функцию function z = f(u, v) // ограниченная [truncated] функция Rosenbrock'а z = 100.0*(v - u.^2).^2 + (1.0 - u).^2 z(find(z > 100)) = 100; endfunction // [2] Зададим сетку для вычисления f() x = linspace(-3, 3, 40); y = linspace(-2, 4, 40); // [3] Очистим содержимое графического окна xbasc(); // [4] Отрисовка fplot3d(x, y, f, 65, 1.5); // [5] Займемся украшательсвом xtitle("Rosenbrock Function", "u", "v"); // [6] Сохраним содержимое окна в файле; преобразуем в PostScript xbasimp(0, "sci2.xps"); unix("scilab -save_p sci2.xps.0 Postscript; " .. + "gzip --best --force sci2.eps"); После обилия нового материала в разделе Scilab: двухмерный график дискретных данных в этом разделе все новости прячутся в блоке [4]: fplot3d(x_vector, y_vector, function_of_x_and_y, alpha, theta) Векторы x_vector и y_vector определяют "сетку", на которой вычисляется функция function_of_x_and_y. Сравнивая fplot3d() с gsplot из Octave, можно заметить, что Scilab создает "решетку" [grid] за нас. fplot3d() -- это надстройка, созданная для удобства над plot3d(x_vector, y_vector, z_matrix, alpha, theta) которая, в свою очередь, уже напоминает gsplot (и mesh() из Tela). Параметры theta и alpha задают высоту и поворот (относительно оси z) камеры [view point] (Чертежники, возможно, знают другое название для view point, но в трехмерке это обычно называется "камера" (в смысле -- видеокамера) -- прим. пер.). Scilab: контурный график функции// [1] Определим функцию function z = g(u, v) z = exp(u) .* (4.0*u.^2 + 2.0*v.^2 + 4.0*u.*v + 2.0*v + 1.0) endfunction // [2] Определим весовую функцию для расстояния между изолиниями function y = pow_weight(x, n) // Отобразим интервал в себя и "взвесим", возводя в степень. d = max(x) - min(x) y = d*((x - min(x))/d).^n + min(x) endfunction // [3] Зададим "решетку" для вычисления g() x = linspace(-4.5, -0.5, 40); y = linspace(-1.0, 3.0, 40); // [4] Вычислим g() на точках, заданных X и Y z = eval3d(g, x, y); // [5] Рассчитаем расстояния изолиний iso_levels = pow_weight(linspace(min(z)*1.01, max(z)*0.99, 12), 3.0); // [6] Очистим графическое окно xbasc(); // [7] Установим формат для изолиний, аннотаций и графика xset("fpf", "%.2f"); contour2d(x, y, z, iso_levels); // [8] Украшения для "Контурного графика g(u, v)" xtitle("Contour Plot of g(u, v)", "u", "v"); // [9] Сохраним графическое окно в файл; преобразуем в PostScript xbasimp(0, "sci3.xps"); unix("scilab -save_p sci3.xps.0 Postscript"); Помните трудности, которые мы испытывали, задавая определяемые пользователем изолинии в Octave/Gnuplot? В Scilab проблема решается без фокусов, магии или взяток. Так же, как в случае plot3d(), Scilab определяет вспомогательную [convenience] функцию fcontour2d(x_vector, y_vector, function_of_x_and_y, levels) служащую "оберткой" для contour2d(x_vector, y_vector, z_matrix, levels) Но, поскольку нам необходимо знать максимальное и минимальное значение, принимаемое g(u, v) на "решетке", fcontour2d() ни в чем не облегчит нашей задачи. Так что в блоке [4] мы вычисляем g(u, v) на точках "сети", задаваемой векторами x и y: z = eval3d(g, x, y) В блоке [7] мы используем z и iso_levels из
блока [5]. Вызов xset() использует формат вывода числе
с плавающей запятой [floating point
format] ("fpf") в " Остальные блоки используют уже известные функции. На посвященном Scilab сайте INRIA можно найти дополнительные демонстрации возможностей построения графиков. Помимо проиллюстрированных выше функций, Scilab предовтавляет много возможностей. Например:
TelaВ разделах об Octave и Scilab
мы видели, что обе программы сохраняют вспомогательное "состояние"
окна отрисовки графика. В максимальной степени это делает Gnuplot:
вызовы В Tela для создания графиков используется дополнительное приложение
PlotMTV. И PlotMTV, и Tela находятся на противоположном конце спектра:
графические окна не имеют сохраняемого состояния и все фактические
параметры создаваемого графика должны быть указаны прямо в вызове
фунции отрисовки. Преимуществом использованного в Tela подхода является
то, что несколько одновременно создаваемых графиков не мешают друг
другу (как мы скоро увидим, это происходит до тех пор, пока мы их
не заставим с помощью вызовов Как и при обсуждении других программ рисования, руководство по PlotMTV полезно иметь под рукой. Tela: двумерный график дискретных данных// [1] Считаем i-й набор данных в матрицу N_i на 2 set1 = import1("l1.ascii"); set2 = import1("l2.ascii"); set3 = import1("l3.ascii"); // [2] Определение функции отрисовки графиков function do_plot(d1, d2, d3) { hold(on); // задержим фактическую отрисовку до вызова hold(off) // отрисовка 1-го набора данных plot(d1[:, 1], d1[:, 2], "linestyle", 0, "markertype", 2, "linelabel", "Набор данных L1", // Займемся украшениями "toplabel", "Сравнение наборов данных", "subtitle", "L1, L2, и L3", "xlabel", "Температура / K", "ylabel", "Напряжение / V", // Определим область отрисовки "xmin", 0, "xmax", 100, "ymin", 8e-8, "ymax", 2e-6); // отрисовка 2-го набора данных plot(d2[:, 1], d2[:, 2], "linestyle", 0, "markertype", 3, "linelabel", "Набор данных L2"); // отрисовка 3-его набора данных plot(d3[:, 1], d3[:, 2], "linestyle", 0, "markertype", 4, "linelabel", "Набор данных L3"); hold(off); // plot! }; // [3] Отрисовка в окно X11 do_plot(set1, set2, set3); // [4] Отрисовка в файл postscript plotopt("-o tela1.eps -printcmd 'cat' -noxplot -print"); do_plot(set1, set2, set3); Мда, этот скрипт не похож на предыдущие! Именно так -- мне пришлось прибегнуть к новому приему для того, чтобы избежать проблем с отсутствием сохраняемых состояний в функциях отрисовки. Поскольку Tela "забывает" о параметрах после отображения графика в окне X11, для перенаправления вывода в файл Postscript мне пришлось бы "набивать" все по новой. Ну, а дублирование кода -- корень многих (но не всех:) зол. Поэтому пришлось "завернуть" отрисовку графиков в Tela в отдельную функцию. Дальше мы получаем изображение графика в окне X11, а затем, изменив выходной файл и его тип, снова выполним отрисовку, но уже в файл eps: do_plot(...); // вывод в окно X11 plotopt("-o foo.eps -printcmd 'cat' -noxplot -print"); // перенаправление вывода do_plot(...); // выполняем отрисовку в формате Encapsulated Postscript // в файл foo.eps Функция do_plot в блоке [2] берет на себя заботу об использовании функции plot(). Общая структура вызова plot(), а на самом деле всех функций для отрисовки графиков в Tela, такова: сначала передаем обязательные агрументы с "датосодержащими" векторами или матрицами, а затем помеченные ключами [key-value pairs] необязательные строки. plot(x_vector, y_vector // данные "option_key1", "option_value1", // 1-я опция "option_key2", "option_value2", // 2-я опция ... "option_keyN", "option_valueN"); // N-я опция Обратите внимания на то, что написание каждой пары ключ-значение в отдельной строке повышает читаемость вызова в целом. Строковые пары ключ-значение понятны сами по себе. Не вполне очевидные соответсвия вроде linestyle => 0 (= невидимой линии) или markertype => 2 (= крестики) необходимо предварительно найти в руководстве или в в примерах по созданию графиков, которые демонстрируют все доступные стили. Tela: трехмерный график функцииfunction v = linspace(a, b; n) { if (isdefined(n)) nn = n else nn = 100; v = a + (0 : nn - 1) * (b - a) / (nn - 1) }; // [1] определение функции function z = f(u, v) { // ограниченная [truncated] функция Rosenbrock'а z = 100.0*(v - u^2)^2 + (1.0 - u)^2; z[find(z > 100.0)] = 100.0; }; // [2] Вычисляем f() на заданных точках x = linspace(-3.0, 3.0, 40); y = linspace(-2.0, 4.0, 40); [xx, yy] = grid(x, y); zz = f(xx, yy); // [3] определение функции рисования function do_plot(x, y, zz) { mesh(zz, "xgrid", x, "ygrid", y, "toplabel", "Rosenbrock Function", "xlabel", "u", "ylabel", "v", "hiddenline", "true", "eyepos.z", 2.0) }; // [4] отрисовка в окно X11 do_plot(x, y, zz); // [5] отрисовка в файл postscript plotopt("-o tela2.eps -printcmd 'cat' -noxplot -print"); do_plot(x, y, zz); system("gzip --best --force tela2.eps"); "Зазипованная" версия файла Encapsulated Postscript (eps.gz) тоже имеется. В Tela нет встроенной функции linspace(), так что ее мне пришлось написать на скорую руку. Блок напоминает блок [2] в разделе Octave: трехмерный график функции, где meshgrid() из Octave заменен на grid() из Tela. Функция mesh(), рисующая трехмерный график, в качестве первого аргумента принимает матрицу z-значений, а спецификация "решетки" указывается в опциях: mesh(z_matrix, "xgrid", x_vector, "ygrid", y_vector); Понятно, что размерности z_matrix, x_vector y_vector и должны быть согласованы. Tela: Контурный график функцииfunction v = linspace(a, b; n) { if (isdefined(n)) nn = n else nn = 100; v = a + (0 : nn - 1) * (b - a) / (nn - 1) }; // [1] определение функции function z = g(u, v) { z = exp(u) * (4.0*u^2 + 2.0*v^2 + 4.0*u*v + 2.0*v + 1.0) }; // [2] весовые функции для определения расстояний // между излолиниями function y = pow_weight(x, n) { // Map interval X onto itself, weight with N-th power. d = max(x) - min(x); y = d*((x - min(x))/d)^n + min(x) }; // [3] Вычисляем f() в заданных точках x = linspace(-4.5, -0.5, 40); y = linspace(-1.0, 3.0, 40); [xx, yy] = grid(x, y); zz = g(xx, yy); // [4] Вычисляем расстояния между изолиниями iso_levels = pow_weight(linspace(min(zz)*1.01, max(zz)*0.99, 12), 3.0); il_str = sformat("``", iso_levels); il_str = il_str[2 : length(il_str)]; // [5] Определение функции, рисующией график function do_plot(x, y, zz, iso_levels_str) { contour(zz, "xgrid", x, "ygrid", y, "toplabel", "Контурный график g(u, v)", "xlabel", "u", "ylabel", "v", "contours", iso_levels_str) }; // [6] Отрисовка в окне X11 do_plot(x, y, zz, il_str); // [7] Отрисовка в postscript plotopt("-o tela3.eps -printcmd 'cat' -noxplot -print"); do_plot(x, y, zz, il_str); В Tela по отношению к PlotMTV приходится прибегать к тому же трюку, который мы использовали, убеждая Octave передать в Gnuplot значения уровдней для контуров. Однако трюк, используемый в блоке [4], не столь хитер. Как и раньше, на первом шаге мы вычисляем вектор с изо-уровнями. Функция sformat() работает аналогично функции sprintf() в Octave, хотя формат векторов различен, что видно из протокола выполнения (il_str отредактирован для того, чтобы уместиться в строке): >il_str = sformat("``", iso_levels); >il_str "#(0.583444, 0.592029, 0.65212, 0.815224, 1.13285, 1.6565, \ 2.43768, 3.5279, 4.97867, 6.84149, 9.16786, 12.0093)" PlotMTV понимает вектора, заключенные в скобки, но символ "решетки" нужно убрать il_str = il_str[2 : length(il_str)]; il_string может быть передана в функцию contour(),
как значения именованного параметра Дополнительные демонстрационные графики, созданные с помощью PlotMTV можно найти на ORNL и на HSC (у меня что-то не вышло сходить по этим ссылкам:( -- прим. пер.). Между прочим, PlotMTV прекрасно подходит на роль графического сервера [backend] для скриптов и программ "домашнего розлива". Руководство по PlotMTV неизменно помогает читателю в создании программных интерфейсов к собственным функциям. Заключительные комментарии по графикамМы узнали о том, как в Octave, Scilab и Tela получить графики функций или экспериментальных данных, готовые для отправки в редакцию. Иногда, из-за ограничений реализации, потребовались довольно хитроумные приемы. Нам, однако, удалось разрешить все проблемы. Мне кажется, что объем ручной работы, необходимой для создания действительно красивых графиков, всегда будет большим, вне зависимости от используемого программного обеспечения. Кристоф Шпиль [Christoph Spiel]Крис управляет консалтинговой компанией по открытому программному обеспечению в верхней Баварии, Германия. По своей специальности - физике -- у него есть докторская степень от Munich University of Technology -- его основные интересы лежат в области теории чисел, гетерогенных программных сред и программного инжиниринга. С ним можно связаться по адресу [email protected]. Copyright (C) 2001, Christoph Spiel.
|
Вернуться на главную страницу |