Журнал LinuxFormat - перейти на главную

LXF85:Maxima

Материал из Linuxformat
Перейти к: навигация, поиск
Учебник Maxima Максимум свободы символьных вычислений

Содержание

Графики и управляющие конструкции

ЧАСТЬ 5 Сегодня Тихон Тарнавский продемонстрирует вам «графические» возможности Maxima, а также безупречную работу конструкций if, for и других...
«А рисовать вы тоже умеете?»
«Рисовать? Кого-нибудь привлечем.»

Как мы уже говорили в прошлый раз, количество различных функций в Maxima разработчики постарались свести к минимуму, а широту размаха каждой конкретной функции, соответственно, к максимуму. Соблюдается эта тенденция и в функциях построения графиков: основных таких функций всего две, с очевидными, как всегда, названиями – plot2d и plot3d (одно из значений слова «plot» – «график», а аббревиатуры 2d и 3d переводятся как «двумерный» и «трехмерный»). Если говорить точнее, возможности графической отрисовки не встроены в Maxima, а реализованы посредством внешних программ, в чем и прослеживается пресловутый Unix-way: «одна задача – одна программа». По умолчанию, построением графиков занимается gnuplot, но кроме него есть разрабатываемый вместе с Maxima и идущий в ее же пакете openmath. Gnuplot необходимо установить (вручную либо автоматически – как зависимость Maxima) из пакета gnuplot-nox, либо просто gnuplot, а для работы openmath нужен командный интерпретатор wish, входящий обычно в пакет tk; и, начиная с версии 5.10.0, еще и xMaxima.

Теперь кратко – о возможностях. Начнем с plot2d. Кратчайший вариант ее вызова такой: plot2d(выражение, [символ, начало, конец]), где выражение задает функцию, график которой нужно построить, символ – неизвестное (он, понятное дело, должен быть единственным неопределенным символом, входящим в выражение), а начало и конец задают отрезок оси Х для построения графика; участок по оси Y в таком варианте записи выбирается автоматически, исходя из минимума и максимума функции на заданном промежутке. Обратите внимание, что неизвестное и концы промежутка нужно задавать не тремя отдельными параметрами, как, скажем, в integrate, а в виде списка. Это связано с тем, что plot2d может принимать еще и дополнительные аргументы – в таком случае они перечисляются следом за таким списком, что исключает всякую путаницу.

После вызова функции plot2d в таком варианте откроется окно gnuplot, в котором будет отображен затребованный график. Никакой интерактивной работы с полученным изображением gnuplot не предусматривает, кроме автоматического его масштабирования при изменении размеров окна. Насмотревшись вдоволь, можно закрыть окно с графиком клавишей Q, либо, в случае работы с Maxima в редакторе TeXmacs или wxMaxima, просто переключиться обратно в интерфейс, оставив окно gnuplot открытым, и продолжить работу:

LXF85 maxima01.png

LXF85 maxima02.png

В некоторых случаях автоматический подбор отображаемого участка вертикальной оси может нас не устроить. Например, он работает не очень хорошо, если функция имеет на заданном промежутке точку разрыва, хотя бы один из односторонних пределов в которой равен бесконечности: тогда промежуток по оси Y будет выбран слишком большим. Да и в других случаях может понадобиться изменить умолчательное поведение. Для этого предусмотрен такой вариант вызова функции: plot2d(выражение, [символ, начало, конец], [y, начало, конец]). Здесь буква y используется в качестве обозначения вертикальной оси, а остальные два параметра имеют тот же смысл, что и выше.


LXF85 maxima03.png

LXF85 maxima04.png

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

Чтобы построить на одной и той же картинке одновременно два графика (или больше), просто передайте функции plot2d вместо отдельного выражения их список:

LXF85 maxima05.png

LXF85 maxima06.png

Здесь [x, 0.01, 5] вместо [x, 0, 5] я написал «по привычке» – Maxima 5.9.x выдавала ошибку, если заданная функция была не определена на одном из концов интервала. В 5.10.0 мне эту ошибку воспроизвести не удалось; так что есть основания полагать, что поведение в таких случаях поправили.

Может plot2d строить и графики параметрически заданных функций. Для этого используется список с ключевым словом parametric: plot2d([parametric, x-выражение, y-выражение, [переменная, начало,конец], [nticks, количество]]). Здесь «x-выражение» и «y-выражение» задают зависимость координат от параметра, то есть, по сути, это две функции вида x(t), y(t), где t – переменная параметризации. Эта же переменная должна фигурировать в следующем аргументе-списке, а параметры «начало», «конец», как и в двух других рассмотренных случаях, задают отрезок, в пределах которого этот параметр будет изменяться. Последний аргумент-список, с ключевым словом nticks, задает количество кусочков, на которые будет разбит интервал изменения параметра при построении графика. Этот аргумент опционален, но на практике он нужен почти всегда: умолчательное значение nticks равно 10; согласитесь, редко бывает нужно в качестве графика получить ломаную из 10 отрезков. Вот пример построения графика параметрической функции:

