【问题标题】:Switch between dependencies在依赖项之间切换
【发布时间】:2019-12-04 23:54:19
【问题描述】:

我有一个通过有限元法求解微分方程的代码。为此,我使用 Eigen,因此我不必自己实现矩阵。现在,在未来,我想在另一个库中尝试相同的代码,比如犰狳。我希望能够在库之间轻松切换。

我是新手,我的问题基本上是以下是否是好的做法。为了使我的代码适用于任何矩阵库,我在标题中定义了 DataTypeMatrixImplementation 类型。后者应该决定是使用一个库还是另一个。

// data type
using DataType = double;

// matrix implementation
template <typename TYPE = DataType, size_t ROWS = 1, size_t COLS = 1>
using MatrixImplementation = Eigen::Matrix<DataType, ROWS, COLS>;

然后我可以检查 Eigen 是否是这样使用的:

// check which matrix implementation
constexpr static bool is_eigen = std::is_same<MatrixImplementation<DataType, 1, 1>,Eigen::Matrix<DataType, 1, 1>>::value;

我现在可以定义IndexType(通常是size_tunsigned int)来访问向量或矩阵的元素,如下所示:

// index type
using IndexType = std::conditional_t<is_eigen, Eigen::Index,
                  std::conditional_t<is_armadillo, ..., 
                  std::conditional_t<is_***, ..., 
                  ...>;

同样,我可以如下定义Matrix 类型:

// matrix type
template <size_t ROWS, size_t COLS>
using Matrix = std::conditional_t<is_eigen, Eigen::Matrix<DataType, ROWS, COLS>, ...>;

等等。此外,我必须定义矩阵上的操作(仅我需要的那些),例如范数:

template <class Matrix>
constexpr auto norm(const Matrix& m)
{
    if constexpr (is_eigen)
        return m.norm();
    else if constexpr (is_armadillo)
        return ...;
    else
        return 0.0;
}

然后我会按如下方式计算范数:

Matrix<3,3> my_matrix;
auto norm_matrix = norm(my_matrix);

这是要走的路吗?还是有更好的选择,也许我应该考虑一些花哨的设计模式?

更新

感谢@MaxLanghof 的评论:

当您可能只在以下位置使用 Eigen 时,您将如何实现 is_armadillo 一点?您是否计划始终包含所有库?

我现在让 CMake 将要使用的矩阵库写入 config.h 文件:

#define MATRIX_LIBRARY EIGEN // this value is written by cmake
#define EIGEN 1
#define ARMADILLO 2

现在,我可以确定代码是使用 Eigen 还是 Armadillo,如下所示:

#if MATRIX_LIBRARY == EIGEN
constexpr static bool is_eigen = true;
#else
constexpr static bool is_eigen = false;
#endif
#if MATRIX_LIBRARY == ARMADILLO
constexpr static bool is_armadillo = true;
#else
constexpr static bool is_armadillo = false;
#endif

【问题讨论】:

  • 当您在某些时候可能只使用Eigen 时,您将如何实现is_armadillo?您是否计划始终包含所有库?
  • 在任何情况下,Eigenarmadillo 都是具有非平凡接口的大型复杂库(可能与任何其他著名的 C++ 矩阵库相同)。核心矩阵类型可能具有足够相似的接口,可以或多或少地将它们换掉,但是(正如您已经注意到的norm),其他功能会有很大不同。并且差异可能不仅仅是表面上的——也许一个库就地执行 SVD,另一个库没有。您必须自己明智地包装所有此类功能。这不是一项小任务,我不确定它是否适用于更大的项目。
  • @MaxLanghof 好点...如果我想使用 Eigen 包含犰狳会很愚蠢...然后我将无法实现 is_armadillo...除非我对其进行硬编码,或者让 cmake 在哪个库可用的函数中为我生成该变量。
  • 您可能想查看预处理器标志(您必须用一些#ifdef 包装MatrixImplementation 的定义)。然后,您可以通过项目的 CMake 配置设置所需的标志,例如 -DMYPROJECT_MATRIX_LIB=eigen/armadillo/adhoc。如果您将 #ifdefs 放在关键定义和依赖项周围 - 您将获得模块化行为。支持这个虽然很痛苦......

标签: c++ dependencies


【解决方案1】:

我对这两个库都不熟悉,但它们可能没有实现通用接口。另一种方法是编写一个包装类,它只是对您在 Eigen 中使用的方法的传递,但要实现一个接口。然后,如果您尝试一个新库,请编写一个实现相同接口的新包装类。然后您可以选择要实例化的包装器,其余代码保持不变。

如果您不熟悉 C++ 中的接口——它只是一个虚拟基类。你可以搜索“C++ 接口”并获得一些不错的文章。

我会这样做的。

【讨论】:

  • 但是,如果我理解你的意思,那么每次调用 norm 或任何函数都会有间接性。我不想那样做。在 FEM 中,您可以拥有数百万个元素,我需要它快速运行。
  • 大多数矩阵库的接口很大,在很多情况下甚至在语法层面上都不兼容。包装它不仅仅是“虚拟基类”。而且至少Eigen 是大量模板化的,不能很好地与虚拟方法混合。
【解决方案2】:

Eigenarmadillo 都是具有非平凡接口的大型复杂库(可能与任何其他著名的 C++ 矩阵库相同)。大概每个都有自己的缺陷和特性,除了基本语法之外,它们肯定会有很多不兼容的地方。

所以在深入研究如何做到这一点之前,我首先要深入研究一下这是否可行。

  • 您是否想编写几百行代码,主要用于处理普通矩阵(比如乘法),在任何 C++ 矩阵库中都应该或多或少地工作相同?听起来可行。

  • 您是否计划在这个项目中包含数千行代码,这些代码与专门的矩阵功能(切片?分解?重新排序?您可能需要稀疏矩阵/算法?)进行深度交互?所有这些功能甚至都存在于每个库中吗?他们甚至远程使用相同的数据结构吗?语法变化很大吗?函数是否采用完全不同的参数?在这种情况下,我预见到你的未来会很痛苦。

我首先建议关闭您的支持库集。尽早定义要支持的库。然后迭代求解器算法的概念部分,并验证所有库中是否存在您需要的一切。如果您希望您的代码与所有这些一起工作,则必须针对所有这些进行开发。您最初可以使用预处理器开关来执行此操作,例如

#ifdef USE_EIGEN
  // Do the matrix decomposition in one way.
#else
  // Do the matrix decomposition in some other way.
#endif

然后将这些块重构为(本质上)包装函数或类。

提前熟悉不同的库也是值得的(与上述所有内容相关)。您不想在项目进行到一半时发现库 X 具有稀疏矩阵但没有有意义的算法来实际操作它们!

您展示的代码 sn-ps 看起来非常合理,除了您可能需要用某些部分替换预处理器标志。但与您最有可能面临的挑战相比(并且考虑到您通常对类型别名和模板感到满意),我认为这些事情不是您现在应该担心的......

【讨论】:

  • 密集矩阵需要很少的东西(行列式、转置、初始化,...,基本的东西)。对于稀疏矩阵,只需编写并调用求解器。我想我不必实施太多。我以为你会提出一种完全不同的方式来做到这一点。看来我没有走错方向……
猜你喜欢
  • 2015-12-13
  • 2017-04-08
  • 1970-01-01
  • 2019-07-01
  • 2021-08-31
  • 2017-04-08
  • 2020-04-26
  • 1970-01-01
  • 2021-03-03
相关资源
最近更新 更多