Eigen 库学习指南2-稠密矩阵和数组的运算(二)

· 6276字 · 13分钟

翻译: Qi Deng

切片和索引 🔗

高级初始化 🔗

逗号表达式 -> 之前已经提过了 🔗

特殊矩阵和数组 🔗

Matrix 和 Array 类有像 Zero() 这样的静态方法,可用于将所有系数初始化为零。共有三种变体。第一个变体不带参数,只能用于固定大小的对象。如果要将动态大小的对象初始化为零,则需要指定大小。因此,第二个变体需要一个参数,可用于一维动态大小的对象,而第三个变体需要两个参数,可用于二维对象。以下示例说明了所有三种变体:

std::cout << "A fixed-size array:\n";
Array33f a1 = Array33f::Zero();
std::cout << a1 << "\n\n";
 
 
std::cout << "A one-dimensional dynamic-size array:\n";
ArrayXf a2 = ArrayXf::Zero(3);
std::cout << a2 << "\n\n";
 
 
std::cout << "A two-dimensional dynamic-size array:\n";
ArrayXXf a3 = ArrayXXf::Zero(3, 4);
std::cout << a3 << "\n";

同样,静态方法 Constant(value) 将所有系数设置为值。如果需要指定对象的大小,则附加参数位于值参数之前,如 MatrixXd::Constant(rows, cols, value)Random() 方法用随机系数填充矩阵或数组。单位矩阵可以通过调用Identity()获得;此方法仅适用于 Matrix,不适用于 Array,因为“单位矩阵”是一个线性代数概念。 LinSpaced(size, low, high) 方法仅适用于向量和一维数组;它产生一个指定大小的向量,其系数在低和高之间等距分布。 LinSpaced() 方法在以下示例中进行了说明,该示例打印了一个表格,其中包含以度为单位的角度、以弧度为单位的相应角度以及它们的正弦和余弦

ArrayXXf table(10, 4);
table.col(0) = ArrayXf::LinSpaced(10, 0, 90);
table.col(1) = M_PI / 180 * table.col(0);
table.col(2) = table.col(1).sin();
table.col(3) = table.col(1).cos();
std::cout << "  Degrees   Radians      Sine    Cosine\n";
std::cout << table << std::endl;

此示例表明可以将像 LinSpaced() 返回的对象这样的对象分配给变量(和表达式)。 Eigen 定义了实用函数,如 setZero()、MatrixBase::setIdentity() 和 DenseBase::setLinSpaced() 来方便地执行此操作。下面的例子对比了三种构造矩阵的方式 $J = \bigl[ \begin{smallmatrix} O & I \ I & O \end{smallmatrix} \bigr]$:

使用静态方法和赋值,使用静态方法和逗号初始化器,或使用 setXxx() 方法。

const int size = 6;
MatrixXd mat1(size, size);
mat1.topLeftCorner(size/2, size/2)     = MatrixXd::Zero(size/2, size/2);
mat1.topRightCorner(size/2, size/2)    = MatrixXd::Identity(size/2, size/2);
mat1.bottomLeftCorner(size/2, size/2)  = MatrixXd::Identity(size/2, size/2);
mat1.bottomRightCorner(size/2, size/2) = MatrixXd::Zero(size/2, size/2);
std::cout << mat1 << std::endl << std::endl;
 
MatrixXd mat2(size, size);
mat2.topLeftCorner(size/2, size/2).setZero();
mat2.topRightCorner(size/2, size/2).setIdentity();
mat2.bottomLeftCorner(size/2, size/2).setIdentity();
mat2.bottomRightCorner(size/2, size/2).setZero();
std::cout << mat2 << std::endl << std::endl;
 
MatrixXd mat3(size, size);
mat3 << MatrixXd::Zero(size/2, size/2), MatrixXd::Identity(size/2, size/2),
        MatrixXd::Identity(size/2, size/2), MatrixXd::Zero(size/2, size/2);
std::cout << mat3 << std::endl;

用作临时对象 🔗

如上所示,Zero() 和 Constant() 等静态方法可用于在声明时或赋值运算符的右侧初始化变量。您可以将这些方法视为返回矩阵或数组;事实上,它们返回所谓的表达式对象,这些对象在需要时计算为矩阵或数组,因此这种语法不会产生任何开销。

