本讲旨在提介绍矩阵、标量和矢量之间的运算 。
Eigen提供矩阵/向量算术运算,可以通过常用C ++算术运算符(如+, - ,*)的重载或通过诸如dot(),cross()等特殊方法提供矩阵/向量算术运算。
对于Matrix类(矩阵和矢量)只有重载才能支持线性代数运算。
例如,matrix1 * matrix2意味着矩阵与矩阵乘积,而vector + scalar不被允许。如果要执行各种数组操作,而不是线性代数,请参见下一页(http://eigen.tuxfamily.org/dox-devel/group__TutorialArrayClass.html)。
对于加减运算,运算符的左侧和右侧必须具有相同数量的行和列。它们也必须具有相同的Scalar类型,因为Eigen不会进行自动类型的转换。操作符有:
#include
#include
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
*/
矩阵与一个标量的乘法和除法也是非常简单的。操作符有:
#include
#include
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
*/
在这个页面上有更高级的解释(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更多的优化机会。
矩阵或向量的转置 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;
对于实际的矩阵,求共轭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()函数。
矩阵 - 矩阵乘法再次用到乘法操作符 *。由于向量是矩阵的一个特殊情况,因此它们也在这里被隐式处理,所以 矩阵-向量乘积实际上只是矩阵-矩阵乘积的一个特例 ,向量-向量外积也是。因此,所有这些情况都是由两个运算符处理的:
#include
#include
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的函数调用。
对于 点积和叉积,需要dot()和cross()函数 。当然, 点积也可以作为u.adjoint()v 的1x1矩阵来获得。
#include
#include
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的点积在第一个变量中是共轭线性的,在第二个变量中是线性的。
Eigen还提供了一些简化操作以将给定的矩阵或向量的操作,诸如对所有系数求总和(由sum(),product(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; //求对角线元素之和
}
/*
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
*/
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