[Eigen中文文档] 编写以特征类型为参数的函数(一)

简介: Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。

文档总目录

[Eigen中文文档] 编写以特征类型为参数的函数(二)

英文原文(Writing Functions Taking Eigen Types as Parameters)

Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。具体而言,这有两个缺点:

  • 将表达式评估为临时变量可能是无用且低效的;
  • 这会限制函数只能读取表达式,无法对表达式进行修改。

幸运的是,所有这些表达式类型都有一些共同的基类和模板。通过让函数接受这些基类的模板参数,可以让它们与Eigen的表达式模板很好地协作。

一些开始的示例

本节将为 Eigen 提供的不同类型的对象提供简单的示例。在开始实际示例之前,我们需要概括一下可以使用哪些基础对象(另请参阅类层次结构)。

  • MatrixBase:所有稠密矩阵表达式的公共基类(与数组表达式相对,与稀疏和特殊矩阵类相对)。在仅适用于稠密矩阵的函数中使用它。
  • ArrayBase:所有稠密数组表达式(与矩阵表达式相对)的公共基类。在仅适用于数组的函数中使用它。
  • DenseBase:所有稠密矩阵表达式的公共基类,即MatrixBaseArrayBase的基类。它可以用在同时适用于矩阵和数组的函数中。
  • EigenBase:该基类统一了可以被计算为稠密矩阵或数组的所有对象类型,例如对角矩阵、置换矩阵等特殊的矩阵类。它可以用于处理任何此类通用类型的函数中。

EigenBase示例

打印 Eigen 中存在的最通用对象的尺寸。它可以是任何矩阵表达式、任何稠密或稀疏矩阵以及任何数组。

#include <iostream>
#include <Eigen/Core>

template <typename Derived>
void print_size(const Eigen::EigenBase<Derived>& b)
{
   
  std::cout << "size (rows, cols): " << b.size() << " (" << b.rows()
            << ", " << b.cols() << ")" << std::endl;
}

int main()
{
   
    Eigen::Vector3f v;
    print_size(v);
    // v.asDiagonal() returns a 3x3 diagonal matrix pseudo-expression
    print_size(v.asDiagonal());
}

输出:

