[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 文档

相关文章
|
存储 编译器 C语言
[Eigen中文文档] 对未对齐数组断言的解释
本文将解释程序因断言失败而终止的问题。
183 0
|
4月前
|
JSON C语言 数据格式
Python导出隐马尔科夫模型参数到JSON文件C语言读取
Python导出隐马尔科夫模型参数到JSON文件C语言读取
30 1
|
5月前
|
Python
python中定义函数时使用位置参数
【7月更文挑战第25天】
72 7
|
7月前
|
机器学习/深度学习 数据采集 算法
python中利用相关特征填充
python中利用相关特征填充
58 1
|
7月前
|
JavaScript
函数形状有几种定义方式;操作符infer的作用
函数形状有几种定义方式;操作符infer的作用
49 3
|
存储 编译器
[Eigen中文文档] 编写以特征类型为参数的函数(二)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
91 0
[Eigen中文文档] 固定大小的可向量化Eigen对象
本文主要解释 固定大小可向量化 的含义。
120 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 来完成这项工作。
212 0
[Eigen中文文档] 高级初始化
本文介绍了几种用于初始化矩阵的高级方法。提供了有关之前介绍的逗号初始化程序的更多详细信息。还解释了如何获得特殊矩阵,例如单位矩阵和零矩阵。
160 0
线性回归 特征扩展的原理与python代码的实现
在线性回归中,多项式扩展是种比较常见的技术,可以通过增加特征的数量和多项式项的次数来提高模型的拟合能力。 举个例子,多项式扩展可以将一个包含 n 个特征的样本向量 x 扩展为一个包含 k 个特征的样本向量,其中 k 可以是 n 的任意多项式。例如,如果我们使用二次多项式扩展,可以将样本向量[x1, x2]扩展为一个包含原始特征和交叉项的新特征向量,例如 [x1, x2, x1^2, x2^2, x1*x2]。这些新特征可以捕捉到更丰富的特征组合和非线性关系,从而提高模型的拟合能力。