Методы программирования
Такие мощные системы, как Mathematica, предназначены, в основном, для решения математических задач без их программирования большинством пользователей. Однако это вовсе не означает, что Mathematica не является языком (или системой) программирования и не позволяет при необходимости программировать решение простых или сложных задач, для которых имеющихся встроенных функций и даже пакетов расширений оказывается недостаточно или которые требуют для реализации своих алгоритмов применения типовых программных средств, присущих обычным языкам программирования. Все обстоит совсем иначе.
Фактически, основой системы Mathematica является проблемно-ориентированный на математические расчеты язык программирования сверхвысокого уровня. По своим возможностям этот язык намного превосходит обычные универсальные языки программирования, такие как Фортран, Бейсик, Паскаль или С. Важно подчеркнуть, что здесь речь идет о языке программированиясистемы Mathematica, а не о языке реализации самой системы. Языком реализации является универсальный язык программирования C++, показавший свою высокую эффективность в качестве языка системного программирования. Как и всякий язык программирования, входной язык системы Mathematica содержит операторы, функции и управляющие стриктуры. Основные операторы и функции этого языка и относящиеся к ним опции мы фактически уже рассмотрели. Набор описанных ранее типовых операторов и функций характерен для большинства современных языков программирования. Мощь системы Mathematica как средства программирования решения математических задач обусловлена необычно большим (в сравнении с обычными языками программирования) набором функций, среди которых немало таких, которые реализуют сложные и практически полезные математические преобразования и современные вычислительные методы (как численные, так и аналитические). Число этих функций только в ядре и библиотеках приближается к тысяче. Среди них такие операции, как символьное и численное дифференцирование и интегрирование, вычисление пределов функций, вычисление специальных математических функций и т. д. — словом, реализации именно тех средств, для создания которых на обычных языках программирования приходится составлять отдельные, подчас довольно сложные программы. Почти столько же новых функций (или модернизированных старых) содержат пакеты расширения (Add-on Packages). Язык программирования системы Mathematica трудно отнести к какому-либо конкретному типу. Можно разве что сказать, что он является типичным интерпретатором и не предназначен для создания исполняемых файлов. Впрочем, для отдельных выражений этот язык может осуществлять компиляцию с помощью функции Compile, что полезно при необходимости увеличения скорости счета. Этот язык вобрал в себя лучшие средства ряда поколений языков программирования, таких как Бейсик, Фортран, Паскаль и С. Благодаря этому он позволяет легко реализовывать все известные типы (концепции) программирования: функциональное, структурное, объектно-ориентированное, математическое, логическое, рекурсивное и т. д. К примеру, вычисление таких функций, как факториал, в Mathematica можно запрограммировать в виде функции пользователя целым рядом способов:
f[n_] =n! f[n_] =Gamma[n-l] f [n_] =n*f [n-1] ;f [0]=l;f [1]=1; f[n_] =Product[i/i,n] f [n_] =Module[t=l,Do[t=t*i,i,n] ;t] f [n_] =Module [ { t=l } , For [ i=l , i<=n , i++ , t*=i ] ; t] f[n_] =Fold [Times,1, Range [n] ]Все их можно проверить с помощью следующего теста:
{f[0],f[1],f[5],f[10]} {1, 1, 120, 3628800}Как отмечалось, внутреннее представление всех вычислений базируется на применении полных форм выражений, представленных функциями. И вообще, функциям в системе Mathematica принадлежит решающая роль. Таким образом, Mathematica. фактически, изначально реализует функциональный метод программирования — один из самых эффективных и надежных. А обилие логических операторов и функций позволяет полноценно реализовать и логический метод программирования. Множество операций преобразования выражений и функций позволяют осуществлять программирование на основе правил преобразования. Надо также отметить, что язык системы позволяет разбивать программы на отдельные модули (блоки) и хранить эти модули в тексте документа или на диске Возможно создание полностью самостоятельных блоков — именованных процедур и функций с локальными переменными. Все это наряду с типовыми управляющими структурами позволяет реализовать структурное и модульное программирование. Столь же естественно язык системы реализует объектно-ориентированное программирование. Оно базируется прежде всего на обобщенном понятии объекта и возможности создания множества связанных друг с другом объектов. В системе Mathematica каждая ячейка документа является объектом и порождается другими, предшествующими объектами. При этом содержанием объектов могут быть математические выражения, входные и выходные данные, графики и рисунки, звуки и т. д. С понятием объекта тесно связаны три основных свойства, перечисленные ниже: инкапсуляция — объединение в одном объекте как данных, так и методов их обработки; наследование — означает, что каждый объект, производный от других объектов, наследует их свойства; полиформизм — свойство, позволяющее передать ряду объектов сообщение, которое будет обрабатываться каждым объектом в соответствии с его индивидуальными особенностями. Приведенный ниже пример объектно-ориентированного программирования дает три определения, ассоциированные с объектом h:
h/ : h [x_] +h [y_] : =hplus [х , у] h/:p[h[x_],x]:=hp[x] h/:f_[h[x_]] :=fh[f,x]В принципе, язык программирования системы Mathematica специально создан для реализации любого из перечисленных подходов к программированию, а также ряда других — например, рекуррентного программирования, при котором очередной шаг вычислений базируется на данных, полученных на предыдущих шагах. Наглядным примером этого может служить вычисление факториала рекуррентным методом. Возможно также создание рекурсивных функций (с обращением к самим себе) и, соответственно, использование рекурсивного программирования. Оно, кстати, играет большую роль в осуществлении символьных преобразований. Средства языка Mathematica позволяют осуществить и визуально-ориентированное программирование. Его смысл заключается в автоматической генерации программных модулей путем визуального выбора интуитивно понятного объекта — чаще всего путем щелчка на кнопке. Mathematica позволяет создавать палитры и панели с различными кнопками, позволяющими управлять программой или вводить новые программные объекты. Однако визуально-ориентированное программирование не является основным. В основном оно ориентировано на создание палитр пользователя с нужными ему функциями. Поскольку алфавит языка программирования системы и набор операторов и функций уже были рассмотрены ранее, в этой главе нам остается рассмотреть лишь специфические средства языка и его управляющие структуры.
Образцы и их применение
Образцы (patterns) в системе Mathematica служат для задания выражений различных классов и придания переменным особых свойств, необходимых для создания специальных программных конструкций, таких как функции пользователя и процедуры. Это необычайно гибкое и мощное средство обобщенного представления математических выражений, используемое при любом подходе к программированию.
Признаком образца являются знаки подчеркивания «_» (от одного до трех). Они обычно выглядят слитно, так что надо внимательно следить за общей длиной символов образцов. Наиболее распространенное применение образцов — указание на локальный характер переменных при задании функций пользователя. Например, функцияfsc[x_,y_]:= х * Sin[y] + у * Cos[х] + zв списке параметров содержит два образца, х_ и у_. В правой части этого выражения переменные х и у, связанные с образцами х_ и у_, становятся локальными переменными, тогда как переменная z будет глобальной переменной. Обратите особое внимание на то, что символы образцов используются только в списках параметров — в правой части выражений они уже не применяются. Образцами можно задавать некоторые общие свойства функций. Например, запись
f[х_,х_] := р[х]означает, что функция f двух идентичных аргументов становится тождественной функции р [ х ]. Следовательно, вызов функции
f[a,a] + f[а,b]даст выход в виде
f[а,b] + р[а]а при вызове
f[a^2- 1, a^2- 1]будет получен результат
р[-1 + а^2]Примеры применения образцов для задания функции вычисления факториала приводились выше. В образце можно указывать его тип данных: x_Integer — образец целочисленный; x_Real — образец с действительным значением; x_Complex — образец с комплексным значением; x_h — образец с заголовком h (от слова head — голова). Задание типов данных с помощью образцов делает программы более строгими и наглядными и позволяет избежать ошибок, связанных с несоответствием типов. В системе Mathematica используются следующие типы образцов.
Обозначение |
Назначение образца |
- |
Любое выражение |
x_ |
Любое выражение, представленное именем х |
: : pattern |
Образец, представленный именем х |
pattern ? test |
Возвращает True, когда test применен к значению образца |
_h |
Любое выражение с заголовком h |
x_h |
Любое выражение с заголовком h, представленное именем х |
- |
Любая последовательность с одним и более выражений |
- |
Любая последовательность с нулем или более выражений |
:x_< ИЛИ х__ |
Последовательности выражений, представленные именем х |
_h или h__ |
Последовательности выражений, каждое с заголовком h |
x _ h или х__h |
Последовательности выражений с заголовком h, представленные именем х |
x_ :v |
Выражение с определенным значением v |
x_h:v |
Выражение с заголовком h и определенным значением v |
x_. |
Выражение с глобально заданным значением по умолчанию |
Optional [x h] |
Выражение с заголовком h и с глобально заданным значением |
по умолчанию |
|
Pattern. . |
Образец, повторяемый один или более раз |
Pattern. . . |
Образец, повторяемый ноль или более раз |
Функции пользователя
Понятие функции ассоциируется с обязательным возвратом некоторого значения в ответ на обращение к функции по ее имени с указанием аргументов (параметров) в квадратных скобках. Возврат функциями некоторых значений позволяет применять их наряду с операторами для составления математических выражений.
Функции подразделяются на встроенные в ядро системы внутренние функции и функции, заданные пользователем. Примером первых могут быть Sin[x], Bessell [n, x] и т.д. Mathematica содержит множество таких функций, охватывающих практически все широко распространенные элементарные и специальные математические функции. Есть и возможность создания функций со специальными свойствами — чистых (pure functions) и анонимных функций. Суть функционального программирования заключается в использовании в ходе решения задач только функций. При этом возможно неоднократное вложение функций друг в друга и применение функций различного вида. В ряде случаев, особенно в процессе символьных преобразований, происходит взаимная рекурсия множества функций, сопровождаемая почти неограниченным углублением рекурсии и нарастанием сложности обрабатываемых системой выражений. Встроенные стандартные функции и их типовые применения уже были описаны в предшествующих уроках. Так что далее мы рассмотрим только задание функций особого вида, создаваемых пользователем или используемых в управляющих структурах программ. Хотя в системах Mathematica имеется около тысячи встроенных функций, любому пользователю рано или поздно может потребоваться создание какой-либо своей функции. Кажется естественным задать ее по правилам, принятым во многих языках программирования. Например, функцию для возведения х в степень п можно было бы определить так:powerxn[x, n] := x^nОднако такая функция отказывается работать:
{powerxn[2, 3], powerxn[a, b]} {powerxn[2, 3] , powerxn[a, b]}Причина этого кроется в том, что в системе Mathematica символы х и n являются обычными символами, не наделенными особыми свойствами. Будучи использованными в качестве параметров функции, они не способны воспринимать формальные параметры [2,3] или [ а, b ]. Так что вычислить нашу ущербную функцию можно лишь при предварительном присваивании х иn нужных значений:
х := 2; n := 3; powerxn[x, n] 8Разумеется, заданная таким образом функция является неполноценной. Для того чтобы функция пользователя нормально воспринимала переданные ей аргументы, в списке параметров надо использовать образцы в виде переменных, но имеющие после своих имен символы подчеркивания. Образцы способны быть формальными параметрами функций и воспринимать значения фактических параметров (в нашем случае значений 2 и 3). Таким образом, правильной будет запись функции пользователя в виде
powerxn[x_, n_] := х^nТеперь вычисление по заданной функции пользователя пройдет гладко, причем как в численном, так и в символьном виде:
{powerxn[2, 3], powerxn[z, у], powerxn[x, n]} {8, zy, 8}Заметим, что для уничтожения определения заданной функции можно использовать команду-функцию Clear [Name_f unction], где Name_f unction — имя функции. Можно также задать функцию пользователя, содержащую несколько выражений, заключив их в круглые скобки:
f[x_] := (t = (1 + х)^2; t = Expand[t])Переменные списка параметров, после имен которых стоит знак «_», являются локальными в теле функции или процедуры. На их место подставляются фактические значения соответствующих параметров, например:
f [а + b] 1+2а+а2+2b+2ab+b2 t 1+2а+а2+2b+2ab+b2Обратите внимание на то, что переменная t в функции f является глобальной. Это поясняет результат последней операции. Применение глобальных переменных . в теле функции вполне возможно, но создает так называемый побочный эффект — в данном случае меняет значение глобальной переменной t. Для устранения побочных эффектов надо использовать образцы и другие специальные способы задания функций, описанные ниже. Итак, можно сформулировать ряд правил для задания функций пользователя: такая функция имеет идентификатор — имя, которое должно быть уникальным и достаточно понятным; в списке параметров функции, размещенном в квадратных скобках после идентификатора, должны использоваться образцы переменных, а не просто переменные; может использоваться отложенное (: =) или немедленное (=) присваивание; тело функции может содержать несколько выражений, заключенных в круглые скобки, при этом возвращается значение последнего выражения; переменные образцов в списке параметров являются локальными и действуют только в пределах тела функции; в теле функции могут использоваться глобальные переменные, но при этом возможны побочные эффекты; возможно обращение к функции из тела этой же функции (рекурсия). Параметрами функций могут быть списки при условии допустимости их комбинации. Например, допустимо задать х списком, an — переменной или числом:
powerxn[{l, 2, 3, 4, 5}, z] {1, 2Z, 3Z, 4Z, 5Z} powerxn[{l, 2, 3, 4, 5}, 2] {1, 4, 9, 16, 25}После своего задания функции пользователя могут использоваться по тем же правилам, что и встроенные функции. Иногда может потребоваться создание функции, не имеющей имени (например, если функция будет использоваться только один раз, сразу же после ее создания). Эта функция представляется только выражением без идентификатора, отсюда и ее название — чистая функция (pure function). Для создания такого объекта служит встроенная функция Function, используемая в одном из следующих вариантов: Function [body] — создает чистую функцию с телом body; Function [ {х}, body ] — создает чистую функцию параметра х с телом body; Function [ {xl, х2,...} ,body] — создает чистую функцию ряда параметров x1, х2, ... с телом body. Для вычисления созданной таких образом функции после нее задается список параметров в квадратных скобках. Например, взятую ранее в качестве примера функцию возведения в степень можно задать и использовать следующим образом:
Function[{x, n}, x^n] Function! {х, п), xn] %[2, 3] 8Чистую функцию можно легко превратить в обычную функцию пользователя, что показывает следующий пример:
fun=Function[{x,n},х^n] Function[ {х, n}, хn] {fun[2,3],fun{z,y}} {8, zy }Предельно компактную форму задания имеют так называемые анонимные функции. Они не имеют ни названия, ни обычного определения и задаются только выражениями специального вида. В этом выражении вместо переменных используют обозначения # (для одной переменной) или #1, #2, ... (для ряда переменных). Завершается тело функции символом «&». Если надо вычислить функцию, то после ее записи в квадратных скобках указывается список фактических параметров. Для нашего примера анонимная функция выглядит так:
#1^#2 &[2, 3] 8 #1^#2 &[у, z] y^zС помощью анонимных функций нетрудно создавать обычные функции пользователя:
f[x_, y_] = #1^#2 &[х, у] хy f[2, 3] 8Несмотря на то что применение анонимных функций открывает возможности компактного задания многих функций, эта форма едва ли интересна для большинства читателей — они наверняка предпочтут пусть немного более длинное, но значительно более очевидное задание функций другими способами.
Суперпозиция функций
При функциональном программировании часто используется суперпозиция функций. Для ее реализации используются следующие функции:
Nest [expr, x, n] — n раз применяет выражение (функцию) ехрг к заданному аргументу х, NestList [f, x, n] — возвращает список результатов (п+1)-кратного применения функции f к заданному аргументу х; Fold[f, x, list] — дает последний элемент в FoldList [f, x, list]; FoldList [f, x, {a,b,...} ] — возвращает список {x,f [x,a],f [f [x,a],b],...}; ComposeList [ { f , f ,...}, x] — генерирует список в форме {х,а[х] ,а[а[х] ],...}. Примеры, иллюстрирующие действие этих функций, представлены ниже:Nest[f, x, 5] f[f[f[f[f[x]]]]] Nest[Exp[x], x, 5] Ех[Ех[Ех[Ех[Ех[х]]]]] NestList[f, x, 3] {x, f[x], f[f[x]], f[f[f[x]]]} Fold[f, x, (-1, 2, 3}] f[f[f[x, 1], 2], 3] FoldList[f, x, {1, 2, 3}] {x, f[x, 1], f[f[x, 1], 2], f[f[f{x, 1], 2], 3]} ComposeList[{Exp, Ln, Sin), x] {x, Ex, Ln[Ex] , SinlLn[Ex]] ]}
В функциональном программировании вместо циклов, описываемых далее, может использоваться следующая функция: FixedPoint [ f, expr ] — вычисляет expr и применяет к нему f, пока результат не перестанет изменяться; FixedPoint [ f, expr, SameTest->comp] — вычисляет expr и применяет к нему f, пока два последовательных результата не дадут True в тесте SameTest.
Пример применения функции FixedPoint:
FixedPoint[Function[t, Print[t]; Floor[t/2]], 27] 27 13 6 3 1 0 0Последний результат (ноль) выводится в отдельной (нумерованной) ячейке вывода и означает завершение процесса итераций — деления t на 2. Следующий пример показывает, как можно создать цепную дробь с помощью функции Nest:
Nest[ Functiontt, 1/(1+t)], у, 3 ] 1/(1/(1/((1+y)+1)+1)+1)Еще одна функция такого рода — это Catch: Catch [expr] — вычисляет expr, пока не встретится Throw [value], затем возвращает value; Catch [expr, form] — вычисляет expr, пока не встретится Throw [value, tag], затем возвращает value; Catch [expr, form, f] — возвращает f [value, tag] вместо value. Ниже представлены некоторые конструкции циклов с оператором Catch:
Catch[ x, a, f ] х Catch[ Throw[ x, у ], у, fun ] fun[x, у] Catch[ NestList[l/(# + 1)&, -3, 5] ] {-3,-1/2, 2, 1/3, 3/4, 4/7} Catch[ NestList[l/(# + 1)&, -3., 5] ] {-3., -0.5, 2., 0.333333, 0.75, 0.571429} Catch[Do[Print[i]; If[i > 4, Throw[i+2]], i, 10]] 1 2 3 4 5 7
Реализация рекурсивных и рекуррентных алгоритмов
Рассмотрим несколько простых примеров, выявляющих суть функционального программирования. Вначале это будет пример, в котором задана функция sen [х, n], вычисляющая сумму синуса в степени n и косинуса в степени n:scn[x_, n_] := Sin[x]^n + Cos[х]^n scn[l, 2] 1 scn[x, 2] 1 scn[x, n] Cos[x]n+ Sin[x]nВ этом простейшем примере результат вычислений есть возвращаемое функцией sen значение — численное или символьное. В свою очередь, функция sen в своем теле имеет встроенные функции синуса и косинуса. Важное место в решении многих математических задач занимают реализации рекурсивных и рекуррентных алгоритмов. Напомним, что рекурсия означает обращение функции к самой себе внутри ее тела, а рекуррентность — получение результата на данном шаге по результатам вычислений на предшествующих шагах. Рассмотрим, как это делается, с помощью описанных выше функций. Классический пример реализации рекурсивного алгоритма — вычисление факториала путем задания функции, в теле которой есть обращение к ней же самой:
f[n_] :=n*f[n-1];f[0]=l;f[1]=1;Полезно, однако, обратить внимание на возможность явного задания результата для конкретных значений аргумента: f [ 0 ] =1 и f [ 1 ] =1. Так что рекурсия реализуется, начиная с n=2 и выше, в соответствии с классическим определением факториала. Для реализации рекуррентных алгоритмов в Mathematica имеется ряд функций, таких как Nest или FixedPoint. В следующих примерах показано вычисление квадратного корня из числа 5 по известному алгоритму Ньютона, записанному в виде функции newtonS:
newtonS [x_] := N[ 1/2 ( х + 5/х )] Nest[newton5, 1.0, 5] 2.23607 NestList [newtonS, 1.0, 5] {1., 3., 2.33333, 2.2381, 2.23607, 2.23607} FixedPoint [newtonS, 1.0] 2.23607 FixedPointList [newtonS, 1.0] {1., 3., 2.33333, 2.2381, 2.23607, 2.23607, 2.23607, 2.23607} FixedPointList [newtonS, 1.0, SameTest -> (Abs[#l- #2] < 10.A-4 &)] {1., 3., 2.33333, 2.2381, 2.23607, 2.23607}Обратите внимание на то, что функции Nest и FixedPoint дают единственный конечный результат, тогда как функции NestList и FixedPointList возвращают еще и все промежуточные результаты итераций. Последний пример иллюстрирует остановку вычислений по заданной погрешности, равной 10 -4 . Далее зададим функцию, реализующую алгоритм Ньютона для нахождения корня произвольного выражения f(x) при начальном значении х 0 = а, по следующим формулам:
x0=a; xn=xn-1-f(xn-1)/f'(xn-1)Эту функцию можно записать следующим образом:
newtoniter[f_, x0_, n_] :=Nest[(# - f [#]/f'[#]) &, N[x0] , n]Тогда вычисления корня из выражения е^x - 2 с начальным приближением х 0 = 0.5 при числе итераций n можно организовать с помощью функций Nest и NestList:
newtoniter [Function [ {х} , Ехр[х] - 2.0], 0.5, 5] 0.693147 newtoniter [Function [ {х }, Ехр[х] - 2.0], 0.5, #] & /@ Range [5] {0.713061, 0.693344, 0.693147, 0.693147, 0.693147} newtoniterl[f_,x0_,n_] := NestList[ (#-f [#] /f ' [#] ) &,N[x0] , n] newtoniterl [Function [{x} , Exp[x] - 2.0], 0.5, 5] {0.5, 0.713061, 0.693344, 0.693147, 0.693147, 0.693147}В первом случае возвращается только окончательный результат, а в других — еще и все промежуточные. Функция FixedPoint позволяет осуществлять итерации до тех пор, пока результат не перестанет изменяться (с машинной точностью). Это иллюстрирует следующий пример:
newtonfp[f_, х0_] := FixedPoint[ (# - f [#]/f'[#]) &, N[xO]] newtonfp[Function[{x} , Exp[x] - 2.0], 0.5] 0.693147Более сложные примеры функционального программирования мы рассмотрим позже, при описании создания пакетов расширения систем Mathematica.
Пример программирования графической задачи
Графические задачи составляют значительную часть задач, решаемых с помощью Mathematica. С точки зрения программирования эти задачи не имеют особой специфики. Большая часть из них сводится к заданию функции, описывающей график, и применению одной из многочисленных графических функций системы с соответствующими опциями и директивами.
На рис. 10.1 показано задание функции GrayCode и ее графическое представление, полученное с помощью встроенной функции ListPlot.
Рис. 10.1. Задание функции GrayCode и ее графическое представление на плоскости
В качестве следующего примера рассмотрим задачу на построение сложного графика функции Мандельброта. Пример задания соответствующей функции MandelbrotFunction и применения графической функции DensityPlot для наглядного визуального представления функции MandelbrotFunction на комплексной плоскости представлен на рис. 10.2. Еще более сложную и любопытную задачу демонстрирует рис. 10.3. Здесь задана функция JuliaFunction, которая представляет одну из моделей деления клеток. На этом же рисунке показано построение множества графиков, дающих прекрасное визуальное представление данной функции.
Рис. 10.2. Пример задания функции MandelbrotFunction и построения ее графика плотности
Разумеется, приведенные примеры далеко не исчерпывают всего многообразия графических возможностей языка программирования систем Mathematica.
Рис. 10.3. Задание функции JuliaFunction и ее графическое представление
Использование процедур
В основе процедурного программирования лежит понятие процедуры и типовых средств управления — циклов, условных и безусловных выражений и т. д. Процедурный подход — самый распространенный в программировании, и разработчики Mathematica были вынуждены обеспечить его полную поддержку. Однако программирование систем Mathematica и в этом случае остается функциональным, поскольку элементы процедурного программирования существуют в конечном счете в виде функций.
Процедуры являются полностью самостоятельными программными модулями, которые задаются своими именами и отождествляются с выполнением некоторой последовательности операций. Они могут быть заданы в одной строке с использованием в качестве разделителя символа «;» (точка с запятой). Вот пример задания однострочной процедуры, отождествленной с именем г:r = (1 + х)^2; r= Expand[г]; r - 1 2Х+Х2Обратите внимание на то, что в теле процедуры символ г используется как вспомогательная переменная. Эта процедура возвращает символьное выражение
Expand[ (1+х)^2] - 1.В общем случае в теле процедуры могут находиться произвольные выражения, разумеется, с синтаксисом, присущим языку программирования системы. Процедура может не возвращать никаких значений, а просто выполнять определенный комплекс операций. Область записи подобных элементарных процедур ограничена ячейкой (строкой) ввода. Для задания процедуры со списком локальных переменных {а, b,...} и телом ргос может использоваться функция Module [ {а, b,...} ,ргос]. С применением этой функции мы столкнемся позже. Для создания полноценных процедур и функций, которые могут располагаться в любом числе строк, может использоваться базовая структура — блок: Block [{x, у,...}, procedure] — задание процедуры с декларацией списка локальных переменных х, у,...; Block[{x = х0, у=у0,...}, procedure] — задание процедуры с декларацией списка переменных х, у,... с заданными начальными значениями. Пример использования базовой структуры:
g[x_] := Block[{u}, u = (1 + х)^2; u = Expand[u] ] g[а + b] l+2a+a2+2b+2ab+b2 u u u = 123456; g[2] 9 u 123456Обратите внимание: последние действия показывают, что переменная и, введенная в тело базовой структуры, является действительно локальной переменной, и присвоение ей символьного выражения (1 + х) ^ 2 в теле блока игнорируется вне этого блока. Если переменная и до применения в функции была не определена, то она так и остается неопределенной. А если она имела до этого некоторое значение (в нашем случае — 123 456), то и по выходе из процедуры она будет иметь это значение. Многие задачи в системе Mathematica решаются с использованием линейных алгоритмов и программ. Они могут быть представлены непрерывной цепочкой выражений, выполняемых последовательно от начала до конца. Однако в большинстве случаев серьезные вычисления базируются на использовании циклических и разветвленных алгоритмов и программ. При этом, в зависимости от промежуточных или исходных данных, вычисления могут идти по разным ветвям программы, циклически повторяться и т. д. Для реализации разветвленных программ язык программирования должен содержать управляющие структуры, то есть специальные конструкции языка, реализующие в программах ветвление. Они используются при различных методах программирования, в том числе при процедурном и функциональном программировании.
Циклы типа Do
К важнейшим управляющим структурам в языках программирования относятся циклы. С их помощью осуществляется циклическое исполнение некоторого выражения ехрr заданное число раз. Это число нередко определяется значением некоторой управляющей переменной (например, i, j и т. д.), меняющейся либо с шагом +1, либо от начального значения imin до конечного значения imax с шагом di. Циклы могут быть одинарными или множественными — вложенными друг в друга. Последние используют ряд управляющих переменных. Такого рода циклы организуются с помощью функции Do: О Do [expr, {imax} ] — выполняет imax раз вычисление ехрг; О Do [expr, {i, imax}] — вычисляет ехрг с переменной i, последовательно принимающей значения от 1 до imax (с шагом 1); Do [expr, {i, imin, imax} ]—вычисляет ехрr с переменной i, последовательно принимающей значения от imin до imax с шагом 1; Do [expr, {i, imin, imax, di}] — вычисляет ехрг с переменной i, последовательно принимающей значения от 1 до imax с шагом di; Do [expr, {i, imin, imax}, {j, jmin, j max},...] — вычисляет expr, организуя ряд вложенных циклов с управляющими переменными j, i и т. д. Примеры организации цикла Do и его исполнения представлены ниже:Do[Print["hello"], {5}] hello hello hello hello hello Do[Print[i], {i, 3}] 1 2 3 Do[Print[i], {i, 5, 8}] 5 6 7 8 Do[Print[i], {i, 0 , 1, 0.25}] 0 0.25 0.5 0.75 1.Нетрудно убедиться в том, что переменная i в теле цикла (итератор) является локальной и по выходе из цикла ее значение остается тем же, что было до входа:
i=2 2 Do[Print[i], i, 1, 5] 1 2 3 4 5 1 2Вся программа с циклом является содержанием одной ячейки, и ее листинг охвачен квадратной скобкой. Для иллюстрации вывода здесь использована команда Print в теле цикла. Нетрудно заметить, что управляющая переменная цикла может принимать как целочисленные, так и вещественные значения. Возможность организации цикла в цикле иллюстрируется следующим примером:
Do [Do [Print [i, " ", j, " ", i + j], {j, 1, 3}], {i, 1, 3}]; 1 1 2 1 2 3 1 3 4 2 1 3 2 2 4 2 3 5 3 1 4 3 2 5 3 3 6Здесь используются два цикла с управляющими переменными i и j. Командой Print выводятся значения переменных i и j, а также их суммы i+j. Следующий пример показывает применение цикла Do для задания функции, вычисляющей п-е число Фибоначчи:
fibonacci[(n_Integer)?Positive] := Module[fnl = 1, fn2 =0, Do[fnl, fn2 = fnl + fn2, fnl, n- 1] ; fnl] fibonacci[10] 55 fibonacci[100] 354224848179261915075 fibonacci[-10] fibonacci[-10]Обратите внимание на применение в этом примере функции Module. Она создает программный модуль с локальными переменными (в нашем случае fnl и fп2), в котором организовано рекуррентное вычисление чисел Фибоначчи. Наконец, последний пример показывает применение цикла Do для создания цепной дроби:
х = у; Do[x = 1/(1 + k х), {k, 2, 8, 2}]; х
Циклы типа For
Другой вид цикла — цикл For — реализуется одноименной функцией:For[start, test, incr, body]В ней сначала один раз вычисляется выражение start, а затем поочередно вычисляются выражения body и incr до тех пор, пока условие test не перестанет давать логическое значение True. Когда это случится, то есть когда test даст False, цикл заканчивается. Следующий пример показывает создание простой программы с циклом For и результат ее выполнения:
Print["i x"] For [x=0; i=0, i < 4, i++ [x += 5*i, Print[i, " ", x]]] i x 15 , 2 15 3 30 4 50 Return[x] Return[50]Программа, приведенная выше, позволяет наблюдать за изменением значений управляющей переменной цикла i и переменной х, получающей за каждый цикл приращение, равное 5*i. В конце документа показан пример на использование функции возврата значений Return [x]. В цикле For не предусмотрено задание локальных переменных, так что надо следить за назначением переменных — при использовании глобальных переменных неизбежны побочные эффекты.
Циклы типа While
Итак, функция For позволяет создавать циклы, которые завершаются при выполнении (эволюции) какого-либо условия. Такие циклы можно организовать и с помощью функции While [test, expr], которая выполняет expr до тех пор, пока test не перестанет давать логическое значение True. Ниже дан практический пример организации и использования цикла While:i := 1; х := 1; Print["i x"] ; While[i < 5, i += 1; x += 2*i; Print[i, " ", N[x]]] i x 2 5. 3 11. 4 19. 5 29. Return[x] Return[29]Циклы типа While, в принципе, могут заменить другие, рассмотренные выше, типы циклов. Однако это усложняет запись и понимание программ. Аппарат локальных переменных в этом типе циклов не используется.
Директивы-функции прерывания и продолжения циклов
В указанных типах циклов и в иных управляющих структурах можно использовать следующие директивы-функции: Abort [ ] — вызывает прекращение вычислений с сообщением $ Aborted; Break [ ] — выполняет выход из тела цикла или уровня вложенности программы, содержащего данный оператор (циклы типа Do, For и While или тело оператора-переключателя Switch). Оператор возвращает Null-значение (без генерации секции выхода); Continue [ ] — задает переход на следующий шаг текущего цикла Do, For или While; Interrupt [ ] — прерывает вычисления с возможностью их возобновления; Return [ ] — прерывает выполнение с возвратом значения Null; Return [expr] — прерывает выполнение с выводом значения выражения ехрr; Throw [value] — задает прекращение выполнения цикла Catch, если в ходе эволюции ехрг встречается значение value (см. примеры выше). На рис. 10.4 представлено применение директив Abort [ ] и Interrupt [ ] в середине набора команд. Нетрудно заметить, что директива Abort [ ] просто прерывает выполнение цепочки команд и выводит сообщение $ Aborted. А вот директива Interrupt [ ] выводит диалоговое окно, с помощью которого можно либо прервать вычисления, либо продолжить их.
Рис. 10.4. Действие директив Abort[] и lnterrupt[]
Если продолжить вычисления (нажав кнопку Continue Evaluation), то вывод выражений командами Print будет продолжен, что видно из рис. 10.5.Условные выражения и безусловные переходы
Для подготовки полноценных программ помимо средств организации циклов необходимы и средства для создания разветвляющихся программ произвольной структуры. Обычно они реализуются с помощью условных выражений, позволяющих в зависимости от выполнения или невыполнения некоторого условия (condition) выполнять те или иные фрагменты программ.
Рис. 10..5. Продолжение вычислений после команды Interrupt[]
Функция IF
Как у большинства языков программирования, условные выражения задаются с помощью оператора или функции IF. Система Mathematica имеет функцию If, формы которой представлены ниже: If [condition, t, f] — возвращает t, если результатом вычисления condition является True, и f, если результат равен False; If [condition, t, f, u ]—то же, но дает и, если в результате вычисления condition не было получено ни True, ни False. Следующий пример показывает создание программной процедуры с циклом Do, выход из которой реализуется с помощью функции I f и директивы прерывания Aborted! ]:х := 1; Print["i x"]; Do[{If [i == 5, Abort[], None], i += 1; x += 2*i; Print[i, " ", N[x]]}, {i, 1, 100}] i x 2 5 3 11. 4 19. 5 29. $Aborted Return[x] Return[1]Тот же пример, но с применением директивы выхода из цикла Break [] в функции If показан ниже:
х := 1; Print["i x"]; Do[{If [i == 5, Break[], None], i += 1; x += 2*i; Print[i, " ", N[x]]}, {i, 1, 100}] i x 2 5. 3 11. 4 19. 5 29. Return[x] Return[29]В данном случае никаких специальных сообщений о выходе из цикла не выдается. Функция If обеспечивает ветвление максимум по двум ветвям программы. Для ветвления по многим направлениям можно использовать древовидные структуры программ с множеством функций If. Однако это усложняет исходный текст программы.
Функции-переключатели
Для организации ветвления по многим направлениям в современных языках программирования используются операторы-переключатели. В системе Mathematica множественное ветвление организовано с помощью функций Which и Switch: Which [testl, valuel, test2, value2,...] — вычисляет в порядке следования каждый из testi, сразу возвращая именно ту величину из valuei, которая относится к первому testi, давшему True; Switch [expr, forml, valuel, form2, value2,...] — вычисляет селектор expr, затем сравнивает его последовательно с каждой из меток f ormi, вычисляя и возвращая то valuei, которое соответствует первому совпадению. Приведем примеры работы функции which:Whichtl == 2,1,2== 2, 2, 3 == 3, 3] 2 Which[l == 2, x, 2 == 2, у, 3 == 3, z] yСледующие примеры иллюстрируют работу функции Switch:
Switch[1, 1, а, 2, b, 3, с] а Switch[2, 1, а, 2, b, 3, с] b Switch[3, 1, а, 2, b, 3, с] с Switch[8, 1, а, 2, b, 3, с] Switch[8, 1, а, 2, b, 3, с]Обратите внимание на последний пример — при неверном задании первого параметра (селектора) просто повторяется запись функции. Следующий пример показывает возможность выбора с применением вещественных значений селектора и меток:
Switch[8., 1.5, а, 2.5, b, 8., с] с Switch[1.5, 1.5, а, 2.5, b, 8., с] а Switch[8, 1.5, а, 2.5, b, 8., с] Switch[8, 1.5, а, 2.5, b, 8., с]Опять-таки, обратите внимание на последний пример — здесь использован селектор в виде целого числа 8, тогда как метка выбора — вещественное число 8. Выбор при этом не происходит, поскольку целочисленное значение 8 не является тождественным вещественной восьмерке.
Безусловные переходы
В целом, условные выражения в языке программирования системы Mathematica позволяют реализовать любой вид ветвления в программах. Однако иногда бывает полезно без лишних раздумий указать в программе явный переход к какой-либо ее части. Для этого используется оператор безусловного перехода Goto [tag]. который дает переход к тому месту программы, которое отмечено меткой Label [tag]. Возможны также формы Goto [expr] и Label [expr], где ехрr — вычисляемое выражение. Применение оператора Goto иллюстрирует следующий пример:(q = 2; Label[start]; Print[q]; q += 2; If[q < 7, Goto[start]]) 2 4 6Здесь с помощью оператора Goto [start] организован цикл с возвратом на метку Label [start], действующий до тех пор, пока значение q меньше 7. При этом q меняется от начального значения 2 с шагом 2, причем добавление 2 к текущему значению q осуществляется укороченным оператором сложения q+=2. Интересной особенностью языка программирования Mathematica является возможность создания переходов по значению вычисляемого выражения. Например, Goto [2+3] дает переход к метке Label [5] или даже Label [1+4], что видно из следующего примера:
Goto[2 + 3]; Print["ааааа"]; Label[1 + 4]; Print["bbbbb"] bbbbbПереходы, задаваемые выражениями, и метки, меняющие свой идентификатор, редко встречаются в обычных языках программирования, хотя они обеспечивают новые обширные и довольно необычные возможности по созданию программ с различными ветвлениями. Для языка программирования системы Mathematica, ориентированного на безупречное и строгое структурное программирование, введение оператора Goto может расцениваться как отступничество от основополагающих идей структурного программирования. Поэтому на применение этого оператора в методах структурного программирования наложено табу. Тем не менее, этот оператор есть, а применять его или нет — дело пользователя.
Проблемы совместимости
Мы уже не раз обращали внимание на то, что при создании документов нередки конфликты между переменными, назначаемыми пользователем, и переменными, входящими в программы ядра, между функциями пользователя и встроенными функциями, между их заголовками и т. д. Ситуация усложняется при использовании пакетов расширения, поскольку в них широко используются переменные и различные функции, причем нередко обозначенные так же, как и встроенные функции.
Особенно коварны побочные эффекты в конструкциях, содержащих вспомогательные переменные, — например, в итерационных циклах, функциях вычисления суммы и произведения и т. п. Они содержат переменные-итераторы i,. j, k и т. д. Обычно избежать конфликтов можно с помощью механизма локализации итераторов. Вернемся к уже обсуждавшимся примерам. Возьмем пример с вычислением суммы:i=2 2 Sum[i,{i,l,4}] 10 i 2Ясно, что сумма вычисляется с применением цикла с заданным числом повторений. В его конце итератор i получает значение 4. Но глобальная переменная с тем же именем имеет значение 1=2, которое она получила до вычисления суммы с помощью функции Sum. В данном случае это достигнуто за счет того, что в теле функции переменная-итератор является локальной. Нетрудно убедиться, что проблемы со статусом переменных возможны и в, казалось бы, изученных функциях суммирования и перемножения. На это явно указывает следующий пример:
func[x_] :=Sum[x^i, {i,4} ] {func[y] ,func[i] } (У +У2+ У3+У4, 30} i 2Результат вычисления func [у] вполне понятен, тогда как вычисление func [i] носит явно обескураживающий характер. Причина его в том, что вместо символьного значения i в данном случае оказались использованы численные значения итератора i. А в этом случае функция Sum просто вычисляет численные значения. Говорят, что она работает по контексту] А теперь рассмотрим пример с циклом For:
For [ i=l , i<=4 , i++ , Print [ i ] ] 1 2 3 4 i 5 .На этот раз переменная i изменила свое значение в конце цикла с 2 на 5. Это говорит о том, что пользователю-программисту надо очень внимательно относиться к статусу переменных во всех итерационных, да и других программах. Разумеется, Mathematica содержит средства для избежания подобного смешения ролей переменных. Одно из них — применение конструкции Module:
i=2 2 Module[{i},For[i=l,i<=4,i++,Print[i]]] 1 2 3 4 i 2На этот раз захвата итератором глобальной переменной i удалось избежать. Однако этот пример носит не более чем частный характер. Вообще говоря, если переменная-итератор задается в теле функции, то она будет локальной, а если она задается за пределами функций, то глобальной. Для разрешения подобных противоречий в системе Mathematica введен особый механизм контекстов. Напомним, что под контекстом подразумевается некоторое разъяснение характера связанных с контекстом объектов. Другими словами, это означает, что с каждым объектом системы Mathematica (например, с переменными или функциями) связан некоторый контекст. Чисто внешне контекст задается в виде Имя_контекста (обратный апостроф в конце имени и есть признак контекста). Итак, контекст фактически является некоторым признаком объекта. Каждый объект системы Mathematica имеет свой контекст, который записывается перед именем объекта (знак «'» при этом является разделителем). Обычно он не виден, но существует. Объекты с одинаковыми именами могут иметь разные контексты и действовать по-разному — то есть по контексту. Пользователям полезно усвоить такую аналогию: контексты — это как бы разные папки со своими именами, куда могут помещаться одноименные файлы-объекты. С другой стороны, один и тот же контекст может принадлежать разным объектам. Например, все системные переменные и встроенные функции имеют контекст System', то есть они относятся к системным объектам, а все символы, вводимые в начале работы с системой, имеют контекст Global (глобальные). В системе Mathematica есть средства для визуализации контекстов. Прежде всего это функция Context:
Context[Tan] System' Context[E] System' Context/@Cos,Pi,Abort {System', System' , System'}Текущее значение контекста определяет системная переменная $Context или функция Context [ ]:
{$Context,Context[]} {Global', Global'}В начале сеанса работы по умолчанию действует контекст Global ~, что означает глобальный статус вводимых символов:
Context/@{q,r,w} {Global', Global', Global'}Однако контекст может быть заменен на любой нужный пользователю просто указанием его перед соответствующим символом или словом:
{new'q, new' w,Global'r} {new'q, new'w, r} Context/@{new' q,new' w,Global' r} {new', new', Global4}Обратите внимание на то, что символы new 4 q и new' w имеют новый контекст new s и отображаются вместе с ним (но контекст указан перед символом). А вот символ Global ~ r отображается лишь своим кратким именем. Причина этого в том, что текущий контекст есть Global 4 , а контекст new 4 отсутствует в списке контекстов (context path). Что касается символов q, r и z, то сами по себе (без новой контекстной приставки) они по-прежнему имеют контекст "Global:
Context/@{q,r,w} {Global 4 , Global 4 , Global 4 }Для вывода списка контекстов используется переменная $ContextPath:
$ContextPath {Graphics 4 Animation 4 , Global 4 , System 4 }С помощью функции Prepend можно добавить в список новый контекст, например new":
$ContextPath=Prepend[$ContextPath,"new4"] {new', Graphics' Animation', Global', System'}Теперь функция Context возвращает только контексты символов new'q, new'w и Global' r:
Context/@{new'q,new'w,Global'r} {new', new', Global'}С помощью функции Begin можно изменить текущий контекст на заданный, например Global' на new':
Begin["new''"] new'q=5; {q,Context[q]} {5, new'}Теперь легко разобраться, как интерпретируются символы с разными контекстами. Любой символ, вводимый без контекстной приставки, то есть своим коротким именем, интерпретируется и выводится с этим именем, если его контекст является текущим. Если символ вводится полным именем, то проверяется, есть ли его контекст в списке $ContextPath. Если он есть, то к символу добавляется самый левый контекст из имеющихся в списке. Таким образом, по мере ввода новых контекстов, имена которых совпадают со старыми, происходит вытеснение новыми контекстами старых. Другими словами, это позволяет обновить уже имеющиеся определения, сохранив их на случай отмены старых контекстов. Этот принципиально важный механизм модификации объектов играет решающую роль в создании пакетов расширений. В них часто уже имеющиеся функции (со старыми контекстами) заменяются новыми, одноименными с ними, но имеющими иные контексты.
Получение списков определений с контекстами
Для получения списка всех определений с заданным контекстом можно использовать функции Names [ "Context' S" ], где S — шаблон, определяющий интересующие нас имена. Например, для получения всех определений с контекстом System' можно использовать функцию Names ["System' *]. Поскольку этот список довольно большой, ограничимся примером вывода всех определений с контекстом System", начинающихся с буквы U:Names["System'U*"] {UnAlias, Underflow, Underoverscript, UnderoverscriptBox, UnderoverscriptBoxOptions, Underscript, UnderscriptBox, UnderscriptBoxOptions, UndocumentedTestFEParserPacket, UndocumentedTestGetSelectionPacket, Unequal, Unevaluated, Uninstall, Union, Unique, UnitStep, Unprotect, UnsameQ, Unset, Up, Update, UpperCaseQ, UpSet, UpSetDelayed, Upvalues, URL, Using)Функция Names [ ] без параметра выводит полный список всех определений как из ядра, так и из пакетов расширений с указанием их контекстов. Таким образом, данная функция дает самую полную информацию об определениях (функциях, константах и т. д.), которые содержит текущая версия системы Mathematica.
Подготовка пакетов расширений системы Mathematica
Мощным средством расширения возможностей системы Mathematica является подготовка пакетов расширений. Пакеты расширений позволяют создавать новые процедуры и функции и хранить их на диске в виде файлов с расширением . m. После считывания такого пакета с диска все входящие в него определения функций становятся доступными для использования в соответствии с правилами, принятыми для встроенных функций. Текст пакета расширения не выводится после его вызова, чтобы не загромождать документ вспомогательными описаниями. В сущности, пакеты расширения — это просто наборы программ на языке программирования системы Mathematica, подобранные по определенной тематике.
Типовая структура пакетов расширения
Структура пакета расширений (программы) в минимальном виде выглядит следующим образом:(* Вводный комментарий *) BeginPackage["Имя_пакета' "] Mean::usage = "Имя функции[Параметры] Текстовый комментарий" Begin[" 'Private' "] Unprotected[Список_имен] Определения новых функций End[ ] Установка атрибутов защиты EndPackage[ ] (* Завершающий комментарий *)Особая структура пакетов расширений связана с реализацией описанной выше идеологии контекстов. Пакет открывается необязательным текстовым комментарием, который обрамляется двойными символами « (*» и «*) ». Он может быть как однострочным, так и многострочным. Обычно вводный комментарий включает в себя имя пакета, наименование фирмы и автора — создателей пакета, историю развития, дату создания и т. д. Если вы программируете для себя, можете на первых порах опустить все эти комментарии. Но не забудьте их ввести после отладки пакета, как того требуют культура и дисциплина программирования. Затем пакет открывается словом BeginPackage. Это слово дается с квадратными скобками, в которых указывается контекст (см. выше) пакета. Обратите внимание на то, что после имени пакета должен стоять апостроф или цепочка символов, обрамленная апострофами. Имя пакета не должно совпадать ни с одним из известных, то есть быть уникальным. Эта команда изменяет список контекстов, и он принимает вид
{Имя_пакета',System'}.Таким образом, на первом месте списка контекстов оказывается имя пакета, а на втором — контекст System'. Теперь любой вводимый и не встроенный символ приобретает контекстную приставку с именем данного пакета. Обратите внимание на то, что контекст System' сохранился в новом списке контекстов, но стал вторым. Это значит, что если вы вводите слова и символы, встроенные в систему, то они будут замещены новыми определениями. К примеру, если вы решили вычислять функцию Sin [x] по новому и ценному для вас алгоритму, то ему будет отдаваться предпочтение при каждом использовании этой функции до тех пор, пока вы работаете с данным пакетом расширения. Однако, как только вы перестанете работать с пакетом, восстановится роль встроенной функции Sin[x]. Следующий блок пакета — сообщения о назначении функций. Эти сообщения выводятся, если после загрузки пакета задать вопросительный знак с последующим именем функции. Эти сообщения не обязательны, но они обеспечивают единство диалога с системой и, безусловно, нужны при профессиональной подготовке пакета. Обычно в этих сообщениях кратко указываются синтаксические правила использования функций и назначение их параметров, указываемых в квадратных скобках. Затем следует главная часть пакета — определения новых функций. Она открывается определением Begin [" ' Private ' "]. Оно, не меняя список контекстов, устанавливает новый текущий контекст Имя_пакета' Private'. Он присваивается всем ранее не встречавшимся символам. Имя Private принято в пакетах расширения системы Mathematica, хотя, в принципе, может быть любым другим именем. После него следуют сами определения, в которых могут использоваться любые средства, включенные в ядро системы. В некоторых случаях имена функций могут повторять ранее определенные в ядре системы. Это полезно, если пользователь считает, что введенное им определение уже известной функции более точно или более универсально, чем использованное в системе. В таких случаях перед новым применением идентификатора надо позаботиться о снятии с него защиты с помощью функции Unprotect. Именно эта часть и определяет существо пакета и его ценность. Завершается эта часть определением End [ ]. При этом восстанавливается контекст, который был до определения Begin [" ' Private' " ], то есть контекст с именем пакета. После этого идет необязательная часть с указанием атрибутов защиты. Пакет завершается определением EndPackage [ ], которое восстанавливает контекст, бывший текущим до загрузки пакета (например Global' 4 ), a контекст Имя_пакета 4 помещает в начало прежнего списка контекстов.. Контексты в системах Mathematica 3 и 4 идентичны — иначе и быть не может, поскольку всякая старшая версия системы должна обеспечивать совместимость с предшествующей версией. Впрочем, в Mathematica 4 включены два новых контекста, Developer 4 и Experimental 4 . Необязательный заключительный комментарий чаще всего дает список тестовых примеров. Он особенно желателен, если пакет содержит определения не вполне очевидных функций. Не забывайте, что этот комментарий не выводится и не исполняется — он нужен лишь на этапе знакомства с пакетом. Разумеется, такое знакомство необходимо при каждой серьезной попытке применения того или иного пакета расширения или применения системы. В принципе, текстовые комментарии могут вводиться на русском языке. Однако при этом возникают определенные трудности. При выводе комментариев на экран дисплея при работе с оболочкой системы Mathematica могут наблюдаться несоответствия между шрифтами, установленными при вводе комментариев и при их выводе. Поэтому лучше использовать комментарии на английском языке, тем более что комментарии ко всем встроенным функциям и к поставляемым расширениям системы даны, естественно, на английском языке.
Средства создания пакетов расширений
Для создания пакетов расширений в общем случае используются следующие средства системы: Begin ["context'"] — устанавливает текущий контекст; BeginPackage ["context'"] — делает context единственным активным контекстом. Возможна также форма BeginPackage [ "context" ", { "needl' ", "need2'",...}];' Return [ ] — возвращает Null; End [ ] — возвращает текущий контекст и переходит к предыдущему; EndAdd [ ] — возвращает текущий контекст и переходит к предыдущему, предварительно добавляя текущий контекст к списку контекстов $Context-Path; EndPackage [ ] — восстанавливает $Context и $ContextPath в их значениях до предшествующего BeginPackage и добавляет текущий контекст к списку $ContextPath; Exit [ ] — завершает сеанс работы Mathematica; Goto [tag] —просматривает текущее составное выражение в поиске Label [tag] и передает управление в эту точку; Interrupt [ ] — производит прерывание в теле вычислений; Label [tag] — представляет точку в составном выражении, в которую управление передается директивой Goto; Quit [ ] — завершает сеанс работы Mathematica. Приведем пример простого фрагмента программы, дающего определение новой функции ExpandBoth с помощью некоторых из представленных средств:(* :Title: ExpandBoth *) (* :Context: ProgramminglnMathematica'ExpandBoth" *) (* : Author: Roman E. Maeder *) ExpandBoth: : usage = "ExpandBoth [e] expands all numerators and denominators in e." Begin ["' Private1"] ExpandBoth [x_Plus] := ExpandBoth /@ x ExpandBoth [x_] := Expand [ Numerator [x] ] / Expand [ Denominator [x] ] End [ ] NullЭтот пример настолько прост, что читателю будет нетрудно разобраться с его сутью — расширением выражения по числителю и знаменателю. Ниже представлен сеанс работы с этим пакетом, файл которого expboth.m размещен в каталоге mypack, включенном в общий каталог пакетов расширений:
<<mypack\expboth.m ?ExpandBoth ExpandBoth [e] expands all numerators and denominators in e. ExpandBoth [124 /12] 31/3 ExpandBoth [1234/12] 617/6Мы вернемся к рассмотрению построения пакетов расширений после более детального рассмотрения некоторых деталей этого процесса.
Текстовые сообщения и комментарии
Ценность многих программ на любом языке программирования нередко сводится к нулю из-за отсутствия подробных текстовых комментариев. Из-за этого даже сами разработчики программ через месяц-другой перестают понимать собственные творения. А что говорить о пользователях, рискующих применить такие программы? Для создания текстовых комментариев различного назначения (как выводимых, так и не выводимых на экран в ходе работы с пакетом) в языке программирования системы Mathematica используются следующие средства: (* Comment *) — задание не выводимого на экран текстового комментария, как однострочного, так и многострочного, в любом месте пакета; Message [symbol: : tag] — вывод сообщения symbol::tag, если только вывод сообщений не отключен; Message [symbol: :tag, e1, e2,...] — выводит сообщение, вставляя значения ei по мере необходимости; $MessageList — глобальная переменная, возвращающая список имен сообщений, вырабатываемых во время вычисления текущей входной строки. Имя каждого сообщения заключено в HoldForm [ ]. $MessageList сохраняется в MessageList [n] и переустанавливается в { } после того, как произведена п-я выходная строка; MessageList [n] — глобальный объект, который является списком имен (сообщений), которые вырабатываются в процессе обработки п-й входной строки; MessageName, применяется в виде symbol: : tag или MessageName [symbol, "tag" ] — имя для сообщения; $MessagePrePrint — глобальная переменная, чье значение, если установлено, применяется к выражениям перед тем, как они помещаются в текст сообщений; $Messages — возвращает список файлов и каналов, в которые направляется вывод сообщений; Messages [symbol] — возвращает все сообщения, присвоенные данному символу symbol. Следует отметить, что широкое применение комментариев обычно является признаком культуры программирования. Это особенно важно для математических систем, реализующих вычисления по сложным и подчас малопонятным для неспециалистов алгоритмам. Без подробных комментариев пакеты расширений и применений теряют свою практическую полезность и превращаются в ребусы — увы, куда менее интересные, чем те, которые публикуются в газетах и журналах.Защита идентификаторов от модификации
Атрибут защиты Protected
Как уже отмечалось, система Mathematica позволяет вводить константы, переменные и функции со своими именами — идентификаторами. Между функциями можно задавать различные отношения, в том числе и те, которые не соответствуют правилам, заданным в ядре системы. Идентификаторы должны быть уникальными, то есть не совпадать с именами встроенных функций, директив, опций, переменных и констант. Однако как быть, если нужно задать новое отношение для уже имеющихся встроенных функций или изменить их определения? Для решения таких вопросов в систему введена защита идентификаторов от модификации, которая при необходимости может сниматься. Все встроенные в ядро именованные объекты языка программирования системы являются защищенными по умолчанию. Они имеют соответствующий признак — атрибут Protected (защищенный).Установка и снятие атрибута защиты
Для управления средствами защиты от модификации используются следующие директивы: Protect [s1, s2,...] — устанавливает атрибут защиты от модификации (Protected) для перечисленных символов si; Protect [\"forml\", \"form2\",...] — устанавливает атрибут защиты от модификации для всех символов, имена которых сопоставимы с любым из указанных строковых шаблонов f ormi; Unprotect [s1, s2,...] — удаляет атрибут защиты от модификации (Protected) для символов si, что делает возможной их модификацию; Unprotect [\"forml\", \"form2\",...] — снимает защиту всех символов, имена которых текстуально (по буквам) сопоставимы с любым из указанных formi.Дополнительные функции защиты
Следующие атрибуты и директивы также используются при управлении модификацией: NProtectedAll — атрибут, устанавливающий, что ни один из аргументов функции не будет модифицирован при применении N [ ]; NProtectedFirst — атрибут, указывающий, что первый аргумент функции не будет модифицирован применением N [ ]; NProtectedRest — атрибут, устанавливающий, что все аргументы после первого аргумента функции не будут модифицированы применением N [ ]. Мы уже рассматривали модификацию функций, в частности снятие и назначение атрибутов защиты. Отметим лишь, что из последующих примеров будет ясно, что эти операции широко применяются в пакетах расширений.Примеры подготовки пакетов расширений
Наиболее сложным моментом работы с системой Mathematica является разработка пакетов расширения профессионального качества. Именно такие пакеты позволяют приспособить всю мощь системы к решению тех задач, которые полезны конкретному пользователю. Начать работу с системой можно за несколько часов. Реальное ее освоение потребует нескольких месяцев упорной работы. А подготовка серьезных пакетов, решающих достаточно сложные задачи, может занять и несколько лет. Для облегчения этого процесса рассмотрим основные приемы подготовки пакетов расширений. Напоминаем, что пакеты можно готовить как в оболочке системы (их затем следует записать на диск как файлы с расширением .т), так и с помощью .внешних текстовых редакторов. В этом разделе представлено несколько примеров построения пакетов расширений системы Mathematica (версии не ниже 3.0), взятых из книги [34], а точнее, из примеров этой книги, включенных в справочную базу данных систем Mathematica. Из примеров удалена большая часть текстовых комментариев, сделанных на английском языке.Пакет проверки выражений на их алгебраичность
Следующий пакет содержит определение функции AlgExpQ [expr], которая позволяет выяснить, является ли выражение ехрг алгебраическим.(* :Title: AlgExp *) (* :Context: Pro gra mminglnMathematica4AlgExp4 *) BeginPackage["ProgramminglnMathematica ' AlgExp '"] AlgExpQ::usage = "AlgExpQ[expr] returns true if expr is an algebraic expression." Begin["'Privateч"] SetAttributes[AlgExpQ, bistable] AlgExpQ[ _Integer ] = True AlgExpQ[ _Rational ] = True AlgExpQ[ c_Complex ] := AlgExpQ[Re[c]] && AlgExpQ[Im[c]] AlgExpQ[ _Symbol ] = True AlgExpQ[ a_ + b_ ] := AlgExpQ[a] && AlgExpQ[b] AlgExpQ[ a_ * b_ ] := AlgExpQ[a] && AlgExpQ[b] AlgExpQ[ a_ ^ b_Integer ] := AlgExpQ[a] AlgExpQ[ a_ ^ b_Rational ] := AlgExpQ[a] AlgExpQ[_] = False End[] EndPackage[]Если выражение является алгебраическим, то функция AlgExpQ возвращает логическое значение True, иначе она возвращает значение False:
<<mypack\algexp.m ? AlgExpQ AlgExpQ[expr] returns true if expr is an algebraic expression. AlgExpQ [a * x ^ 2 + b * x + c] True AlgExpQ[Sqrt[x]] True AlgExpQ["x^2+l"] False AlgExpQ[1] True AlgExpQ[1.0] False
Пакет реализации метода Рунге—Кутта
Теперь рассмотрим, как выглядит пакет расширения, решающий систему дифференциальных уравнений хорошо известным численным методом Рунге—Кутта четвертого порядка. Ниже представлена распечатка данного пакета.(* :Title: RungeKutta *) (* iContext: ProgramminglnMathematica'RungeKutta' *) BeginPackage["ProgramminglnMathematica'RungeKutta'"] RKSolve::usage = "RKSolve[{el,e2,..}, {yl,y2,..}, {al,a2,..}, {tl, dt}] numerically integrates the ei as functions of the yi with inital values ai.The integration proceeds in steps of dt from 0 to tl. RKSolve[{el, e2,..},{yl,y2,..},{al,a2,..},{t,t0,tl, dt} ] integrates a time-dependent system from t0 to tl." Begin["'Private'"] RKStep[f_, y_, y0_, dt_] := Module [{ kl, k2, k3, k4 }, kl = dt N[ f /. Thread[y -> yO] ]; k2 = dt N[ f /. Thread[y -> y0 + kl/2] ]; k3 = dt N[ f /. Thread [y -> yO + k2/2] ] ; k4 = dt N[ f /. Thread [y -> yO + k3] ] ; y0 + (kl + 2 k2 + 2 k3 + k4)/6 RKSolve[f_List, y_List, y0_List, {tl_, dt_}] := NestList[ RKStepff, y, #, N[dt]]&, N[y0], Round [N [ tl /dt ]] ] /; Length [f] == Length [y] == Length [y0] RKSolve [f_List, y_List, y0_List, {t_, t0_, tl_, dt_}] := Module f { res } , res = RKSolve [ Append[f, 1], Append[y, t] , Append[y0, t0], {tl-t0, dt} ] ; Drop[#, -1]& /@ res /; Length [f] == Length [y] == Length [y0] End[] Protect [ RKSolve ] EndPackage[]Знающие реализацию этого метода обратят внимание на естественность записи общеизвестных математических операций. Пакет содержит определения двух функций: основной (RKSolve) и вспомогательной (RKStep). Последняя содержит вычисление решения на очередном шаге алгоритма по результатам вычислений на предшествующем шаге. Используется подстановка для переменной х и вычисление решения на очередном шаге по известной формуле Рунге— Кутта четвертого порядка точности. Теперь рассмотрим, как можно использовать такой пакет, создать который можно в любом текстовом редакторе, например в редакторе NotePad, входящем в состав Windows 95/98. Для удобства работы можно поместить файл этого пакета rk4.m в папку Mypack, расположенную в папке со стандартными пакетами. В этом случае вызов пакета и проверка его загрузки осуществляются следующим образом:
<< mypack\rk4.m ?RKSolve RKSolve [ {el, e2, ..}, {yl,y2,..}, {al,a2,..}, {tl, dt}] numerically integrates the ei as functions of the yi with inital values ai.The integration proceeds in steps of dt from 0 to tl. RKSolve [ {el, e2, ..}, {yl,y2,..}, {al,a2,..}, {t, t0, tl, dt}] integrates a time-dependent system from t0 to tl .Итак, при обращении ?RKSolve выводится информация о формате применения функции RKSolve. Она задана на английском языке. Можно записать эту информации и на русском языке, однако при этом возможна нестыковка наборов шрифтов. Поэтому рекомендуется подобную информацию давать на английском языке. В нашем случае решается система дифференциальных уравнений первого порядка в форме Коши, заданная правыми частями {el, е2,...} с переменными {yl, у2,...} и их начальными значениями {al, а2,...} в интервале времени от 0 до .1 при фиксированном шаге dt. Во второй форме записи функции время t может меняться от tO до tl с шагом dt. Приведенный ниже пример демонстрирует, как этот пакет используется на практике для решения системы дифференциальных уравнений y' = t*y + z и z' = t + y*z при начальных значениях у = z = 1 и t, меняющемся от 1 до 1.5 с шагом 0.1:
RKSolve[{t*y + z, t + y*z}, {у, z}, {1, 1}, {t, 1, 1.5, 0.1}] {{!., 1.}, {1.22754, 1.22844), {1.52241, 1.53202), {1.90912, 1.95373}, {2.42456, 2.57444), {3.12741, 3.55937}}Решение представлено списком значений {yi, zi}, определяющим зависимости y(t) и z(t). Этот пример хорошо иллюстрирует реализацию популярного численного метода для решения систем дифференциальных уравнений.
Пакет символьных преобразований тригонометрических функций
Следующий пакет служит для демонстрации символьных преобразований тригонометрических функций синуса и косинуса.(* :Title: TrigDefine *) (* :Context: ProgramminglnMathematica'TrigDefine" *) BeginPackage["ProgramminglnMathematica' TrigDefine'"] TrigDefine::usage = "TrigDefine.m defines global rules for putting products of trigonometric functions into normal form." Begin["'Private'"] (* set the private context *) (* unprotect any system functions for which rules will be defined *) protected = Unprotect[ Sin, Cos ] (* linearization *) Sin/: Sin[x_] Cos[y_] := Sin[x+y]/2 + Sin[x-y]/2 Sin/: Sin[x_] Sin[y_] := Cos[x-y]/2 - Cos[x+y]/2 Cos/: Cos[x_] Cos[y_] := Cos[x+y]/2 + Cos[x-y]/2 Sin/: Sin[x_]An_Integer?Positive := Expandt (1/2- Cos[2x]/2) Sin [x]^(n-2) ] Cos/: Cos[x_]An_Integer?Positive := Expand[(l/2 + Cos[2x]/2) Cos[x]^(n-2)] Protect[ Evaluate[protected]](* restore protection of system symbols *) End[] (* end the private context *) EndPackage[] (* end the package context *)Данный пакет задает преобразования для произведений sin(x) cos(x), sin(x) sin(y) и cos(x) cos(y), а также для sin(x) n и cos(x) n . Следующие примеры наглядно показывают работу с этим пакетом:
<< mypack\trigdefine.m ?Sin Sin[z] gives the sine of z. Sin[a]*Cos[b] 1/2Sin[a-b] + 1/2 Sin[a+b] Sin[a]*Sin[b] 1/2Cos[a-b] - 1/2Cos[a+b] Cos[a]*Cos[b] 1/2 Costa-b] + 1/2Cos[a+b] Sin[x]^2 1/2-1/2 Cos[2x] Cos[x]^3 Sec[x]/4 +1/2Cos[2x] Sec[x] + 1/4(1/2 + 1/2 Cos[4x]) Sec[x] Sin[x]^n Sin[x]nДанный пример — наглядная иллюстрация программирования символьных вычислений.
Пакет вычисления функций комплексного переменного
Еще один пакет расширений для вычисления функций комплексного переменного (блок пакетов ALGEBRA) представлен распечаткой, приведенной ниже.(* :Title: Relm *) (* :Authors: Roman Maeder and Martin Buchholz *) BeginPackage [ "Algebra 'RelrrT "] RealValued::usage = "RealValued[f] declares f to be a real-valued function (for real-valued arguments)." SBegin["'Private'"] protected = Unprotect[Re, Im, Abs, Conjugate, Arg] (* test for "reality", excluding numbers *) realQ[x_] /; !NumberQ[x] := Im[x] == 0 imagQ[x_] /; !NumberQ[x] := Re[x] == 0 (* fundamental rules *) Re[x_] := x /; realQ[x] Arg[x_] := 0 /; Positive[x] Arg[x_J :=Pi /; Negative[x] Conjugate[x_] := x /; realQ[x] Conjugate[x_] := -x /; imagQ[x] (* there must not be a rule for Im[x] in terms of Re[x] !! *) (* things known to be real *) Im[Re[_]] := 0 Im[Im[_]] := 0 Im[Abs[_]] := 0 Im[Arg[_]] := 0 Im[x_?Positive] = 0 Im[x_?Negative] = 0 Im[x_ ^ y_] := 0,/; Positive[x] && Im[y] == 0 Im[Log[r ?Positive]] := 0 (*' arithmetic *) Re[x_Plus] := Re /@ x Im[x_Plus] := Im /@ x Re[x_ y_Plus] := Re[Expand[x y]] Im[x_ y_Plus] := Im[Expand[x y]] Re[x_ y_] := Re[x] Re[y]— Im[x] Im[y] Im[x_ y_] := Re[x] Im[y] + Im[x] Re[y] (* products *) Re[(x_?Positive y_) ^k_] := Re[x^k y^k] Im[(x_?Positive y_)^k_] := Im[x^k yAk] (* nested powers *) Re[(x_?Positive ^ y_ /; Im[x]==0)^k_] := Re[x^(y k)] Im[(x_?Positive ^ y_ /; Im[x]==0)"kj := Im[хл(у k)] Re[ l/x_ ] := Re[x] / (Re[x]^2 + Im[х]^2) Im[ l/x_ ] := -Im[x] / (Re[x]"2 + Im[x]A2) Im[x_^2] := 2 Re[x] Im[x]Как нетрудно заметить, в этом пакете задано вычисление действительной и мнимой частей для ряда тригонометрических, гиперболических и числовых функций.Re[ x_^n_Integer ] := Block[{a, b},
a = Round[n/2]; b = n-a; Re[x^a] Re[x^b] - Im[х^а] 1т[х^b] ] Im[ x_^n_Integer ] :=Block[{a, b}, a = Round[n/2]; b = n-a; Re[x^a] Im[х^b] + Im[х^a] Re[x^b] ] Re[x_IntegerAn_Rational] := 0 /; IntegerQ[2n] && Negative[x] Im[x_IntegerAn_Rational] := (-х)лп (-1)л((Numerator[n]-l)/2 /; IntegerQ[2n] && Negative[x] (* functions *) Re[Log[r_?Negative]] := Log[-r] Im[Log[r_?Negative]] := Pi Re[Log[z_]] := Log[Abs[z]] /; realQ[z] Re[Log[z_]] := (1/2) Log[Re[z]^2 + Im[z]^2] Im[Log[z_]] := Arg[z] Re[Log[a_ b_]] := Re[Log[a] + Log[b]] Im[Log[a_ b_]] := Im[Log[a] + Log[b]] Re[Log[a_^c_]] := Re[c Log[a]] Im[Log[a_^c_]] := Im[c Log[a]] Ке[Е^х_] :=Cos[Im[x]] Exp[Re[x]] Im[Е^х_] := Sin[Im[x]] Exp[Re[x]] Re[Sin[x_]] := Sin[Re[x]] Cosh[Im[x]] Im[Sin[x_]] :=Cos[Re[x]] Sinh[Im[x]] Re[Cos[x_]] := Cos[Re[x]] Cosh[Im[x]] Im[Cos[x_]] := -Sin[Re[x]] Sinh[Im[x]] Re[Sinh[x_]] := Sinh[Re[x]] Cos[Im[x]] Im[Sinh[x_J] := Cosh[Re[x]] Sin[Im[x]] Re[Cosh[x_]] := Cosh[Re[x]] Cos[Im[x]] Im[Cosh[x_]] := Sinh[Re[x]] Sin[Im[x]] (* conjugates *) Re[Conjugate[z_]] := Re[z] Im[Conjugate[z_]] := Conjugate[x_Plus]:= Conjugate /@ x Conjugate[x_Times]:= Conjugate /@ x Conjugate[x_^n_Integer]:= Conjugate[x]An Conjugate[Conjugate[x_]]:= x (* real-valued rules *) Attributes[RealValued] = {Listable, HoldAll} Attributes[RealValuedQ] = {HoldFirst} RealValued[f_Symbol] := (f/: RealValuedQ[f] = True; f) RealValued[f ] := RealValued /@ {f} Im[ (_?RealValuedQ) [_? (Im[#J ==0&)...] ] := 0 (* define built-in function to be real-valued *) DoRules[flist_] := Block[{protected}, protected = Unprotect[flist]; RealValued[flist]; Protect[Evaluate[protected]] ] DoRules[{Sin, Cos, Tan, ArcSin, ArcCos, ArcTan, ArcCot, Sinh, Cosh, Tanh, ArcSinh, ArcCosh, ArcTanh, Floor, Ceiling, Round, Sign, Factorial}] Protect[Evaluate[protected]] End[] Protect[RealValued] EndPackage[]
Пакет расширения графики
Следующий пример иллюстрирует подготовку графического пакета расширения, который строит графики ряда функций с автоматической установкой стиля линий каждой кривой.(* :Title: Plot *) (* :Context: ProgramminglnMathematica"Plot" *) BeginPackage["ProgramminglnMathematica4 Plot4"] Plot::usage = Plot::usage <> " If several functions are plotted, different plot styles are chosen automatically." Begin["'Private'"] protected = Unprotect[Plot] $PlotActive = True Plot[f_List, args__]/; $PlotActive := Block[{$PlotActive = False}, With[{styles = NestList[nextStyle, firstStyle, Length[Unevaluated[f]]-1]}, Plot[f, args, PlotStyle -> styles] ] ] (* style definitions *) unit = 1/100 max = 5 firstStyle = Dashing[{}] nextStyle[Dashing[{alpha__, x_, y_, omega__}]] /; x > у + unit := Dashing[{alpha, x, у + unit, omega}] nextStyle[Dashing[l_List]] := Dashing[Prepend[Table[unit, {Length[1] +1}], max unit]] Protect! Evaluate[protected] ] End[] EndPackage[]Рисунок 10.6 показывает применение данного пакета.
Пакеты-пустышки
Разумеется, эти примеры не исчерпывают всего разнообразия пакетов расширений. В сущности, они не дают ничего нового, поскольку приведенные листинги являются просто упрощением гораздо более полных и мощных пакетов, уже входящих в систему. В Mathematica 3 и 4 многие функции из пакетов расширения перекочевали в ядро системы, что позволило существенно ускорить вычисления. Поэтому в пакетах расширения можно встретить определения-пустышки, просто сообщающие об этом и не содержащие новых определений функций. Примером такого рода является модуль countroot.m, листинг которого приведен ниже.
Рис. 10.6. Пример применения функции Plot из пакета расширения plot.m (* :Name: Algebra"CountRoots' *)
(* :Copyright: Copyright 1994-1996, Wolfram Research, Inc.*) (* :Summary:All CountRoots functionality is now provided by Algebra'Rootlsolation". The package Algebra'CountRoots" is obsolete. *) Needs["Algebraч Rootlsolation'" ] CountRoots::obslt = "All CountRoots functionality is now provided by Algebra'Rootlsolation'. The package Algebra'CountRoots" is obsolete." Message[CountRoots::obslt]Надо прямо сказать, что в области математики пользователь средней квалификации едва ли может придумать что-либо такое, что еще не включено в ядро или в пакеты расширений системы. Разумно готовить такие пакеты лишь для тех специальных областей применения математики, с которыми работает пользователь, — например в области физики, химии, механики, электротехники и радиотехники и т. д. Однако более вероятно, что пользователь предпочтет готовить не пакеты расширений, а пакеты применений. Пакеты применений — это группы документов с программами, предназначенные для решения определенного класса математических или научно-технических проблем и задач. В отличие от пакетов расширения, в документах пакетов применений обычно дается подробно комментируемое описание всех основных алгоритмов решения задач. При этом комментарий, как правило, выводится на экран дисплея. Довольно часто в пакетах применений используется прием объединения ряда ячеек в одну с общим текстовым заголовком. Это особенно полезно для организации вспомогательных и промежуточных вычислений, ячейки которых загромождают экран и лишают текст документа наглядности. Данный прием скрывает такие вычисления, но позволяет в любой момент вывести их на экран дисплея при активизации маленького прямоугольника, отмечающего такие совмещенные ячейки. Тексты документов, поставляемых с системой, являются прекрасными образцами использования этого приема. Документы пакетов применения — это конечный продукт практического использования системы Mathematica. Поэтому они могут включать в себя все ранее описанные средства системы. Как уже неоднократно отмечалось, документы записываются на диск в виде файлов с расширением .т (в ранних версиях Mathematica — .та), а их полный битовый образ (включающий рисунки) сохраняется во вспомогательных файлах с расширением .mb. При большом числе сложных рисунков в документе эти файлы могут быть весьма большими — сотни килобайт и даже единицы мегабайт.
Создание средств визуального программирования
Что такое визуально-ориентированное программирование
Под визуально-ориентированным программированием обычно понимается автоматическая генерация кодов программ на некотором языке программирования при активизации различных графических объектов — чаще всего кнопок с наглядным изображением программируемых действий или с надписями, указывающими на-такие действия. Mathematica изначально реализует визуально-ориентированное программирование с помощью палитр, содержащих математические операторы и символы. Однако язык программирования системы поддерживает возможность создания таких панелей для произвольных программных модулей. Целый ряд документов, готовящих средства визуально-ориентированного программирования, включен в справочную систему и дает наглядное представление о технике программирования в этой области.Пример создания палитры функций
С помощью директивы Notebook [...] можно создать документ-«блокнот». Ниже представлен такой документ, создающий палитру из нескольких простых функций.Notebook[{ Cell[BoxData[GridBox[{{ ButtonBox[\(Create\ a\ New\ Notebook\), ButtonFunction:>CompoundExpression[ Needs[ "Graphics"Graphics*"] , Needs[ "Graphics'Colors' "], Clear[ targetNB], Set[ targetNB, NotebookCreate[ ] ] ] , ButtonEvaluator->Automatic]}, {ButtonBox[\(f[x_] := \)]}, {ButtonBox[\(Apply\ DefinitionX), ButtonFunction:>CompoundExpression[ NotebookWrite[ targetNB, Cell[ BoxData[ FractionBox[ RowBox[ { RowBox[ {"f", "[", "x", "]"}]/ "-", RowBox[ {"f", "[", "a", "]"}]}], RowBox[ {"x", "-", "a"}]]], "Input"], All], SelectionEvaluateCreateCell[ targetNB]], ButtonEvaluator->Automatic]}, {ButtonBox[\(Cancel[\[SelectionPlaceholder]]\)]}, {ButtonBox[\(Limit[\[SelectionPlaceholder] , x -> a]\)]}, {ButtonBox[\(DisplayX TogetherX), ButtonFunction:>CompoundExpression[ NotebookWrite[ targetNB, Cell[ BoxData[ RowBox [ { RowBox[ {"DisplayTogether", "[", "\n", "\t'V RowBox[ {RowBox[ {"Plot", "[", RowBox[ {RowBox[ {"f", "[", "x", "]"}], ",", RowBox[ {"{", RowBox[ {"x", ",", RowBox[ {"-", "5"}], ",", "5"}], "}"}],",", RowBox[ {"PlotStyle", "->", RowBox[ {"{", "Orange", "}"}]}]}], "]"]], RowBox[ {"Plot", "[", RowBox[ {"\[Placeholder]", ",", RowBox[ {"{", RowBox[ {"a", ",", D/-M7i2^vr ;» "» "5"}] " , " , "5"}] '} " } ] , " , " KOWBOX [1~л -3)J, ,, Э ] \ , s ) J , ,, RowBox[ {"PlotStyle", "->", RowBox [ {"{", "Violet", "}"}]}]}], "]"}]}']/ "]"}], ";"}]], "Input"], All]], ButtonEvaluator->Automatic]}}, RowSpacings->0, ColumnSpacings->0, GridDefaultElement:>ButtonBox[ "\\[Placeholder]"]]], NotebookDefault, CellMargins->{{Inherited, Inherited}, {5, Inherited}}, Evaluatable->True, CellGroupingRules->"InputGrouping", PageBreakAbove->True, PageBreakWithin->False, GroupPageBreakWithin->False, CellLabelMargins->{{11, Inherited}, {Inherited, Inherited}}, DefaultFormatType->DefaultInputFormatType, LineSpacing->{!.25, 0}, AutoItalicWords->{}, FormatType->InputForm, ScriptMinSize->9, ShowStringCharacters->True, NumberMarks->True, Counterlncrements->"lnput", StyleMenuListing->None, FontFamily->"Courier", FontWeight->"Bold"]}, FrontEndVersion->"Microsoft Windows 3.0", ScreenRectangle->{{0, 800}, {0, 544}}, Editable->False, WindowToolbars->{},PageWidth->358 WindowSize->{151, 105}, WindowMargins->{{291, Automatic} Automatic, 19}}, WindowFrame->"Palette", WindowElements->{}, WindowFrameElements->"CloseBox", WindowClickSelect->False, ScrollingOptions->{"PagewiseScrolling"->True}, ShowCellBracket->False, CellMargins->{{0, 0}/ {Inherited, 0}}, Active->True, CellOpen->True, ShowCellLabel->False, ShowCellTags->False, ImageMargins->{{0, Inherited}, {Inherited! 0}}, Magnification->l]Справа показано окно, в котором выполнены операции, шаблоны которых выводит созданная палитра функций. Это окно создается если нажать кнопку Create New Notebook (Создать новый документ)
Рис. 10.7. Работа с созданной палитрой функций
Созданная чисто демонстрационная палитра позволяет вводить в окно документа шаблоны нескольких операций. Например, если нажать кнопку f[x ]:= то шаблон этой операми (задание функции пользователя) Аналогично можно ввести в документ шаблоны и для ряда операций. К сожалению, даже из приведенного простейшего примера видно, что программы, создающие визуально-ориентированные инструментальные средства, достаточно громоздки. Они используют целый ряд функций, не имеющих никакого отношения к математическим вычислениям и служащих лишь для создания элементов пользовательского интерфейса. Объем данной книги не позволяет описать эти функции более подробно. Читателю рекомендуется просмотреть функции, вошедшие в приведенный пример, и изучить ряд других примеров на создание элементов пользовательского интерфейса. Среди этих примеров стоит отметить построение в виде палитры элементов периодической системы Менделеева (рис. 10.8). Активизация любой кнопки с именем химического элемента заносит в строку ввода текущего документа данные об этом элементе.
Рис. 10.8. Палитра периодической системы Менделеева и ее применение
Отладка и трассировка программ
Отладка программ, за исключением самых простейших, дело далеко не простое. Начальный опыт программирования на любом языке приходит спустя годы практической работы с ним. Эти сроки намного сокращаются, если пользователь всерьез знаком хотя бы с одним, а лучше с несколькими языками программирования.
Но даже такой пользователь нуждается в специальных средствах диагностики и контроля программ. Чем их больше, тем совершеннее система программирования. При этом пользователь-программист должен заботиться и о том, чтобы такие средства входили в программные модули, которые создает он сам.Некоторые правила культурного программирования
Выше мы описали множество методов программирования на языке системы Mathematica. Попробуем сформулировать некоторые общие правила так называемого культурного программирования с учетом специфики систем Mathematica, позволяющие создавать надежные и эффективные программные средства: Тщательно продумайте алгоритм решения задачи. Порой выбор лучшего алгоритма позволяет кардинально повысить скорость вычислений и упростить программу (впрочем, одновременно это достигается далеко не всегда). Используйте прежде всего возможности функционального программирования — из него родились основы языка программирования систем Mathematica. Разделяйте задачу на малые части и оформляйте их в виде законченных программных модулей — прежде всего функций. Pie скупитесь на программные комментарии — чем их больше, тем понятнее программа и тем больше шансов, что она заинтересует пользователей и будет долго жить. Учтите, что ясность программы в большинстве случаев важнее скорости ее работы. Тщательно готовьте сообщения об ошибках и диагностические сообщения, а также наименования программных модулей и описания их назначения. Тщательно производите диагностику программных модулей, в том числе с самыми безумными значениями и типами параметров — хорошо спроектированный модуль должен диагностировать любые виды ошибочных ситуаций и реагировать на них адекватным образом. Используйте имена переменных и констант в стиле, принятом в Mathematica, и обязательно с использованием понятных по смыслу обозначений. По мере возможности не используйте в именах зарегистрированные идентификаторы команд и функций. Заменяйте циклы функциями обработки списков, например функциями суммирования и произведения. Применяйте эффективные варианты упрощенных операторов и функций. В максимальной степени используйте функции ядра системы. Обращайтесь к пакетам расширений только в том случае, когда это действительно необходимо. Проводите тщательное тестирование своих модулей, в том числе с выполнением их трассировки. Помните, что нет программы, которую нельзя хоть чуть-чуть, но улучшить и сократить. Однако при этом цените затраченное на это время! По мере возможности используйте готовые апробированные программные модули — изобретать велосипед и делать то, что уже сделано, неразумно. Обращайте особое внимание на реализацию механизма контекстов, позволяющего избежать грубых ошибок при модернизации различных объектов программ, прежде всего наборов функций. Не слишком оригинальничайте! Не применяйте программные трюки и недокументированные приемы программирования. Такие программы в момент создания могут выглядеть удивительно эффектными и потрясающе оригинальными, но вполне возможно, что в следующей версии системы они перестанут работать вообще, поскольку разработчики обычно стараются исключить любые недокументированные трюки в своих программах. Применение этих рекомендаций на практике позволит вам создавать программы, которые нужны не только вам, но и многим пользователям системы Mathematica. Только такие программы могут быть размещены в Интернете и, вполне возможно, войти в пакеты расширения и электронные книги системы Mathematica.Трассировка программных модулей
В практике подготовки и отладки программ важное значение имеет наличие специальных средств отладки программ по шагам — средств трассировки. Mathematica имеет ряд функций для осуществления трассировки своих программных конструкций. Функция Trace [ехрг] позволяет выполнить трассировку выражения ехрг. Возьмем простой пример — вычисление выражения 2 (3 + 4) 2 /5:Trace[2 (3 + 4)^2 / 5] {{{{3+4, 7 },7^2,49}, {1/5,1/5}, 49/5, 49/5}, 249/5, 98/5}Результат трассировки представлен вложенными списками, имеющими два элемента — вычисляемое выражение и результат вычислений. В частности, для приведенного примера отчетливо видно, что вначале вычисляется выражение в круглых скобках (3 + 4) и получается результат 7, который затем возводится в квадрат — получается число 49. Затем вызывается явно не записанная единица для деления на 5, потом 49 умножается на 1/5 и, наконец, 49/5 умножается на 2 и получается конечный результат. Отсюда ясно, что даже равноценные операции умножения и деления Mathematica разделяет по приоритету — деление выполняется перед умножением! Символьные операции также могут трассироваться:
Trace[а*а/(b*b)] {{ {{bb,b^2}, 1/b^2, 1/b^2}, aa/b^2, a^2/b^2}Можно выполнить и трассировку рекуррентных вычислений. Ниже представлен пример трассировки вычисления чисел Фибоначчи:
fib[n_] := fib[n - 1] + fib[n - 2] fib[0] = fib[l] = 1 1 Trace[fib[5], fib[n_] -> n] {5, {4, {3, {2, {!}, {0}}, {!}}, {2, {1}, {0}}}, {3, {2, {!}, {0}}, {!}}} Trace[fib[3]] {fib[3], fib[3-l] + fib[3-2] , {{3- 1, 2}, fib[2] , fib[2-l] + fib[2- 2] , {{2-1, 1}, fibtl], 11, {{2-2, 0}, fib[0] , 1}, 1+1, 2}, {{3-2, 1}, fib[l], 1}, 2+1, 3}Функция TracePrint [expr] дает распечатку последовательности действий при вычислении выражения ехрг:
TracePrint[a*b/c] ab/c Times а b _1 с Power 1/c ab/сПомимо указанных примеров выполнения трассировки и отладки возможны и иные их варианты, осуществляемые с помощью ряда функций. Эти функции представлены в приложении. Надо, однако, отметить, что применение этих функций на современном уровне программирования ограничено — в подобной трассировке особой необходимости нет, поскольку система выдачи диагностических сообщений позволяет выполнять отладку более удобными средствами. В этом уроке мы научились: Работать с образцами. Создавать функции. Использовать функции FixedPoint и Catch. Реализовывать рекурсивные и рекуррентные алгоритмы. Использовать процедуры. Создавать циклы различного типа. Использовать условные выражения и безусловные переходы. Работать с контекстами. Готовить пакеты расширений системы Mathematica. Создавать простейшие средства визуального программирования. Использовать средства отладки и трассировки программ.