[327e91]: ru / maxima-tarnavsky-6-diff.xml  Maximize  Restore  History

Download this file

257 lines (198 with data), 39.1 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="../main.xsl"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" id="maxima-tarnavsky-6-diff" xml:lang="ru">
<head>
<title>Тихон Тарнавский. Пишем свой diff()</title>
<link rel="schema.DC" href="http://purl.org/dc/elements/1.1/"/>
<link rel="schema.DCTERMS" href="http://purl.org/dc/terms/"/>
<meta name="DC.identifier" scheme="DCTERMS.URI" content="http://maxima.sourceforge.net/ru/maxima-tarnavsky-6-diff.html"/>
</head>
<body>
<p>Впервые было опубликовано в «<a href="http://www.linuxformat.ru/">Linux Format</a>» <a href="http://www.linuxformat.ru/download/85.pdf">№12&#x00a0;(86), декабрь&#x00a0;2006&#x00a0;г</a>.</p>
<p>Файл к статье: <a href="deriv.mac">deriv.mac</a>.</p>
<p>Сначала я хотел рассмотреть несколько отдельных практических примеров: и маленьких, и чуть побольше. Но потом мне подумалось, что один, но более серьезный пример будет значительно лучше: с одной стороны, его можно строить понемногу, отрабатывая отдельные приемы точно так же, как это было бы сделано и с меньшими примерами, а с другой&#x00a0;— в результате все эти приемы переплетутся между собой во что-то объемное, и на этих переплетениях возникнет более цельное ощущение возможностей программы, чем на несвязанных маленьких кусочках. К тому же по ходу дела мы соорудим несколько небольших вспомогательных функций, а заодно, для дополнительной практики, и более расширенную версию одной из них, которая, вполне возможно, пригодится вам и в дальнейшем.</p>
<p>А писать мы будем настоящую функцию дифференцирования. Практически такую же, как встроенная <kbd>diff()</kbd>, только без вычисления полного дифференциала&#x00a0;— чтобы не слишком сложно было «охватить» пониманием сразу весь пример. Ну а если будет интерес, то дописать вычисление полного дифференциала к этой же функции вы можете попробовать самостоятельно&#x00a0;— после освоения возможностей, которые сейчас будут продемонстрированы, это будет уже несложно. Примеров применения по ходу создания функции я давать не буду. Если вы хотите смотреть на практические результаты, по мере добавления кода можно сохранять его в файле, скажем, <kbd>~/.maxima/deriv.mac</kbd> и выполнять в Maxima строку <kbd>load(deriv)$ deriv(<var>какое-нибудь-выражение</var>);</kbd>.</p>
<p>Я буду писать код постепенно и по ходу написания давать комментарии к последнему написанному участку. Комментировать буду, просто вставляя куски кода в текст. К слову: Maxima поддерживает комментарии в коде «в стиле Си», то есть комментарий начинается символами <kbd>/*</kbd>, а заканчивается <kbd>*/</kbd>. Причем, в отличие от Си, допускаются вложенные комментарии: <kbd>/*&#x00a0;вот&#x00a0;/*&#x00a0;такие&#x00a0;*/&#x00a0;*/</kbd>.</p>
<p>Чтобы не повторять каждый раз весь код от самого начала, я буду сокращать его с помощью многоточия. Если вы будете проверять код по мере чтения, не забывайте о разделяющих запятых после последних строк предыдущих участков.</p>
<p>Начнем с «подготовительных работ»: проверки определенных условий и сохранения нужных значений в локальных переменных.</p>
<pre>deriv([l]):=block([f,len,x],
len:length(l),
if len=0 then
error("deriv can't be used without arguments"),
f:l[1],
x:listofvars(f)
)$</pre>
<p>Итак, по порядку. Символ в квадратных скобках означает, что ему будет присвоен список из всех аргументов, с которыми вызвана функция. Эта конструкция предназначена для создания функций с переменным числом аргументов.</p>
<p>Функция <kbd>block()</kbd>&#x00a0;— это расширенный аналог составного оператора. Отличается она двумя вещами. Во-первых, поддерживается возврат значений через <kbd>return()</kbd>, точно так же как из цикла, то есть по <kbd>return(<var>выражение</var>)</kbd> будет осуществлен выход из блока и результатом вычисления блока станет «выражение». А во-вторых, в блоке можно использовать локальные переменные&#x00a0;— то есть такие, которые не повлияют на значения символов вне блока, даже если будут иметь совпадающие с ними имена. Такие локальные символы перечисляются в виде списка в самом начале блока.</p>
<p>Далее мы сохраняем в одной из таких локальных переменных длину списка аргументов (функция <kbd>length</kbd>) и в случае, если она равна нулю (то есть аргументов нет), генерируем ошибку функцией <kbd>error</kbd>, которая может принимать произвольное число аргументов, которые она вычисляет и выводит прежде чем создать ошибку.</p>
<p>Функция <kbd>listofvars</kbd> возвращает список переменных переданного ей выражения. Этот список понадобится нам для небольшого расширения возможностей: так как мы не будем вычислять полный дифференциал, то вызов с одним аргументом у нас освобождается, и мы будем его использовать аналогично функции <kbd>solve</kbd>: если переданное выражение включает в себя только одну неизвестную, будем дифференцировать его по ней. Продолжаем:</p>
<pre>deriv([l]):=block([f,len,x],
...
x:listofvars(f),
if len=1 then (
if length(x)=0 then
return(0),
if length(x)>1 then
error("Expression has more than one unknowns and none was
specified.","Unknowns given:", x),
x:x[1] )
else
x:l[2]
)$</pre>
<p>Если параметр дифференцирования в списке аргументов не задан, то проверяем длину списка неизвестных. Если она равна нулю&#x00a0;— то это константа и следовательно возвращаем ноль. Если больше единицы, то неизвестно, по чему дифференцировать, следовательно, снова генерируем ошибку. Ну а в случае единицы, просто превращаем список из одного элемента в сам этот элемент. Если же список аргументов длиннее, то берем параметр оттуда.</p>
<pre>deriv([l]):=block([f,len,x],
...
else
x:l[2]
if len>=3 then
error("More than 2 arguments not implemented yet.")
)$</pre>
<p>Пока ограничимся производной первого порядка по одной переменной. Когда этот этап будет пройден, остальное будет уже нетрудно написать на основе имеющегося кода. Теперь, когда проверки закончены, приступаем непосредственно к реализации. Строить эту функцию мы будем поэтапно. Для начала научим ее дифференцировать просто переменную и константу:</p>
<pre>
deriv([l]):=block([f,len,x],
...
error("More than 2 arguments not implemented yet.")
if atom(f) or subvarp(f) then
if f=x then
return(1)
else
return(0),
else
return ('diff(f,x))
)$</pre>
<p>Предикат <kbd>atom()</kbd> проверяет, является ли его аргумент атомарным выражением, то есть константой (целой либо с плавающей точкой) или одиночным символом. Второй предикат&#x00a0;<kbd>subvarp()</kbd>&#x00a0;— расшифровывается как <kbd>subscripted&#x00a0;variable&#x00a0;(predicate)</kbd>, где первые два слова означают «индексированная переменная», то есть что-то вида <kbd>a[1]</kbd>. Добавлен этот предикат в эту же проверку в связи с тем, что Maxima такие выражения атомарными не считает, а с точки зрения дифференцирования они как раз являются атомами. Дальше в этом варианте все просто: если атомарное выражение является параметром дифференцирования, то результат будет равен единице, иначе&#x00a0;— нулю: в полном соответствии с правилами дифференцирования.</p>
<p>В самом конце функции добавляем строку, которая в нештатном случае (таком, который мы еще не посчитали) просто вернет несовершенную форму производной от оставшегося выражения. Эта строка у нас вплоть до самой полной реализации будет оставаться последней, а все остальное мы будем вписывать до нее, сокращая тем самым этому некрасивому умолчательному случаю шансы на выживание. А двигаться дальше мы будем достаточно интересным способом, с помощью уже упомянутой в статье рекурсии. Мы будем постепенно обучать нашу функцию все новым и новым трюкам (точнее, правилам дифференцирования), разбивая неизвестные выражения некоторыми способами на более простые, уже обработанные варианты; то есть действуя снова по известному «принципу чайника». И вы увидите, что математики не зря так любят этот принцип: с его помощью такая, на первый взгляд, сложная задача будет разбита на множество простых подзадачек и таким образом упростится сама. Например, первым пойдет вычитание. Точнее, унарный минус или попросту отрицательные величины: бинарного минуса в Maxima по сути не существует, а любое выражение вида <kbd>a–b</kbd> имеет внутреннюю форму <kbd>a+(–b)</kbd>, то есть сводится по все тому же принципу к плюсу. Итак, приступим:</p>
<pre>setup_autoload(stringproc,sequal)$
deriv([l]):=block([f,len,x,o],
...
else
return(0),
o:op(f),
return (
if sequal(o,"-") then
-deriv(-f,x)
else
'diff(f,x)
)
)$</pre>
<p>Тут мы уже начинаем использовать те самые функции по «глубокой» обработке выражений. Функция <kbd>op()</kbd> возвращает основной оператор заданного выражения. Основным считается самый внешний; например <kbd>op(a+b/c)</kbd> будет равен <kbd>"+"</kbd>, <kbd>op((a+b)*2)</kbd>&#x00a0;<kbd>"*"</kbd>, а <kbd>op(sin(x^2+y^2))</kbd>&#x00a0;<kbd>sin</kbd>. Дальше включается «принцип чайника»: для отрицательного выражения мы просто выносим минус за скобки, а для остального, теперь уже положительного, вызываем саму же функцию <kbd>deriv</kbd>.</p>
<p>Здесь для сверки значения оператора с минусом используется не <kbd>equal()</kbd>, а ее строковый аналог&#x00a0;<kbd>sequal()</kbd>, проверяющий на равенство две строки. Связано это с тем, что разные операторы Maxima хранит в разном виде, и при сверке, скажем, того же минуса, который хранится как текстовый знак с синусом, хранящимся как символ (идентификатор) Maxima, обычный <kbd>equal()</kbd> просто выдаст ошибку.</p>
<p>Функция <kbd>sequal()</kbd>&#x00a0;— внешняя, она хранится в файле <kbd>stringproc</kbd> (от фразы «<em>string processing</em>»&#x00a0;— обработка строк), который и нужно подгрузить до использования этой функции. А для того чтобы, файл не приходилось загружать вручную, но при этом он и не загружался бы при каждом вызове функции (как было бы в случае вызова <kbd>load()</kbd> внутри функции <kbd>deriv()</kbd>), есть, с одной стороны, традиционный способ: определить внутри файла некую константу или свойство, а перед его загрузкой проверять их наличие: если нету&#x00a0;— тогда и подгружать. Мы же используем не общепринятый, но в чем-то более простой метод: рассмотренную в статье функцию <kbd>setup_autoload</kbd>. Благодаря ей, нам с одной стороны, не надо лезть в исходники библиотек (которые, кстати говоря, часто бывают не на языке Maxima, а на Lisp) и искать там флаги; а с другой&#x00a0;— мы все же уверены, что файл будет загружаться не больше одного раза: именно это и гарантируется функцией <kbd>setup_autoload</kbd>.</p>
<p>И последний момент в этом кусочке: обратите внимание на оператор <kbd>if</kbd>, сместившийся внутрь функции <kbd>return()</kbd>. Напомню, что <kbd>if</kbd> в Maxima является полноценным оператором, то есть всегда возвращает последнее вычисленное значение. А раз так, нет никакого смысла вызывать <kbd>return()</kbd> много раз. По большому счету, здесь и один вызов <kbd>return()</kbd> не нужен: результатом <kbd>block()</kbd>, как и примитивного составного оператора, будет последнее вычисленное выражение. Так что для еще большей краткости напишем даже так:</p>
<pre>...
o:op(f),
if sequal(o,"-") then
-deriv(-f,x)
else
'diff(f,x)
)$</pre>
<p>После минуса логично было бы заняться плюсом; но поскольку сумма при дифференцировании переходит в сумму, то проще будет реализовать ее сразу для произвольного числа слагаемых, а это уже немного сложнее. Потому начнем с более простых в реализации арифметических действий: умножения и деления.</p>
<pre>...
if sequal(o,"-") then
-deriv(-f,x)
else if sequal(o,"*") then
deriv(first(f),x)*rest(f)+first(f)*deriv(rest(f),x)
else if sequal(o,"//") then
(deriv(first(f),x)*last(f)-first(f)*deriv(last(f),x))/last(f)^2
else
'diff(f,x)
)$</pre>
<p>Здесь мы сталкиваемся с одним очень интересным и весьма полезным свойством: многие из функций работы со списками, которых в Maxima немало, воспринимают как списки также и любые выражения. Так, «списковая» функция <kbd>first()</kbd>, возвращающая первый элемент заданного списка, вызванная как <kbd>first(a*b*c)</kbd>, вернет <kbd>a</kbd>; а у функции <kbd>rest()</kbd> («остаток»), отдающей (в варианте вызова с одним аргументом), наоборот, весь список кроме первого элемента, на том же выражении результатом будет <kbd>b*c</kbd>. Этим мы и воспользовались, вызывая при этом снова для каждого слагаемого саму функцию <kbd>deriv()</kbd>. Если сомножителей будет больше чем два, то вызов <kbd>deriv(rest(f),x)</kbd> пройдет по этой же ветке и отсечет еще один.</p>
<p>Так же мы поступаем и с делением. Здесь, так как аргумента всегда два, вместо <kbd>rest()</kbd> используется функция <kbd>last()</kbd>&#x00a0;— последний элемент списка (<kbd>rest()</kbd> в этом же случае вернула бы список из одного элемента, а потому <kbd>last()</kbd> более удобна). Только одно «но»: деление почему-то обозначается во внутреннем представлении Maxima не одиночной, а двойной косой чертой.</p>
<p>Точно таким же образом можно обработать последний бинарный оператор (кроме оставленного на закуску сложения)&#x00a0;— возведение в степень. Здесь тоже нет никаких сложностей, и даже нечего дополнительно объяснять по сравнению с делением:</p>
<pre>...
else if sequal(o,"^") then
first(f)^last(f)*log(first(f))*deriv(last(f),x)+
first(f)^(last(f)-1)*last(f)*deriv(first(f),x)
else
'diff(f,x)
)$</pre>
<p>Теперь вернемся к сложению. Тут нам уже пригодятся упомянутые в статье функции по работе с функциями, а конкретно&#x00a0;— функция <kbd>map()</kbd>. Она принимает в качестве первого аргумента имя функции и как бы вкладывает эту функцию внутрь выражений&#x00a0;— последующих аргументов. Проще всего будет пояснить на примере: <kbd>map(f,[a,b,c])</kbd> даст результат <kbd>[f(a),f(b),f(c)]</kbd>. И, что самое замечательное, она, точно так же, как и «списковые» функции, работает не только со списками, но и с любыми выражениями; например, <kbd>map(f,a+b+c)</kbd>&#x00a0;<kbd>f(a)+f(b)+f(c)</kbd>. Как хорошо подходит для нашей задачи, не правда ли? Именно так и должна действовать на сумму функция дифференцирования. Все было бы совсем хорошо, если бы <kbd>deriv()</kbd> принимала, кроме выражения, только один аргумент. С двумя выражениями <kbd>map</kbd> тоже умеет работать, но только если у них одинаковый основной оператор; то есть сумму можно «отобразить» только на сумму: <kbd>map(f,a+b+c,x+y+z)</kbd>&#x00a0;<kbd>f(c,z)+f(b,y)+f(a,x)</kbd>. Проблема здесь в том, что у нас второй аргумент во всех вызовах <kbd>deriv()</kbd>, которые должны попасть внутрь суммы, одинаков, а выражение вида <kbd>x+x+x</kbd> передать невозможно: оно автоматически упростится в <kbd>3*x</kbd>. Но, как известно, из любой безвыходной ситуации всегда есть как минимум два выхода. И в данном случае один из этих выходов достаточно прост: написать небольшую функцию-«обертку» вокруг map:</p>
<pre>map1st(f,expr,x):=block([o],
o:op(expr),
subst(o,"[",map(f,subst("[",o,expr),makelist(x,i,1,length(expr))))
)$</pre>
<p>Еще одна новая функция «глубинной» работы с выражениями: subst(). Она способна заменять в выражении… да почти что угодно и почти на что угодно. Вызывается так: subst(стало, было, выражение), заменяя в «выражении» все, что «было», на «стало». Опять же, в качестве подвыражений могут использоваться операторы, то есть <kbd>subst("*","+",x+y+z)</kbd>&#x00a0;<kbd>x*y*z</kbd>. Мы используем ее для временной подмены основного оператора выражения оператором списка (который обозначается как <kbd>"["</kbd>, то есть <kbd>[a,b,c]</kbd>&#x00a0;— это, по сути, <kbd>"["(a,b,c)</kbd>). Затем генерируем список такой же, как выражение, длины, заполненный заданной переменной,&#x00a0;— и применяем к двум полученным спискам функцию <kbd>map()</kbd>, а затем возвращаем назад вместо списка первоначальный базовый оператор. То есть теперь, к примеру, <kbd>map1st(f,a+b+c,x)</kbd> будет равно как раз <kbd>f(c,x)+f(b,x)+f(a,x)</kbd>. Et voila, как говорят французы! И теперь внутри <kbd>deriv()</kbd> можно применить к сложению именно эту новую функцию. Заодно применим ее и к списку&#x00a0;— (<kbd>deriv([f,g],x)</kbd> будет равно <kbd>[deriv(f,x),deriv(g,x)]</kbd>) и, чего уж там мелочиться, и к множеству:</p>
<pre>...
o:op(f),
if sequal(o,"+") or sequal(o,"[") or sequal(o,set) then
map1st(deriv,f,x)
else if sequal(o,"-") then
-deriv(-f,x)
...</pre>
<p>Множества, к слову, в Maxima реализованы в самом что ни на есть математическом смысле: множество может включать в себя каждый элемент только один раз; и это учитывается и встроенными операциями по работе с множествами: пересечением, объединением и&#x00a0;т.&#x00a0;д. Есть еще некоторые ошибки, но они документированы и потому не неожиданны.</p>
<p>Движемся дальше. У нас уже реализована производная от всех бинарных операторов, а дальше мы нарисуем «таблицу производных» и будем работать с нею:</p>
<pre>deriv([l]):=block([f,len,o,x,func,fdrv],
...
o:op(f),
func:[sqrt, sin, cos, abs, exp, log, tan, cot, sec, csc, asin, acos, atan,
acot, asec, acsc, sinh, cosh, tanh, coth, asinh, acosh, atanh, acoth,
asech, acsch],
fdrv:[1/2/arg, cos(arg), -sin(arg), arg/abs(arg), exp, 1/arg, sec(arg)^2,
-csc(arg)^2, tan(arg)*sec(arg), -cot(arg)*csc(arg), 1/sqrt(1-arg^2),
-1/sqrt(1-arg^2), 1/(1+arg^2), -1/(1+arg^2), 1/arg^2/sqrt(1-1/arg^2),
-1/arg^2/sqrt(1-1/arg^2), cosh(arg), sinh(arg), sech(arg), -csch(arg),
1/sqrt(arg^2+1), 1/sqrt(arg^2-1), 1/(1-arg^2), 1/(1-arg^2),
-1/arg^2/sqrt(1/arg^2-1), -1/arg^2/sqrt(1/arg^2+1)],
if sequal(o,"+") or sequal(o,"[") or sequal(o,set) then
...</pre>
<p>Для упрощения работы с «таблицей» напишем еще две небольших вспомогательных функции: одна будет проверять, входит ли заданный элемент в заданный список, а вторая&#x00a0;— возвращать номер, соответствующий заданному элементу в заданном списке, при условии что он там есть.</p>
<pre>smember(expr,list):=
if sequal(true,
for i in list do
if sequal(expr,i) then
return(true) )
then true$
sindex(expr,list):=block([num],
num:for i:1 thru length(list) do
if sequal(expr,list[i]) then
return(i),
if integerp(num) then num
)$</pre>
<p>Здесь есть только одна тонкость, связанная с небольшой проблемой. Заключается эта проблема в том, что для возвращения значения из блока и из цикла в Maxima используется одна и та же функция <kbd>return()</kbd>. Это приводит к тому, что выйти из блока, находясь внутри цикла в нем, невозможно&#x00a0;— приходится выдумывать некоторые несложные ухищрения. Теперь с использованием двух новых функций заменяем элементы «таблицы» их производными; с помощью уже знакомой нам <kbd>subst</kbd>, которая подставит нужное выражение внутрь табличной функции вместо ключевого слова <kbd>arg</kbd>.</p>
<pre>...
else if smember(o,func) then
deriv(first(f),x)*subst(first(f),arg,fdrv[sindex(o,func)])
else
'diff(f,x)
)$</pre>
<p>Вот так, начиная с самых простых элементов, а затем, подобно Мюнхгаузену, вытаскивая самих себя сантиметр за сантиметром, мы и получили полноценную функцию дифференцирования. Правда, пока только первого порядка и только по одному аргументу. Но имея то, что имеем, двигаться дальше, следуя известному принципу, уже совсем не сложно: просто заменим строку «<kbd>if&#x00a0;len>=3&#x00a0;then&#x00a0;error…</kbd>» следующим куском:</p>
<pre>...
if len=3 then (
integerp(l[3]) or
return('diff(x,l[3])),
if l[3]=0 then
return(f),
if l[3]&lt;0 then
error("Improper count to deriv:",l[3]),
if l[3]>1 then
return(deriv(deriv(f,x,l[3]-1),x))
),
if len>3 then (
if(evenp(len)) then
l:endcons(1,l),
return(deriv(apply(deriv,rest(l,-2)),l[len],l[len+1]))
),
...</pre>
<p>Пройдемся по нескольким неосвещенным моментам. В силу способов вычисления в Maxima (которые сродны таковым во многих языках программирования) конструкция вида «<kbd><var>условие</var>&#x00a0;or&#x00a0;<var>выражение</var></kbd>» равносильно «<kbd>if&#x00a0;not&#x00a0;<var>условие</var>&#x00a0;then&#x00a0;<var>выражение</var></kbd>»&#x00a0;— и использована здесь исключительно для разнообразия, в учебных целях. Здесь мы в случае нецелого порядка дифференцирования просто возвращаем несовершенную форму&#x00a0;— точно так же, как это делает и штатная функция <kbd>diff()</kbd>.</p>
<p>Производная нулевого порядка от любой функции&#x00a0;— это сама функция. А производные отрицательных порядков некорректны, о чем мы и генерируем сообщение. Для порядков, больших единицы, понижаем порядок как и раньше&#x00a0;— за счет самовызова.</p>
<p>Далее я немного усовершенствовал поведение функции по сравнению со встроенной: если та не умеет принимать четное количество аргументов больше двух (то есть с неуказанным порядком дифференцирования по последней неизвестной когда неизвестных больше одной), то у нас в данном случае, так же как и для одной неизвестной, будет подразумеваться единица. Здесь предикат <kbd>evenp()</kbd> проверяет число на четность (even&#x00a0;— четный), а функция <kbd>endcons()</kbd> добавляет заданный элемент в конец заданного списка. Ее имя носит исторический характер: парная к ней функция <kbd>cons()</kbd>, добавляющая элемент в начало списка, свое имя позаимствовала из Lisp, а слово end здесь добавлено «по смыслу».</p>
<p>Далее мы, снова самовызовом, укорачиваем список параметров дифференцирования. При этом используется еще одна функция, работающая с функциями,&#x00a0;<kbd>apply()</kbd> (<em>применять</em>). Она принимает два аргумента, первый из которых&#x00a0;— имя функции, а второй&#x00a0;— список, и применяет заданную функцию к списку как к списку аргументов. Также здесь использован более широкий вариант вызова <kbd>rest()</kbd>: он может принимать второй аргумент&#x00a0;— целое число, не равное нулю. Если число положительно, то такое количество элементов выбрасывается из начала списка, а если отрицательно&#x00a0;— то с конца; в данном случае мы теряем последние два элемента.</p>
<p>Вот и все. Мы уже имеем полную функцию дифференцирования, берущую производные с произвольным количеством параметров и любых порядков. Полный текст всех созданных функций вы можете найти в файле <kbd>deriv.mac</kbd> на прилагаемом к журналу диске.</p>
<p>Дополнительно хочется остановиться на одной незамысловатой функции, которая, тем не менее, может неплохо помочь в отладке собственных модулей. Это функция <kbd>display()</kbd>, которая принимает имена и отображает их значения в виде «<kbd><var>имя</var>=<var>значение</var></kbd>». В качестве эксперимента можете добавить ее где-нибудь внутри функции <kbd>deriv()</kbd> и отследить процесс самовызова (в файле на диске вызов <kbd>display()</kbd> достаточно раскомментировать).</p>
<p>И в качестве финального аккорда сделаем еще и более универсальную версию вспомагательной функции <kbd>map1st()</kbd>&#x00a0;— возможно, тогда она вам пригодится и еще где-нибудь.</p>
<pre>mapany(f,[lst]):=block([o,l],l:lst,
if length(setify(map(length,l)))>1 then
error("Arguments to mapany are not of the same length"),
o:op(l[1]),
for i:1 thru length(l) do
l[i]:subst("[",op(l[i]),l[i]),
subst(o,"[",apply(map,cons(f,l)))
)$</pre>
<p>Здесь я уже воздержусь от столь подробных комментариев, так как практически все, что используется в этой функции, уже было в той или иной степени разъяснено в процессе описания <kbd>deriv()</kbd>. Остановлюсь только на одной строчке:</p>
<pre>if length(setify(map(length,l)))>1 then</pre>
<p>Здесь используется не совсем простой прием для проверки длин списков на одинаковость. Так как <kbd>l</kbd>&#x00a0;— это список из списков, то сначала получаем список длин «вкручиванием» внутрь внешнего списка функции <kbd>length()</kbd>. Дальше&#x00a0;— интереснее. Функция <kbd>setify</kbd> (дословно&#x00a0;— что-то вроде «множествицировать») превращает список в множество. Так как множество не может содержать несколько равных между собой элементов, то такие элементы при этом «склеиваются»: из них остается один. Таким образом если «длина» (количество элементов) множества больше единицы, то как минимум два элемента в первоначальном списке были неравны между собой.</p>
<p>И вернувшись к рассмотренной функции дифференцирования, хочется еще раз обратить ваше внимание на использованный прием: конструировать большие и сложные функции из более маленьких и простых кусочков с помощью рекурсии. Этот метод очень часто и продуктивно используется в функциональном программировании, к которому Maxima, в силу своих Lisp-овских корней, очень близка.</p>
</body>
</html>

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:

JavaScript is required for this form.





No, thanks