【1.2】稠密矩阵和数组操作——矩阵运算

本讲旨在提介绍矩阵、标量和矢量之间的运算 。

1.介绍

Eigen提供矩阵/向量算术运算,可以通过常用C ++算术运算符(如+, - ,*)的重载或通过诸如dot(),cross()等特殊方法提供矩阵/向量算术运算。

对于Matrix类(矩阵和矢量)只有重载才能支持线性代数运算
例如,matrix1 * matrix2意味着矩阵与矩阵乘积,而vector + scalar不被允许。如果要执行各种数组操作,而不是线性代数,请参见下一页(http://eigen.tuxfamily.org/dox-devel/group__TutorialArrayClass.html


2.自身加减法运算

对于加减运算,运算符的左侧和右侧必须具有相同数量的行和列。它们也必须具有相同的Scalar类型,因为Eigen不会进行自动类型的转换。操作符有:

  • 二进制运算符+ , 如 a+b
  • 二元运算符 - , 如 a-b
  • 一元运算符 - , 如 -a
  • 复合运算符 += , 如 a+=b
  • 复合运算符 -= , 如 a-=b
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
int main()
{
  Matrix2d a;           //定义一个固定大小的2*2矩阵a.
  a << 1, 2,
       3, 4;
  MatrixXd b(2,2);      //定义一个动态大小的2*2矩阵b.
  b << 2, 3,
       1, 4;
  std::cout << "a + b =\n" << a + b << std::endl;       //执行假发运算.
  std::cout << "a - b =\n" << a - b << std::endl;       //执行减法运算.

  std::cout << "Doing a += b;" << std::endl;            
  a += b;                                               //执行复合运算a = a + b.
  std::cout << "Now a =\n" << a << std::endl;

  Vector3d v(1,2,3);    
  Vector3d w(1,0,0);
  std::cout << "-v + w - v =\n" << -v + w - v << std::endl; //执行向量的加减法.
}
/*
output:
a + b =
3 5
4 8

a - b =
-1 -1
 2  0

Doing a += b;
Now a =
3 5
4 8

-v + w - v =
-1
-4
-6
*/

3.与标量乘除法运算

矩阵与一个标量的乘法和除法也是非常简单的。操作符有:

  • 二进制运算符*,如 matrix*scalar
  • 二进制运算符*,如 scalar*matrix
  • 二元运算符 / , 如 matrix/scalar
  • 复合运算符* = ,如 matrix*=scalar
  • 复合运算符/ = , 如 matrix/=scalar
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
int main()
{
  Matrix2d a;
  a << 1, 2,
       3, 4;
  Vector3d v(1,2,3);
  std::cout << "a * 2.5 =\n" << a * 2.5 << std::endl;
  std::cout << "0.1 * v =\n" << 0.1 * v << std::endl;
  std::cout << "Doing v *= 2;" << std::endl;
  v *= 2;
  std::cout << "Now v =\n" << v << std::endl;
}
/*
output:
a * 2.5 =
2.5   5
7.5  10

0.1 * v =
0.1
0.2
0.3

Doing v *= 2;
Now v =
2
4
6
*/

4.关于表达式模板

在这个页面上有更高级的解释(http://eigen.tuxfamily.org/dox-devel/TopicEigenExpressionTemplates.html), 但现在就提一下它也是有必要的。

在Eigen中,诸如算术运算符+本身不执行任何计算,它们只返回描述要执行的计算的“表达式对象”。实际的计算发生在稍后,当整个表达式被评估,典型地在运算符=。
虽然这可能听起来很绕,但任何现代优化编译器都可以优化这种抽象,并且结果是完美优化的代码。例如,当你这样做的时候:

VectorXf a(50), b(50), c(50), d(50);    //定义四个含50个元素的列向量
...
a = 3*b + 4*c + 5*d;

Eigen将它编译为一个for循环,以便数组遍历一次。简化(例如忽略SIMD优化),此循环如下所示:

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

因此,您不应该害怕使用Eigen使用相对较大的算术表达式:它只会给Eigen更多的优化机会。


5.矩阵的转置和共轭

矩阵或向量的转置 a^T^,共轭 ,和伴随(共轭转置) a^T^,分别由成员函数transpose()conjugate()adjoint() 获得

MatrixXcf a = MatrixXcf::Random(2,2);       //随机初始化一个2*2的复杂float类型矩阵
cout << "矩阵a\n" << a << endl;
cout << "矩阵的转置 a^T\n" << a.transpose() << endl;
cout << "共轭矩阵\n" << a.conjugate() << endl;
cout << "伴随矩阵a^*\n" << a.adjoint() << endl;

image

对于实际的矩阵,求共轭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;
/*
output:
Here is the matrix a:
1 2
3 4
and the result of the aliasing effect:
1 2
2 4
*/

这就是所谓的 别名问题http://eigen.tuxfamily.org/dox-devel/group__TopicAliasing.html)。 在“调试模式”下,即断言未被禁用时,会自动检测到这种常见的缺陷。

对于 就地换位,例如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 after being transposed:\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()函数。


6.矩阵与矩阵的乘法

矩阵 - 矩阵乘法再次用到乘法操作符 *。由于向量是矩阵的一个特殊情况,因此它们也在这里被隐式处理,所以 矩阵-向量乘积实际上只是矩阵-矩阵乘积的一个特例 ,向量-向量外积也是。因此,所有这些情况都是由两个运算符处理的:

  • 二进制运算符 ,如 a b
  • 复合运算符 * = , 如 a*=b(相当于a = a*b)
#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
int main()
{
  Matrix2d mat;
  mat << 1, 2,
         3, 4;
  Vector2d u(-1,1), v(2,0);
  std::cout << "Here is mat*mat:\n" << mat*mat << std::endl;
  std::cout << "Here is mat*u:\n" << mat*u << std::endl;
  std::cout << "Here is u^T*mat:\n" << u.transpose()*mat << std::endl;
  std::cout << "Here is u^T*v:\n" << u.transpose()*v << std::endl;
  std::cout << "Here is u*v^T:\n" << u*v.transpose() << std::endl;
  std::cout << "Let's multiply mat by itself" << std::endl;
  mat = mat*mat;
  std::cout << "Now mat is mat:\n" << mat << std::endl;
}

输出:

注意:如果您阅读上述关于表达式模板的内容,并担心m=m * m会造成锯齿问题,请立即放心:Eigen将矩阵乘法作为一种特殊情况处理,并在此处引入临时变变量来处理
因此它将编译m=m*m为:
- tmp = m * m;
- m = tmp;

如果您知道您的矩阵产品可以安全地评估到目标矩阵中,而不会出现别名问题,那么可以使用noalias()函数来避免临时锯齿,例如:

c.noalias()+ = a * b;

有关此主题的更多详细信息,请参阅别名上的页面*(http://eigen.tuxfamily.org/dox-devel/group__TutorialMatrixArithmetic.html)。

注意:对于担心性能的BLAS用户而言,诸如c.noalias() -= 2 * a.adjoint() * b;完全优化的表达式会触发一个类似gemm的函数调用。


7.点积和叉积运算

对于 点积和叉积,需要dot()和cross()函数 。当然, 点积也可以作为u.adjoint()v 的1x1矩阵来获得。

#include <iostream>
#include <Eigen/Dense>
using namespace Eigen;
using namespace std;
int main()
{
  Vector3d v(1,2,3);
  Vector3d 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;
}


点积:


叉积:

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


8.基本的算术简化操作

Eigen还提供了一些简化操作以将给定的矩阵或向量的操作,诸如对所有系数求总和(由sum(),product(prod())或最大值(maxCoeff())和最小值(minCoeff())。

#include <iostream>
#include <Eigen/Dense>
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;   //求对角线元素之和
}
/*
Here is mat.sum():       10
Here is mat.prod():      24
Here is mat.mean():      2.5
Here is mat.minCoeff():  1
Here is mat.maxCoeff():  4
Here is mat.trace():     5
*/

矩阵 的迹可以由函数trace()返回 ,是对角线系数的总和并且也可以作为有效地利用来计算a.diagonal().sum(),我们将在后面看到。

还有 函数minCoeff和maxCoeff函数通过参数返回各个系数的坐标

Matrix3f m = Matrix3f::Random();        //随机初始化一个3*3的浮点型矩阵m.
std::ptrdiff_t i, j;                    //定义指针记录元素的位置.
float minOfM = m.minCoeff(&i,&j);       //m.minCoeff(&i,&j)返回矩阵中最小的元素.
cout << "Here is the matrix m:\n" << m << endl;
cout << "Its minimum coefficient (" << minOfM 
       << ") is at position (" << i << "," << j << ")\n\n";

RowVector4i v = RowVector4i::Random();  //随机初始化一个四维向量.
int maxOfV = v.maxCoeff(&i);            //v.maxCoeff(&i)返回向量的最大值.
cout << "Here is the vector v: " << v << endl;
cout << "Its maximum coefficient (" << maxOfV 
       << ") is at position " << i << endl;

/*      
output:
Here is the matrix m:
  0.68  0.597  -0.33
-0.211  0.823  0.536
 0.566 -0.605 -0.444
Its minimum coefficient (-0.605) is at position (2,1)

Here is the vector v:  1  0  3 -3
Its maximum coefficient (3) is at position 2
*/

9.操作的有效性

Eigen检查您执行的操作的有效性。如果允许的话,它会在编译时检查它们,从而产生编译错误。这些错误信息可能很长很难看,但是Eigen会在UPPERCASE_LETTERS_SO_IT_STANDS_OUT中写入重要的信息。例如:

Matrix3f m;
Vector4f v;
v = m * v;      //编译时错误:YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES

当然,在许多情况下,例如在检查动态大小时,检查不能在编译时进行。然后Eigen使用运行时断言。这意味着,如果在“调试模式”下运行,那么程序将在执行非法操作时中止并显示错误消息,并且如果断言被关闭,则可能会崩溃。

MatrixXf m(3,3);
VectorXf v(4);
v = m * v;      //运行时断言失败:“无效矩阵产品


有关此主题的更多详细信息,请参阅此页面(http://eigen.tuxfamily.org/dox-devel/TopicAssertions.html)。

2018.01.20

猜你喜欢

转载自blog.csdn.net/wuyanmin1995/article/details/79116860