LXF85 maxima07.png

Кроме parametric, функция plot2d понимает еще одно ключевое слово: discrete. Предназначено оно, как нетрудно догадаться, для отображения на плоскости дискретных множеств; точнее говоря, конечных наборов точек. По записи аргументов такой вариант распадается еще на два: plot2d([discrete, x-список, y-список]) и plot2d([discrete, [x, y]-список]). В первом варианте координаты задаются как два отдельных списка [x1, x2, ..., xn], [y1, y2, ..., yn], а во втором – как список пар координат отдельных точек [[x1, y1], [x2, y2], ..., [xn, yn]].

Если мы, к примеру, имеем набор статистических значений, зависящих от номера, мы можем отобразить его, задав в качестве x-координат сами эти номера, то есть натуральные числа:

LXF85 maxima08.png

По умолчанию множество отображается в виде ломаной с вершинами в заданных точках; такое поведение можно изменить и получить вывод, к примеру, в виде отдельных точек. Это достигается использованием специальных опций, применимых как к plot2d, так и к plot3d, поэтому давайте перейдем к рассмотрению последней.

Придаем объем

Функция plot3d имеет два варианта вызова: один для явного задания функции и один для параметрического. В обоих случаях функция принимает три аргумента. Для явно заданной функции: plot3d(выражение,[переменная1, начало, конец], [переменная2, начало, конец]); аргументы аналогичны plot2d, с той разницей, что здесь независимых переменных две.

LXF85 maxima09.png

Построение нескольких поверхностей на одном графике не поддерживается – потому, вероятно, что на таком рисунке проблематично было бы что-либо разглядеть. Посему для параметрически заданной функции ключевое слово parametric не требуется: вызов с первым аргументом-списком уже не с чем перепутать. График параметрически заданной функции строится так: plot3d([выражение1, выражение2, выражение3], [переменная1, начало, конец], [переменная2, начало, конец]), где «выражения» отвечают, по порядку, x(u, v), y(u, v), z(u, v).

LXF85 maxima10.png

LXF85 maxima11.png

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

LXF85 maxima12.png

И отсюда мы плавно переходим к опциям функций построения графиков, посредством использованной выше опции grid. Каждая опция имеет некоторое умолчательное значение, а изменить его можно, добавив к аргументам список вида [имя-опции, значение]. Строго говоря, рассмотренные выше y и nticks также являются опциями; в предпоследнем примере мы задали опции nticks значение 120, а в примере перед ним в качестве значения опции y использовалась пара чисел 0, 5. В документации к Maxima символ x, выступавший в примерах выше в качестве обязательного параметра, также приводится как опция; на самом деле опцией он является только в случае parametric и действует тогда так же, как и опция y, только по другой оси. Опция grid, использованная выше, применима к трехмерным графикам вместо опции nticks, используемой для двумерных. Она, также как и y, задается в виде двух целых значений, которые для поверхностей задают размер ячеек сетки, в виде которой отображается поверхность; первое число – вдоль оси X, второе – вдоль оси Y; либо, в случае параметрического задания, по первому и по второму параметру соответственно. Для кривых из этих параметров действует только один, но писать нужно опять же оба, дабы не нарушать синтаксис; и здесь этот параметр имеет в точности тот же смысл, что nticks для кривых на плоскости. Но перейдем к другим опциям.

С претензией на красоту


Первая опция, которую мы рассмотрим, задает формат вывода результата; так она и называется: plot_format. Формат может принимать одно из четырех значений, первое из которых действует по умолчанию: gnuplot, mgnuplot, openmath и ps. В умолчательном варианте (значение gnuplot) данные для отображения передаются напрямую программе gnuplot, которая сама по себе имеет достаточно гибкое управление, и параметры ей можно передавать прямо из Maxima с помощью дополнительных опций функций plot2d/3d. Параметров этих настолько много, что gnuplot могла бы стать темой отдельной статьи; так что обращайтесь за ними к документации по gnuplot. В противовес своим богатым возможностям, gnuplot имеет перед следующими двумя интерфейсами (если откровенно – скорее, лишь перед одним из них) только один недостаток: она генерирует статичное изображение, тогда как mgnuplot и openmath позволяют в реальном времени масштабировать и передвигать картинку, а plot3d – еще и вращать линию или поверхность в разные стороны в пространстве

