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