张量基础与 Numpy 应用
所谓张量,是坐标变换下不变的量,也就是说,必须有一种坐标变换关系与之对应。
常见的张量:
- 0阶:常见形式为标量,温度、势等;
- 1阶:常见形式为矢量,力、速度等;
- 2阶:常见形式为矩阵,应力、应变等;
- 3阶:应力梯度等;
- 4阶:弹性模量等。
张量在力学中十分常用,而 Numpy 对张量运算和矩阵运算都有支持,需要小心使用。
张量与矩阵
矩阵:矩阵就是一种二维的数据结构,长宽任意,与坐标变换没有关系。而三维空间中的二阶张量一定是\(3\times3\)。
向量:在讨论矩阵时,常把向量当作矩阵的特例,这是向量可分为行向量、列向量;但是若把向量作为张量,则并不存在“行、列”的概念。这一点在 Matlab 和 Numpy 中差别明显:
1
2
3
4
5
6
7> % Matlab 基于矩阵,数据默认为矩阵
> A = [1,2,3]
1 2 3
> A'
1
2
3
1
2
3
4
5
6
7
8
9# Numpy 对矩阵没有偏爱,维度取决于方括号
1,2,3]) A = np.array([
A.T
array([1,2,3])
1,2,3]]) B = np.array([[
B.T
array([[1],
[2],
[3]])
爱因斯坦求和约定
这是数学史上的一大发现,若不信的话,可以试着返回那不使用这方法的古板日子。
简言之,就是(N项式中的)一项中,如果一个下标出现两次,就把它作为从 1 变化到 3 的求和变量(称为哑标);如果出现一次,就认为它可从 1 到 3 自由取值(称为自由标)。例如矩阵乘法:
\[ \mathbf{C}=\mathbf{AB}\Leftrightarrow c_{ij}=\sum_{k=1}^3a_{ik}b_{kj}\Leftrightarrow c_{ij}=a_{ik}b_{kj} \]
(不过张量计算中不用直接并在一起表示矩阵相乘)
在 Numpy 中,有一个函数einsum()
来按照求和约定求和,但是哑标和自由表没有完全遵守规定:
用法 | 爱因斯坦记法 | 注解 |
---|---|---|
c = einsum('i,i',a,b) |
\(c=a_ib_i\) | |
c = einsum('ii',a) |
\(c=a_{ii}\) | 迹,相当于trace(a) |
c = einsum('ii->i',a) |
\(c_j=a_{ii}\) | 缩并,非标准的标记,理论上是ii->j |
c = einsum('ij->i',a) |
\(c_i=\sum_ja_{ij}\) | 单向求和,非标准的标记 |
c = einsum(a,[0,1],b,[1,0]) |
\(c=a_{ij}b_{ji}\) | 相当于用数字代替了脚标 |
可以用einsum_path
查看其求和过程,输出是一个完整的 log (长长的字符串)。
张量的乘法计算
类型众多,而且很容易和矩阵运算混淆,甚至本身就有些定义不明……下面列出主要运算和 Numpy 函数。注意:
- 整体表示法只是个记号,爱因斯坦表示法则能揭示其具体运算。
- 因为张量的阶数是不定的,因此大部分用二阶作为例子,也可以推广到更多。
- 张量积、外积、克罗内克积众说纷纭,我取了互不矛盾且符合 Numpy 函数的版本。
名称 | 整体表示法 | 爱因斯坦表示法 | Numpy 函数 | 注解 |
---|---|---|---|---|
张量积 | \(\mathbf{C}=\mathbf{AB}\) | \(C_{ijkl}=A_{ij}B_{kl}\) | tensordot(A,B,0) |
阶数为二者之和 |
内积/点积 | \(\mathbf{C}=\mathbf{A}\cdot\mathbf{B}\) | \(C_{ij}=A_{ik}B_{kj}\) | tensordot(A,B,1) 或A @ B |
二阶相当于矩阵相乘 |
克罗内克积 | \(\mathbf{C}=\mathbf{A}\otimes\mathbf{B}\) | \(\mathbf{C}=[A_{ij}\mathbf{B}]\) | kron(A,B) |
相当于张量积展开 |
二次点积 | \(C=\mathbf{A}:\mathbf{B}\) | \(C=A_{ij}B_{ij}\) | tensordot(A,B,2) |
仅用于二阶;交换律 |
矩阵与向量的乘法运算
这样就可以看出我们熟知的矩阵、向量的运算与张量运算的关系。其中矢量运算也同样适用于一阶张量。以下大部分运算都可以通过@
和.T
的组合完成,不再列出。
名称 | 矩阵/向量表示 | 对应张量运算 | Numpy 函数 | 注解 |
---|---|---|---|---|
矩阵乘法 | \(\mathbf{AB}\) | 内积 | matmul(A,B) |
|
矩阵外积 | \(\mathbf{A}\circ\mathbf{B}\) | 克罗内克积 | kron(A,B) |
形状与克罗内克不同 |
矢量内积 | \(\vec{a}\cdot\vec{b}\Leftrightarrow\vec{a}^T\vec{b}\) | 内积 | inner(a,b) |
|
矢量叉积 | \(\vec{a}\times\vec{b}\) | 无 | cross(a,b) |
一般用于一阶张量 |
矢量外积 | \(\vec{a}\circ\vec{b}\Leftrightarrow\vec{a}\vec{b}^T\) | 张量积 | outer(a,b) |
坐标变换
坐标变换是张量的根本。设原基为 \(\mathbf{i}\),新基为 \(\bar{\mathbf{i}}\),则变换矩阵 \(\mathbf{\beta}=\bar{\mathbf{i}}\cdot\mathbf{i}\)
对于一阶:
\[ \left\{ \begin{split} \bar{\mathbf{a}}&=\mathbf{\beta}\cdot\mathbf{a}\\ \mathbf{a}&=\mathbf{\beta}^T\cdot\bar{\mathbf{a}} \end{split} \right. \]
对于二阶:
\[ \left\{ \begin{split} \bar{\mathbf{A}}&=\mathbf{\beta}\cdot\mathbf{A}\cdot\mathbf{\beta}^T\\ \mathbf{A}&=\mathbf{\beta}^T\cdot\bar{\mathbf{A}}\cdot\mathbf{\beta} \end{split} \right. \]
张量微分
拉普拉斯算符 \(\vec{\nabla}=(\frac{\partial}{\partial x},\frac{\partial}{\partial y},\frac{\partial}{\partial z})\) ,以此可表示各种微分运算。设 \(\mathbf{A}(x,y,z)\) 为一 N 阶张量函数。
函数操作 | 运算 | 阶数 |
---|---|---|
梯度 | \(\mathrm{grad}\ \mathbf{A}=\mathbf{A}\vec{\nabla}\) | N+1 |
散度 | \(\mathrm{div}\ \mathbf{A}=\mathbf{A}\cdot\vec{\nabla}\) | N-1 |
旋度 | \(\mathrm{rot}\ \mathbf{A}=\mathbf{A}\times\vec{\nabla}\)(矢量叉积) | N |
总结
其实很多运算说不清到底适用于谁,例如克罗内克积和矩阵外积,矢量叉积……在 Numpy 中,为了保持语义清晰,可以:
- 鉴于最常用的运算就是张量内积 / 矩阵乘法,应尽量使用通用的
@
,这在两种语境下都行得通。 - 进行物理运算时,采用张量方法;计算线性代数时,采用矩阵方法。
- 进行矩阵运算:将矢量表示为二维;尽量使用
@
。 - 进行张量运算:将矢量表示为一维;尽量使用
@
和;由于无法转置,进行矢量运算时,灵活使用inner(),outer(),cross()
。