Следующий вариант – mgnuplot – является дополнительным интерфейсом к gnuplot, написанным на Tcl/Tk, но динамика у него настолько «задумчивая», а остальные возможности настолько бедны, что я не вижу смысла останавливаться на нем подробнее.

И перехожу сразу к openmath. Он тоже не очень-то поддается управлению, зато предоставляет хорошую интерактивность, особенно ценную в трехмерном варианте: после того, как объект сгенерирован, его можно масштабировать и очень динамично вращать, разглядывая со всех сторон. Особенно это помогает для сложных поверхностей, когда, глядя на статичную «сетку» gnuplot, непросто понять форму поверхности. Справедливости ради нужно отметить, что gnuplot позволяет задавать точку обзора трехмерного объекта в качестве одного из многочисленных параметров, то есть хотя картинка и статична, но с какой стороны на нее смотреть, мы можем указать произвольно.

Ну и последнее значение опции plot_format подталкивает Maxima к непосредственной генерации PostScript-документа с изображением. Но и здесь надо сказать: генерировать PostScript-вывод умеет и все тот же gnuplot.

Большинство остальных опций относятся только к формату вывода gnuplot. А мы рассмотрим еще одну универсальную, пригодную для всех форматов и преобразующую не результирующее изображение, а сам процесс построения графика; точнее, систему координат. Называется эта опция transform_xy, по умолчанию она равна false. Передавать ей нужно выражение, сгенерированное функцией make_transform([x, y, z], f1(x, y, z), f2(x, y, z), f3(x, y, z)). Кроме того, существует одно встроенное преобразование, известное как polar_xy и соответствующее make_transform([r, th, z], r*cos(th), r*sin(th), z), то есть переходу к полярной цилиндрической системе координат. В качестве примера использования transform_xy приведу преобразование к полярным сферическим координатам, раз уж во встроенном виде его нет:


LXF85 maxima13.png

Обратите внимание: в первом аргументе-списке к make_transform последним должен идти зависимый символ, то есть тот, который будет выступать функцией от двух других.

Если вам нужно постоянно работать со сферическими координатами, можете задать, скажем, spherical_xy:make_transform([t, f, r], r*sin(f)*sin(t), r*cos(f)*sin(t), r*cos(t)), и затем при построении графиков писать [transform_xy, spherical_xy].

Ветвитесь и повторяйтесь

До сих пор мы двигались только по прямой, а теперь поговорим о средствах «изменения траектории»: условном операторе и циклах.

Начнем с условия. В Maxima, в отличие от большинства «традиционных» процедурных и объектных языков программирования, где существует так называемый условный оператор, привычная связка if-then-else является не синтаксической конструкцией, а самым настоящим оператором. По своему действию он больше всего похож на тернарный оператор языка C, только с более «человеческим» синтаксисом: if условие then выражение1 else выражение2. При выполнении «условия» из двух «выражений» вычисляется только первое и возвращается как результат оператора; в противном случае выполняется только второе и оно же является значением всего выражения if-then-else. Часть конструкции else выражение2, как и в большинстве языков программирования, опциональна. Если ее нет, а условие все-таки не выполнилось, результат оператора if будет равен false.

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

LXF85 maxima14.png

Немного о самих условиях, которые могут проверяться оператором if. Условия >, <, >=, <= записываются и расшифровываются традиционно, так же как и логические операторы and, or, not. А вот о равенствах-неравенствах нужно сказать пару слов. Равенство в Maxima есть двух видов: синтаксическое и логическое. Знаком = обозначается как раз первое, а второе вычисляется с помощью функции equal(). Чтобы не быть многословными, отличие синтаксического равенства от логического продемонстрируем на примере; здесь дополнительно используется предикат по имени is, которые проверяет на истинность свой аргумент.

LXF85 maxima15.png

Ну и неравенств, соответственно, тоже существует два, с тем же смыслом. Синтаксическое неравенство обозначается достаточно непривычно – через #; видимо, этот символ разработчики сочли наиболее визуально схожим со знаком . Ну а логическое неравенство обоз начено через notequal().

Конечно, кроме упомянутых сравнений в условном операторе можно использовать любые предикаты, то есть функции, возвращающие логические значения true/false. Функций таких достаточно много, но все они достаточно просты, поэтому не буду тратить время на их описание: его можно почерпнуть в том же объеме из документации.

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

  for переменная:начало step шаг thru конец do выражение
  for переменная:начало step шаг while условие do выражение
  for переменная:начало step шаг unless условие do выражение