这些表达式也可以用作临时对象。我们在此处复制的入门指南中的第二个示例已经说明了这一点。

#include <iostream>
#include <Eigen/Dense>
 
using Eigen::MatrixXd;
using Eigen::VectorXd;
 
int main()
{
  MatrixXd m = MatrixXd::Random(3,3);
  m = (m + MatrixXd::Constant(3,3,1.2)) * 50;
  std::cout << "m =" << std::endl << m << std::endl;
  VectorXd v(3);
  v << 1, 2, 3;
  std::cout << "m * v =" << std::endl << m * v << std::endl;
}

表达式 m + MatrixXf::Constant(3,3,1.2) 构造 3×3 矩阵表达式,其所有系数等于 1.2 加上 m 的相应系数。

逗号初始化器也可用于构造临时对象。下面的示例构造一个大小为 2×3 的随机矩阵,然后将该矩阵的左侧乘以 $\begin{smallmatrix} 0 & 1 \ 1 & 0 \end{smallmatrix}$

MatrixXf mat = MatrixXf::Random(2, 3);
std::cout << mat << std::endl << std::endl;
mat = (MatrixXf(2,2) << 0, 1, 1, 0).finished() * mat;
std::cout << mat << std::endl;

一旦我们的临时子矩阵的逗号初始化完成,这里就需要 finished() 方法来获取实际的矩阵对象。

Reductions, Visitors and boradcast 🔗

规约计算 🔗

在 Eigen 中,归约是指一个接受矩阵或数组并返回单个标量值的函数。最常用的归约之一是 .sum() ,返回给定矩阵或数组内所有系数的总和。

#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;
}

函数 trace() 返回的矩阵迹是对角线系数的总和,可以等效地计算 a.diagonal().sum()

范数计算 🔗

欧几里得范数,也叫 $\ell^2$ ,或者平方范数,可以用 squaredNorm() 函数来计算,它等于向量本身的点积,等于其系数的绝对值平方和。Eigen 还提供了 norm() 方法,该方法返回 squaredNorm() 的平方根。

这些操作也可以对矩阵进行操作;在这种情况下,n×p 矩阵被视为大小为 (n*p) 的向量,因此例如 norm() 方法返回“Frobenius”或“Hilbert-Schmidt”范数。我们避免谈论 $\ell ^2$ 矩阵范数,因为这可能意味着不同的事情。

如果你需要其它的 coefficient-wise 的范数,$\ell^p$, 可以使用 lpNorm<p>() 方法,如果需要使用特殊值比如,无穷大,可以使用 Eigen::Infinity 来指定 p, 以达到计算 $\ell^{\infin}$ 的效果,它是系数绝对值的最大值。

下面的代码展示了这些计算方法的使用:

#include <Eigen/Dense>
#include <iostream>
 
int main()
{
  Eigen::VectorXf v(2);
  Eigen::MatrixXf m(2,2), n(2,2);
  
  v << -1,
       2;
  
  m << 1,-2,
       -3,4;
 
  std::cout << "v.squaredNorm() = " << v.squaredNorm() << std::endl;
  std::cout << "v.norm() = " << v.norm() << std::endl;
  std::cout << "v.lpNorm<1>() = " << v.lpNorm<1>() << std::endl;
  std::cout << "v.lpNorm<Infinity>() = " << v.lpNorm<Eigen::Infinity>() << std::endl;
 
  std::cout << std::endl;
  std::cout << "m.squaredNorm() = " << m.squaredNorm() << std::endl;
  std::cout << "m.norm() = " << m.norm() << std::endl;
  std::cout << "m.lpNorm<1>() = " << m.lpNorm<1>() << std::endl;
  std::cout << "m.lpNorm<Infinity>() = " << m.lpNorm<Eigen::Infinity>() << std::endl;
}

输出是:

v.squaredNorm() = 5
v.norm() = 2.23607
v.lpNorm<1>() = 3
v.lpNorm<Infinity>() = 2

m.squaredNorm() = 30
m.norm() = 5.47723
m.lpNorm<1>() = 10
m.lpNorm<Infinity>() = 4

布尔规约 🔗

