4.. Введение

Данная лекция преследует две цели: 1) показать, как в Mathematica можно работать с такими достаточно сложными математическими объектами как тензоры и 2) продемонстрировать на примере тензорного исчисления, как можно расширить стандартные возможности Mathematica с помощью написания своих собственных программ.

4.. Встроенные возможности Mathematica для работы с тензорами

Напомним, что тензором типа (p,q) на n-мерном векторном пространстве V называется приписанный кажому базису (Subscript[e, 1]),… ,(Subscript[e, n]) набор чисел (Subscript[T, (Subscript[j, 1])...(Subscript[j, q])]^((Subscript[i, 1])...(Subscript[i, p]))), где каждый из p+q штук индексов (Subscript[i, α]) и (Subscript[j, β]) независимо пробегают значения от 1 до n. При этом требуется, чтобы наборы, приписанные разным базисам, были связаны друг с другом специальным образом. Эта связь называется тензорным законом и полностью определяется количествами верхних и нижних индексов. Число p+q называется рангом тензора.

Из сказангного выше вытекает, что, при фиксированном базисе, тензор – это многомерная таблица, состоящая из (n^(p+q)) чисел. Скаляр, т.е. тензор типа (0,0), представляет собой нульмерную таблицу, вектор или ковектор, являющиеся соответственно тензорами типа (1,0) и (0,1) – одномерные, скалярное произведение (тенхор типа (0,2)) или линейный оператор (типа (1,1)) – двумерные.

В Mathematica реализованы многообразные способы задания и работы с такими, и даже существенно более общими, таблицами. Мы уже неоднократно пользовались ими, работая с теми или иными списками List. Выясним, чем выделяются списки, соответствующие тензорам, из общих List, а также какие имеются встроенные возможности использования List для моделирования тензоров. Отметим сразу, что непосредственное использование встроенных в Mathematica возможностей не приводит к желаемому результату, поэтому разрабатываются специальные  дополнительные библиотеки функций, которые подключаются к Mathematica командой  Get[] или Needs[]. Вот некоторые примеры таких пакетов:

пакет Ricci, разработанный John M. Lee и Co (http://www.math.washington.edu/~lee/Ricci/)

TensorManipulation, Ruнz-Tolosa, J. R. and Castillo, E.  (http://personales.unican.es/castie/tensors/)

Riemannian Geometry & Tensor Calculus @ Mathematica, Sotirios Bonanos (http://www.inp.demokritos.gr/~sbonano/RGTC/RiemannTensorCalculus.html)

Tools of Tensor Calculus A.Balfagón , P.Castellví and X.Jaén (http://baldufa.upc.edu/xjaen/ttc/index.htm)

Приведенные пакеты бесплатны. Имеются также платные, например,

Cartan и MathTnsors, H. Soleng (http://library.wolfram.com/infocenter/Articles/2280/)

Тем не менее, мы проиллюстрируем, как научить Mathematica работе с тензорами на примере нашей собственной разрабоки.

4... Списки, соответствующие тензорам

Напомним, что спмсок List в Mathematica представляет собой записанную в фигурных скобках последовательность любых объектов, например,

{f[x],Plot[(x^2),{x,-1,1}],{c,"это - текст"},e}

Тензоры, с которыми мы будем здесь работать, отличаются от общих списков, во-первых, тем, что конечные элементы списка – числа или неизвестные. Во-вторых, тензорам соответствуют однородные списки. Последнее означает, что каждый из подсписков состоит из одного и того же числа элементов, являющихся или числами (неизвестными), или подсписками одной и той же структуры.

Ниже приведены примеры двух списков, первый из которых – тензор, а второй – нет.

{{1,2},{3,4}} 
{{1,2},{3,4,5}}

Чтобы по списку T, представляющему тензор ранга больше нуля, понять размерность пространства V, достаточно выполнить команду Length[T], например,

Length[{{1,2},{3,4}} ]

Можно также выполнить команду Dimensions и взять произвольный элемент, например, первый:

Dimensions[{{1,2},{3,4}} ]
First@Dimensions[{{1,2},{3,4}} ] (* напомним, что f@x – инфиксная форма команды f[x]*)

Отметим, что Dimensions применима к спискам или выражениям, у которых на каждом уровне находятся списки или выражения одной и той же структуры, однако длины подсписков могут быть уже разными (поэтому команда Dimensions выдает длины посписков всех уровней):

Dimensions[{{1,2,3},{4,4,6}} ]

Чтобы по списку T, представляющему тензор ранга больше нуля, понять, чему равен его ранг, выполните команду ArrayDepth[T], например,

ArrayDepth[{{1,2},{3,4}} ]

Тензор ранга 0, значение которого – число или простая неизвестная, также правильно обрабатывается:

ArrayDepth[3]
ArrayDepth[x]

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

ArrayDepth[(Subscript[x, 1])]

хотя

ArrayDepth[{(Subscript[x, 1])}]

Замечание. Не путайте ArrayDepth с Depth, который выдает число индексов, необходимых для описания всех частей выражения, плюс 1. Например,

ArrayDepth[{{1,2},{(Subscript[x, 1]),4}} ]
Depth[{{1,2},{(Subscript[x, 1]),4}} ]

В последнем случае 4 получатся, во-первых, за счет +1, а, во-вторых, из-за (Subscript[x, 1]), на описание частей которого требуется  два индекса (чтобы добраться до нижнего индекса 1).

Тензоры сложной структуры можно нагялдно представить с помощью команды MatrixForm:

DynamicModule[{vars,k,r},
Manipulate[vars=Table[Symbol["i"<>ToString[k]],{k,r}];
MatrixForm@Table[Plus@@vars,Evaluate[Sequence@@({#,3}&/@vars)]],{r,Range[5]}]
]

4... Создание таблиц

К стандартным средствам создания таблиц относятся команды Table, Array, ConstantArray и SparseArray. С первыми двумя командами мы познакомились еще в прошлом семестре.

Table[{i,j,k},{i,3},{j,3} ,{k,3}]
Array[{#1,#2,#3}&,{3,3,3} ]

Команда 
ConstantArray[c,n] создает список из n копий элемента c, а 
ConstantArray[c,{(Subscript[n, 1]),(Subscript[n, 2]),…}]создает (Subscript[n, 1])×(Subscript[n, 2])×… список вложенных списков, содержащих копии элемента c.

ConstantArray[5,10]
ConstantArray[5,{3,3,3}]

Команда SparseArray предназначена для создания списков, в которых лишь некоторые элементы отличаются от данного элемента c, являющегося по умолчанию нулем. При этом отличные от c элементы (Subscript[val, i]) задаются по формату (Subscript[pos, i])->(Subscript[val, i]), где (Subscript[pos, i]) – положение этого элемента. При этом, если не указывать специально, размеры полученной таблицы выбираются наименьшими возможными. В приведенном ниже примере строится таблица 3×4×3.

SparseArray[{{2,3,2}->1,{3,4,3}->9}]
SparseArray[{{2,4,2}->1,{3,4,3}->9}]//MatrixForm

Если нужно построить таблицу заданных размеров, эти размеры можну указать списком вследующем аргументе.

SparseArray[{{2,3,2}->1,{3,4,3}->9},{4,4,4}]
SparseArray[{{2,4,2}->1,{3,4,3}->9},{4,4,4}]//MatrixForm

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

SparseArray[{{2, 3, 2} -> 1, {3, 4, 3} -> 9},{4,4,4},x]
SparseArray[{{2, 4, 2} -> 1, {3, 4, 3} -> 9},{4,4,4},x] // MatrixForm

4... Функции, для работы с таблицами

Как мы уже выясняли в прошлом семестре, в Mathematica имеется ряд команд манипуляции со списками. К наиболее популярным из них относятся команды, имена которых начинаются с Map: Map, MapThread, MapIndexed и др. Команда Map настолько широко используется, что для нее была придумана специальная форма, а именно, f /@ list (этой формой записи команды Map мы будем пользоваться многократно).

Map[f[#]&,{{a,b},{c,d}}]
f[#]&/@{{a,b},{c,d}}
MapThread[f[#1,#2,#3]&,{{{a},{b}},{{c},{d}},{{e},{f}}}] (* #1 соответствует элементам первого списка, #2 - второго и т.д.*)
MapIndexed[f[#1][#2]&,{{a,b},{c,d}}] (* #2 соответствует номеру текущего элемента списка, взятого в фигурные скобки *)

Эти команды допускают явное указание уровня списка, к элементам которого и нужну применять функцию.

Map[f[#]&,{{a,b},{c,d}},{2}]
MapThread[f[#1,#2,#3]&,{{{a},{b}},{{c},{d}},{{e},{f}}},2] (* #1 соответствует элементам первого списка, #2 - второго и т.д.*)
MapIndexed[f[#1][#2]&,{{a,b},{c,d}},{2}] (* #2 соответствует номеру текущего элемента списка, взятого в фигурные скобки *)

Заметьте, что теперь номер текущего элемента является списком длины 2. Такая общность интерпретации команды  MapIndexed и объясняет, почему в номер элемента берется в фигурные скобки.

Следующая команда Tuples строит или n-кратное декартого произведение списка на себя,

Tuples[{a,b,c},3]

или, более общо, декртово произведение списков

Tuples[{{(Subscript[a, 1]),(Subscript[a, 2])},{(Subscript[b, 1]),(Subscript[b, 2]),(Subscript[b, 3])},{(Subscript[c, 1]),(Subscript[c, 2])}}]

Наконец, еще две очень популярные команды: Outer и Inner, являющиеся обобщениями тензорного произведения и свертки тензоров (в частности, скалярного произведения).

Команда Outer[f,(Subscript[list, 1]),(Subscript[list, 2]),…] применяет произвольную функцию f к всевозможным комбинациям элементов списков (Subscript[list, 1]),(Subscript[list, 2]),…

Outer[CircleTimes,{(Underscript[e, 1]),(Underscript[e, 2]),(Underscript[e, 3])},{(Overscript[e, 1]),(Overscript[e, 2]),(Overscript[e, 3])},{(Underscript[e, 1]),(Underscript[e, 2])}]

Команда Inner[f,(Subscript[list, 1]),(Subscript[list, 2]),g]является обобщение команды Dot (.)

{a,b,c}.{x,y,z}

причем f играет роль умножения, а g – сложения.

Inner[Min,{a,b,c},{x,y,z},Max]
Inner[CircleTimes,{a,b,c},{x,y,z},Plus]

4... Тензорные поля

В курсе дифференциальной геометрии тензоры используются для изучения многообразий и порождают тензорные поля, т.е. тензоры одного и того же типа, заданные на касательных пространствах к многообразию. Теперь базисы выбираются исходя из локальных координат, а тензорный закон использует матрицу Якоби перехода от одной системы координат к другой. Таким образом, чтобы получить представление тензорного поля, нужно вместо базисов (Subscript[e, 1]),… ,(Subscript[e, n]) брать локальные координаты (x^1),… ,(x^n), вместо чисел (Subscript[T, (Subscript[j, 1])...(Subscript[j, q])]^((Subscript[i, 1])...(Subscript[i, p]))) – функции (Subscript[T, (Subscript[j, 1])...(Subscript[j, q])]^((Subscript[i, 1])...(Subscript[i, p])))((x^1),…  ,(x^n)), а тензорный закон писать с использованием матриц Якоби.

4.. Пример создания пакета для работы с тензорами.

Опишем разработанный нами пакет для работы с тензорами. Мы приведем здесь  лишь небольшую часть того, что необходимо при работе с тензорами и тензорными полями. Наш центральный принцип – сделать работу с тензорами наиболее наглядной. Чтобы загрузить наш пакет, следует выполнить команду

Needs["Tensors`",NotebookDirectory[]<>"tensors.m"]

где второй аргумент – это имя файла, в котором пакет содержится. (Мы предполагаем, что файл tensors.m расположен в той же папке, что и файл, с которым Вы сейчас работаете, причем Ваш файл или был открыт, уже находясь в указанной папке, или был сохранен в ней, иначе значение  NotebookDirectory[] не определено.)

4... Правила работы в пакете Tensors

В данном разделе мы опишем возможности текущей версии пакета. Разумеется, их можно и нужно расширять.

Начало работы, создание базиса и тензоров, тензорные операции

В пакете реализованы возможности работы как с алгебраическими тензорами (т.е. тензорами в линейном пространстве), так и с тензорными полями (в последнем случае, в текущей версии пакета, тензорные поля рассматриваются на многообразии, состоящем из одной карты, т.е. на области). Чтобы начать работу в пакете, следует задать или базис линеного пространства (функция makeBasis), или локальные координаты и соответствующий канонический базис (функция makeBasisDif). Чтобы посмотреть формат интересующей Вас функции пакета, следует выполнить команду ?<имя функции>. Например,

?makeBasis

Зададим базис в трехмерном пространстве.

makeBasis[e,3]

Все создаваемые базисы последовательно нумеруются. Номер текущего базиса записывается в переменную curBas, а сам базис – в список базисов basis.

curBas
basis[curBas]

Отметим, что на самом деле команда создает также двойственный базис. Обратите вниание, что мы пишем индексы непосредственно над и под буквами (а не справа вверху и справа внизу). Это объясняется тем, что правый верхний индекс Mathematica  воспринимает как степень. Набирать такие индексы удобно с помощью сочентаний клавиш Ctrl = для нижнего индекса и Ctrl 7 для верхнего  индекса. Если же надо поставить одновременно верхний и нижний индексы, то следует набрать одновременно Ctrl = 5.

После того как базис фиксирован, можно создать тензор любого типа. При этом, предусмотрена возможность задать абстрактный тензор с неопределенными компонентами (makeTensor) и возможность задать тензор с конкретными значениями компонент (makeTensorTab) .

makeTensor[T,1,1]; makeTensorTab[W,{2,b,c},1,0];

Чтобы посмотреть значения компонент тензора, надо выполнить команду val

val[T]
val[W]

Над заданными тензорами можно выполнять тензорные операции. Реализованы линейная комбинация, тензорное произведение, свертка, перестановка индексов, симметрирование, альтернирование.

makeTensor[ξ,0,1]

Значек тензорного произведения можно получить, набрав Esc c* Esc.

val[ξ⊗W]

Линейная комбинация допустима только для тензоров одного типа

val[x T+4ξ⊗W]//MatrixForm

val[T+2ξ]

Результат применения тензорных операций можно присвоить с помощью обычного оператора Set (=).

R=x T + 4 ξ⊗W;
val[R]

Свертка записывается просто указанием номеров индексов, по которым надо сворачивать.

val[(Underoverscript[R, 1, 1])]

Перестановка τ индексов указывается в виде списка, элементы которого суть τ-образы последовательных натуральных чисел, начиная с 1. При этом список заключается в дополнительные фигурные скобки и пишется сверху или снизу, в зависимости от того, какие индексы мы собираемся переставлять.

makeTensor[B,0,2]
Row[{"val[B] = ",val[B]//MatrixForm,",       val[(Underscript[B, {{2, 1}}])] = ",val[(Underscript[B, {{2,1}}])]//MatrixForm}]

Аналогично выглядят команды альтернирования и симметрирования:

val[(Underscript[B, alt])]//MatrixForm

val[(Underscript[B, sym])]//MatrixForm

Также часто удобно вывести конкретную компоненту тензора. Это делается с помощью явного указания списков соответствующих  индексов

(Underoverscript[ξ⊗W⊗ξ, ({1,3}), ({2})])

Аналогично, имеется возможность породить сразу симметричный или кососимметричный тензор типа (0,q). Для этого используются  команды makeSymm, makeSkew, makeSymmTab, makeSkewTab. Для симметричных и кососимметричных тензоров определены, соответственно,  операции симметричного ∘ (SmallCircle, EscscEsc) и кососимметричного ⋀ (Wedge, Esc^Esc)произведения. Пакет относит к симметричным (кососимметричным) тензорам или тензоры, созданные перечисленными 4-мя операциями, или полученные в результате симметрирования или альтернирования тензоров типа  (0,q).

makeSkew[ν,2]; makeSkewTab[ζ,{a,b,c}]
val[ν]
val[ζ]
val[ζ⋀ν]

makeSymm[B,2]; makeSymm[U,1]
val[B]
val[U]
val[B∘U]

Отметим, что при создании (косо-)симметричного тензора по таблице значений соответствующая функция не проверяет (косо-)симметричность таблицы, а просто использует только часть данных, доопределяя значения по симметрии.

makeSkewTab[aa,(({{(Subscript[a, 11]), (Subscript[a, 12]), (Subscript[a, 13])}, {(Subscript[a, 21]), (Subscript[a, 22]), (Subscript[a, 23])}, {(Subscript[a, 31]), (Subscript[a, 32]), (Subscript[a, 33])}}))]; makeSymmTab[bb,(({{(Subscript[a, 11]), (Subscript[a, 12]), (Subscript[a, 13])}, {(Subscript[a, 21]), (Subscript[a, 22]), (Subscript[a, 23])}, {(Subscript[a, 31]), (Subscript[a, 32]), (Subscript[a, 33])}}))];
val[aa]//MatrixForm

val[bb] // MatrixForm

Запись тензора в тензорном базисе, вычисление значения тензора на наборе ковекторов и векторов

Имеется возможность записать созданный тензор в базисе пространства тензоров данногог типа.

makeTensor[T,1,2];
toBasis[T]

toBasis[ζ⋀ν]

Для (косо-)симметричных тензоров можно воспользоваться базисом в пространстве (косо-)симметричных тензоров.

toBasisSkew[ζ⋀ν]

val[B]

toBasisSymm[(Underscript[B, sym])]

Предусмотрен также набор обратных операций, вычисляющих список компонент тензора, заданного в виде линейной комбинации базисных. Это функции toList, toListSkew, toListSymm.

toBasis[T]

toList[(Overscript[e, 1])⊗(Underscript[e, 1]) 2+(Overscript[e, 1])⊗(Underscript[e, 2]) (Underoverscript["T", ("1"), ("2")])]

makeTensor[BB,0,2]
toBasisSkew[(Underscript[BB, alt])]

toListSkew[toBasisSkew[(Underscript[BB, alt])]]

Напомним, что тензор типа (p,q) можно представлять себе как полилинейное отображение, заданное на прямом произведении p экземпляров двойственного пространства и q экземпляров самого пространства. Чтобы вычислить значение такого отображения, в пакете имеется функция apply.

val[T]
val[ζ]
makeTensor[W,1,0]
val[W]
apply[T,ζ,W,{1,0,0}]

При этом, как видно из примера, имеется возможность подставлять сразу компоненты тензора. Если количество или вид аргуменов не соответствует типу, выдается сообщение об ошибке, или функция просто не выполняется

apply[T,ζ,W⊗W,W]

apply[T,ζ,W]

Перезапуск

Чтобы отмениь все созданные базисы, координаты и пр. следует выполнить команду resetTensors

basis[curBas]
resetTensors
basis[curBas]

Особенности работы с локальными координатами и тензорными полями

Если Вы задали локальные координаты и базис (команда makeBasisDif), то появляется возможность задавать тензорные поля, компоненты которых суть абстрактные функции от координат. Соответствующий набор функций – это makeTensorField, makeSkewField и makeSymmField. Результат их работы – соответствующее тензорное поле, к которому можно применять обычные тензорные операции.

resetTensors;
makeBasisDif[x,2];
makeTensorField[v,1,0];makeTensorField[ξ,0,1];
val[ξ⊗v]

Замена координат или базиса, пересчет компонент, граф замен

Если мы хотим сделать замену координат, то следует создать новые координаты или новый базис с помощью команд makeBasisDif и makeBasis, указав в них явно связь с каким-нибудь уже заданным базисом или координатами (по умолчанию – с текущими). В линейно-алгебраическом случае это делается с помощью задания матрицы перехода (столбцы которой – суть компоненты векторов нового базиса в старом),   а в случае локальных координат следует задавать функции перехода, выражающие текущие координаты через новые, см. примеры ниже. Кроме того, в качестве четвертого аргумента функции makeBasisDif можно задать функции, задающие обратное отображение, и выражающие новые координаты через текущие. Проверка взаимной обратности остается на совести пользователя.  Если переход задан, то новые координаты становятся текущими и значения всех тензоров автоматически в них пересчитываются.

Приведем соответствующие примеры. Чтобы удобнее было контролировать вычисления, пакет создает ориентированный граф graphBas замен координат (базисов). Вершины этого графа – системы координат. Две вершины соединены стрелкой, если соответствующая замена координат задана явно. Этот граф удобно вывести динамически. В приведенной ниже программе предусмотрен переход к координатам, выбранным мышкой. А именно, чтобы сделать замену, нужно кликнуть в соответствующую вершину графа.

(*Создаем евклидовы координаты на плоскости*)
resetTensors;
makeBasisDif[{x,y}];
(*Задаем в них постоянную билинейную форму и произвольный вектор*)
makeTensorTab[G,(({{1, 0}, {0, 4}})),0,2];makeTensorTab[W,{a,b},1,0];
(*Задаем полярные координаты. При этом записываем функции перехода от них к текущим координатам, т.е. функции x[r,φ] и y[r,φ]. При этом текущими координатами становятся полярные*)
makeBasisDif[{r,φ},{r Cos[φ],r Sin[φ]}];

(*Программа, динамически выводящая граф замен*)
DynamicModule[{},
Dynamic[Deploy[GraphPlot[graphBas, DirectedEdges -> True, VertexRenderingFunction -> ({White, EdgeForm[Black], EventHandler[Disk[#, .15], {"MouseDown" :> (changeBas[curBas, #2])}], If[#2 == curBas, Red, Black], EventHandler[Style[Text[Column[{coor[#2], #2}, Center], #1],FontSize->Scaled[0.03]], {"MouseDown" :> (changeBas[curBas, #2])}]} &),ImageSize->{300,350}]
  ]
 ],Initialization:> Needs["Tensors`",NotebookDirectory[]<>"tensors.m"]]

Dynamic[{val[W],apply[G,W,W]}//Simplify]

(* Задаем еще одни координаты (u,v). Функции перехода можно задать к любым имеющимся координатам, указав явно их номер. В следующем примере мы задаем функции перехода к декартовым координатам,т.е. функции x[u,v] и y[u,v]*)
makeBasisDif[{u,v},1,{u^2-v^2,2 u v}];
(*А здесь - функции перехода к полярным координатам r[p,q] и φ[p,q]*)
makeBasisDif[{p,q},2,{(p^(1/3)),(q/3)}];

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

(*Задаем переход от 4х координат к полярным (вторым)*)
makeTransDif[4,2,{(r^3),3φ}];
(*И еще переходы*)
makeTransDif[3,2,{Sqrt[r] Cos[φ/2],Sqrt[r] Sin[φ/2]}];
makeTransDif[2,1,{Sqrt[x^2+y^2],ArcTan[y/x]}];

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

Получающиеся выражения не всегда легко упрощаются. Это объективно связано с тем, что функции перехода определены не всюду. В пакете предусмотрена возможность добавлять предположения о значениях тех или иных переменных или убирать их в список предположений ($Assumptions), которым Mathematica пользуется при упрощениях. Соответствующие команды  – это addAssumptions и clearAssumptions. Отметим, что предположения о вещественности заводимых координат делается автоматически.

$Assumptions
addAssumptions[p>0,0<q<π,r>0,-π/2<φ<π/2];
$Assumptions
clearAssumptions[0<q<π];
$Assumptions
addAssumptions[p>0,0<q<π/6,(u^2)-(v^2)>0,u v>0];
$Assumptions

Отметим, что сама команда замены координат имеет вид

changeBas[<номер исходного базиса>,<номер целевого базиса>].

После ее успешного выполнения, значения компонен тензоров возвращаются в уелевом базисе. Если же выполнить замену не удалось, что бывает, если матрицы (функции) перехода не заданы и не могут быть вычислены внутри программы, то выдается сообщение, предлагающее задать необходимые мвтрицы (функции).

Дифференциальные формы: их разложение по базису, внешнее умножение и дифференцирование

В настоящий момент в пакете реализован ряд возможностей для работы с дифференицальными формами. Как уже упоминалось выше, можно задать кососимметрический тензор или тензорное поле типа (0,q) с помощью команд makeSkew , makeSkewTab или makeSkewField.

resetTensors;
makeBasisDif[x,5];
makeSkew[ξ,3];makeSkew[ζ,2];

Дифференциальную форму можно разложить по базису, их можно перемножить внешним оьразом (как описанные выше кососимметрические тензоры),

toBasisSkew[ξ]
toBasisSkew[ξ⋀ζ]

Можно задать форму с коэффициентами-функциями:

resetTensors;
makeBasisDif[x,3];
makeSkewField[ω,1];
toBasisSkew[ω]

От такой формы можно взять внешний дифференциал, который, как и положено, будет дифференциальной формой

toBasisSkew[d[ω]]

с которой можно делать все, что и с обычной формой.

Можно задать форму непосредственно, как вот такую форму P:

makeBasisDif[x,5];
P=Cos[(Overscript[x, 1])] (Overscript[dx, 1])⋀(Overscript[dx, 2])⋀(Overscript[dx, 3])+Sin[(Overscript[x, 3])] (Overscript[dx, 1])⋀(Overscript[dx, 2])⋀(Overscript[dx, 4])+(Overscript[x, 4]) (Overscript[dx, 1])⋀(Overscript[dx, 2])⋀(Overscript[dx, 5])+((Overscript[x, 5])^2) (Overscript[dx, 1])⋀(Overscript[dx, 3])⋀(Overscript[dx, 4]);

От неe тоже можно взять внешний диффернциал

d[P]

однако ни P, ни d[P], в этом случае не являются тезорами:

tensor[P]
skew[P]
tensor[d[P]]
tensorBas[P]
skewBas[P]
tensorBas[d[P]]
skewBas[d[P]]

Превратить их в тензорное можно так:

makeSkewTab[dP,toListSkew[d[P]]]
tensor[dP]
(Underscript[dP, {1,2,3,4}])

Риманова метрика, риманова и аффинная связности

В пакете предусмотрена возможность объявить рассматриваемое многообразие (в текущей версии, напомним, многообразие состоит из одной карты) римановым. При этом, или задается абстрактная метрика g и обратный тензор ig (команда makeRiemann), или метрика с конкретной матрицей (команда makeRiemannTab[<матрица>]). В обоих случаях, для метрики и обратного тензора зарезервированы символы g и ig.

resetTensors;
makeBasisDif[{x,y}];
makeRiemann;

Row[{val[g]//MatrixForm,val[ig]//MatrixForm},Spacer[10]]

Отметим, что оба построенных тензора по определению симметричны. Аналогично:

resetTensors;
makeBasisDif[{x,y}];
makeRiemannTab[(({{1, 0}, {0, 1}}))];
val[g]//MatrixForm

При этом, конечно, метрика – это обычный тензор, компоненты которого пересчитываются при замене координат. Так, например, можно вычислить компоненты евклидовой метрики в полярных координатах.

makeBasisDif[{r,φ},{r Cos[φ],r Sin[φ]}];
val[g]//MatrixForm

Если задана метрика, то можно вычислить символы Кристоффеля соответствующей симметричной римановой связности, что делается с помощью команды makeCrRiemann. Для символов Кристоффеля зарезервирована буква Γ (большая греческая буква гамма, Esc G Esc). При этом, с символами Кристоффеля во многом можно обращаться как с тензрами типа (1,2), а именно, можно посмотреть конкретную компоненту, можно вычислять их значение в текущих координатах с помощью все той же функции val.

resetTensors;
makeBasisDif[{x,y,z}];
makeRiemannTab[(({{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}))];
makeCrRiemann
val[Γ]

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

makeBasisDif[{r,φ,z},{r Cos[φ],r Sin[φ],z}];
val[Γ]

Или, вот конкретная компонента:

(Underoverscript[Γ, ({2,2}), ({1})])

Кроме того, предусмотрена возможность задавать абстрактную аффинную связность. При этом, функции makeCrAffine[] и makeCrAffineTab[<таблица значений>] предусмотрены как без явного укзания имени связности (в этом случае, ей присваивается имя Γ), так и с указанием этого имени в виде последнего аргумента.

makeCrAffine[];
val[Γ]

makeCrAffine[AA];
val[AA]

makeCrAffineTab[val[AA]];
(Underoverscript[Γ, ({3,2}), ({1})])

makeCrAffineTab[val[AA],BB];
(Underoverscript[BB, ({2,1}), ({3})])

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

makeRiemannField – создает поля g и ig

makeCrAffineField[]  создает связность Γ

makeCrAffineField[<имя связности>]создает связность с заданным именем.

resetTensors;
makeBasisDif[x,2];
makeRiemannField;
makeCrRiemann;
(Underoverscript[Γ, ({1,2}), ({1})])

4... Как работает пакет (краткое описание основных идей)

В этом разделе мы кратко опишем основные идеи и технические приемы, положенные в основу написания пакета. Сами тексты программ находится в файле tensors.m. Этот файл тоже можно открыть в Mathematica (откройте). Возникнет специальным образом оформленное окно работы с пакетными файлами. Как и любой пакет, наш начинается оператором

BeginPackage["Tensors`", {"Combinatorica`", "GraphUtilities`"}],

где первый аргумент – это имя пакета, а второй – список используемых пакетов, и заканчивается  EndPackage[] . В нашем пакете всего две клетки, первая – состоит из команд вида <имя функции>::usage="текст описания.", служащих для описания пользовательских функций. Именно этот текст появляется на экране при выполнении команды ?<имя функции>. Если имеется несколько одноименных функций, то будут выведены все описания. Наконец, по команде ??<имя функции> на экран выводится весь код, задающий функцию.

?makeBasis

??makeTensor

Полный список функций пакета можно увидеть, если кликнуть в кнопку Functions в окне работы с файлом пакета.

Создание объекта с помощью описания его свойств и правил

Посмотрим, как реализован в пакете объект tensor и подобные ему.  Создание нового тензора (тензорного поля) и работа с уже имеющимися происходит всегда в некотором базисе (системе координат), поэтому первым всегда должен быть выполнен один из операторов makeBasis или makeBasisDif. Первый оператор имеет, как уже говорилось выше, следующий формат: makeBasis[<имя базиса>,<размерность>,<откуда>,<как>], где первый аргумент – имя, присваиваемое элементам базиса, второй – размерность пространства. Два оставшихся необязательных аргумента связывают новый базис с уже имеющимися: третий (не обязательный, по умолчанию устанавливается в номер текущего базиса) задает номер базиса, из которого осуществляется переход, и четвертый – матрица перехода, столбцы которой суть компоненты векторов создаваемого базиса в указанном в третьем аргументе.

makeBasis[e_Symbol, n_Integer, num_Integer: - 1, mat_List: {}] := 
     Module[{nnum}, 
		If[num == -1, nnum = curBas, nnum = num]; 
		numBas++; curBas = numBas; 
        If[mat != {} && coor[nnum] == {}, 
			matCC[{nnum, curBas}] = mat; matCC[{curBas, nnum}] = Inverse[mat]; 
               graphBas = Join[graphBas,{nnum -> curBas, curBas -> nnum}]]; 
        basis[curBas] = Join[Table[Overscript[e, i], {i, n}], 
          Table[Underscript[e, i], {i, n}]]; coor[curBas] = {}; initScan];

Сам модуль присваивает базису очередной номер, устанавливает номер текущего базиса в номер вновь созданного, сохраняет, если заданы, матрицу перехода и обратную к ней в массив матриц matCC и заносит информацию о том, что переход задан в граф graphBas. Наконец, в массив базисов basis помещается список символов базисных векторов и ковекторов. Оператор makeBasisDif устроен аналогично.

После того, как базис определен, можно выполнить операцию makeTensor или какую либо ее разновидность.

Для написания команд такого типа мы воспользовались возможностью Mathematica задавать значения функции только для тех значений аргумента, для которых мы этого хотим. При выполнении команды makeTensor[T] происходит следующее:

1. Устанавливаются значения на аргументе T для функции tensor, возвращающей True на именах заданных тензоров, функции type, возвращающей тип, и функции inBasis, хранящей номер базиса, в котором будут сейчас заданы компоненты тензора:
tensor[T] = True;type[T] = {p, q}; inBasis[T] = curBas;

2. Затем определяется 
val[T] = <таблица значений тензора в текущей системе координат – просто символы с индексами в нужном месте и числе>;

3. Также, в некоторых случаях переопределяются значения функций symm и skew – признаков (косо-)симметричности и кососимметричности тензора.

Кроме того, написаны аналоги функций tensor и type, работающие с произвольным количеством аргументов:

tensorQ[T__] := MatchQ[And @@ tensor /@ {T}, True]; 
typeQ[T__] := SameQ @@ type /@ {T};

Первая проверяет, что последовательность (sequence) аргументов T__ состоит из тензоров, а вторая – что тип у всех тензоров из T__ одинаков.

Теперь можно реализовать тензорные операции в виде рекурсивных правил пересчета этих функций. Например:

(*сумма тензоров - тензор, при условии, что типы совпадают*)
tensor[Plus[T__]] := Head[T] === Plus && typeQ @@ T; 
(* произведение тензора и чего угодно - тензор*)
tensor[(a_)*(T_)] /; tensor[T] := True;

Чтобы правильно вычислялись значения компонент тензоров, нужно правильно написать функцию val

(*сложение тензоров одного типа - покомпонентное*)
val[Plus[T__]] /; Head[T] === Plus && typeQ @@ T := Plus @@ val /@ List @@ T; 
(*умножение тензора на что угодно - покомпонентное*)
val[(a_)*(T_)] /; tensor[T] := a*val[T];

SetDelayed  с условиями как средство избежать бесконечной рекурсии

Пересчет значения тензора в новых координатах также выполняется с помощью функции val

val[T_Symbol] /; inBasis[T] != curBas := 
Module[{inB, res}, inB = inBasis[T]; inBasis[T] = curBas; 
           res = changeVal[T, inB, curBas]; 
           val[T] = res[[1]]; 
            If[ ! res[[2]], inBasis[T] = inB]; res[[1]]];

Казалось бы, мы вычисляем val[T] через val[T], и программа должна зацикливаться. Чтобы понять, почему  этого не происходит,  обратим внимание на условие, участвующее в нашем определении. Напомним, что оператор

f[x_]/; cond[x]:=<тело функции>;

задает функцию, действующую только при выполнении условия cond[x]. При этом, функции с одним и тем же именем, но разными условиями, воспринимаются Mathematica как разные функции. Поэтому приведенная выше функция val[T_Symbol] /; inBasis[T] != curBas работает, только если текущий базис curBas отличается базиса inBasis[T], в котором были вычислены предыдущий раз компоненты тензора T. При обращении же к val[T] внутри модуля, это условие не выполнено, так как уже произошло присвоение inBasis[T] = curBas. Поэтому, на самом деле, вызывается другая функция. Отметим, что сам тензорный закон спрятан в более громоздкую фунцуию changeVal, которая пользователю не доступна. Эта функция возвращает список, первый элемент которого – персчитанные или не пересчитанные значения компонент тензора (последнее происходит, если связь между координатами неизвестна), а второй – логическая переменная, равная True, если пересчет произошел, и False в противном случае.  Это позволяет вернуть начальное значение inBasis[T], если пересчет не произошел. Отметим, что если просто написать условный оперетор присовения

If[<можно пересчитать>, inBasis[T] = curBas],

то произойдет зацикливание программы в случае, когда условие <можно пересчитать> не выполняется.

Использование незанятых операторов и программирование их свойств

Для таких операций, как тензорное произведение, внешнее произведение, симметричное произведение мы использовали предусмотренные в Mathematica незанятые операторы. Такие операторы удобно программировать с помощью свойств и атрибутов. Посмотрим на более простой пример внешнего произведения (незанятый оператор Wedge, верхняя галка ⋀, которая, с клавиатуры вводится Esc ^ Esc ), действующего на разложенных по базису формах (базисные ковекторы считаются разложенными по базису). Напомним, что запись аргумента определяемой функции в виде <имя>_?<условие> используется для задания функции только на тех аргуменах, которые удовлетворяют этому условию.

(*Устанавливаем атрибуты: Flat дает ассоциативность,
 OneIdentity дает возможность записать произведение 
одного элемента: Wedge[x] теперь равно x*)
SetAttributes[Wedge, {Flat, OneIdentity}];
(*Делаем Wedge на формах дистрибутивным по сложению*)
((T_)?skewBasQ + (S_)?skewBasQ) ⋀ (R_)?skewBasQ := T ⋀ R + S ⋀ R; 
(R_)?skewBasQ ⋀ ((T_)?skewBasQ + (S_)?skewBasQ) := R ⋀ T + R ⋀ S; 
(* разрешаем выносить скалярные множители*)
(R_)?skewBasQ ⋀ ((a_)*(S_)?skewBasQ) := a*R ⋀ S; 
((a_)*(R_)?skewBasQ) ⋀ (S_)?skewBasQ := a*R ⋀ S; 
(* умножение на нуль*)
(R_)?skewBasQ ⋀ 0 := 0; 
0 ⋀ (R_)?skewBasQ := 0;
(*косая симметрия на базисных элементах, при повторах нуль *)
Wedge[(T__)?basisQ] /; Signature[{T}] != 1 := 
If[Signature[{T}] == 0, 0, -(Wedge[Sequence @@ Sort[{T}]])];

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

val[Wedge[T__]] /; skewQ[T] := 
Module[{n, q, prod}, n = nn[basis[curBas]]; q = Last[type[Wedge[T]]]; 
        prod = Wedge @@ toBasisSkew /@ {T}; 
		If[prod === 0, ConstantArray[0, ConstantArray[n, q]], 
					toListSkew[prod]]];

А именно, мы сначала раскладываем сомножители по базису форм (toBasisSkew применяется к последовательности аргументов функции Wedge), там выполняем внешнее умножение, определенное приведенными выше правилами, а потом – или возвращаем массив нулей нужного размера, или переводим результат обратно в массив компонент с помощью функции toListSkew.

Операции тензорного произведения (CircleTimes) и симметричного произведения (SmallCircle) реализованы аналогично. Операция внешнего дифференцирования реализована так же, хотя оператор для нее не понадобился, она  названа обычной буквой d.  Это, впрочем, вызвало дополнительные трудности, возникшие при написании правил для основных функций типа tensor. Сравним следующие две строки кода:

tensor[Wedge[T__]] := skewQ[T]; 
tensor[h_[T_]] /; h === d := skewQ[T];

Дело, видимо, в том, что, функцию d в выражении типа tensor[d[T]] (в отличие от Wedge в аналогичном выражении) Mathematica стремится сразу выполнить, что приводит к ошибкам при обработке определений типа

tensor[d[T_]]:= skewQ[T];

Чтобы избежать их, был придуман трюк с переменным именем функции (h_) и условием равенства этого переменного имени конкретному значению.

Обратим также внимание на операцию присвоения, написанную нами для тензоров. Так как оператор Set – встроенный, то, прежде чем его менять, приходится выполнять команду Unprotect. Благодаря условию на второй аргумент, встроенная операция Set остается неизменной для всех случаев, кроме того, когда второй ее аргумент определен нами как тензор. Сама операция, по сути, представляет собой модификацию описанной выше операции makeTensor

Unprotect[Set]; 
(A_=(B_)?tensorQ) := 
	(tensor[A] = True; type[A] = type[B]; inBasis[A] = inBasis[B]; 
		val[A] = val[B]; If[skewQ[B], skew[A] = True]; 
		If[symmQ[B], symm[A] = True]; If[crist[B] === True, crist[A] = True]); 
Protect[Set];

4..3. Возможности дальнейшего развития

Еще раз оговоримся, что наш пакет носит учебный характер. Наверняка он не свободен от ошибок, которые будут устраняться в процессе тестирования пакета пользователями. Кроме того, в нем реализованы далеко не все функции и понятия, нужные для работы с тензорными полями. Написание, на основе имеющихся, некоторых естествнных функций, таких как поднятие-опускание индекса на римановом многообразии, операция звездочка Ходжа на дифференицальных формах, вывод и численное решение уравнений параллельного переноса и уравнений геодезических на римановом (аффинном) многообразии, вычисление компонент тензора кривизны и тензора Риччи, а также полной кривизны, на наш взгляд, не вызовет принципиальных трудностей и предлагается Вам в качестве задач.

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

Следующий шаг – задание отображений между пространствами или многообразиями, реализация дифференциала отображения, переноса форм, проверки регулярности отображения и т.д.

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

Реализация этой программы, конечно, потребует труда и времени.

4.. Задачи

1. Для многообразия (ℝ^3)  с координатами (x,y,z) и метрикой, заданной матрицей ((Subscript[g, ij])), реализовать операцию звездочка. Проверить, чему равен квадрат этой операции, результат применения ее к постоянной функции, к базисным формам. В случае евклидовой метрики (Subscript[g, ij])=(Subscript[δ, ij]) реализовать возможность вычислять результат применения операции звездочка в цилиндрических и сферических координатах, используя граф замен graphBas.

2. Для многообразия (ℝ^3)  с координатами (x,y,z) и метрикой, заданной матрицей ((Subscript[g, ij])), реализовать вычисление градиента функции. В случае евклидовой метрики (Subscript[g, ij])=(Subscript[δ, ij]) реализовать возможность вычислять градиент в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

3. Для многообразия (ℝ^3)  с координатами (x,y,z) и метрикой, заданной матрицей ((Subscript[g, ij])), реализовать вычисление дивергенции векторного поля. В случае евклидовой метрики (Subscript[g, ij])=(Subscript[δ, ij]) реализовать возможность вычислять дивергенцию в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

4. Для многообразия (ℝ^3)  с координатами (x,y,z) и метрикой, заданной матрицей ((Subscript[g, ij])), реализовать вычисление ротора векторного поля. В случае евклидовой метрики (Subscript[g, ij])=(Subscript[δ, ij]) реализовать возможность вычислять ротор в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

5. Для многообразия (ℝ^3)  с координатами (x,y,z) и метрикой, заданной матрицей ((Subscript[g, ij])), реализовать вычисление оператора Лапласа функции. В случае евклидовой метрики (Subscript[g, ij])=(Subscript[δ, ij]) реализовать возможность вычислять оператор Лапласа в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

6. Реализовать операцию опускания/поднятия индексов для многообразия (ℝ^3)  с координатами (x,y,z) и метрики, заданной матрицей ((Subscript[g, ij])). Визуализировать результат применения этой операции к тензорному полю, записанному в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

7. Реализовать вычисление коммутатора векторных полей. Проверить свойства билинейности, кососимметричности, правило Лейбница и тождество Якоби.  Визуализировать результат применения этой операции к векторным полям, записанным в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas.

8. Реализовать вычисление коммутатора векторных полей. Визуализировать результат применения этой операции к векторным полям, записанным в евклидовых, цилиндрических и сферических координатах, используя граф замен graphBas. Проверить формулу Картана на примерах 1- и 2-форм.

9. Проверить, что внешнее дифференцирование и внешнее произведение коммутируют с заменой координат.

10. Проверить формулу Лейбница для внешнего дифференцировния и внешнего произведения.

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

12. Написать уравнения параллельного переноса на трехмерной сфере, решить их численно и визуализировать поведение нормальной плоскости к геодезической при параллельном переносе вдоль нее. В качестве модели использовать стереографическую прооекцию. Предусмотреть возможность менять точку на геодезической. Для фиксированной пары точек, предусмотреть возможность вычислить матрицу оператора переноса.

13. Написать уравнения параллельного переноса на поверхности вращения. Визуализировать параллельный перенос вдоль параллели, в зависимости от выбора параллели. Параллель выбирать динамически.

14. Написать уравнения геодезических на трехмерной сфере. Убедиться, что большие круги – решения. Визуализировать их в модели стереографической проекции.

15. Написать уравнения геодезических в трехмерном пространстве Лобачевского в модели стереографической проекции, т.е. в трехмерном шаре.  Визуализировать "все" геодезические, проходящие через динамически выбранную точку.

16. Написать уравнения геодезических в трехмерном пространстве Лобачевского в модели в верхнем полупространстве.  Визуализировать "все" геодезические, проходящие через динамически выбранную точку.

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

18. Используя операцию свертки, реализовать вычисление скалярной кривизны двумерного риманова многообразия по компонентам заданных заранее тензора Римана и римановой метрики.

19. Для трехмерного многообразия с заданными тензором кривизны Римана и римановой метрикой,  проверить, что компоненты тензора кривизны выражаются через компоненты тензора Риччи.

20. Реализовать вычисление скалярной кривизны двумерной поверхности в (ℝ^3)  через компоненты тензора Римана римановой связности индуцированной метрики. Проверить на примерах конкретных поверхностей выполнение теоремы Гаусса о связи скалярной и гауссовых кривизн.