> For the complete documentation index, see [llms.txt](https://chenxiaowei.gitbook.io/cmake-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://chenxiaowei.gitbook.io/cmake-cookbook/3.0-chinese/3.7-chinese.md).

# 3.7 检测Eigen库

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-07> *中找到，包含一个C++的示例。该示例在CMake 3.9版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*<https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-03/recipe-06> *中也有一个适用于CMake 3.5的C++示例。*

BLAS库为矩阵和向量操作提供了标准化接口。不过，这个接口用Fortran语言书写。虽然已经展示了如何使用C++直接使用这些库，但在现代C++程序中，希望有更高级的接口。

纯头文件实现的Eigen库，使用模板编程来提供接口。矩阵和向量的计算，会在编译时进行数据类型检查，以确保兼容所有维度的矩阵。密集和稀疏矩阵的运算，也可使用表达式模板高效的进行实现，如：矩阵-矩阵乘积，线性系统求解器和特征值问题。从3.3版开始，Eigen可以链接到BLAS和LAPACK库中，这可以将某些操作实现进行卸载，使库的实现更加灵活，从而获得更多的性能收益。

本示例将展示如何查找Eigen库，使用OpenMP并行化，并将部分工作转移到BLAS库。

本示例中会实现，矩阵-向量乘法和\[LU分解]\([https://zh.wikipedia.org/wiki/LU%E5%88%86%E8%A7%A3](https://zh.wikipedia.org/wiki/LU分解))，可以选择卸载BLAS和LAPACK库中的一些实现。这个示例中，只考虑将在BLAS库中卸载。

## 准备工作

本例中，我们编译一个程序，该程序会从命令行获取的随机方阵和维向量。然后我们将用LU分解来解线性方程组**Ax=b**。以下是源代码(`linear-algebra.cpp`):

```cpp
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <vector>

#include <Eigen/Dense>

int main(int argc, char **argv)
{
  if (argc != 2)
  {
    std::cout << "Usage: ./linear-algebra dim" << std::endl;
    return EXIT_FAILURE;
  }
  std::chrono::time_point<std::chrono::system_clock> start, end;
  std::chrono::duration<double> elapsed_seconds;
  std::time_t end_time;
  std::cout << "Number of threads used by Eigen: " << Eigen::nbThreads()
            << std::endl;

  // Allocate matrices and right-hand side vector
  start = std::chrono::system_clock::now();
  int dim = std::atoi(argv[1]);
  Eigen::MatrixXd A = Eigen::MatrixXd::Random(dim, dim);
  Eigen::VectorXd b = Eigen::VectorXd::Random(dim);
  end = std::chrono::system_clock::now();

  // Report times
  elapsed_seconds = end - start;
  end_time = std::chrono::system_clock::to_time_t(end);
  std::cout << "matrices allocated and initialized "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y
  %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";

  start = std::chrono::system_clock::now();
  // Save matrix and RHS
  Eigen::MatrixXd A1 = A;
  Eigen::VectorXd b1 = b;
  end = std::chrono::system_clock::now();
  end_time = std::chrono::system_clock::to_time_t(end);
  std::cout << "Scaling done, A and b saved "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";
  start = std::chrono::system_clock::now();
  Eigen::VectorXd x = A.lu().solve(b);
  end = std::chrono::system_clock::now();

  // Report times
  elapsed_seconds = end - start;
  end_time = std::chrono::system_clock::to_time_t(end);
  double relative_error = (A * x - b).norm() / b.norm();
  std::cout << "Linear system solver done "
            << std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
            << "elapsed time: " << elapsed_seconds.count() << "s\n";
  std::cout << "relative error is " << relative_error << std::endl;

  return 0;
}
```

矩阵-向量乘法和LU分解是在Eigen库中实现的，但是可以选择BLAS和LAPACK库中的实现。在这个示例中，我们只考虑BLAS库中的实现。

## 具体实施

这个示例中，我们将用到Eigen和BLAS库，以及OpenMP。使用OpenMP将Eigen并行化，并从BLAS库中卸载部分线性代数实现:

1. 首先声明CMake最低版本、项目名称和使用C++11语言标准:

   ```
   cmake_minimum_required(VERSION 3.9 FATAL_ERROR)

   project(recipe-07 LANGUAGES CXX)

   set(CMAKE_CXX_STANDARD 11)
   set(CMAKE_CXX_EXTENSIONS OFF)
   set(CMAKE_CXX_STANDARD_REQUIRED ON)
   ```
2. 因为Eigen可以使用共享内存的方式，所以可以使用OpenMP并行处理计算密集型操作:

   ```
   find_package(OpenMP REQUIRED)
   ```
3. 调用`find_package`来搜索Eigen(将在下一小节中讨论):

   ```
   find_package(Eigen3 3.3 REQUIRED CONFIG)
   ```
4. 如果找到Eigen，我们将打印状态信息。注意，使用的是`Eigen3::Eigen`，这是一个`IMPORT`目标，可通过提供的CMake脚本找到这个目标:

   ```
   if(TARGET Eigen3::Eigen)
     message(STATUS "Eigen3 v${EIGEN3_VERSION_STRING} found in ${EIGEN3_INCLUDE_DIR}")
   endif()
   ```
5. 接下来，将源文件声明为可执行目标:

   ```
   add_executable(linear-algebra linear-algebra.cpp)
   ```
6. 然后，找到BLAS。注意，现在不需要依赖项:

   ```
   find_package(BLAS)
   ```
7. 如果找到BLAS，我们可为可执行目标，设置相应的宏定义和链接库:

   ```
   if(BLAS_FOUND)
     message(STATUS "Eigen will use some subroutines from BLAS.")
     message(STATUS "See: http://eigen.tuxfamily.org/dox-devel/TopicUsingBlasLapack.html")
     target_compile_definitions(linear-algebra
       PRIVATE
           EIGEN_USE_BLAS
       )
     target_link_libraries(linear-algebra
       PUBLIC
           ${BLAS_LIBRARIES}
       )
   else()
       message(STATUS "BLAS not found. Using Eigen own functions")
   endif()
   ```
8. 最后，我们链接到`Eigen3::Eigen`和`OpenMP::OpenMP_CXX`目标。这就可以设置所有必要的编译标示和链接标志:

   ```
   target_link_libraries(linear-algebra
     PUBLIC
       Eigen3::Eigen
       OpenMP::OpenMP_CXX
     )
   ```
9. 开始配置:

   ```
   $ mkdir -p build
   $ cd build
   $ cmake ..

   -- ...
   -- Found OpenMP_CXX: -fopenmp (found version "4.5")
   -- Found OpenMP: TRUE (found version "4.5")
   -- Eigen3 v3.3.4 found in /usr/include/eigen3
   -- ...
   -- Found BLAS: /usr/lib/libblas.so
   -- Eigen will use some subroutines from BLAS.
   -- See: http://eigen.tuxfamily.org/dox-devel/TopicUsingBlasLapack.html
   ```
10. 最后，编译并测试代码。注意，可执行文件使用四个线程运行:

    ```
    $ cmake --build .
    $ ./linear-algebra 1000

    Number of threads used by Eigen: 4
    matrices allocated and initialized Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.0492328s
    Scaling done, A and b saved Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.0492328s
    Linear system solver done Sun Jun 17 2018 11:04:20 AM
    elapsed time: 0.483142s
    relative error is 4.21946e-13
    ```

## 工作原理

Eigen支持CMake查找，这样配置项目就会变得很容易。从3.3版开始，Eigen提供了CMake模块，这些模块将导出相应的目标`Eigen3::Eigen`。

`find_package`可以通过选项传递，届时CMake将不会使用`FindEigen3.cmake`模块，而是通过特定的`Eigen3Config.cmake`，`Eigen3ConfigVersion.cmake`和`Eigen3Targets.cmake`提供Eigen3安装的标准位置(`<installation-prefix>/share/eigen3/cmake`)。这种包定位模式称为“Config”模式，比`Find<package>.cmake`方式更加通用。有关“模块”模式和“配置”模式的更多信息，可参考官方文档 <https://cmake.org/cmake/help/v3.5/command/find_package.html> 。

虽然Eigen3、BLAS和OpenMP声明为`PUBLIC`依赖项，但`EIGEN_USE_BLAS`编译定义声明为`PRIVATE`。可以在单独的库目标中汇集库依赖项，而不是直接链接可执行文件。使用`PUBLIC/PRIVATE`关键字，可以根据库目标的依赖关系调整相应标志和定义。

## 更多信息

CMake将在预定义的位置层次结构中查找配置模块。首先是`CMAKE_PREFIX_PATH`，`<package>_DIR`是接下来的搜索路径。因此，如果Eigen3安装在非标准位置，可以使用这两个选项来告诉CMake在哪里查找它:

1. 通过将Eigen3的安装前缀传递给`CMAKE_PREFIX_PATH`:

   ```
   $ cmake -D CMAKE_PREFIX_PATH=<installation-prefix> ..
   ```
2. 通过传递配置文件的位置作为`Eigen3_DIR`:

   ```
   $ cmake -D Eigen3_DIR=<installation-prefix>/share/eigen3/cmake ..
   ```