以下规约返回布尔值或处理布尔表达式

  • 如果给定 Matrix 或 Array 中的所有系数都计算为 true ,则 all() 返回 true
  • 如果给定 Matrix 或 Array 中的至少一个系数的计算结果为 true ,则 any() 返回 true
  • count() 返回给定矩阵或数组中计算结果为真的系数的数量。

这些通常与 Array 提供的系数比较和相等运算符结合使用。例如, array > 0 是一个与 array 大小相同的 Array ,在 array 的相应系数为正的那些位置为 true 。因此,(array > 0).all() 测试数组的所有系数是否为正。这可以在以下示例中看到:

如何判断一个矩阵式半正定矩阵或者正定矩阵?

例子:

#include <Eigen/Dense>
#include <iostream>
 
int main()
{
  Eigen::ArrayXXf a(2,2);
  
  a << 1,2,
       3,4;
 
  std::cout << "(a > 0).all()   = " << (a > 0).all() << std::endl;
  std::cout << "(a > 0).any()   = " << (a > 0).any() << std::endl;
  std::cout << "(a > 0).count() = " << (a > 0).count() << std::endl;
  std::cout << std::endl;
  std::cout << "(a > 2).all()   = " << (a > 2).all() << std::endl;
  std::cout << "(a > 2).any()   = " << (a > 2).any() << std::endl;
  std::cout << "(a > 2).count() = " << (a > 2).count() << std::endl;
}

输出:

(a > 0).all()   = 1
(a > 0).any()   = 1
(a > 0).count() = 4

(a > 2).all()   = 0
(a > 2).any()   = 1
(a > 2).count() = 2

Visitors 🔗

当想要获得系数在矩阵或数组中的位置时,访问者很有用。最简单的示例是 maxCoeff(&x,&y)minCoeff(&x,&y),它们可用于查找矩阵或数组中最大或最小系数的位置。

传递给访问者的参数是指向要存储行和列位置的变量的指针。这些变量应该是 Index 类型,如下所示:

例子:

#include <iostream>
#include <Eigen/Dense>
 
int main()
{
  Eigen::MatrixXf m(2,2);
  
  m << 1, 2,
       3, 4;
 
  //get location of maximum
  Eigen::Index maxRow, maxCol;
  float max = m.maxCoeff(&maxRow, &maxCol);
 
  //get location of minimum
  Eigen::Index minRow, minCol;
  float min = m.minCoeff(&minRow, &minCol);
 
  std::cout << "Max: " << max <<  ", at: " <<
     maxRow << "," << maxCol << std::endl;
  std:: cout << "Min: " << min << ", at: " <<
     minRow << "," << minCol << std::endl;
}

输出:

Max: 4, at: 1,1
Min: 1, at: 0,0

这两个函数还返回最小或最大系数的值。

部分规约 🔗

部分缩减是可以对矩阵或数组按列或行进行操作的缩减,对每一列或行应用缩减操作并返回具有相应值的列或行向量。使用 colwise()rowwise() 应用部分缩减。

例子:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
int main()
{
  Eigen::MatrixXf mat(2,4);
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;
  
  std::cout << "Column's maximum: " << std::endl
   << mat.colwise().maxCoeff() << std::endl;
}

输出:

Maximum sum at position 2
The corresponding vector is: 
6
7
And its sum is is: 13

请注意,按列操作返回行向量,而按行操作返回列向量。

将部分规约与其他操作相结合 🔗

也可以使用部分归约的结果做进一步的处理。下面是另一个示例,它查找矩阵中元素总和最大的列。对于逐列部分归约,这可以编码为:

#include <iostream>
#include <Eigen/Dense>
 
int main()
{
  Eigen::MatrixXf mat(2,4);
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;
  
  Eigen::Index   maxIndex;
  float maxNorm = mat.colwise().sum().maxCoeff(&maxIndex);
  
  std::cout << "Maximum sum at position " << maxIndex << std::endl;
 
  std::cout << "The corresponding vector is: " << std::endl;
  std::cout << mat.col( maxIndex ) << std::endl;
  std::cout << "And its sum is is: " << maxNorm << std::endl;
}

输出为:

Maximum sum at position 2
The corresponding vector is: 
6
7
And its sum is is: 13

Broadcasting 广播 🔗

广播背后的概念类似于部分归约,不同之处在于广播构造一个表达式,其中通过在一个方向上复制向量(列或行)将其解释为矩阵。