size (rows, cols): 3 (3, 1)
size (rows, cols): 9 (3, 3

DenseBase示例

打印稠密表达式的子块。接受任何稠密矩阵或数组表达式,但不接受稀疏对象,也不接受特殊矩阵类(例如 DiagonalMatrix)。

template <typename Derived>
void print_block(const DenseBase<Derived>& b, int x, int y, int r, int c)
{
   
    std::cout << "block: " << b.block(x,y,r,c) << std::endl;
}

ArrayBase示例

打印数组或数组表达式的最大系数。

template <typename Derived>
void print_max_coeff(const ArrayBase<Derived> &a)
{
   
    std::cout << "max: " << a.maxCoeff() << std::endl;
}

MatrixBase示例

打印给定矩阵或矩阵表达式的逆条件数。

template <typename Derived>
void print_inv_cond(const MatrixBase<Derived>& a)
{
   
    const typename JacobiSVD<typename Derived::PlainObject>::SingularValuesType& sing_vals = a.jacobiSvd().singularValues();
    std::cout << "inv cond: " << sing_vals(sing_vals.size()-1) / sing_vals(0) << std::endl;
}

多个模板化参数示例

计算两点之间的欧几里德距离。

template <typename DerivedA,typename DerivedB>
typename DerivedA::Scalar squaredist(const MatrixBase<DerivedA>& p1,const MatrixBase<DerivedB>& p2)
{
   
    return (p1-p2).squaredNorm();
}

请注意,我们使用了两个模板参数,每个参数一个。这允许函数处理不同类型的输入,例如:

squaredist(v1,2*v2)

其中第一个参数 v1 是向量,第二个参数 2*v2 是表达式。

这些示例只是为了让读者对如何编写接受普通和常量Matrix或Array参数的函数有第一印象。它们还旨在为读者提供有关最常见基类是作为函数的最佳候选的想法。在下一节中,将更详细地说明一个示例以及可以实现它的不同方式,同时讨论每个实现的问题和优点。在下面的讨论中,MatrixArray以及MatrixBaseArrayBase可以互换使用,所有参数仍然保持不变。

如何编写通用但非模板化的函数?

在所有以前的示例中,函数都必须是模板函数。这种方法允许编写非常通用的代码,但通常希望编写非模板函数并仍然保持一定程度的通用性,以避免对参数进行愚蠢的复制。典型的例子是编写接受MatrixXfMatrixXf块的函数。这正是Ref类的目的。这里是一个简单的例子:

#include <iostream>
#include <Eigen/SVD>

float inv_cond(const Eigen::Ref<const Eigen::MatrixXf>& a)
{
   
  const Eigen::VectorXf sing_vals = a.jacobiSvd().singularValues();
  return sing_vals(sing_vals.size()-1) / sing_vals(0);
}

int main()
{
   
  Eigen::MatrixXf m = Eigen::MatrixXf::Random(4, 4);
  std::cout << "matrix m:\n" << m << "\n\n";
  std::cout << "inv_cond(m):          " << inv_cond(m)                      << "\n";
  std::cout << "inv_cond(m(1:3,1:3)): " << inv_cond(m.topLeftCorner(3,3))   << "\n";
  std::cout << "inv_cond(m+I):        " << inv_cond(m+Eigen::MatrixXf::Identity(4, 4)) << "\n";
}

输出:

matrix m:
   0.68   0.823  -0.444   -0.27
 -0.211  -0.605   0.108  0.0268
  0.566   -0.33 -0.0452   0.904
  0.597   0.536   0.258   0.832

inv_cond(m):          0.0562343
inv_cond(m(1:3,1:3)): 0.0836819
inv_cond(m+I):        0.160204

在对inv_cond的前两个调用中,不会发生复制,因为参数的内存布局与Ref <MatrixXf>接受的内存布局匹配。然而,在最后一次调用中,我们有一个通用表达式,将通过Ref <>对象自动评估为一个临时的MatrixXf

Ref对象也可以是可写的。这是一个计算两个输入矩阵的协方差矩阵的函数示例,其中每行都是一个观测值:

void cov(const Ref<const MatrixXf> x, const Ref<const MatrixXf> y, Ref<MatrixXf> C)
{
   
  const float num_observations = static_cast<float>(x.rows());
  const RowVectorXf x_mean = x.colwise().sum() / num_observations;
  const RowVectorXf y_mean = y.colwise().sum() / num_observations;
  C = (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}

下面是两个不带任何副本调用 cov 的示例:

MatrixXf m1, m2, m3
cov(m1, m2, m3);
cov(m1.leftCols<3>(), m2.leftCols<3>(), m3.topLeftCorner<3,3>());

Ref<> 类还有另外两个可选模板参数,允许控制无需任何副本即可接受的内存布局类型。有关详细信息,请参阅类 Ref 文档

相关文章
|
SQL 负载均衡 数据可视化
第六章:参数和变量
第六章:参数和变量
516 1
|
存储 编译器 C语言
[Eigen中文文档] 对未对齐数组断言的解释
本文将解释程序因断言失败而终止的问题。
159 0
|
C++
[Eigen中文文档] 按值将Eigen对象传递给函数
对于 Eigen,这一点更为重要:按值传递固定大小的可向量化 Eigen 对象不仅效率低下,而且可能是非法的或使程序崩溃! 原因是这些 Eigen 对象具有对齐修饰符,在按值传递时会不遵守这些修饰符。
162 0
|
存储 索引
[Eigen中文文档] 扩展/自定义Eigen(三)
本页面针对非常高级的用户,他们不害怕处理一些Eigen的内部细节。在大多数情况下,可以通过使用自定义一元或二元函数避免使用自定义表达式,而极其复杂的矩阵操作可以通过零元函数(nullary-expressions)来实现,如前一页所述。 本页面通过示例介绍了如何在Eigen中实现新的轻量级表达式类型。它由三个部分组成:表达式类型本身、包含有关表达式编译时信息的特性类和评估器类,用于将表达式评估为矩阵。
134 1
|
存储 编译器
[Eigen中文文档] 编写以特征类型为参数的函数(二)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
85 0
|
存储 编译器 对象存储
[Eigen中文文档] 包含Eigen对象的结构体
如果定义的结构体包含固定大小的可向量化 Eigen 类型成员,则必须确保对其调用 operator new 来分配正确的对齐缓冲区。如果仅使用足够新的编译器(例如,GCC>=7、clang>=5、MSVC>=19.12)以 [c++17] 模式编译,那么编译器会自动处理所有事情,可以跳过本节。 否则,必须重载它的 operator new 以便它生成正确对齐的指针(例如,Vector4d 和 AVX 的 32 字节对齐)。幸运的是,Eigen 为提供了一个宏 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 来完成这项工作。
202 0
[Eigen中文文档] 固定大小的可向量化Eigen对象
本文主要解释 固定大小可向量化 的含义。
110 0
[Eigen中文文档] 高级初始化
本文介绍了几种用于初始化矩阵的高级方法。提供了有关之前介绍的逗号初始化程序的更多详细信息。还解释了如何获得特殊矩阵,例如单位矩阵和零矩阵。
146 0
|
测试技术 API C++
[Eigen中文文档] 扩展/自定义Eigen(一)
在本节中,将介绍如何向MatrixBase添加自定义方法。由于所有表达式和矩阵类型都继承自MatrixBase,因此将方法添加到MatrixBase会立即使其对所有表达式可用!一个典型的用例是,使Eigen与另一个API兼容。
265 0
|
存储 对象存储 索引
[Eigen中文文档] 扩展/自定义Eigen(二)
CwiseNullaryOp 类的主要目的是定义过程矩阵,例如由Ones()、Zero()、Constant()、Identity()和Random()方法返回的常量或随机矩阵。然而,通过一些想象力,可以用最小的努力实现非常复杂的矩阵操作,因此很少需要实现新的表达式。
114 0