From 8e71918ef81a9ecef30cd4587b7f9263ac57e489 Mon Sep 17 00:00:00 2001 From: Simon Praetorius <simon.praetorius@tu-dresden.de> Date: Mon, 19 Oct 2020 10:39:54 +0000 Subject: [PATCH] Rewrite FindParMETIS to provide imported targets Test for ParMETIS added Move HAVE_PARMETIS and dune_register_package_flags to AddParMETISFlags Add ENABLE_PTSCOTCH_PARMETIS for enabling ptscotch replacement for ParMETIS --- cmake/modules/AddParMETISFlags.cmake | 27 +-- cmake/modules/FindMETIS.cmake | 1 - cmake/modules/FindParMETIS.cmake | 265 ++++++++++++--------------- config.h.cmake | 8 +- dune/common/test/CMakeLists.txt | 11 ++ dune/common/test/parmetistest.cc | 97 ++++++++++ 6 files changed, 242 insertions(+), 167 deletions(-) create mode 100644 dune/common/test/parmetistest.cc diff --git a/cmake/modules/AddParMETISFlags.cmake b/cmake/modules/AddParMETISFlags.cmake index bf0842a07..4d37bf730 100644 --- a/cmake/modules/AddParMETISFlags.cmake +++ b/cmake/modules/AddParMETISFlags.cmake @@ -10,20 +10,23 @@ # A list of targets to use ParMETIS with. # +# set HAVE_PARMETIS for config.h +set(HAVE_PARMETIS ${ParMETIS_FOUND}) +# register all ParMETIS related flags +if(PARMETIS_FOUND) + dune_register_package_flags( + COMPILE_DEFINITIONS "ENABLE_PARMETIS=1" + LIBRARIES "ParMETIS::ParMETIS" + ) +endif() + +# add function to link against the ParMETIS library function(add_dune_parmetis_flags _targets) if(PARMETIS_FOUND) foreach(_target ${_targets}) - target_link_libraries(${_target} ${PARMETIS_LIBRARY} ${METIS_LIBRARY}) - GET_TARGET_PROPERTY(_props ${_target} INCLUDE_DIRECTORIES) - string(REPLACE "_props-NOTFOUND" "" _props "${_props}") - SET_TARGET_PROPERTIES(${_target} PROPERTIES INCLUDE_DIRECTORIES - "${_props};${PARMETIS_INCLUDE_DIRS}") - GET_TARGET_PROPERTY(_props ${_target} COMPILE_DEFINITIONS) - string(REPLACE "_props-NOTFOUND" "" _props "${_props}") - SET_TARGET_PROPERTIES(${_target} PROPERTIES COMPILE_DEFINITIONS - "${_props};ENABLE_PARMETIS") - endforeach(_target ${_targets}) - add_dune_mpi_flags(${_targets}) - endif(PARMETIS_FOUND) + target_link_libraries(${_target} ParMETIS::ParMETIS) + target_compile_definitions(${_target} "ENABLE_PARMETIS=1") + endforeach(_target) + endif() endfunction(add_dune_parmetis_flags) diff --git a/cmake/modules/FindMETIS.cmake b/cmake/modules/FindMETIS.cmake index a5cde3bcd..81d0c9544 100644 --- a/cmake/modules/FindMETIS.cmake +++ b/cmake/modules/FindMETIS.cmake @@ -97,7 +97,6 @@ unset(METIS_HEADER_FILE CACHE) # Scotch provides METIS-3 interface only in version < 6.07, but provides an option to # select the API-version in later Scotch releases if(ENABLE_SCOTCH_METIS) - include(CMakeFindDependencyMacro) find_package(PTScotch QUIET COMPONENTS SCOTCH) set(HAVE_SCOTCH_METIS ${PTScotch_FOUND}) if (PTScotch_FOUND) diff --git a/cmake/modules/FindParMETIS.cmake b/cmake/modules/FindParMETIS.cmake index f1b12ddb1..9b842547e 100644 --- a/cmake/modules/FindParMETIS.cmake +++ b/cmake/modules/FindParMETIS.cmake @@ -1,163 +1,128 @@ -# .. cmake_module:: -# -# Module that checks whether ParMETIS is available. -# -# You may set the following variables to configure this modules behavior: -# -# :ref:`PARMETIS_ROOT` -# Prefix where ParMETIS is installed. -# -# :ref:`PARMETIS_LIB_NAME` -# Name of the ParMETIS library (default: parmetis). -# -# :ref:`PARMETIS_LIBRARY` -# Full path of the ParMETIS library -# -# Sets the following variables: -# -# :code:`PARMETIS_FOUND` -# True if ParMETIS was found. -# -# :code:`PARMETIS_LIBRARY` -# Full path of the ParMETIS library. -# -# :code:`PARMETIS_LIBRARIES` -# List of all libraries needed for linking with ParMETIS, -# -# .. cmake_variable:: PARMETIS_ROOT -# -# You may set this variable to have :ref:`FindParMETIS` look -# for the ParMETIS library and includes in the given path -# before inspecting default system paths. -# -# .. cmake_variable:: PARMETIS_LIB_NAME -# -# You may set this variable to specify the name of the ParMETIS -# library that :ref:`FindParMETIS` looks for. -# -# .. cmake_variable:: PARMETIS_LIBRARY -# -# You may set this variable to specify the full path to the ParMETIS -# library, that should be used by :ref:`FindParMETIS`. -# - -# find METIS first +#[=======================================================================[.rst: +FindParMETIS +------------ + +Find Parallel Graph Partitioning library ParMETIS +(see http://glaros.dtc.umn.edu/gkhome/metis/parmetis/overview) + +Imported targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` target: + +``ParMETIS::ParMETIS`` + The libraries, flags, and includes to use for ParMETIS, if found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +``ParMETIS_FOUND`` + The ParMETIS library with all its dependencies is found + +Cache Variables +^^^^^^^^^^^^^^^ + +The following variables may be set to influence this module's behavior: + +``PARMETIS_INCLUDE_DIR`` + Include directory where the parmetis.h is found. + +``PARMETIS_LIBRARY`` + Full path to the ParMETIS library + +``ENABLE_PTSCOTCH_PARMETIS`` + Use the PTScotch library as ParMETIS compatibility library. This library + provides an interface of some ParMETIS library functions. + +#]=======================================================================] + +# text for feature summary +include(FeatureSummary) +set_package_properties("ParMETIS" PROPERTIES + DESCRIPTION "Parallel Graph Partitioning" +) + +# The PTScotch library provides a wrapper around some functions of ParMETIS, since not +# the full interface, you have to request it explicitly. +option(ENABLE_PTSCOTCH_PARMETIS "Use the (PT)Scotch library as ParMETIS compatibility library" OFF) + +# find package dependencies first find_package(METIS QUIET) -if(NOT METIS_FOUND) - find_package_handle_standard_args( - "ParMETIS" - DEFAULT_MSG "METIS not found which is required for ParMETIS." - ) +find_package(MPI QUIET) + +find_path(PARMETIS_INCLUDE_DIR parmetis.h + PATH_SUFFIXES parmetis) + +# search ParMETIS library +find_library(PARMETIS_LIBRARY parmetis) +if(ENABLE_PTSCOTCH_PARMETIS) + find_library(PARMETIS_LIBRARY ptscotchparmetis) endif() +mark_as_advanced(PARMETIS_INCLUDE_DIR PARMETIS_LIBRARY) -find_path(PARMETIS_INCLUDE_DIR parmetis.h - PATHS ${PARMETIS_DIR} ${PARMETIS_ROOT} - PATH_SUFFIXES include parmetis - NO_DEFAULT_PATH - DOC "Include directory of ParMETIS") -find_path(PARMETIS_INCLUDE_DIR parmetis.h - PATH_SUFFIXES include parmetis) - -set(PARMETIS_LIB_NAME parmetis - CACHE STRING "Name of the ParMETIS library (default: parmetis).") -set(PARMETIS_LIBRARY ParMETIS_LIBRARY-NOTFOUND - CACHE FILEPATH "Full path of the ParMETIS library") - -# check ParMETIS headers -include(CMakePushCheckState) -cmake_push_check_state() # Save variables -set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${MPI_DUNE_INCLUDE_PATH} ${METIS_INCLUDE_DIRS} ${PARMETIS_INCLUDE_DIR}) -set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${MPI_DUNE_COMPILE_FLAGS}") -# set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${METIS_LIBRARIES}") -check_include_file(parmetis.h PARMETIS_FOUND) - -if(PARMETIS_FOUND) - set(ParMETIS_INCLUDE_PATH ${CMAKE_REQUIRED_INCLUDES}) - set(ParMETIS_COMPILE_FLAGS "${CMAKE_REQUIRED_FLAGS} -DENABLE_PARMETIS=1") - - # search ParMETIS library - find_library(PARMETIS_LIBRARY ${PARMETIS_LIB_NAME} - PATHS ${PARMETIS_DIR} ${PARMETIS_ROOT} - PATH_SUFFIXES lib - NO_DEFAULT_PATH) - find_library(PARMETIS_LIBRARY ${PARMETIS_LIB_NAME}) - - # check ParMETIS library - if(PARMETIS_LIBRARY) - set(_CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}") # do a backup - set(_PARMETIS_LIBRARIES "${PARMETIS_LIBRARY};${MPI_DUNE_LIBRARIES}") - list(APPEND CMAKE_REQUIRED_LIBRARIES "${_PARMETIS_LIBRARIES};${METIS_LIBRARIES}") - include(CheckFunctionExists) - check_function_exists(ParMETIS_V3_PartKway HAVE_PARMETIS) - if(NOT HAVE_PARMETIS) - # Maybe we are using static scotch libraries. In this case we need to link - # the other scotch libraries too. Let's make a best effort. - # Get the path where ParMETIS_LIBRARY resides - get_filename_component(_lib_root ${METIS_LIBRARY} DIRECTORY) - # Search for additional libs only in this directory. - # Otherwise we might find incompatible ones, e.g. for int instead of long - find_library(PTSCOTCH_LIBRARY ptscotch PATHS ${_lib_root} "The PT-Scotch library." - NO_DEFAULT_PATH) - find_library(PTSCOTCHERR_LIBRARY ptscotcherr PATHS ${_lib_root} "The Scotch error library." - NO_DEFAULT_PATH) - if(PTSCOTCH_LIBRARY AND PTSCOTCHERR_LIBRARY) - set(_PARMETIS_LIBRARIES ${PARMETIS_LIBRARY} ${PTSCOTCH_LIBRARY} - ${PTSCOTCHERR_LIBRARY} ${METIS_LIBRARIES} ${MPI_DUNE_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES ${_PARMETIS_LIBRARIES} - ${_CMAKE_REQUIRED_LIBRARIES}) - unset(HAVE_PARMETIS CACHE) - check_function_exists(ParMETIS_V3_PartKway HAVE_PARMETIS) - endif() - endif() - set(CMAKE_REQUIRED_LIBRARIES "${_CMAKE_REQUIRED_LIBRARIES}") # get backup +# determine version of ParMETIS installation +find_file(PARMETIS_HEADER_FILE parmetis.h + PATHS ${PARMETIS_INCLUDE_DIR} + NO_DEFAULT_PATH) +if(PARMETIS_HEADER_FILE) + file(READ "${PARMETIS_HEADER_FILE}" parmetisheader) + string(REGEX REPLACE ".*#define PARMETIS_MAJOR_VERSION[ ]+([0-9]+).*" "\\1" + ParMETIS_MAJOR_VERSION "${parmetisheader}") + string(REGEX REPLACE ".*#define PARMETIS_MINOR_VERSION[ ]+([0-9]+).*" "\\1" + ParMETIS_MINOR_VERSION "${parmetisheader}") + if(ParMETIS_MAJOR_VERSION GREATER_EQUAL 0 AND ParMETIS_MINOR_VERSION GREATER_EQUAL 0) + set(ParMETIS_VERSION "${ParMETIS_MAJOR_VERSION}.${ParMETIS_MINOR_VERSION}") + endif() +endif() +unset(PARMETIS_HEADER_FILE CACHE) + +# set a flag whether all ParMETIS dependencies are found correctly +if(METIS_FOUND AND MPI_FOUND) + set(PARMETIS_DEPENDENCIES_FOUND TRUE) + + # minimal requires METIS version 5.0 for ParMETIS >= 4.0 + if (ParMETIS_VERSION VERSION_GREATER_EQUAL "4.0" + AND METIS_VERSION VERSION_LESS "5.0") + set(PARMETIS_DEPENDENCIES_FOUND FALSE) + endif() +endif() + +# If ptscotch-parmetis is requested, find package PTScotch +if(ENABLE_PTSCOTCH_PARMETIS) + find_package(PTScotch QUIET COMPONENTS PTSCOTCH) + set(HAVE_PTSCOTCH_PARMETIS ${PTScotch_FOUND}) + if(PTScotch_FOUND AND MPI_FOUND) + set(PARMETIS_DEPENDENCIES_FOUND TRUE) endif() endif() # behave like a CMake module is supposed to behave include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - "ParMETIS" - DEFAULT_MSG - PARMETIS_INCLUDE_DIR - PARMETIS_LIBRARY - HAVE_PARMETIS +find_package_handle_standard_args("ParMETIS" + REQUIRED_VARS + PARMETIS_LIBRARY PARMETIS_INCLUDE_DIR PARMETIS_DEPENDENCIES_FOUND + VERSION_VAR + ParMETIS_VERSION ) -mark_as_advanced(PARMETIS_INCLUDE_DIR PARMETIS_LIBRARY PARMETIS_LIB_NAME) - -#restore old values -cmake_pop_check_state() - -if(PARMETIS_FOUND) - set(PARMETIS_INCLUDE_DIRS ${PARMETIS_INCLUDE_DIR}) - set(PARMETIS_LIBRARIES "${_PARMETIS_LIBRARIES}" - CACHE FILEPATH "ParMETIS libraries needed for linking") - set(PARMETIS_LINK_FLAGS "${DUNE_MPI_LINK_FLAGS}" - CACHE STRING "ParMETIS link flags") - # log result - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determing location of ParMETIS succeeded:\n" - "Include directory: ${PARMETIS_INCLUDE_DIRS}\n" - "Library directory: ${PARMETIS_LIBRARIES}\n\n") - # deprecate versions < 4 - file(READ "${PARMETIS_INCLUDE_DIR}/parmetis.h" parmetisheader) - string(REGEX MATCH "#define PARMETIS_MAJOR_VERSION[ ]+[0-9]+" versionMacroDef "${parmetisheader}") - string(REGEX MATCH "[0-9]+" ParMetisMajorVersion "${versionMacroDef}") - if("${versionMacroDef}" STREQUAL "" OR "${ParMetisMajorVersion}" LESS 4) - message(AUTHOR_WARNING "Support for ParMETIS older than version 4.x is deprecated in Dune 2.7") - endif() -else() - # log erroneous result - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determing location of ParMETIS failed:\n" - "Include directory: ${PARMETIS_INCLUDE_DIR}\n" - "ParMETIS library directory: ${PARMETIS_LIBRARY}\n\n") -endif() +# create imported target ParMETIS::ParMETIS +if(PARMETIS_FOUND AND NOT TARGET ParMETIS::ParMETIS) + add_library(ParMETIS::ParMETIS UNKNOWN IMPORTED) + set_target_properties(ParMETIS::ParMETIS PROPERTIES + IMPORTED_LOCATION ${PARMETIS_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${PARMETIS_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES MPI::MPI_C + ) -# register all ParMETIS related flags -if(PARMETIS_FOUND) - dune_register_package_flags(COMPILE_DEFINITIONS "ENABLE_PARMETIS=1" - LIBRARIES "${PARMETIS_LIBRARIES}" - INCLUDE_DIRS "${PARMETIS_INCLUDE_DIRS}") + # link against PTScotch or METIS if needed + if(ENABLE_PTSCOTCH_PARMETIS AND PTScotch_FOUND) + set_property(TARGET ParMETIS::ParMETIS APPEND PROPERTY + INTERFACE_LINK_LIBRARIES PTScotch::PTScotch) + else() + set_property(TARGET ParMETIS::ParMETIS APPEND PROPERTY + INTERFACE_LINK_LIBRARIES METIS::METIS) + endif() endif() diff --git a/config.h.cmake b/config.h.cmake index 6abb0aea1..e4c9a8804 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -180,11 +180,11 @@ /* Define to 1 if METIS is available */ #cmakedefine HAVE_METIS 1 +/* Define to 1 if you have the ParMETIS library. */ +#cmakedefine HAVE_PARMETIS 1 -/* Define to ENABLE_PARMETIS if you have the Parmetis library. - This is only true if MPI was found - by configure _and_ if the application uses the PARMETIS_CPPFLAGS */ -#cmakedefine HAVE_PARMETIS ENABLE_PARMETIS +/* Define to 1 if you have the PTScotch replacement for ParMETIS is used. */ +#cmakedefine HAVE_PTSCOTCH_PARMETIS 1 /* Define to 1 if PT-Scotch is available */ #cmakedefine HAVE_PTSCOTCH 1 diff --git a/dune/common/test/CMakeLists.txt b/dune/common/test/CMakeLists.txt index f4b671d96..dd576aab6 100644 --- a/dune/common/test/CMakeLists.txt +++ b/dune/common/test/CMakeLists.txt @@ -274,6 +274,17 @@ dune_add_test(SOURCES parametertreetest.cc LINK_LIBRARIES dunecommon LABELS quick) +find_package(ParMETIS 4.0) +dune_add_test(SOURCES parmetistest.cc + MPI_RANKS 4 + TIMEOUT 300 + COMPILE_DEFINITIONS "HAVE_PARMETIS" + CMAKE_GUARD ParMETIS_FOUND + LABELS quick) +if (ParMETIS_FOUND) + target_link_libraries(parmetistest ParMETIS::ParMETIS) +endif() + dune_add_test(SOURCES pathtest.cc LINK_LIBRARIES dunecommon LABELS quick) diff --git a/dune/common/test/parmetistest.cc b/dune/common/test/parmetistest.cc new file mode 100644 index 000000000..c5622c429 --- /dev/null +++ b/dune/common/test/parmetistest.cc @@ -0,0 +1,97 @@ +#include <config.h> +#include <cassert> +#include <iostream> +#include <vector> + +#if ! HAVE_PARMETIS +#error "ParMETIS is required for this test." +#endif + +#include <mpi.h> + +#if HAVE_PTSCOTCH_PARMETIS +extern "C" { + #include <ptscotch.h> +} +#endif + +extern "C" { + #include <parmetis.h> +} + +int main(int argc, char **argv) +{ +#if defined(REALTYPEWIDTH) + using real_t = ::real_t; +#else + using real_t = float; +#endif + +#if defined(IDXTYPEWIDTH) + using idx_t = ::idx_t; +#elif HAVE_PTSCOTCH_PARMETIS + using idx_t = SCOTCH_Num; +#else + using idx_t = int; +#endif + + MPI_Init(&argc, &argv); + + MPI_Comm comm; + MPI_Comm_dup(MPI_COMM_WORLD, &comm); + + int rank, size; + MPI_Comm_rank(comm, &rank); + MPI_Comm_size(comm, &size); + + // This test is design for 3 cores + assert(size == 3); + + // local adjacency structure of the graph + std::vector<idx_t> xadj; // size n+1 + std::vector<idx_t> adjncy; // size 2*m + + if (rank == 0) { + xadj = std::vector<idx_t>{0,2,5,8,11,13}; + adjncy = std::vector<idx_t>{1,5,0,2,6,1,3,7,2,4,8,3,9}; + } + else if (rank == 1) { + xadj = std::vector<idx_t>{0,3,7,11,15,18}; + adjncy = std::vector<idx_t>{0,6,10,1,5,7,11,2,6,8,12,3,7,9,13,4,8,14}; + } + else if (rank == 2) { + xadj = std::vector<idx_t>{0,2,5,8,11,13}; + adjncy = std::vector<idx_t>{5,11,6,10,12,7,11,13,8,12,14,9,13}; + } + + // Array describing how the vertices of the graph are distributed among the processors. + std::vector<idx_t> vtxdist{0,5,10,15}; + + // No weights + idx_t wgtflag = 0; + // C-style numbering that starts from 0. + idx_t numflag = 0; + // Number of weights that each vertex has + idx_t ncon = 1; + // Number of sub-domains + idx_t nparts = size; + // Fraction of vertex weight that should be distributed to each sub-domain for each + // balance constraint + std::vector<real_t> tpwgts(ncon * nparts, 1.0/nparts); + std::vector<real_t> ubvec(ncon, 1.05); + std::vector<idx_t> options{0, 0, 0}; + + idx_t edgecut; + std::vector<idx_t> part(xadj.size()-1, 0); + + ParMETIS_V3_PartKway(vtxdist.data(), xadj.data(), adjncy.data(), + nullptr, nullptr, &wgtflag, &numflag, &ncon, &nparts, tpwgts.data(), + ubvec.data(), options.data(), &edgecut, part.data(), &comm); + + for (std::size_t part_i = 0; part_i < part.size(); ++part_i) { + std::cout << "[" << rank << "] " << part_i << " => " << part[part_i] << std::endl; + } + + MPI_Finalize(); + return 0; +} \ No newline at end of file -- GitLab