一个简单的例子是将某个列向量添加到矩阵中的每一列。这可以通过以下方式完成:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
int main()
{
  Eigen::MatrixXf mat(2,4);
  Eigen::VectorXf v(2);
  
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;
         
  v << 0,
       1;
       
  //add v to each column of m
  mat.colwise() += v;
  
  std::cout << "Broadcasting result: " << std::endl;
  std::cout << mat << std::endl;
}

输出是:

Broadcasting result: 
1 2 6 9
4 2 8 3

我们可以用两种等价的方式解释指令 mat.colwise() += v 。它将向量 v 添加到矩阵的每一列。或者,它可以解释为将向量 v 重复四次以形成一个四乘二矩阵,然后将其添加到 mat: $$ \begin{bmatrix} 1 & 2 & 6 & 9 \ 3 & 1 & 7 & 2 \end{bmatrix} + \begin{bmatrix} 0 & 0 & 0 & 0 \ 1 & 1 & 1 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 2 & 6 & 9 \ 4 & 2 & 8 & 3 \end{bmatrix}. $$ 运算符 -=、+ 和 - 也可以按列和按行使用。在数组上,我们还可以使用运算符 =、/=、 和 / 来执行按系数乘法和按列或按行进行除法。这些运算符在矩阵上不可用,因为不清楚它们会做什么。如果要将矩阵 mat 的第 0 列与 v(0) 相乘,将第 1 列与 v(1) 相乘,依此类推,请使用 mat = mat * v.asDiagonal()

需要指出的是,要按列或按行相加的向量必须是 Vector 类型,不能是 Matrix。如果不满足,那么您将收到编译时错误。这也意味着在使用 Matrix 操作时,广播操作只能应用于 Vector 类型的对象。这同样适用于 Array 类,其中 VectorXf 的等效项是 ArrayXf。与往常一样,您不应在同一个表达式中混合使用数组和矩阵。

在行方向执行同样的操作,我们可以使用:

#include <iostream>
#include <Eigen/Dense>
 
using namespace std;
int main()
{
  Eigen::MatrixXf mat(2,4);
  Eigen::VectorXf v(4);
  
  mat << 1, 2, 6, 9,
         3, 1, 7, 2;
         
  v << 0,1,2,3;
       
  //add v to each row of m
  mat.rowwise() += v.transpose();
  
  std::cout << "Broadcasting result: " << std::endl;
  std::cout << mat << std::endl;
}

输出结果是:

Broadcasting result: 
 1  3  8 12
 3  2  9  5

将广播与其他操作相结合 🔗

广播还可以与其他操作相结合,例如矩阵或数组操作、规约和部分规约。

现在已经介绍了广播、规约和部分规约,我们可以深入研究一个更高级的示例,即在矩阵 m 的列中找到向量 v 的最近邻域。本例将使用欧氏距离,使用名为 squaredNorm() 的部分规约计算平方欧氏距离:

#include <iostream>
#include <Eigen/Dense>
 
int main()
{
  Eigen::MatrixXf m(2,4);
  Eigen::VectorXf v(2);
  
  m << 1, 23, 6, 9,
       3, 11, 7, 2;
       
  v << 2,
       3;
 
  Eigen::Index index;
  // find nearest neighbour
  (m.colwise() - v).colwise().squaredNorm().minCoeff(&index);
 
  std::cout << "Nearest neighbour is column " << index << ":" << std::endl;
  std::cout << m.col(index) << std::endl;
}

输出:

Nearest neighbour is column 0:
1
3

怎么解释这行做的事情呢? (m.colwise() - v).colwise().squaredNorm().minCoeff(&index);

  • m.colwise() - v 是一个广播操作,从 m 中的每一列中减去 v。此操作的结果是一个新矩阵,其大小与矩阵 m 相同:

    $$\mbox{m.colwise() - v} = \begin{bmatrix} -1 & 21 & 4 & 7 \ 0 & 8 & 4 & -1 \end{bmatrix}$$

  • (m.colwise() - v).colwise().squaredNorm() 是部分缩减,按列计算平方范数。此操作的结果是一个行向量,其中每个系数是 m 和 v 中每列之间的平方欧氏距离:

    $$\mbox{(m.colwise() - v).colwise().squaredNorm()} = \begin{bmatrix} 1 & 505 & 32 & 50 \end{bmatrix}$$

  • 最后,minCoeff(&index) 用于获取 m 中最接近 v 的列的欧氏距离索引。

