矩阵和向量运算

(这篇只是翻译了一下官方文档 https://eigen.tuxfamily.org/dox/group__TutorialMatrixArithmetic.html)

密集矩阵和数据运算

这一章主要是如何使用Eigen在矩阵 向量 标量之间进行运算的概括和详情

介绍

Eigen通过重载常见的C++运算符如(+ - * / )等,或使用一些特殊的方法如 dot(), cross() 等来提供矩阵 / 向量运算方法。对于Matrix类(矩阵和向量),运算符仅重载以支持线性运算。例如: matrix1 * matrix2 意味着 矩阵-矩阵乘积。向量 + 标量则是非法的。如果你想进行所有数据运算而不是线性代数,请看下一节。

加减运算

左右两边必须行数和列数都保持一致。他们必须是相同的Scaler(标量)类型,因为Eigen不会进行自动类型转换,如下:

二元运算符 + 如 a + b

二元运算符 - 如 a - b

一元运算符 - 如 - a

复合运算符 += 如 a += b

复合运算符 -= 如 a -= b

标量乘除

标量的乘法和除法也很简单:

二元运算符 * 向量 * 标量

二元运算符 * 标量 * 向量

二元运算符 / 向量 / 标量

复合运算符 *= 向量 *= 标量

复合运算符 /= 向量 /= 标量

关于表达式模板的说明

这个内容对于这章来讲有点朝纲了,但是必须提一嘴。Eigen中的运算符比如 +, 其实它本身并不会进行任何计算,而是仅仅返回一个描述将要执行的计算的"表达式对象"。实际的运算过程发生在后面整个表达式被评估的时候,一般会在运算符=这里。虽然听起来很头大,但是现代的优化编译器都能把这些抽象过程优化掉,最终只得到最优的代码。例如执行下述运算时:

VectorXf a(50), b(50), c(50), d(50);
...
a = 3*b + 4*c + 5*d;

Eigen 会把它编译成一个for循环, 这样数组只会被便利一边。就类似于这样:

for(int - = 0; i < 50; ++i)
  a[i] = 3*b[i] + 4*c[i] + 5*d[i];

所以,我们可以尽情在Eigen中使用较大的算术表达式: 这样反而给Eigen提供了更多优化的机会。

转置和共轭

一个矩阵或者向量a 的转置矩阵 共轭矩阵以及伴随矩阵(共轭转置)可以通过成员函数 transpose(), conjugate(), and adjoint() 来求得。

对于实矩阵来讲, conjugate() 是无运算函数,所以adjoint() 等同于 transpose()

实矩阵: 矩阵中所有数都是实数的矩阵。

共轭矩阵:

对于基本的算数运算符来说,transpose() 和 adjoint() 并没有经过实际转换,而是仅仅返回了一个代理对象。当你进行 b = a.transpose() 运算时,转换发生的同时会将结果写进b的值中。然而这样还有点麻烦之处,如果你进行的是 a = a.transpose() 运算,Eigen则会在转换运算完成之前就开始将结果写进a的值中。因此,指令 a = a.transpose() 不会将a替换为它的转置,这一点跟人们预判的一致。

Matrix2i a; a << 1,2,3,4;
cout << "Here is the matrix a:\n" << a << endl;

a = a.transpose(); // !!! do not do this, 看 这样就会输出有问题的结果
cout << "and the result of the aliasing effect:\n" << a << endl;

输出:

Here is the matrix a:
1 2 
3 4
and the result of the aliasing effext:
1 2
2 4 // 看 这结果就有问题 所以不要这么干

这就是所说的 aliasing issue(混叠问题)。在 debug 模式下,当断言没有被禁用时,这种常见的错误就会被检测到。

在就地转置中,例如 a = a.transpose(), 可以使用 transposeInPlace() 函数:

MatrixXf a(2,3); a << 1,2,3,4,5,6;
cout << "Here is the initial matrix a:\n" << a << endl;

a.transposeInPlace();
cout << "and afer being trasnposed:\n" << a << endl;

输出:

Here is the initial matrix a:
1 2 3
4 5 6
and after being transposed:
1 4
2 5
3 6

除此之外也有用于复杂矩阵的 adjointInPlace() 方法;

矩阵-矩阵乘法和矩阵-向量乘法

矩阵-矩阵乘法还是用*运算符进行的,由于向量也是矩阵的一种特殊情况,他们也被这样隐式地处理了,所以说矩阵-向量乘法只是矩阵-矩阵乘法的一种特殊情况,向量-向量外积也是如此。因此,所有的这些情况都可以用两个操作符搞定。

  • 二元操作符 * 如 a * b
  • 复合运算符 *= 如 a*=b (右侧乘法 a *=b 等于 a = a * b)

⚠️ Eigen将矩阵乘法视为一种特殊情况,并且做了特殊处理,引入了一个临时值,所以 m = m * m 不会导致混叠问题。

在确定矩阵乘积不会导致混叠问题的情况下,可以使用noalias() 函数来省下临时变量, 例如:

c.noalias() += a * b

BLAS 用户可能会担心性能问题,累细雨 c.noalias() -= 2 * a.adjoint() * b 就是已经充分优化过的,它仅仅出发一个 gemm-like 表达式。

点乘和叉乘

点乘和叉乘使用 dot() 和 cross() 表达式。当然,点乘也可以当作一个 1*1 矩阵来通过 u.adjoint()*v 来计算。

记住,叉乘只用于3维向量,点乘适用于任何大小的向量。使用复数时,Eigen的点积在第一个变量中是共轭线性的, 在第二个变量中是线性的。

#include 
#inlcude 

using namespace Eigen;
using namespace std;
int main()
{
  Vector3d v(1,2,3);
  Vertor3d w(0,1,2);
  
  cout << "Dot product: " << v.dot(w) << endl;
  double dp = v.adjoint()*w; // automatic conversion of the inner product to a scalar
  cout << "Dot product via a matrix product: " << dp << endl;
  cout << "Cross product:\n" << v.cross(w) << endl;
}
Dot product: 8
Dot product via a matrix product: 8
Cross product:
 1
-2
 1

基本算数规约运算

Eigen 提供了一些规约操作来将一个矩阵或向量规约到某个值,例如求和(sum()),乘积(prod()),最大值(maxCoeff()),最小值(minCoeff())它的所有系数。

#include 
#include 
 
using namespace std;
int main()
{
  Eigen::Matrix2d mat;
  mat << 1, 2,
         3, 4;
  cout << "Here is mat.sum():       " << mat.sum()       << endl;
  cout << "Here is mat.prod():      " << mat.prod()      << endl;
  cout << "Here is mat.mean():      " << mat.mean()      << endl;
  cout << "Here is mat.minCoeff():  " << mat.minCoeff()  << endl;
  cout << "Here is mat.maxCoeff():  " << mat.maxCoeff()  << endl;
  cout << "Here is mat.trace():     " << mat.trace()     << endl;
}

用trace() 方法得到的矩阵的trace,是矩阵对角线系数的和,用 a.diagonal().sum() 计算也是一样的好,接下来咱们会提到。

minCoeff函数和maxCoeff函数也有一些变体,它们可以通过参数来返回相应系数的的坐标。


你可能感兴趣的:(矩阵和向量运算)