Skip to content

Commit

Permalink
[cmake] Export a convenience macro find_arpackng() and update usage…
Browse files Browse the repository at this point in the history
… documentation

This macro finds a working installation of ARPACK-NG while dealing with
version-to-version differences of ARPACK-NG's CMake interface.
  • Loading branch information
krivenko committed Sep 3, 2022
1 parent 1463313 commit 232cb37
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 50 deletions.
52 changes: 34 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,28 +107,29 @@ cmake_minimum_required(VERSION 3.13.0 FATAL_ERROR)
project(myproject LANGUAGES CXX)
# ARPACK_NG_ROOT and EZARPACK_ROOT are installation prefixes of
# ARPACK-NG and ezARPACK respectively
set(arpack-ng_DIR ${ARPACK_NG_ROOT}/lib/cmake)
set(ezARPACK_DIR ${EZARPACK_ROOT}/lib/cmake)
# Import ARPACK-NG targets
find_package(arpack-ng 3.6.0 REQUIRED)
# Import ezARPACK targets
# ezARPACK_ROOT is the installation prefix of ezARPACK.
set(ezARPACK_DIR ${ezARPACK_ROOT}/lib/cmake)
# Import ezARPACK target.
find_package(ezARPACK 1.0 CONFIG REQUIRED)
# Import Eigen (Blaze, Armadillo, etc) targets
find_package(Eigen3 CONFIG)
# Build an executable called 'test'
add_executable(test test.cpp)
# Import Eigen (Blaze, Armadillo, etc) targets.
find_package(Eigen3 CONFIG REQUIRED)
# Build an executable called `myprog`.
add_executable(myprog myprog.cpp)
target_link_libraries(myprog ezarpack Eigen3::Eigen)
# Find a usable version of ARPACK-NG.
# Macro find_arpackng() can be instructed to use a specific ARPACK-NG
# installation by setting the CMake variable `ARPACK_NG_ROOT`.
find_arpackng(3.6.0 REQUIRED)
# Make ezARPACK and Eigen headers visible to the compiler
# and link to ARPACK-NG libraries.
target_link_libraries(test PRIVATE
ezarpack Eigen3::Eigen ${arpack_ng_LIBRARIES})
# Link the executable to the ARPACK library.
target_link_libraries(myprog ${ARPACK_LIBRARIES})
```

Here is how `test.cpp` could look like.
Here is how `myprog.cpp` could look like.
```c++
#include <cmath>
#include <iostream>
Expand Down Expand Up @@ -212,7 +213,7 @@ int main() {
matrix_op(eigenvec.head(N), lhs.head(N)); // calculate A*v
rhs = lambda(i) * eigenvec; // and \lambda*v

std::cout << i << ": deviation = " << (rhs - lhs).squaredNorm() / (N * N)
std::cout << i << ": deviation = " << (rhs - lhs).norm() / N
<< std::endl;
}

Expand All @@ -234,6 +235,21 @@ The same eigenproblem can be solved using an MPI-parallelized solver that wraps
PARPACK routines. In this case one needs to additionally link the executable
to MPI libraries.

```cmake
# Parallel ARPACK (MPI)
# Build another executable `myprog_mpi`.
add_executable(myprog_mpi myprog_mpi.cpp)
target_link_libraries(myprog_mpi ezarpack Eigen3::Eigen)
# Detect and MPI-3.0 implementation.
find_package(MPI 3.0 REQUIRED)
# Link the executable to the Parallel ARPACK library and to the MPI.
target_include_directories(myprog_mpi PRIVATE ${MPI_CXX_INCLUDE_PATH})
target_link_libraries(myprog_mpi ${PARPACK_LIBRARIES} ${MPI_CXX_LIBRARIES})
```