Reshape 重塑 🔗

从 3.4 版本开始,Eigen 提供了方便的方法来将一个矩阵重塑为另一个不同大小或向量的矩阵。所有情况都通过 DenseBase::reshaped(NRowsType,NColsType)DenseBase::reshaped() 函数处理。这些函数不执行就地重塑,而是返回输入表达式的视图。

重塑 2D 视图 🔗

更一般的 reshape 转换是通过以下方式处理的:reshaped(nrows,ncols)。下面是一个将 4x4 矩阵重塑为 2x8 矩阵的示例:

Matrix4i m = Matrix4i::Random();
cout << "Here is the matrix m:" << endl << m << endl;
cout << "Here is m.reshaped(2, 8):" << endl << m.reshaped(2, 8) << endl;

输出:

Here is the matrix m:
 7  9 -5 -3
-2 -6  1  0
 6 -3  0  9
 6  6  3  9
Here is m.reshaped(2, 8):
 7  6  9 -3 -5  0 -3  9
-2  6 -6  6  1  3  0  9

默认情况下,无论输入表达式的存储顺序如何,输入系数始终按列优先顺序解释。有关排序、编译时大小和自动大小扣除的更多控制,请参阅 DenseBase::reshaped(NRowsType,NColsType) 的文档,其中包含所有详细信息和许多示例。

一维线性视图 🔗

Reshape 的一个非常常见的用法是在给定的二维矩阵或表达式上创建一维线性视图。在这种情况下,可以推导出大小,因此可以省略,如下例所示:

Matrix4i m = Matrix4i::Random();
cout << "Here is the matrix m:" << endl << m << endl;
cout << "Here is m.reshaped().transpose():" << endl << m.reshaped().transpose() << endl;
cout << "Here is m.reshaped<RowMajor>().transpose():  " << endl << m.reshaped<RowMajor>().transpose() << endl;

输出:

Here is the matrix m:
 7  9 -5 -3
-2 -6  1  0
 6 -3  0  9
 6  6  3  9
Here is m.reshaped().transpose():
 7 -2  6  6  9 -6 -3  6 -5  1  0  3 -3  0  9  9
Here is m.reshaped<RowMajor>().transpose():  
 7  9 -5 -3 -2 -6  1  0  6 -3  0  9  6  6  3  9

此快捷方式始终返回列向量,默认情况下,输入系数始终按列优先顺序解释。同样,请参阅 DenseBase::reshaped() 的文档以获得对排序的更多控制。

ReshapeInPlace 🔗

上面的示例创建了重塑视图,但是如何重塑给定矩阵 in place 呢?当然,此任务仅适用于具有运行时维度的矩阵和数组。在许多情况下,这可以通过 PlainObjectBase::resize(Index,Index) 来完成:

MatrixXi m = Matrix4i::Random();
cout << "Here is the matrix m:" << endl << m << endl;
cout << "Here is m.reshaped(2, 8):" << endl << m.reshaped(2, 8) << endl;
m.resize(2,8);
cout << "Here is the matrix m after m.resize(2,8):" << endl << m << endl

reshape 和 resize 的区别是什么?

输出:

Here is the matrix m:
 7  9 -5 -3
-2 -6  1  0
 6 -3  0  9
 6  6  3  9
Here is m.reshaped(2, 8):
 7  6  9 -3 -5  0 -3  9
-2  6 -6  6  1  3  0  9
Here is the matrix m after m.resize(2,8):
 7  6  9 -3 -5  0 -3  9
-2  6 -6  6  1  3  0  9

但是请注意,与 reshaped 不同,resize 的结果取决于输入的存储顺序。因此它的行为类似于 reshaped<AutoOrder>

Matrix<int,Dynamic,Dynamic,RowMajor> m = Matrix4i::Random();
cout << "Here is the matrix m:" << endl << m << endl;
cout << "Here is m.reshaped(2, 8):" << endl << m.reshaped(2, 8) << endl;
cout << "Here is m.reshaped<AutoOrder>(2, 8):" << endl << m.reshaped<AutoOrder>(2, 8) << endl;
m.resize(2,8);
cout << "Here is the matrix m after m.resize(2,8):" << endl << m << endl;