Первый прокручивает цикл, изменяя «переменную» с заданным «шагом» от «начала» до «конца»; второй – от «начала» и пока выполняется «условие»; третий – наоборот, пока «условие» не выполняется. К примеру, мы можем получить список из первых десяти членов последовательности из позапрошлого примера:

LXF85 maxima16.png

Как видите, в качестве оператора цикл в простейшем его виде, в отличие от условия, использовать смысла нет, так как его возвращаемое значение всегда равно done. В этом примере один из элементов циклического оператора не указан; шаг, как видите, может быть опущен и по умолчанию равен единице. Самое интересное в этом операторе то, что опустить позволяется любую его часть, кроме do; и в том числе в любых комбинациях. К примеру, опустив кроме step еще и for, мы получаем из этого же оператора традиционные циклы while и unless (второй и третий варианты). А проделав то же самое с первым вариантом записи, получим цикл без счетчика вида thru число do выражение, который просто повторится заданное число раз. Можно, наоборот, опустить условие окончания и получить цикл с индексной переменной, но бесконечный. А оставив только do, получим самый простой вариант бесконечного цикла. Из таких бесконечных циклов можно выйти с помощью оператора return(выражение) (точнее, конечно, конструкции из двух операторов вида if условие then return(выражение)), который прервет выполнение цикла и вместо done вернет заданное выражение. Естественно, оператор return() можно применять во всех видах циклов, а не только в бесконечных.

Но и это еще не все. Кроме всех уже рассмотренных вариаций, цикл может принимать еще две ипостаси. Во-первых, вместо step может использоваться конструкция next выражение, смысл которой лучше тоже продемонстрировать на примере

LXF85 maxima17.png

После next может стоять любое вычислимое выражение относительно индекса цикла, и применяться эта конструкция может во всех трех вариантах цикла (thru/while/unless).

А «во-вторых» – это еще один отдельный вариант цикла: for переменная in список do выражение; либо расширенная форма: for переменная in список условие do выражение. Здесь цикл будет прокручен с «переменной», изменяющейся по всем элементам «списка»; плюс можно задать еще и дополнительное «условие» на прерывание цикла. Вот теперь мы с циклами действительно закончили. Как видите, все достаточно разнообразно. Я, признаться, ничего, что здесь не реализовано, и придумать не смог.

Но рассказ о циклах и условном операторе остается неполным, пока я не рассказал о группировке выражений – ведь в обычном варианте после then или do можно написать всего одно из них. А группировка, или, как ее принято называть, составной оператор в Maxima – это опять-таки самый настоящий оператор, который тоже, как и положено оператору, возвращает некоторое значение. Обозначается он скобками, самыми что ни на есть круглыми и обыкновенными; а разделяются сгруппированные операторы/выражения внутри этих скобок не менее обыкновенными запятыми. Возвращаемым значением составного оператора является последнее вычисленное выражение.

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

Мы также поговорим о математических аналогах объявления переменных и рассмотрим практические примеры с применением уже достаточно богатого известного нам инструментария. LXF

Скорость обновления Maxima

Существование свободной программы в такой сложной и профессиональной области, как символьная математика, конечно, само по себе не может не радовать. Но вместе с тем, на данный момент Maxima во многих направлениях заметно отстает от коммерческих приложений этого же назначения – Mathematica и Maple, в развитие которых уже много лет подряд вкладываются большие деньги. На этом фоне несколько лет кряду выглядели неутешительно и темпы развития самой Maxima. По сравнению с упомянутыми коммерческими приложениями сроки выпуска новых версий, конечно, более чем на уровне. Но как для открытого проекта – это не те темпы, которые были бы поводом для счастья. Судите сами: дата выхода версии 5.9.0 – 10.02.2003, 5.9.1 – 24.09.2004, 5.9.2 – 17.10.2005. Итого, больше года между соседними релизами.

И вот за последний год время между выпусками сократилось минимум вдвое, и таким образом скорость выпуска вышла на хороший уровень активно развивающегося FOSS-проекта. Начиная этот цикл, я еще считал «почти новой» (по старым меркам) версию 5.9.2: ей тогда было немногим больше полугода; хотя 5.9.3 за эти пол-года уже успела выйти – 21 марта. И вот сейчас (по состоянию на начало октября) уже доступна следующая «мажорная» версия – 5.10.0, увидевшая свет 23 сентября. И это дает основания надеяться, что с нынешнего момента Maxima все же начнет догонять дорогие закрытые продукты по основным возможностям.

Персональные инструменты
купить
подписаться
Яндекс.Метрика