# 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 ..
   ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/3.0-chinese/3.7-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