输出:

Here is the matrix m:
 7 -2  6  6
 9 -6 -3  6
-5  1  0  3
-3  0  9  9
Here is m.reshaped(2, 8):
 7 -5 -2  1  6  0  6  3
 9 -3 -6  0 -3  9  6  9
Here is m.reshaped<AutoOrder>(2, 8):
 7 -2  6  6  9 -6 -3  6
-5  1  0  3 -3  0  9  9
Here is the matrix m after m.resize(2,8):
 7 -2  6  6  9 -6 -3  6
-5  1  0  3 -3  0  9  9

最后,目前不支持将重塑矩阵分配给自身,并且会由于别名而导致未定义的行为。下面的行为是行不通的:

A = A.reshaped(2,8); 
A = A.reshaped(2,8)

但是下面的行为是可行的:

A = A.reshaped(2,8).eval(); 
A = A.reshaped(2,8).eval();

为什么加了 eval 就能跑了呢

STL 迭代器和算法 🔗

从 3.4 版本开始,Eigen 的稠密矩阵和数组提供了 STL 兼容的迭代器。如下所示,这使它们自然地与范围循环和 STL 的算法兼容。

遍历一维数组和向 🔗

任何密集的一维表达式都会公开一对 begin()/end() 方法来迭代它们。

VectorXi v = VectorXi::Random(4);
cout << "Here is the vector v:\n";
for(auto x : v) cout << x << " ";
cout << "\n";

输出:

Here is the vector v:
7 -2 6 6 

一维表达式也可以很容易地传递给 STL 算法:

Array4i v = Array4i::Random().abs();
cout << "Here is the initial vector v:\n" << v.transpose() << "\n";
std::sort(v.begin(), v.end());
cout << "Here is the sorted vector v:\n" << v.transpose() << "\n";
Here is the initial vector v:
7 2 6 6
Here is the sorted vector v:
2 6 6 7

std::vector 类似,一维表达式也公开了一对 cbegin()/cend() 方法,以方便地获取非 const 对象上的 const 迭代器。

迭代二维数组和矩阵的系数 🔗

STL 迭代器本质上设计用于迭代一维结构。这就是 2D 表达式禁用 begin()/end() 方法的原因。通过 reshaped() 创建一维线性视图,仍然可以轻松地迭代二维表达式的所有系数:

Matrix2i A = Matrix2i::Random();
cout << "Here are the coeffs of the 2x2 matrix A:\n";
for(auto x : A.reshaped())
  cout << x << " ";
cout << "\n";
Here are the coeffs of the 2x2 matrix A:
7 -2 6 6

迭代二维数组和矩阵的行或列 🔗

也可以在 2D 表达式的行或列上获取迭代器。这些可以通过 rowwise()colwise() 代理获得。这是对矩阵的每一行进行排序的示例:

ArrayXXi A = ArrayXXi::Random(4,4).abs();
cout << "Here is the initial matrix A:\n" << A << "\n";
for(auto row : A.rowwise())
  std::sort(row.begin(), row.end());
cout << "Here is the sorted matrix A:\n" << A << "\n";
Here is the initial matrix A:
7 9 5 3
2 6 1 0
6 3 0 9
6 6 3 9
Here is the sorted matrix A:
3 5 7 9
0 1 2 6
0 3 6 9
3 6 6 9

与原始缓冲区的接口:Map 类 🔗

此页面解释了如何使用“原始”C/C++ 数组。这在各种情况下都很有用,特别是在将向量和矩阵从其他库“导入”到 Eigen 中时。

介绍 🔗

有时,您可能有一个预定义的数字数组,您希望在 Eigen 中将其用作向量或矩阵。虽然一种选择是复制数据,但最常见的情况是您可能希望将此内存重新用作 Eigen 类型。幸运的是,使用 Map 类很容易做到这一点

映射类型和声明映射变量 🔗

Map 对象具有由其等效的 Eigen 定义的类型:

Map<Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime> >

请注意,在这种默认情况下,Map 只需要一个模板参数。

要构造一个 Map 变量,您需要另外两条信息:指向定义系数数组的内存区域的指针,以及矩阵或向量的所需形状。例如,要定义一个在编译时确定大小的浮点矩阵,您可以执行以下操作:

