Главная страница
Навигация по странице:

  • Как можно породить случайное рациональное число

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


    Скачать 4.43 Mb.
    НазваниеНастоящий учебник посвящен системе Mathematica прикладному пакету компьютерной алгебры, при помощи которого можно решать любые задачи, в которых в той или иной форме встречается математика
    АнкорМатематика
    Дата11.05.2022
    Размер4.43 Mb.
    Формат файлаpdf
    Имя файла106-108.pdf
    ТипУчебник
    #521834
    страница12 из 38
    1   ...   8   9   10   11   12   13   14   15   ...   38
    Упражнение. Если Вы внимательно прочитали предыдущую фразу, ска- жите, что произойдет, если попытаться вычислить D[Pi,Pi]?
    Ответ. А ничего, система просто ответит, что по Pi дифференцировать нельзя: General:
    Pi is not a valid variable.
    § 12. Непрерывные дроби и рациональные приближения
    В настоящем параграфе мы рассматриваем еще один способ задания ве- щественных чисел и операции, превращающие приближенное вещественное число в точное рациональное число. Содержание этого параграфа носит чисто развлекательный характер и не используется в дальнейшем.
    Rationalize[x,d]
    хорошее рациональное приближение к x
    ContinuedFraction[x,n]
    разложение x в непрерывную дробь
    FromContinuedFraction[x]
    восстановление x по непрерывной дроби
    Функция Rationalize[x] строит хорошее рациональное приближение к приближенному вещественному числу x. Попытка вычислить Rational- ize[Pi] вернет π, потому что система не знает, приближение какой точно- сти нас интересует. Не приведет к успеху и попытка вычисления Ratio- nalize[N[Pi]]. Дело в том, что по используемому функцией Rationalize критерию у вычисленного с машинной точностью числа N[Pi] нет хороших рациональных приближений. А именно, рациональное число p/q рассмат- ривается в качестве хорошего приближения вещественного числа x, если
    |x − p/q| < c/q
    2
    , где по умолчанию c = 10
    4
    Правильная форма состоит в том, чтобы спросить Rationalize[Pi,10^- n], явно задав допуск (tolerance). Вот построение рациональных прибли- жений числа π с точностью до 1, 2, . . . , 10 десятичных знаков после запятой:
    In[29]:=Map[Rationalize[Pi,10^(-#)]&,Range[10]]
    Out[29]=
    {22/7, 22/7, 201/64, 333/106, 355/113, 355/113, 75948/24175,
    100798/32085, 103993/33102, 312689/99532
    }
    А вот, что такое на самом деле используемое машинное приближение числа π.
    Вычисление Rationalize[N[Pi],0] с нулевым допуском дает
    245850922/78256779.

    174
    Функция ContinuedFraction[x,n] выдает список первых n членов в раз- ложении x в непрерывную дробь. Функция ContinuedFraction[x], вызван- ная с одним аргументом порождает список всех членов, которые можно получить исходя из заданной точности x. Список
    {n1,...,ns} истолковы- вается обычным образом:
    [n
    1
    , . . . , n
    s
    ] = n
    1
    +
    1
    n
    2
    +
    1
    n
    3
    +
    1
    n4+...
    В случае квадратичных иррациональностей ответ имеет вид
    {m1,...,ms,{n1,...,nt}}
    где m
    1
    , . . . , m
    s
    — начальные члены непрерывной дроби, а n
    1
    , . . . , n
    t

    период.
    Вычислим для примера выражения нескольких важнейших квадратич- ных иррациональностей:
    In[30]:=Map[ContinuedFraction,
    {GoldenRatio,GoldenRatio^-1,Sqrt[2],Sqrt[3]}]
    Out[30]=
    {{1,{1}},{0,{1}},{1,{2}},{1,{1,2}}}
    Функция FromContinuedFraction является обратной к функции Contin- uedFraction. Она действует обычным образом, восстанавливая исходное рациональное число — или при наличии периода, исходную квадратич- ную иррациональность!! — по его/ее разложению в непрерывную дробь.
    Например, вычисление FromContinuedFraction[
    {1,{1,2,3}}] дает (1 +

    37)/3.
    Хорошее рациональное приближение к вещественному числу x можно построить также применив к числу x функцию ContinuedFraction[x,n] с большим значением n, а потом применив к результату функцию FromCon- tinuedFraction[x]. Вот, для примера, первые 24 знака разложения e в непрерывную дробь:
    In[31]:=ContinuedFraction[E,24]
    Out[31]=
    {2,1,2,1,1,4,1,1,6,1,1,8,1,1,10,1,1,12,1,1,14,1,1,16}
    Если Вы не видели этого раньше (например, не читали Кнута!), это долж- но произвести впечатление и Вы, конечно, тут же попытаетесь вычислить
    ContinuedFraction[E,1000]. Результат будет поучителен во всех отноше- ниях — как с точки зрения рациональных приближений, так и с точки зрения работы системы. Итак, вот эта дробь в традиционной математиче- ской форме:
    2 +
    1 1 +
    1 2+
    1 1+
    1 1+
    1 4+
    1 1+
    1 1+
    1 6+
    1 1+
    1 1+
    1 8+
    1 1+
    1 1+
    1 10+
    1 1+
    1 1+
    1 12+
    1 1+
    1 1+
    1 14+
    1 1+
    1 1+
    1 16+1

    175
    Несложное вычисление с использованием функции FromContinuedFraction показывает, что значение этой непрерывной дроби равно
    In[32]:=FromContinuedFraction[
    {2,1,2,1,1,4,1,1,6,1,1,8,1,1,10,1,1,
    12,1,1,14,1,1,16
    }]
    Out[32]=14013652689/5155334720
    Это рациональное число, для записи которого требуется 21 цифра, совпа- дает с e с точностью до 20 десятичных знаков.
    § 13. Комплексные числа
    В системе Mathematica комплексное число тоже может быть точным или приближенным. Обычно комплексное число представляется в алгебраиче- ской форме z = x + yi, причем оно считается точным в том и только том случае, когда как его вещественная часть x = re(z), так и его мнимая часть
    y = im(z) точные.
    I
    i
    мнимая единица z=x+I*y
    z = x + iy
    комплексное число z, где x, y
    R
    Re[z]
    re(z)
    вещественная часть z
    Im[x]
    im(z)
    мнимая часть z
    Abs[z]
    |z|
    модуль x
    Arg[z]
    arg(z)
    аргумент z
    Conjugate[z]
    z = x
    − iy сопряженное к z
    ComplexExpand[z]
    выделение re(z) и im(z)
    Использование большинства этих функций совершенно ясно из названия.
    Так, I обозначает мнимую единицу i, а Re[z] и Im[z] дают вещественную и мнимую часть комплексного числа z, соответственно. Функция Conju- gate[z] сопоставляет числу z = x + yi комплексно сопряженное z = x
    − yi.
    Вопрос от точности или приближенности решается отдельно для ве- щественной и мнимой частей.
    Например, у комплексного числа 1.+1*I
    вещественная часть приближенная, но мнимая часть точная. Тем самым,
    вычисление Re[1.+1*I] даст приближенное вещественное число 1., но вы- числение мнимого числа Im[1.+1*I] дает точное целое число 1 — несмотря на то, что число 1.+1*I приближенное!! Функция N обычным образом дей- ствует на комплексных числах. Так, например, вычисление N[1+I] дает
    1.+1.*I.
    Если мнимая часть комплексного числа точно равна 0, то оно автома- тически упрощается до вещественного числа. Так, например, вычисление
    Head[1+0*I] дает Integer, а вычисление Head[1.+0*I] дает Real. В то же время, если мнимая часть комплексного числа приближенно равна 0,
    то оно все равно считается комплексным!!! Так, в частности, вычисление
    Head[1+0.*I] дает Complex. Это различие становится весьма существен- ным, когда мы начинаем реально использовать многозначные функции.
    Следующий в высшей степени поучительный диалог иллюстрирует раз- ницу между Simplify и FullSimplify:

    176
    In[33]:=Simplify[Re[z]+I*Im[z]]
    Out[33]=I*Im[z]+Re[z]
    In[34]:=FullSimplify[Re[z]+I*Im[z]]
    Out[34]=z
    Кроме алгебраической формы комплексное число можно задавать и в полярной форме Abs[z]*Exp[I*Arg[z]], которую в русской учебной лите- ратуре принято называть тригонометрической. В этой форме используется две другие функции:
    Модуль Abs[z] комплексного числа z = x + yi, равный p
    x
    2
    + y
    2
    Аргумент Arg[z], т.е. такое число ϕ ∈ [−π, π], что tg(ϕ) = y/x. В
    тех случаях, когда это возможно, Arg возвращает точные значения. Так,
    например, Arg[-1/2+Sqrt[3]*I/2] дает 2π/3, Arg[-1/2-Sqrt[3]*I/2] дает
    2π/3.
    Особенно интересна функция ComplexExpand, которая явным образом выделяет из комплексного числа вещественную и мнимую часть, считая,
    что все входящие в выражение этого числа переменные являются веще- ственными.
    In[35]:=ComplexExpand[Exp[x+y*I]]
    Out[35]=e^x Cos[y]+Ie^x Sin[y]
    In[36]:=ComplexExpand[Log[x+y*I]]
    Out[36]=I*Arg[x+I*y]+1/2*Log[x^2+y^2]
    In[37]:=ComplexExpand[Cos[x+y*I]]
    Out[37]=Cos[x]*Cosh[y]-I*Sin[x]*Sinh[y]
    In[38]:=ComplexExpand[Sin[x+y*I]]
    Out[38]=Cosh[y]*Sin[x]+I*Cos[x]*Sinh[y]
    § 14. Генерация случайных чисел
    Для многих целей, например для тестирования программ, полезно уметь генерировать (псевдо)-случайные числа, которые можно вводить в каче- стве данных в вычисления для проверки корректности и скорости рабо- ты используемых алгоритмов. В дальнейшем мы будем опускать префикс псевдо- и говорить просто о случайных числах. В системе Mathematica случайные числа генерируются при помощи следующих программ.
    Random[]
    случайное вещественное число на отрезке [0, 1]
    Random[Integer]
    0 или 1 с вероятностью 1/2
    Random[Integer,
    {m,n}] случайное целое число между m и n
    Random[Real,x]
    случайное вещественное число на отрезке [0, x]
    Random[Real,
    {x,y}]
    случайное вещественное число на отрезке [x, y]
    Random[Complex]
    случайное комплексное число в [0, 1]
    2
    Random[Complex,
    {z,w}] случайное комплексное число в прямоугольнике
    SeedRandom[]
    перезапустить генератор случайных чисел
    $RandomState текущее состояние генератора случайных чисел

    177
    Функцию Random можно вызвать вообще без аргументов, с одним или двумя аргументами, причем первый аргумент обязан быть типом числа
    (Integer, Real или Complex), а второй аргумент может быть числом или списком из двух чисел.
    По умолчанию функция Random[], вызванная без аргументов, или,
    что то же самое, Random[Real], дает равномерно распределенное случайное вещественное число на отрезке [0, 1].
    Функция Random, вызванная в формате Random[Real,x] дает случай- ное число на отрезке [0, x], при этом x не обязано быть положительным!
    Наконец, функция Random, вызванная в формате Random[Real,
    {x,y}] дает случайное число на отрезке [x, y].
    То же самое относится к целым числам. Функция Random[Integer]
    случайным образом генерирует 0 или 1 с вероятностью 1/2. Функция Ran- dom[Integer,n] генерирует случайное целое между 0 и n включительно.
    Наконец, функция Random[Integer,
    {m,n}] порождает случайное целое на отрезке [m, n]
    Z — включая концы!!
    Чуть труднее приноровиться к вызову функции Random в комплексной области. В этом случае функция Random[Complex,
    {z,w}] порождает слу- чайное комплексное число в прямоугольнике на комплексной плоскости с углами w и z. Этот прямоугольник имеет вид
    {x + yi | a ≤ x ≤ b, c ≤ y ≤ d},
    где мы для краткости положили
    a = min(re(z), re(w)),
    b = max(re(z), re(w)),
    c = min(im(z), im(w)),
    d = max(im(z), im(w)).
    Конечно, этот прямоугольник может быть и вырожденным. Так, скажем,
    Random[Complex,
    {-1+I,-1+3*I}] даст случайное число на отрезке длины
    2, параллельном мнимой оси.
    Разумеется, того же эффекта можно до- биться посредством -1+I*Random[Real,
    {1,3}]. То же, конечно, относится и к общему случаю. Из общих соображений совершенно ясно, что ника- ких особых алгоритмов для генерации комплексных чисел не применяется,
    а отдельно генерируются вещественная и мнимая части в нужных преде- лах. В том, что это фактически так и есть, можно убедиться, например,
    следующим непритязательным образом:
    In[39]:=SeedRandom[117]; Random[Complex]
    Out[39]=0.85043+0.266285*I
    In[40]:=SeedRandom[117]; Random[]+I*Random[]
    Out[40]=0.85043+0.266285*I
    Таким образом, для генерации комплексных чисел в прямоугольнике мы могли бы точно с таким же успехом пользоваться, например, такой коман- дой:

    178
    Random[Real,
    {a,b}]+I*Random[Real,{c,d}]
    Упражнение. Тип случайной переменной может быть только целым, ве- щественным или комплексным числом. Попытка задать случайное рацио- нальное число посредством Random[Rational] немедленно приведет к сле- дующему сообщению об ошибке:
    Random: Type specification Rational in Random[Rational]
    should be Real, Integer, or Complex.

    Как можно породить случайное рациональное число?
    Упражнение. Как породить случайное комплексное число в единичном круге?
    В действительности, как должно быть ясно уже из приведенного выше примера, когда мы дважды разными командами породили одно и то же
    “случайное” комплексное число, функция Random является точной функ- цией, хотя случайному наблюдателю это не должно быть видно. Например,
    вызвав функцию
    Table[Random[Integer,
    {0,9}],{i,1,50}]
    Вы получите случайную последовательность из 50 цифр.
    Вызвав эту функцию еще раз, Вы получите уже совершенно другую после- довательность. Это связано с тем, что каждый вызов функции Random меняет текущее значение системной переменной $RandomState. Однако ча- сто при отладке или сравнении программ желательно несколько раз вызы- вать одно и то же случайное число. Это делается одним из двух способов:
    Функция SeedRandom[n] перезапускает генератор случайных чисел, ис- пользуя n в качестве затравки. Эта функция позволяет несколько раз по- лучать одну и ту же последовательность случайных чисел!!! Например,
    вызвав функцию
    SeedRandom[123]; Table[Random[Integer,
    {0,9}],{i,1,50}]
    Вы как и раньше получите случайную последовательность из 50 цифр.
    Вы- звав эту функцию еще раз, Вы получите ту же самую последо- вательность. Чтобы получить другую последовательность, Вам теперь нужно изменить аргумент SeedRandom!!! Функция SeedRandom[ ], вызван- ная вообще без аргументов, перезапускает генератор случайных чисел, ис- пользуя в качестве затравки время суток. Другой народный способ, смысл которого состоит в том, чтобы ослабить роль человеческого фактора, со- стоит в том, чтобы вначале исполнить команду seed=Random[Integer,
    {2000,3000}],
    а уже потом подставлять SeedRandom[seed].
    Еще один способ получить ту же самую последовательность таков.
    Мы уже упоминали, что значение системной переменной $RandomState ме- няется при каждом обращении к команде Random. Например, исполнив несколько раз программу
    Random[]; Total[IntegerDigits[$RandomState]]

    179
    можно убедиться в том, что $RandomState представляет собой целое число в котором от 2400 до 2800 цифр. В любой момент сессии выполнив присваи- вание state=$RandomState; можно записать текущее состояние генератора случайных чисел. В дальнейшем можно восстановить это значение посред- ством $RandomState=state; для последующего получения тех же самых случайных чисел.
    Предостережение.
    Наивная точка зрения состоит в том, что мы могли бы снова явно породить state=Random[Integer,
    {10^2400,10^2800}] в нужных пределах. Однако в отличие от затравки RandomSeed, которую можно порождать произвольно, далеко не все что угодно может быть значением $RandomState!!! Скорее всего, результатом попытки исполнить команду $RandomState=state будет сообщение об ошибке
    $RandomState: blabla<>blabla is not a valid random state.
    § 15. Бесконечность и неопределенность
    Есть еще несколько очень важных вещественных/комплексных не-чи- сел (not-numbers), которые могут возникать в вычислениях, как арифме- тических, так и аналитических, в особенности при вычислении пределов,
    бесконечных сумм и произведений, интегралов и пр.
    Infinity вещественная бесконечность
    Indeterminate неопределенность
    ComplexInfinity комплексная бесконечность
    DirectedInfinity направленная комплексная бесконечность
    При попытке вычислить 1/0 выдается сообщение об ошибке
    Power: Infinite expression 1/0 encountered.
    Тем не менее, Mathematica знает ответ ComplexInfinity. Как мы сейчас убедимся, при попытке вычислить 0/0 мы получим сразу два сообщения об ошибке. Вот четыре классических неопределенности:
    In[41]:=
    {0/0,0*Infinity,Infinity/Infinity,Infinity-Infinity}
    Power: Infinite expression 1/0 encountered.
    Infinity: Indeterminate expression 0*ComplexInfinity encountered.
    Infinity: Indeterminate expression 0*Infinity encountered.
    Infinity: Indeterminate expression 0*Infinity encountered.
    Infinity: Indeterminate expression -Infinity+Infinity encountered.
    Out[41]=
    {Indeterminate,Indeterminate,Indeterminate,Indeterminate}
    Кроме того, в соответствии с аналитическими традициями, Mathematica ве- рит, что 0^0 тоже является неопределенностью. Алгебраисты знают, что на самом деле 0 0
    = 1. В действительности, в это должен верить каждый,

    180
    кто верит в справедливость формулы (x + y)
    2
    = x
    2
    y
    0
    + 2x
    1
    y
    1
    + x
    0
    y
    2
    . Од- нако принятое в Mathematica соглашение весьма обычно при вычислении пределов и других аналитических вычислениях.
    Неопределенность действует следующим образом. Если в процессе вы- числений встретился неопределенный результат, то действие обычных пра- вил арифметики прекращается и все последующие содержашие его резуль- таты тоже автоматически становятся неопределенными.
    Что касается бесконечностей, то они точное соотношение между ними следующее.
    Самой общей из них является функция DirectedInfinity.
    А именно, DirectedInfinity[z] порождает бесконечность в направлении комплексного числа z. С внутренней точки зрения остальные перечислен- ные разновидности бесконечностей являются просто сокращениями:
    Комплексная бесконечность ComplexInfinity является просто сокра- щением для DirectedInfinity[], т.е. для направленной бесконечности вы- званной вообще без направления (= с неопределенным направлением).
    Вещественная бесконечность Infinity рассматривается как сокраще- ние для DirectedInfinity[1], т.е. для направленной бесконечности вы- званной в направлении положительной вещественной полуоси
    Вещественная бесконечность -Infinity рассматривается как сокраще- ние для DirectedInfinity[-1], т.е. для направленной бесконечности вы- званной в направлении отрицательной вещественной полуоси
    Для многих аналитических вычислений, скажем, при вычислении кон- турных интегралов, нужна бесконечность в других направлениях, ну, ска- жем, DirectedInfinity[I] направленная вдоль положительной мнимой полуоси. В ответе такая бесконечность будет выводиться как I*Infinity,
    но ее полная внутренняя форма, все-таки DirectedInfinity[I].
    Вещественные бесконечности Infinity и -Infinity можно использовать при задании итераторов в суммах и произведениях, при вычислении пре- делов, задании пределов интегрирования и всех других аналогичных мани- пуляциях. В подавляющем большинстве случаев Mathematica верно истол- ковывает Ваши намерения. Лет 25 назад злопыхатели широко распростра- няли слухи о мнимых ошибках в Mathematica и Maple, однако, как выясни- лось, в действительности, ошибки содержались в (составленных людьми!!!)
    таблицах рядов и интегралов, использовавшихся в библиотеках этих си- стем! В действительности, вычисление Sum[1/i^2,
    {i,1,Infinity}] дает точное значение π
    2
    /6, как и задумано. Вероятность наткнуться на ошибку во всех обычных вычислениях такого рода меньше, чем вероятность на- ткнуться на ошибку в печатных версиях таблиц рядов и интегралов.

    181
    Глава 5. ПЕРЕМЕННЫЕ И ИХ ЗНАЧЕНИЯ
    Язык их ясен и конкретен.
    Для простоты дела все животные,
    ракушки, птицы, рыбы, ягоды, мох и змеи, кроме самых ядовитых,
    называются у них одинаково.
    Александр и Ольга Флоренские. Движение в сторону Йые
    Osborn's Law: Variables won't; constants aren't.
    В этой главе мы начинаем обсуждать ключевые понятия, которые по сво- ей сути относятся к программированию и требуют уточнения традиционно- го математического языка посредством введения в него (не на лексическом,
    а на грамматическом уровне!!!) идеи времени. Математика не знает идеи времени, в то время как для фактического вычисления последовательность выполнения операций (flow of calculation) становится исключительно важным, а часто решающим обстоятельством. Однако при этом традици- онное программирование имело дело с весьма ограниченным набором вы- числений с достаточно примитивными типами объектов. Иными словами,
    математики проводили нетривиальные вычисления с интересными объек- тами, но делали это плохо — по крайней мере с точки зрения возможности перенести их вычисления на компьютер!!! Основным, чтобы не сказать единственным, инструментом математиков всегда было понимание, так что вопрос о воспроизводимости, не говоря уже об эффективности, вы- числения их не интересовал. В то же время программисты традиционно проводили бессодержательные вычисления с лишенными интереса объек- тами, но всегда делали это правильно и систематически. В этом смысле точка зрения компьютерной алгебры является эффективным синтезом ма- тематической и программистской точек зрения. Как и в математике, мы проводим любые вычисления с любыми объектами, доступными нашему умозрению, но как и в программировании делаем это эффективно перено- симыми методами, такими методами, которые опираются не на медитацию и непосредственный контакт с миром идей, в мистических и практических аспектах, а лишь на четко описанные простые, явные и действенные алго- ритмы.
    § 1. Математика как неточная наука
    The problem with you mathematicians is that you are so imprecise.
    Real Programmer
    Люди, не имеющие отношения к математике, обычно характеризуют ее как точную науку . В этой фразе верно все, кроме слов точная и наука.
    Как говорит само ее название, математика является учением, доктриной и областью знания. Кроме того, в настоящее время она организационно,
    а отчасти идеологически и психологически, близка к некоторым областям науки. В то же время математика, конечно, не является наукой, так как ее цели, метод и критерии ценности гораздо ближе к искусству или религии

    182
    и имеют мало общего с целями, методами и установками, характерными для естествознания. С другой стороны, и точность в математике важна только как инструмент, или побочный продукт, а вовсе не как самоцель.
    Более того,
    чем лучше мы понимаем, что говорится, тем менее точными мы можем позволить себе быть.
    По стандартам многих других видов деятельности, скажем, программи- рования, математика является весьма неточным занятием. В предыдущей главе мы уже упоминали, что обычные математические обозначения крайне неоднозначны. Как отмечает Виктор Анатольевич Уфнаровский
    50
    , все три минуса, встречающихся в выражении
    (1 2), использованы в трех со- вершенно разных смыслах:
    первый минус обозначает унарную операцию x 7→ −x;
    второй минус является частью имени числа 1;
    третий минус обозначает бинарную операцию (x, y) 7→ x − y.
    Причина, по которой это мало кого волнует и практически никогда не со- здает серьезных неудобств, состоит в том, что пользующийся этими обо- значениями человек иногда до некоторой степени понимает смысл текста.
    Но, вероятно, самым сомнительным математическим знаком является знак =. Вот простенький текст, подтверждающий эту декларацию: “Пусть
    f = x
    2
    − ax + 1. Положим a = 2. Тогда f = (x − 1)
    2
    и, значит, уравнение
    x
    2
    − ax + 1 = 0 имеет единственный корень x = 1 кратности 2”. Что мы подразумеваем, когда пишем f (x) = g(x)? Предлагаем ли мы положить значение неизвестной функции f в точке x равным g(x)? Или мы пред- лагаем положить его равным g(x) в любой точке x? Утверждаем ли, что значения известных нам функций f и g равны при любом
    51
    x из области определения? Или из какого-то содержащегося в ней множества? Или при каком-то/каких-то x, который/которые предлагается найти? Ясно, что все это абсолютно различные смыслы и читающий математический текст че- ловек, который в состоянии следить за происходящим, обычно в состоянии истолковать, какой из этих смыслов имеется в виду: конечно, это зависит от степени его квалификации, сомнительная ситуация легко поставит на- чинающего в тупик, но зрелый читатель, скорее всего, решит, что либо в тексте допущена опечатка, либо автор сам не понимает, о чем говорит.
    С точки зрения языка Mathematica знак равенства в математическом тексте может обозначать любую из (по крайней мере!!!) восьми абсолютно различных вещей:
    шесть операторов =, :=, ->, :>, ^=, ^:=;
    два отношения ==, ===.
    50
    В.А.Уфнаровский, Математический аквариум. — Кишинев, Штиинца, 1987, с.1–
    215; стр.30.
    51
    Мы совершенно оставляем в стороне структурные вопросы, которые с таким трудом усваиваются начинающими, например, чем равенство семейств отличается от равенства функций, и почему это из того, что f (x) = g(x) для любого x из области определения еще не следует, что f = g?

    183
    Как смысл этих вещей, так и — тем более!!! — их использование, настоль- ко отличаются между собой, что непоколебимое упорство математиков в желании обозначать все эти понятия одним и тем же символом = служит еще одним парадоксальным подтверждением сакраментальной дефиниции
    Пуанкаре:
    математика — это искусство называть разные вещи одним именем. В частности, в предыдущем тексте употреблено пять совершенно разных знаков равенства: :=, =, ===, ==, ->.
    А ведь до сих пор мы практически не упоминали того, что известно в
    Computer Science как ход вычисления (flow of calculation), иными словами, то, что сами значения f , g и x меняются в процессе вычисления
    — математик использовал бы в этом смысле тысячу индексов, что было бы,
    конечно, чуть точнее, чем принятое в программировании понятие пере- менной, но гораздо менее удобно!!! Например, когда мы говорим, `положим
    a = 2', то совершенно неясно, имеем ли мы в виду
    присваивание: a становится навсегда равным 2, на языке Mathema- tica a=2, или
    подстановку: a становится равным 2 только в этом вычислении,
    на языке Mathematica a->2.
    Но ведь если мы сами этого не понимаем, нам вряд ли удастся объяснить это даже такой интеллигентной системе, как Mathematica.
    § 2. Переменные и их значения
    Переменная — это область в памяти компьютера, где может хра- ниться некоторое значение для использования его в программе.
    Харви Дейтел, Пол Дейтел, “Как программировать на C++”,
    С точки зрения математика переменная — например, буковка x в запи- си многочлена f = a
    n
    x
    n
    + . . . + a
    1
    x + a
    0
    ∈ K[x] над полем K — является некоторым новым трансцендентным объектом. Этот объект не принад- лежит основному полю K и сам не имеет никакого значения, но именно поэтому его можно без противоречия специализировать в различные эле- менты поля K — или, на классическом языке, придавать ему различные значения. В этом случае математик напишет x
    7→ x
    1
    , x
    7→ x
    2
    , . . . , но са- ма переменная никогда не меняется — ее можно только заменить,
    но в ней нет ничего, что можно было бы изменить. Для традиционно- го программиста, который имел дело только с численными вычислениями,
    переменная это нечто совсем иное. Это не какой-то потусторонний объ- ект, а просто метка или ярлык, вроде номера ячейки, которая обозначает конкретное число, получающееся из каких-то других данных в программе и/или хранящееся где-то в памяти компьютера. Число, хранящееся под этим ярлыком, может меняться, но программист ссылается на эти разные значения как на одну и ту же переменную, он считает, что сама пере- менная x равна x
    1
    , x
    2
    , . . .
    в разные моменты времени. Точка зрения компьютерной алгебры представляет собой соединение двух этих противо- положных точек зрения. С точки зрения flow of calculation переменная

    184
    компьютерной алгебры — это переменная традиционного программирова- ния, но только теперь ее значения совсем не обязательно являются числами!
    В действительности, то, что делает компьютерную алгебру столь мощным инструментом, это как раз полиномиальные вычисления, т.е. способ- ность истолковывать значения переменных (в компьютерном смысле) как переменные (в математическом смысле)!!
    Основой любых символьных вычислений является способность системы рассматривать символы как переменные и присваивать им значения,
    которые могут быть любыми допустимыми в контексте проводимого вы- числения выражениями, например, числами или другими символами. При этом, как и в традиционном программировании, в разные моменты вычис- ления один и тот же символ может иметь различные значения. То значение,
    которое символ имеет в данный момент вычисления, называется текущим значением. В следующих параграфах мы обсудим основные способы при- сваивания, временного присваивания (подстановки) и модификации значе- ний переменных. Сделаем пока несколько общих замечаний:
    Если какому-то символу не присваивалось никаких значений, значени- ем этого символа является он сам,
    рассматриваемый как независимая полиномиальная переменная. В частности, до дальнейших инструк- ций числа Pi, E – и даже GoldenRatio!!! – рассматриваются как независи- мые переменные.
    Все переменные сохраняют свои значения на протяжении сес- сии, до тех пор, пока эти значения не были модифицированы или вычи- щены.
    Основной причиной ошибок при вычислениях является использование в них переменных, которым уже присвоены зна- чения, в §§ 7–10 мы обсудим несколько способов избежать этого (исполь- зование подстановок вместо присваиваний, чистка и создание локальных переменных). Однако самое простое — давать переменным говорящие име- на.
    Некоторые вещи, не являющиеся символами, (целые и рациональные числа, стринги и объекты некоторых других специальных форматов) с точки зрения подавляющего большинства команд считаются имеющими постоянное значение, и их текущее значение в любом выражении все- гда равно этому постоянному значению. Такие вещи называются сырыми объектами (raw objects) в том смысле, что перед употреблением в пищу их не обрабатывают.
    Предостережение. Из приведенных выше примеров можно сделать поспешный вывод,
    что сырыми бывают только атомарные объекты. Однако в действительности сырой объ- ект может быть устроен сколь угодно сложно, например, подавляющее большинство команд рассматривает разреженные объекты типа SparseArray как сырые, это делает- ся для того, чтобы избежать вычислений с матрицами с миллиардами или десятками миллиардов коэффициентов. Ясно, что вычисления с такими матрицами на бытовом компьютере по обычным алгоритмам работы над списками, когда каждая компонента занимает собственный сектор памяти, физически невозможны. Поэтому их можно об- рабатывать только при помощи небольшого количества специальных команд, которые правильно учитывают их структуру и специфику (некоторые из этих команд называ-

    185
    ются обычными именами, но работают совсем иначе). Точно также стринги являются сырыми объектами только с точки зрения обычных математических вычислений. Спе- циальные текстовые команды работы над стрингами позволяют сливать, изменять и редактировать стринги, подставлять в них результаты математических вычислений и прочее и прочее и прочее.
    Все правильно составленные выражения, в которые входят сырые объ- екты и символы, тоже приобретают текущие значения, которые определя- ются текущими значениями символов с учетом определений всех входящих в эти выражения функций и всех определенных для них правил преобра- зования, как тех, которые встроены в систему, так и (в первую очередь!!!)
    тех, которые определены пользователем. Вычисление текущего значения выражения называется просто вычислением или эвалюацией (evalua- tion) этого выражения. Текущим значением выражения является другое
    — либо то же самое!!! — выражение, причем система вычисляет значение выражение f[x,y,...z] следующим образом:
    вычисляется заголовок f выражения;
    поочередно вычисляются все элементы x, y, . . . , z — кроме тех, вычис- ление которых явным образом запрещено специальной командой Hold или ее вариантами и/или родственниками HoldFirst, HoldRest, HoldAll,
    HoldPattern, HoldSequence и т.д.;
    используются ассоциативность, дистрибутивность и коммутативность
    — именно в таком порядке!!! — если f обладает этими свойствами.
    используются все правила и подстановки, определенные пользователем для функций, входящих в определение x, y, . . . , z;
    используются все внутренние правила и подстановки, для функций,
    входящих в определение x, y, . . . , z;
    используются все правила и подстановки, определенные пользователем для функции f ;
    используются все внутренние правила и подстановки, для функций,
    входящих в определение f .
    В некоторых случаях применение всех этих правил не упрощает выраже- ния, тогда система оставляет его в первоначальном виде (unevaluated).
    Иными словами, текущим значением такого выражения является само это выражение.
    § 3. Многочлены
    Как мы уже обсуждали, основой системы Mathematica, как и других систем компьютерной алгебры, являются полиномиальные вычисления.
    В Главе 3 мы видели, как выделить компоненты многочлена, рассмат- риваемого как выражение, при помощи команд работы с выражениями и списками Part и Level. В языке Mathematica для этой цели есть и специ- альные команды. Мы будем обсуждать их главным образом для многочле- нов от одной переменной, но в действительности все они работают и для

    186
    многочленов от нескольких переменных.
    Variables[f]
    список переменных многочлена f
    Exponent[f,x]
    наибольшая степень с которой x входит в f
    Coefficient[f,x,n]
    коэффициент при x
    n
    в f
    CoefficientList[f,x]
    список коэффициентов f как многочлена от x
    PolynomialQ[f,x]
    является ли f многочленом от x?
    Прежде всего заметим, что многочлен небольшой степени можно задать явным образом, как a+b*x+c*x^2+d*x^3. Однако, для многочленов высо- кой степени нужны какие-то более интеллигентные способы. Простейший состоит, конечно, в том, чтобы использовать итератор, например так:
    poly[n ,a ][x ]:=Sum[a[i]*x^i,
    {i,0,n}]
    С другой стороны, конечно, как всегда в Mathematica существуют десятки других способов добиться того же результата, скажем,
    poly[n ,a ][x ]:=Dot[Table[a[i],
    {i,0,n}],Table[x^i,{i,0,n}]]
    Теперь вычисление poly[5,a][x] даст a[0]+x*a[1]+x^2a[2]+x^2a[3]+x^4a[4]+x^5a[5].
    Для тестирования программ полезно выбирать многочлены со случайными коэффициентами, в этом случае мы просто напишем Random[] вместо a[i].
    Команда Variables[f] говорит нам, что именно в данный момент рас- сматривается в качестве входящих в f независимых переменных. Напри- мер, когда мы пишем f=a+b*v+c*x^2 мы, скорее всего, имеем в виду, что
    f является многочленом от переменной x с коэффициентами a, b, c. Од- нако система думает несколько иначе.
    Вычисление Variables[f] дает
    {a,b,c,x} (конечно, если переменным a, b, c до этого не присваивалось ни- каких значений!) Тоньше всего использование этой команды с точными ве- щественными или комплексными числами. Поскольку для чисел E, Pi и т.д.
    тест NumericQ[x] дает значение True, они не рассматриваются в качестве независимых переменных!! Таким образом, вычисление Variables[E+Pi]
    дает
    {}. В этом месте мы предлагаем тому, кто раньше не задумывался над работой систем компьютерной алгебры, сделать глубокий вдох и немного помедитировать. С точки зрения всех точных вычислений E и Pi явля- ются независимыми полиномиальными переменными. В то же время они рассматриваются как числа, а не как независимые переменные. Подоб- ный моральный релятивизм и постоянная смена фокуса типичны скорее для чистой математики, чем для программирования!
    Команда Exponent[f,x] вызванная с двумя аргументами возвращает степень многочлена f по переменной x. Например, для f=a+b*v+c*x^2
    вычисление Exponent[f,x] даст 2, а вычисление Exponent[f,a] даст 1. В
    действительности, в полном формате команда Exponent вызывается с тре- мя аргументами как Exponent[f,x,g], где g — функция, которую следует применить к списку всех показателей степени, с которыми x входит в f .
    По умолчанию параметр g полагается равным Max, но, конечно, в качестве

    187
    g можно взять много других функций, скажем Min, List, Sequence, Plus,
    etc., etc. Ну, например, при вычислениях с многочленами Лорана
    f = a
    −l
    x
    −l
    + . . . + a
    1
    x
    1
    + a
    0
    + a
    1
    x
    1
    + . . . + a
    n
    x
    n
    ∈ K[x, x
    1
    ]
    естественно одновременно рассматривать порядок, т.е. наименьшее m та- кое, что a
    m
    6= 0 и степень, т.е. наибольшее n такое, что a
    n
    6= 0. Это де- лается при помощи команды Exponent[f,x,
    {Min[##],Max[##]}&]. В этой команде мы вызываем функцию, сопоставляющую списку пару, состоящую из его наименьшего и наибольшего элементов в формате чистой функции.
    Обращение к элементам списка в формате последовательности аргумен- тов ## необходимо потому, что мы априори не знаем, сколько элементов в этом списке. Последовательность ## вызывает их все. Точно так же Ex- ponent[f,x,List] породит список всех показателей степени, с которыми x
    входит в f и т.д.
    Упражнение. Что мы получим при вычислении
    Exponent[0,x,
    {Min[##],Max[##]}&]?
    Ответ. Да уж, конечно, правильный ответ
    {Infinity,-Infinity}. Как и принято в математике, функция Min[], вызванная вообще без аргументов,
    дает Infinity, а функция Max[], естественно, -Infinity.
    Функции Coefficient и CoefficientList служат тому, чтобы породить индивидуальный коэффициент и список коэффициентов многочлена f , со- ответственно. Их использование абсолютно понятно исходя из названий. А
    именно, Coefficient[f,x,i] возвращает коэффициент с которым x
    i
    вхо- дит в f . С другой стороны, CoefficientList[f,x] возвращает список всех коэффициентов от степени 0 до Exponent[f,x].
    Mожет показаться, что функции Coefficient и CoefficientList дуб- лируют друг друга. Действительно, функция CoefficientList легко вы- ражается через Coefficient, например, так clist[f ,x ]:=Table[Coefficient[f,x,i],
    {i,0,Exponent[f,x]}]
    Естественно, функция clist всегда возвращает тот же результат, что и
    CoefficientList. Но для случайных многочленов степени несколько тысяч она делает это раз в 100 медленнее!! В этом можно убедиться, например,
    определив случайный многочлен rapo=Sum[Random[]*x^i,
    {i,0,1000}]; —
    кстати, почему мы написали здесь =, а не :=?? — и вычислив
    TrueQ[clist[rapo,x]==CoefficientList[rapo,x]].
    Точно так же, если f проходит тест PolynomialQ[f,x], т.е. если f дей- ствительно является многочленом от x, то Coefficient[f,x,i] даст тот же результат, что CoefficientList[f,x][[i+1]], хотя, конечно, снова го- раздо быстрее! Обратите внимание на сдвиг номера, список коэффициен- тов начинается с нулевой степени, однако соответствующий коэффициент является первой компонентой списка!!! Таким образом для многочленов функции Coefficient и CoefficientList действительно выражаются друг через друга.

    188
    Однако в действительности функция Coefficient является значитель- но более общей! Для того, чтобы к выражению f можно было применить функцию CoefficientList, оно действительно должно быть многочленом.
    В частности, оно не может содержать отрицательных или дробных степе- ней x, не говоря уже о трансцендентных функциях от x. Попытка вы- числить что-нибудь в духе CoefficientList[x^-3+a+b*x+c*x^2,x] момен- тально приведет к сообщению об ошибке General: a+1/x^3+b*x+c*x^2 is not a polynomial. В то же время, функция Coefficient знает, что делать в подобных случаях. Вычисление
    Map[Coefficient[a*x^-3+b*Sqrt[x]+c*Sin[x],x,#]&,
    {-3,1/2}]
    дает
    {a,b}, как и задумано.
    § 4. Немедленное и отложенное присваивание:
    = Set versus := SetDelayed
    Символ нуля — 0. А символ ноля — О.
    Даниил Хармс, Нуль и ноль
    Вот две основные команды, которые придают значения переменным:
    немедленное присваивание = Set и отложенное присваивание :=
    SetDelayed. Уяснение различия между ними имеет ключевое значение для того, кто никогда раньше не занимался программированием!!! Собственно именно с этого места программирование начинает отличаться от матема- тики!
    x=y
    Set[x,y]
    немедленное присваивание x:=y
    SetDelayed[x,y]
    отложенное присваивание
    Еще раз объясним основное различие между двумя этими командами,
    или как принято говорить в программировании, операторами присваи- вания:
    Когда мы выполняем непосредственное присваивание lhs=rhs, его правая часть rhs вычисляется один раз в тот момент, когда мы вы- полняем присваивание;
    Когда мы выполняем отложенное присваивание lhs:=rhs, его правая часть rhs заново вычисляется каждый раз, когда мы обращаемся к значению его левой части lhs.
    В качестве иллюстрации этого рассмотрим следующий диалог:
    In[1]:=x=1; y=x; x=2; y
    Out[1]=1
    In[2]:=x=1; y:=x; x=2; y
    Out[2]=2
    In[3]:=ClearAll[x,y]

    189
    Хорошо видно, что в первом случае мы придаем переменной y текущее значение переменной x на момент присваивания. В то же время в случае отложенного присваивания значение переменной y вычисляется исходя из текущего значения переменной x на момент обращения.
    Еще раз поясним два момента, фигурирующих в этом вычислении. Во первых, в нем использована точка с запятой. Вместо этого мы могли бы просто напечатать присвоения x=1, y=x, x=2 в трех отдельных строках, а x
    — в четвертой строке. Однако нас интересует только значение x в конце вычисления, поэтому мы помещаем все четыре шага в одну строку, разде- ляя их точками с запятой. В этом случае все эти вычисления выполняются,
    но на экран выводится только результат последнего из них.
    ,
    разделение аргументов функции или компонент списка
    ;
    разделение команд или частей аргумента
    Постановка ; в конце инпута интерпретируется как выполнение пустой операции. Тем самым, предшествующая операция производится, но ее результат не выводится.
    Во-вторых, в конце вычисления использована команда ClearAll. Она нужна, чтобы убрать определения и текущие значения переменных x и y,
    иначе на протяжении всей сессии программа будет считать, что x = y =
    2. Мы уже упоминали, что давать всем используемым переменным такие имена, как x или y — это не слишком хорошая идея.
    Критическое различие между Set и SetDelayed особенно хорошо видно в следующем диалоге:
    In[4]:=xxx=Random[]; Table[xxx,
    {5}]
    Out[4]=
    {0.325926,0.325926,0.325926,0.325926,0.325926}
    In[5]:=yyy:=Random[]; Table[yyy,
    {5}]
    Out[5]=
    {0.116906,0.216847,0.821755,0.754748,0.485576}
    В первом случае функция Random вызывается один раз, а во втором —
    каждый раз заново при каждом обращении к ней. Обозвать перемен- ные xxx и yyy — это еще один грязный трюк, позволяющий избежать ис- пользования присвоенных xxx и yyy значений в дальнейших вычислениях,
    в следующий раз мы просто назовем переменные funx и funy или xbis,
    ybis, или еще как-нибудь в таком духе.
    Не всякое присваивание фактически удается осуществить: написав 2=x,
    Вы получите сообщение об ошибке cannot assign to raw object 2.
    Предостережение. Обычно сопротивление системы удается преодолеть и в таких слу- чаях, но не пытайтесь это сделать!!! Вот, что по этому поводу говорится в The Mathe- matica Book: Raw should be only used under very special circumstances. Присваивание 2
    значения 3 является одним из самых надежных способов уронить ядро Mathematica и потерять все несохраненные во время сессии результаты.
    Mathematica помнит только последнее по времени присваивание, кото- рое удалось осуществить: x=y; x=z даст нам x равное z, а не y.

    190
    Некоторым системным переменным (таким как $RecursionLimit и т.д.)
    нельзя присваивать произвольные значения. Не думайте об этом, пока не столкнетесь с сообщением recursion limit exceeded, после этого положи- те $RecursionLimit=Infinity.
    § 5. Секунды, такты и шаги
    Допустим, нам требуется переименовать переменные x и y. Мы не можем просто присвоить x=y, так как при этом старое значение x будет утрачено.
    По той же причине мы не можем и присвоить y=x. Основные традиционные способы сделать это таковы
    52
    :
    Ввести новую переменную z и выполнить следующую последователь- ность присваиваний: z=x; x=y; y=z.
    Рассмотреть (x, y) как вектор и выполнить следующую последователь- ность присваиваний: x=x+y; y=x-y; x=x-y. С точки зрения линейной ал- гебры речь здесь идет, конечно же, о том, что перестановка двух координат вектора может быть реализована как цепочка операций, каждая из кото- рых меняет не более одной координаты. Или, как нас учат/мы учим на первом курсе, элементарное преобразование третьего типа можно предста- вить в виде произведения элементарных преобразований первого и второго типов.
    Однако первый из этих способов требует создания дополнительной пере- менной, а второй — некоторой выдумки (или знания линейной алгебры!)
    Система Mathematica сконструирована так, чтобы пользователь мог обхо- диться без того и другого
    53
    . А именно, естественный способ выполнить эту операцию; более того, единственно правильный способ выполнить эту операцию; тот, который задуман для подобных ситуаций творцами систе- мы; тот, под который она оптимизирована; состоит в том, чтобы выполнить оба присваивания одновременно:
    Выполнить присваивание списком {x,y}={y,x}.
    Этот пример должен подготовить начинающего к мысли, что измеряемое шагами течение внутреннего времени в алгоритме отличается не только от математического отсутствия времени, не только от измеряемого секундами физического течения времени, но и от хода тактового времени при фактиче- ском исполнении этого алгоритма процессором!! Фактически, разумеется,
    при выполнении присваивания
    {x,y}={y,x} присваивание x=y выполняется на несколько тактов раньше, чем присваивание y=x. Однако с точки зрения алгоритма они происходят за один шаг, поэтому в присваивании y=x ис- пользуется старое, а не новое значение x. В то же время при нашей неудач-
    52
    А.Шень, Программирование. Теоремы и задачи. — М., МЦНМО, 2004, с.1–296;
    стр.8.
    53
    Разумеется, на уровне фактического вычисления компьютер при этом создает но- вые переменные, но это тот уровень вычисления, который спрятан в код на языке C,
    т.е. сознательно скрыт не только от пользователя, но и от от того уровня, на котором происходит мышление самой системы!

    191
    ной попытке написать x=y; y=x присваивания происходят последовательно не только с точки зрения процессора, но и с точки зрения алгоритма, т.е.
    не только в разных тактах, но и на разных шагах!!
    В качестве метафоры подобного тройного контроля времени секундами,
    тактами и шагами можно привести съемку фильма. Измерение времени в шагах отвечает внутреннему времени сюжета. Измерение времени в тактах
    — съемочным дням. Ну и, наконец, процесс производства фильма занима- ет год или два — а то и десять лет!! — физического времени. Ясно, что хотя эти три способа контроля времени подчинены некоторым общим огра- ничениям, устанавливающим связи между ними, это соответствие вовсе не является прямым. В то же время, трудно не заметить, что связь съемоч- ных дней с астрономическими гораздо более тесная, чем с сюжетными!!
    Нас как зрителей не интересует, снят более ранний по сюжету эпизод до или после последующего. Нам важно только как смонтирован фильм!!!
    В этой книге в большинстве ситуаций нас интересует исключительно развитие сюжета — и в дальнейшем мы как правило обсуждаем только ас- пекты, связанные с шагами рассматриваемых алгоритмов. Тем не менее в некоторых случаях — ну, например, когда мы сами снимаем фильм — нас может начать волновать и прямой контроль времени, в том числе и физиче- ского. Такой контроль оказывается важным с практической точки зрения в тот момент, когда эффективность используемых алгоритмов становится недостаточной для того, чтобы фактически осуществить интересующее нас вычисление. Следующие команды дают глобальные временные параметры сессии:
    AbsoluteTime[]
    время в секундах начиная с 1 января 1900 года
    SessionTime[]
    время в секундах с начала текущей сессии
    TimeUsed[]
    время работы CPU в секундах с начала сессии
    Вот например, как выглядят параметры сессии, сопровождавшей написа- ние этого параграфа:
    In[6]:=
    {AbsoluteTime[],SessionTime[],TimeUsed[]}
    Out
    {3.3062861285750416 10^9, 47872.8077168, 14.479}
    Обращает на себя внимание, что работа CPU, направленная собственно на вычисление, представляет собой ничтожно малую долю общей продолжи- тельности сессии!!
    Однако в большинстве случаев нас значительно больше интересует кон- троль времени индивидуального вычисления.
    Timing[x]
    время работы CPU при вычислении x
    AbsoluteTiming[x]
    полное время вычисления x
    $TimeUnit минимальный интервал фиксируемый Timing
    TimeConstrained[x,t]
    вычисление x с контролем времени t секунд
    Команда Timing[x] вычисляет x и возвращает время работы CPU, необ- ходимое для этого вычисления и полученное значение x. Вот некоторые особенности ее использования:

    192
    В некоторых случаях нас интересует только затраченное время, но не получившийся результат. В таких случаях нужно вычислять Timing[x;]

    1   ...   8   9   10   11   12   13   14   15   ...   38


    написать администратору сайта