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

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

文档总目录

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

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

采用普通矩阵或数组参数的函数在哪些情况下有效?

如果不使用模板函数,并且没有 Ref 类,先前 cov 函数的简单实现可能如下所示:

MatrixXf cov(const MatrixXf& x, const MatrixXf& y)
{
   
  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;
  return (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}

与一开始想的相反,除非需要一个可以与双矩阵一起使用的通用实现,否则这个实现是可以的,除非不关心临时对象。为什么会这样?哪些地方涉及到临时变量?下面给出的代码是如何编译的?

MatrixXf x,y,z;
MatrixXf C = cov(x,y+z);

在这种特殊情况下,示例是可以的并且能够工作,因为两个参数都声明为const引用。编译器会创建一个临时变量,将表达式x+z计算到这个临时变量中。一旦函数运行完成,临时变量就会被释放,并将结果分配给C

注意:对 Matrix(或 Array)进行 const 引用的函数可以以临时变量为代价来处理表达式。

在哪些情况下,采用普通矩阵或数组参数的函数会失败?

在这里,我们考虑上面给出的函数的稍微修改版。这一次,我们不想返回结果,而是传递一个额外的非常量参数,以便我们可以存储结果。一个第一次的朴素实现可能如下所示:

// Note: This code is flawed!
void cov(const MatrixXf& x, const MatrixXf& y, 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;
}

当尝试执行以下代码时:

MatrixXf C = MatrixXf::Zero(3,6);
cov(x,y, C.block(0,0,3,3));

编译器会失败,因为将 MatrixXf::block() 返回的表达式转换为非常量 MatrixXf& 是不可能的。这是因为编译器希望避免将结果写入临时对象的风险。在这种特殊情况下,这种保护并不适用 - 如果想要写入临时对象。那么我们如何解决这个问题呢?

目前首选的解决方案是基于一点技巧。需要将常量引用传递给矩阵,并且需要在内部丢弃常量。 C98 兼容编译器的正确实现是:

template <typename Derived, typename OtherDerived>
void cov(const MatrixBase<Derived>& x, const MatrixBase<Derived>& y, MatrixBase<OtherDerived> const & C)
{
   
  typedef typename Derived::Scalar Scalar;
  typedef typename internal::plain_row_type<Derived>::type RowVectorType;

  const Scalar num_observations = static_cast<Scalar>(x.rows());

  const RowVectorType x_mean = x.colwise().sum() / num_observations;
  const RowVectorType y_mean = y.colwise().sum() / num_observations;

  const_cast< MatrixBase<OtherDerived>& >(C) =
    (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}

上面的实现现在不仅可以使用临时表达式,而且还允许使用具有任意浮点标量类型的矩阵的函数。

注意:const cast 仅适用于模板化函数。它不适用于 MatrixXf实现,因为无法将 Block 表达式转换为 Matrix引用!

如何在通用实现中调整矩阵大小?

为了使协方差函数具有普遍适用性,有如下代码:

MatrixXf x = MatrixXf::Random(100,3);
MatrixXf y = MatrixXf::Random(100,3);
MatrixXf C;
cov(x, y, C);

当我们使用以 MatrixBase 为参数的实现时,情况就不同了。一般来说,Eigen 支持自动调整大小,但不能在表达式上这样做。为什么要允许重调矩阵块大小?它是一个子矩阵的引用,我们绝对不想重调它的大小。那么,如果我们不能在 MatrixBase 上调整大小,如何合并调整大小呢?解决方法是调整派生对象的大小,如此实现:

template <typename Derived, typename OtherDerived>
void cov(const MatrixBase<Derived>& x, const MatrixBase<Derived>& y, MatrixBase<OtherDerived> const & C_)
{
   
  typedef typename Derived::Scalar Scalar;
  typedef typename internal::plain_row_type<Derived>::type RowVectorType;

  const Scalar num_observations = static_cast<Scalar>(x.rows());

  const RowVectorType x_mean = x.colwise().sum() / num_observations;
  const RowVectorType y_mean = y.colwise().sum() / num_observations;

  MatrixBase<OtherDerived>& C = const_cast< MatrixBase<OtherDerived>& >(C_);

  C.derived().resize(x.cols(),x.cols()); // resize the derived object
  C = (x.rowwise() - x_mean).transpose() * (y.rowwise() - y_mean) / num_observations;
}

这个实现现在可以处理参数为表达式和参数为矩阵且尺寸大小不正确的情况。在这种情况下,调整表达式的大小不会有任何问题,除非它们确实需要调整。这意味着,传递一个尺寸不正确的表达式将导致运行时错误(仅在调试模式下),而传递尺寸正确的表达式将正常工作。

注意:在上面的讨论中,术语 MatrixArray 以及 MatrixBaseArrayBase 可以互换,并且所有参数仍然有效。

总结

  • 总之,采用不可写(const 引用)对象作为函数参数的实现并不是一个大问题,也不会在编译和运行程序方面导致问题。然而,一个简单的实现可能会在代码中引入不必要的临时对象。为了避免将参数转换为临时对象,将它们作为(const)引用传递给 MatrixBaseArrayBase(因此将函数模板化)。
  • 需要接受可写的(非 const)参数的函数必须采用 const 引用,并在函数体内强制转换 const 属性。
  • 接受 MatrixBase(或 ArrayBase)对象作为参数的函数,如果这些对象可以调整大小(即重置大小),则必须在派生类上调用 resize()函数,由 derived() 返回。
相关文章
|
5月前
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch基础之模型保存与重载模块、可视化模块讲解(附源码)
PyTorch基础之模型保存与重载模块、可视化模块讲解(附源码)
58 1
|
8月前
|
存储 编译器 C语言
[Eigen中文文档] 对未对齐数组断言的解释
本文将解释程序因断言失败而终止的问题。
74 0
|
8月前
|
C++
[Eigen中文文档] 按值将Eigen对象传递给函数
对于 Eigen,这一点更为重要:按值传递固定大小的可向量化 Eigen 对象不仅效率低下,而且可能是非法的或使程序崩溃! 原因是这些 Eigen 对象具有对齐修饰符,在按值传递时会不遵守这些修饰符。
72 0
|
8月前
[Eigen中文文档] 编写以特征类型为参数的函数(一)
Eigen使用表达式模板的方式导致每个表达式的类型可能都不同。如果将这样的表达式传递给一个需要Matrix类型参数的函数,则表达式将隐式地被评估为一个临时Matrix,然后再传递给函数。这意味着失去了表达式模板的好处。
81 0
MindOpt APL建模语言自定小义函数的重要性和示例
在编程和建模语言中,函数是一段独立的、可重复使用的代码块,用于执行特定任务。在MindOpt APL中,自定义函数的使用非常重要,因为它们提高了建模过程的效率、可读性和灵活性。
|
9月前
|
数据采集 Python 数据可视化
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](三)
[Python] 数据预处理(缺失值、异常值、重复值) [相关方法参数说明、代码示例、相关概念](三)
|
5月前
|
机器学习/深度学习 算法 PyTorch
PyTorch 的 10 条内部用法
PyTorch 的 10 条内部用法
46 0
|
8月前
[Eigen中文文档] 固定大小的可向量化Eigen对象
本文主要解释 固定大小可向量化 的含义。
52 0
|
8月前
|
存储 编译器 对象存储
[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 来完成这项工作。
122 0
|
8月前
|
测试技术 API C++
[Eigen中文文档] 扩展/自定义Eigen(一)
在本节中,将介绍如何向MatrixBase添加自定义方法。由于所有表达式和矩阵类型都继承自MatrixBase,因此将方法添加到MatrixBase会立即使其对所有表达式可用!一个典型的用例是,使Eigen与另一个API兼容。
111 0