Map<MatrixXf> mf(pf,rows,columns);

其中,其中 pf 是一个 float * 指向内存数组。固定大小的只读整数向量可以声明为, 其中 pi 是一个 int *。在这种情况下,不必将大小传递给构造函数,因为它已经由 Matrix/Array 类型指定。

请注意,Map 没有默认构造函数;您必须传递一个指针来初始化对象。但是,您可以解决此要求(请参阅更改映射数组)。

Map 足够灵活以适应各种不同的数据表示。还有两个其他(可选)模板参数:

Map<typename MatrixType,int MapOptions,typename StrideType>
  • MapOptions 指定指针是对齐的还是未对齐的。默认为未对齐。
  • StrideType 允许您使用 Stride 类为内存阵列指定自定义布局。一个示例是指定数据数组以行优先格式组织:

例子:

int array[8];
for(int i = 0; i < 8; ++i) array[i] = i;
cout << "Column-major:\n" << Map<Matrix<int,2,4> >(array) << endl;
cout << "Row-major:\n" << Map<Matrix<int,2,4,RowMajor> >(array) << endl;
cout << "Row-major using stride:\n" <<
  Map<Matrix<int,2,4>, Unaligned, Stride<1,4> >(array) << endl;

输出:

Column-major:
0 2 4 6
1 3 5 7
Row-major:
0 1 2 3
4 5 6 7
Row-major using stride:
0 1 2 3
4 5 6 7

使用映射变量 🔗

您可以像使用任何其他 Eigen 类型一样使用 Map 对象:

例子:

typedef Matrix<float,1,Dynamic> MatrixType;
typedef Map<MatrixType> MapType;
typedef Map<const MatrixType> MapTypeConst;   // a read-only map
const int n_dims = 5;
  
MatrixType m1(n_dims), m2(n_dims);
m1.setRandom();
m2.setRandom();
float *p = &m2(0);  // get the address storing the data for m2
MapType m2map(p,m2.size());   // m2map shares data with m2
MapTypeConst m2mapconst(p,m2.size());  // a read-only accessor for m2
 
cout << "m1: " << m1 << endl;
cout << "m2: " << m2 << endl;
cout << "Squared euclidean distance: " << (m1-m2).squaredNorm() << endl;
cout << "Squared euclidean distance, using map: " <<
  (m1-m2map).squaredNorm() << endl;
m2map(3) = 7;   // this will change m2, since they share the same array
cout << "Updated m2: " << m2 << endl;
cout << "m2 coefficient 2, constant accessor: " << m2mapconst(2) << endl;
/* m2mapconst(2) = 5; */   // this yields a compile-time error

输出:

m1:   0.68 -0.211  0.566  0.597  0.823
m2: -0.605  -0.33  0.536 -0.444  0.108
Squared euclidean distance: 3.26
Squared euclidean distance, using map: 3.26
Updated m2: -0.605  -0.33  0.536      7  0.108
m2 coefficient 2, constant accessor: 0.536

所有 Eigen 函数都被编写为接受 Map 对象,就像其他 Eigen 类型一样。但是,当您编写自己的采用 Eigen 类型的函数时,这不会自动发生:Map 类型与其 Dense 等效类型不同。有关详细信息,请参阅编写以特征类型为参数的函数

更改映射数组 🔗

可以在声明后更改 Map 对象的数组,使用 C++“placement new”语法:

int data[] = {1,2,3,4,5,6,7,8,9};
Map<RowVectorXi> v(data,4);
cout << "映射向量v是:" << v << "\n";
new (&v) Map<RowVectorXi>(data+4,5);
cout << "Now v is: " << v << "\n";
cout << "现在v是:" << v << "\n";

不管表面如何,这并没有调用内存分配器,因为语法指定了存储结果的位置。

这种语法可以在不知道映射数组在内存中的位置的情况下声明一个 Map 对象:

Map<Matrix3f> A(NULL);  // don't try to use this matrix yet!
VectorXf b(n_matrices);
for (int i = 0; i < n_matrices; i++)
{
  new (&A) Map<Matrix3f>(get_matrix_pointer(i));
  b(i) = A.trace();
}

Aliasing 别名 🔗