```c++
#include <cmath>
#include <iostream>
Expand Down
78 changes: 78 additions & 0 deletions cmake/ezARPACKConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,82 @@
#
# This file is part of ezARPACK, an easy-to-use C++ wrapper for
# the ARPACK-NG FORTRAN library.
#
# Copyright (C) 2016-2022 Igor Krivenko <igor.s.krivenko@gmail.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

@PACKAGE_INIT@

# This macro finds a working installation of ARPACK-NG while dealing with
# version-to-version differences of ARPACK-NG's CMake interface.
#
# When called as find_arpackng([VERSION] [REQUIRED]), it forwards its optional
# arguments to the underlying `find_package()` calls.
#
# Setting CMake variable `ARPACK_NG_ROOT` will instruct `find_arpackng()` to
# look for ARPACK-NG at a specific installation prefix first.
#
# Upon successful detection of ARPACK-NG, `find_arpackng()` sets two variables
# that can be later passed to `target_link_libraries()`,
# - `ARPACK_LIBRARIES` - list of ARPACK libraries and linker flags
# - `PARPACK_LIBRARIES` - list of Parallel ARPACK libraries and linker flags
macro(find_arpackng)
# Try ARPACK_NG_ROOT first ...
set(ARGV_NO_REQUIRED ${ARGV})
list(REMOVE_ITEM ARGV_NO_REQUIRED "REQUIRED")
find_package(arpack-ng ${ARGV_NO_REQUIRED} QUIET CONFIG
NAMES arpackng arpack-ng
PATHS ${ARPACK_NG_ROOT}/lib/cmake
${ARPACK_NG_ROOT}/lib/cmake/arpack-ng
NO_DEFAULT_PATH)

# If failed, try default CMake locations
if(NOT arpack-ng_FOUND)
find_package(arpack-ng ${ARGV} CONFIG NAMES arpackng arpack-ng)
endif(NOT arpack-ng_FOUND)

if(arpack-ng_FOUND)
message(STATUS "Found ARPACK-NG version ${arpack-ng_VERSION}: "
"${arpack-ng_DIR}")

# ARPACK-NG 3.8.0 and newer exports CMake targets
if(TARGET ARPACK::ARPACK)
set(ARPACK_LIBRARIES ARPACK::ARPACK)

# Has target PARPACK::PARPACK been also exported by ARPACK-NG?
if(TARGET PARPACK::PARPACK)
set(PARPACK_LIBRARIES ARPACK::ARPACK PARPACK::PARPACK)
endif(TARGET PARPACK::PARPACK)

# Property INTERFACE_LINK_DIRECTORIES is set only by ARPACK-NG>=3.9
# 3.8.0 sets variable ${libdir} instead.
get_target_property(ARPACK_LINK_DIRECTORIES
ARPACK::ARPACK
INTERFACE_LINK_DIRECTORIES)
if(NOT ARPACK_LINK_DIRECTORIES)
set_target_properties(ARPACK::ARPACK PROPERTIES
INTERFACE_LINK_DIRECTORIES ${libdir})
if(TARGET PARPACK::PARPACK)
set_target_properties(PARPACK::PARPACK PROPERTIES
INTERFACE_LINK_DIRECTORIES ${libdir})
endif(TARGET PARPACK::PARPACK)
endif(NOT ARPACK_LINK_DIRECTORIES)

# ARPACK-NG prior to version 3.8.0 sets ${arpack_ng_LIBRARIES}
# It contains full paths to both ARPACK and PARPACK libraries
elseif(arpack_ng_LIBRARIES)
set(ARPACK_LIBRARIES ${arpack_ng_LIBRARIES} ${LAPACK_LIBRARIES})
set(PARPACK_LIBRARIES ${arpack_ng_LIBRARIES} ${LAPACK_LIBRARIES})
else()
message(WARNING "Could not deduce a list of ARPACK-NG libraries.")
endif(TARGET ARPACK::ARPACK)

endif(arpack-ng_FOUND)

endmacro(find_arpackng)

include("${CMAKE_CURRENT_LIST_DIR}/ezARPACKTargets.cmake")
check_required_components("@PROJECT_NAME@")
80 changes: 48 additions & 32 deletions doc/guide/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ How to use ezARPACK in your project
Depending on what build system your C++ project is based on, there are
two ways to use ezARPACK. Either way, you would need

* A reasonably recent compiler supporting C++11 or newer;
* A compiler supporting C++11 or newer;
* a working `ARPACK-NG installation
<https://github.com/opencollab/arpack-ng>`_ (version 3.6.0 or newer).
* One of supported :ref:`linear algebra libraries <backends>` (unless
Expand Down Expand Up @@ -34,10 +34,15 @@ directory. A compiler command line for your program can be as simple as
g++ -O3 -I<EZARPACK_ROOT>/include -L<ARPACK_NG_ROOT>/lib -larpack \
-o myprog myprog.cpp
(similar for ``clang++`` and other compilers). More ``-I`` flags might be needed
# For executables using the Parallel ARPACK (MPI) solvers
g++ -O3 -I<EZARPACK_ROOT>/include -L<ARPACK_NG_ROOT>/lib -lmpi \
-larpack -lparpack -o myprog_mpi myprog_mpi.cpp
(similar for ``clang++`` and other compilers). More ``-I`` flags may be needed
if the linear algebra framework you choose is not visible to the compiler by
default. Similarly, adding ``-L<MPI_ROOT>/lib -lmpi`` may be necessary if you
are using an MPI implementation installed in a non-system location.
default. Similarly, adding ``-I<MPI_ROOT>/include -L<MPI_ROOT>/lib`` may be
necessary if you are using an MPI implementation installed in a non-system
location.

.. note::

Expand All @@ -57,46 +62,57 @@ project.
cmake_minimum_required(VERSION 3.13.0 FATAL_ERROR)
project(myproject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
# EZARPACK_ROOT is the installation prefix of ezARPACK
set(ezARPACK_DIR ${EZARPACK_ROOT}/lib/cmake)
# ezARPACK_ROOT is the installation prefix of ezARPACK.
set(ezARPACK_DIR ${ezARPACK_ROOT}/lib/cmake)
# Import ezARPACK target
# Import ezARPACK target.
find_package(ezARPACK 1.0 CONFIG REQUIRED)
# Import Eigen (Blaze, Armadillo, etc) targets
find_package(Eigen3 CONFIG)
# Import Eigen (Blaze, Armadillo, etc) targets.
find_package(Eigen3 CONFIG REQUIRED)
# Build an executable called 'myprog'
# Build an executable called `myprog`.
add_executable(myprog myprog.cpp)
target_link_libraries(myprog PRIVATE ezarpack Eigen3::Eigen)
target_link_libraries(myprog ezarpack Eigen3::Eigen)
# Find a usable version of ARPACK-NG.
find_arpackng(3.6.0 REQUIRED)
# ARPACK_NG_ROOT is the installation prefix of ARPACK-NG
set(arpack-ng_DIR ${ARPACK_NG_ROOT}/lib/cmake)
# Link the executable to the ARPACK library.
target_link_libraries(myprog ${ARPACK_LIBRARIES})
# Find ARPACK-NG
find_package(arpack-ng 3.6.0 REQUIRED)
Linking your targets to ARPACK-NG libraries can be a bit of a hassle, as CMake
interface of ARPACK-NG changed a few times since version 3.6.0. In particular,
CMake scripts of the versions prior to 3.8.0 do not export any targets. Instead,
they expose library information via a variable ``arpack_ng_LIBRARIES``.
In order to make linking more user-friendly, ezARPACK exports a macro called
``find_arpackng()``. It finds an ARPACK-NG installation while taking care of
said CMake interface differences. Upon successful detection of ARPACK-NG,
``find_arpackng()`` sets two variables that can be later passed to
``target_link_libraries()``,

# Link the executable to ARPACK-NG
if(TARGET ARPACK::ARPACK) # ARPACK-NG 3.8.0 and newer
target_link_libraries(myprog ARPACK::ARPACK)
else() # ARPACK-NG prior to version 3.8.0
target_link_libraries(myprog ${arpack_ng_LIBRARIES})
endif()
- ``ARPACK_LIBRARIES`` - list of ARPACK libraries and linker flags
- ``PARPACK_LIBRARIES`` - list of Parallel ARPACK libraries and linker flags

In order to make the :ref:`Parallel ARPACK solvers <mpi>` work, ``myprog``
must be additionally linked to MPI and PARPACK (only for ARPACK-NG>=3.8.0) .
Setting CMake variable ``ARPACK_NG_ROOT`` will instruct ``find_arpackng()``
to look for ARPACK-NG at a specific installation prefix before proceeding
to system locations.

Making the :ref:`Parallel ARPACK solvers <mpi>` work requires additional
linking to MPI and PARPACK.

.. code:: cmake
# Find MPI-3.0 or newer
find_package(MPI 3.0)
# Parallel ARPACK (MPI)
# Build another executable `myprog_mpi`.
add_executable(myprog_mpi myprog_mpi.cpp)
target_link_libraries(myprog_mpi ezarpack Eigen3::Eigen)
# Add MPI include directories and link to MPI libraries
target_include_directories(myprog ${MPI_CXX_INCLUDE_PATH})
target_link_libraries(myprog ${MPI_CXX_LIBRARIES})
# Detect an MPI-3.0 implementation.
find_package(MPI 3.0 REQUIRED)
if(TARGET PARPACK::PARPACK) # ARPACK-NG 3.8.0 and newer
target_link_libraries(myprog PARPACK::PARPACK)
endif()
# Link the executable to the Parallel ARPACK library and to the MPI.
target_include_directories(myprog_mpi PRIVATE ${MPI_CXX_INCLUDE_PATH})
target_link_libraries(myprog_mpi ${PARPACK_LIBRARIES} ${MPI_CXX_LIBRARIES})

0 comments on commit 232cb37

Please sign in to comment.